MODSET=beta"
exit 1
endif
# ensure a clean branch
git diff -s --exit-code || (echo "local repository not clean"; exit 1)
# update files with new version
sed -i.bak 's/$(PREVIOUS_VERSION)/$(RELEASE_CANDIDATE)/g' versions.yaml
sed -i.bak 's/$(PREVIOUS_VERSION)/$(RELEASE_CANDIDATE)/g' ./cmd/builder/internal/builder/config.go
sed -i.bak 's/$(PREVIOUS_VERSION)/$(RELEASE_CANDIDATE)/g' ./cmd/builder/test/core.builder.yaml
sed -i.bak 's/$(PREVIOUS_VERSION)/$(RELEASE_CANDIDATE)/g' ./cmd/otelcorecol/builder-config.yaml
sed -i.bak 's/$(PREVIOUS_VERSION)/$(RELEASE_CANDIDATE)/g' examples/k8s/otel-config.yaml
find . -name "*.bak" -type f -delete
# commit changes before running multimod
git add .
git commit -m "prepare release $(RELEASE_CANDIDATE)"
$(MAKE) multimod-prerelease
# regenerate files
$(MAKE) -C cmd/builder config
$(MAKE) genotelcorecol
git add .
git commit -m "add multimod changes $(RELEASE_CANDIDATE)" || (echo "no multimod changes to commit")
.PHONY: clean
clean:
test -d bin && $(RM) bin/*
.PHONY: checklinks
checklinks:
command -v $(DOCKERCMD) >/dev/null 2>&1 || { echo >&2 "$(DOCKERCMD) not installed. Install before continuing"; exit 1; }
$(DOCKERCMD) run -w /home/repo --rm \
--mount 'type=bind,source='$(PWD)',target=/home/repo' \
lycheeverse/lychee \
--config .github/lychee.toml \
--root-dir /home/repo \
-v \
--no-progress './**/*.md'
# error message "failed to sync logger: sync /dev/stderr: inappropriate ioctl for device"
# is a known issue but does not affect function.
.PHONY: crosslink
crosslink:
@echo "Executing crosslink"
$(GO_TOOL) crosslink --root=$(shell pwd) --prune
FILENAME?=$(shell git branch --show-current)
.PHONY: chlog-new
chlog-new:
$(GO_TOOL) chloggen new --config $(CHLOGGEN_CONFIG) --filename $(FILENAME)
.PHONY: chlog-validate
chlog-validate:
$(GO_TOOL) chloggen validate --config $(CHLOGGEN_CONFIG)
.PHONY: chlog-preview
chlog-preview:
$(GO_TOOL) chloggen update --config $(CHLOGGEN_CONFIG) --dry
.PHONY: chlog-update
chlog-update:
$(GO_TOOL) chloggen update --config $(CHLOGGEN_CONFIG) --version $(VERSION)
.PHONY: builder-integration-test
builder-integration-test:
cd ./cmd/builder && ./test/test.sh
.PHONY: mdatagen-test
mdatagen-test:
cd cmd/mdatagen && $(GOCMD) install .
cd cmd/mdatagen && $(GOCMD) generate ./...
cd cmd/mdatagen && $(MAKE) fmt
cd cmd/mdatagen && $(GOCMD) test ./...
GITHUBGEN_ARGS ?= -skipgithub
GITHUBGEN := $(GO_TOOL) githubgen $(GITHUBGEN_ARGS)
.PHONY: generate-gh-issue-templates
generate-gh-issue-templates:
$(GITHUBGEN) issue-templates
.PHONY: generate-codeowners
generate-codeowners:
$(GITHUBGEN) --default-codeowner "open-telemetry/collector-approvers" codeowners
.PHONY: gengithub
gengithub: generate-codeowners generate-gh-issue-templates
.PHONY: gendistributions
gendistributions:
$(GITHUBGEN) distributions
.PHONY: generate-chloggen-components
generate-chloggen-components:
$(GITHUBGEN) chloggen-components
================================================
FILE: Makefile.Common
================================================
SHELL = /bin/bash
# ALL_PKGS is the list of all packages where ALL_SRC files reside.
ALL_PKGS := $(sort $(shell go list ./...))
# COVER_PKGS is the list of packages to include in the coverage
COVER_PKGS := $(shell go list ./... | tr "\n" ",")
CURR_MOD := $(shell go list -m | tr '/' '-' )
GOCMD?= go
GOOS := $(shell $(GOCMD) env GOOS)
GOARCH := $(shell $(GOCMD) env GOARCH)
GOTEST_TIMEOUT?=240s
# -race is not supported on windows arm64
GOTEST_OPT?= -timeout $(GOTEST_TIMEOUT) $(if $(and $(filter windows,$(GOOS)), $(filter arm64,$(GOARCH))),, -race)
# SRC_ROOT is the top of the source tree.
SRC_ROOT := $(shell git rev-parse --show-toplevel)
TOOLS_MOD_DIR := $(SRC_ROOT)/internal/tools
TOOLS_MOD_FILE := $(TOOLS_MOD_DIR)/go.mod
GO_TOOL := $(GOCMD) tool -modfile $(TOOLS_MOD_FILE)
CHLOGGEN_CONFIG := .chloggen/config.yaml
# no trailing slash
JUNIT_OUT_DIR ?= $(TOOLS_MOD_DIR)/testresults
.PHONY: test
test:
# GODEBUG=fips140=only is used to surface any FIPS-140-3 non-compliant cryptographic
# calls into the Go standard library. See: https://go.dev/doc/security/fips140#fips-140-3-mode
# disabling fips only to unblock CI. See https://github.com/open-telemetry/opentelemetry-collector/issues/13925
# GODEBUG=fips140=only $(GO_TOOL) gotestsum --packages="./..." -- $(GOTEST_OPT)
$(GO_TOOL) gotestsum --packages="./..." -- $(GOTEST_OPT)
.PHONY: test-with-cover
test-with-cover:
mkdir -p $(PWD)/coverage/unit
$(GO_TOOL) gotestsum \
--packages="./..." -- \
$(GOTEST_OPT) -cover -covermode=atomic -coverpkg $(COVER_PKGS) -args -test.gocoverdir="$(PWD)/coverage/unit"
.PHONY: test-with-junit
test-with-junit:
mkdir -p $(JUNIT_OUT_DIR)
$(GO_TOOL) gotestsum \
--packages="./..." --junitfile $(JUNIT_OUT_DIR)/$(CURR_MOD)-junit.xml -- \
$(GOTEST_OPT) ./...
.PHONY: benchmark
benchmark:
MEMBENCH=yes $(GO_TOOL) gotestsum \
--packages="$(ALL_PKGS)" -- \
-bench=. -run=notests ./... | tee benchmark.txt
.PHONY: fmt
fmt: common/gofmt common/goimports common/gofumpt
.PHONY: modernize
modernize:
$(GO_TOOL) modernize \
-fix -test -v -any -fmtappendf -forvar -mapsloop -minmax -newexpr -omitzero -plusbuild \
-rangeint -reflecttypefor -slicescontains -slicessort -stditerators -stringscut \
-stringscutprefix -stringsseq -stringsbuilder -testingcontext -unsafefuncs -waitgroup ./...
.PHONY: tidy
tidy:
rm -fr go.sum
$(GOCMD) mod tidy -compat=1.25.0
.PHONY: lint
lint:
$(GO_TOOL) golangci-lint run
.PHONY: common/gofmt
common/gofmt:
gofmt -w -s ./
.PHONY: common/goimports
common/goimports:
$(GO_TOOL) goimports -w -local go.opentelemetry.io/collector ./
.PHONY: common/gofumpt
common/gofumpt:
$(GO_TOOL) gofumpt -l -w -extra .
.PHONY: vulncheck
vulncheck:
$(GO_TOOL) govulncheck ./...
.PHONY: generate
generate:
$(GOCMD) generate ./...
.PHONY: impi
impi:
$(GO_TOOL) impi \
--local go.opentelemetry.io/collector \
--scheme stdThirdPartyLocal ./...
.PHONY: moddownload
moddownload:
$(GOCMD) mod download
timebenchmark:
go test -bench=. -benchtime=1s ./...
================================================
FILE: README.md
================================================
---
Getting Started
•
Getting Involved
•
Getting In Touch
Vision
•
Configuration
•
Monitoring
•
Security
•
Package
---
#
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.) 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.
## Community
The OpenTelemetry Collector SIG is present at the [#otel-collector](https://cloud-native.slack.com/archives/C01N6P7KR6W)
channel on the CNCF Slack and [meets once a week](https://github.com/open-telemetry/community#implementation-sigs) via
video calls. Everyone is invited to join those calls, which typically serves the following purposes:
- meet the humans behind the project
- get an opinion about specific proposals
- look for a sponsor for a proposed component after trying already via GitHub and Slack
- get attention to a specific pull-request that got stuck and is difficult to discuss asynchronously
We rotate our video calls between three time slots, in order to
allow everyone to join at least once every three meetings. The rotation order is as follows:
Tuesday:
- [17:00 PT](https://dateful.com/convert/pst-pdt-pacific-time?t=1700)
Wednesday:
- [09:00 PT](https://dateful.com/convert/pst-pdt-pacific-time?t=0900)
- [05:00 PT](https://dateful.com/convert/pst-pdt-pacific-time?t=0500)
Contributors to the project are also welcome to have ad-hoc meetings for synchronous discussions about specific points.
Post a note in #otel-collector-dev on Slack inviting others, specifying the topic to be discussed. Unless there are strong
reasons to keep the meeting private, please make it an open invitation for other contributors to join. Try also to
identify who would be the other contributors interested on that topic and in which timezones they are.
Remember that our source of truth is GitHub: every decision made via Slack or video calls has to be recorded in the
relevant GitHub issue. Ideally, the agenda items from the meeting notes would include a link to the issue or pull
request where a discussion is happening already. We acknowledge that not everyone can join Slack or the synchronous
calls and don't want them to feel excluded.
## Supported OTLP version
This code base is currently built against using OTLP protocol v1.10.0,
considered Stable. [See the OpenTelemetry Protocol Stability
definition
here.](https://github.com/open-telemetry/opentelemetry-proto?tab=readme-ov-file#stability-definition)
## Stability levels
See [Stability Levels and versioning](docs/component-stability.md) for more details.
## Compatibility
When used as a library, the OpenTelemetry Collector attempts to track the currently supported versions of Go, as [defined by the Go team](https://go.dev/doc/devel/release#policy).
Removing support for an unsupported Go version is not considered a breaking change.
Support for Go versions on the OpenTelemetry Collector is updated as follows:
1. The first release after the release of a new Go minor version `N` will add build and tests steps for the new Go minor version.
2. The first release after the release of a new Go minor version `N` will remove support for Go version `N-2`.
Official OpenTelemetry Collector distro binaries will be built with a release in the latest Go minor version series.
## Verifying the images signatures
> [!NOTE]
> To verify a signed artifact or blob, first [install Cosign](https://docs.sigstore.dev/cosign/system_config/installation/), then follow the instructions below.
We are signing the images `otel/opentelemetry-collector` and `otel/opentelemetry-collector-contrib` using [sigstore cosign](https://github.com/sigstore/cosign) tool and to verify the signatures you can run the following command:
```console
$ cosign verify \
--certificate-identity=https://github.com/open-telemetry/opentelemetry-collector-releases/.github/workflows/base-release.yaml@refs/tags/ \
--certificate-oidc-issuer=https://token.actions.githubusercontent.com \
```
where:
- ``: is the release that you want to validate
- ``: is the image that you want to check
Example:
```console
$ cosign verify --certificate-identity=https://github.com/open-telemetry/opentelemetry-collector-releases/.github/workflows/base-release.yaml@refs/tags/v0.98.0 --certificate-oidc-issuer=https://token.actions.githubusercontent.com ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector-contrib:0.98.0
Verification for ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector-contrib:0.98.0 --
The following checks were performed on each of these signatures:
- The cosign claims were validated
- Existence of the claims in the transparency log was verified offline
- The code-signing certificate was verified using trusted certificate authority certificates
[{"critical":{"identity":{"docker-reference":"ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector-contrib"},"image":{"docker-manifest-digest":"sha256:5cea85bcbc734a3c0a641368e5a4ea9d31b472997e9f2feca57eeb4a147fcf1a"},"type":"cosign container image signature"},"optional":{"1.3.6.1.4.1.57264.1.1":"https://token.actions.githubusercontent.com","1.3.6.1.4.1.57264.1.2":"push","1.3.6.1.4.1.57264.1.3":"9e20bf5c142e53070ccb8320a20315fffb41469e","1.3.6.1.4.1.57264.1.4":"Release Contrib","1.3.6.1.4.1.57264.1.5":"open-telemetry/opentelemetry-collector-releases","1.3.6.1.4.1.57264.1.6":"refs/tags/v0.98.0","Bundle":{"SignedEntryTimestamp":"MEUCIQDdlmNeKXQrHnonwWiHLhLLwFDVDNoOBCn2sv85J9P8mgIgDQFssWJImo1hn38VlojvSCL7Qq5FMmtnGu0oLsNdOm8=","Payload":{"body":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiIxMzVjY2RlN2YzZTNhYjU2NmFmYzJhYWU3MDljYmJlNmFhMDZlZWMzNDA2MWNkZjMyNmRhYzM2MmY0NWM4Yjg4In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJUURFbDV6N0diMWRVYkM5KzR4c1VvbDhMcWZNV2hiTzhkdEpwdExyMXhUNWZnSWdTdEwwN1I0ZDA5R2x0ZkV0azJVbmlJSlJhQVdrVDJNWDVtRXJNSlplc2pRPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVaG9ha05EUW5jeVowRjNTVUpCWjBsVlNETkNjRFZTYlVSU1VpOXphMWg0YVdWUFlrcFhSbmRrUjNNNGQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFJkMDVFUlhoTlJGRjRUMFJOTlZkb1kwNU5hbEYzVGtSRmVFMUVVWGxQUkUwMVYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZyWlRsSE1ubHNjMjkzYVZZMmRFOVZSazlRVVhNd2NXY3hTSEV5WmpsVUx6UTJZbEFLU1ZSNE0ybFRkVXBhV0hGc1dEUldWV2Q1VlZndmNVazJhblZ2WlZSVEswaG5XVUoyYjBseVNERTFUeTltZEd0VmVtRlBRMEpwZDNkbloxbHZUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZHTkRrMUNrdDFNRWhqTm5rek1rNUNTVTFFU21ReVpuWkxNMHBCZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDJkWldVZEJNVlZrUlZGRlFpOTNVamhOU0hGSFpVZG9NR1JJUW5wUGFUaDJXakpzTUdGSVZtbE1iVTUyWWxNNWRtTkhWblZNV0ZKc1lrZFdkQXBhV0ZKNVpWTTVkbU5IVm5Wa1IxWnpXbGN4YkdSSVNqVk1WMDUyWWtkNGJGa3pVblpqYVRGNVdsZDRiRmxZVG14amVUaDFXakpzTUdGSVZtbE1NMlIyQ21OdGRHMWlSemt6WTNrNWFWbFlUbXhNV0Vwc1lrZFdhR015VlhWbFYwWjBZa1ZDZVZwWFducE1NMUpvV2pOTmRtUnFRWFZQVkdkMVRVUkJOVUpuYjNJS1FtZEZSVUZaVHk5TlFVVkNRa04wYjJSSVVuZGplbTkyVEROU2RtRXlWblZNYlVacVpFZHNkbUp1VFhWYU1td3dZVWhXYVdSWVRteGpiVTUyWW01U2JBcGlibEYxV1RJNWRFMUNTVWREYVhOSFFWRlJRbWMzT0hkQlVVbEZRa2hDTVdNeVozZE9aMWxMUzNkWlFrSkJSMFIyZWtGQ1FYZFJiMDlYVlhsTlIwcHRDazVYVFhoT1JFcHNUbFJOZDA1NlFtcFpNa2swVFhwSmQxbFVTWGROZWtVeFdtMWFiVmxxVVhoT1JGazFXbFJCWkVKbmIzSkNaMFZGUVZsUEwwMUJSVVVLUWtFNVUxcFhlR3haV0U1c1NVVk9kbUp1VW5saFYwbDNVRkZaUzB0M1dVSkNRVWRFZG5wQlFrSlJVWFppTTBKc1lta3hNRnBYZUd4aVYxWXdZMjVyZGdwaU0wSnNZbTVTYkdKSFZuUmFXRko1WlZNeGFtSXllSE5hVjA0d1lqTkpkR050Vm5OYVYwWjZXbGhOZDBoM1dVdExkMWxDUWtGSFJIWjZRVUpDWjFGU0NtTnRWbTFqZVRrd1dWZGtla3d6V1hkTWFtczBUR3BCZDA5M1dVdExkMWxDUWtGSFJIWjZRVUpEUVZGMFJFTjBiMlJJVW5kamVtOTJURE5TZG1FeVZuVUtURzFHYW1SSGJIWmliazExV2pKc01HRklWbWxrV0U1c1kyMU9kbUp1VW14aWJsRjFXVEk1ZEUxSlIwbENaMjl5UW1kRlJVRlpUeTlOUVVWS1FraHZUUXBsUjJnd1pFaENlazlwT0haYU1td3dZVWhXYVV4dFRuWmlVemwyWTBkV2RVeFlVbXhpUjFaMFdsaFNlV1ZUT1haalIxWjFaRWRXYzFwWE1XeGtTRW8xQ2t4WFRuWmlSM2hzV1ROU2RtTnBNWGxhVjNoc1dWaE9iR041T0hWYU1td3dZVWhXYVV3elpIWmpiWFJ0WWtjNU0yTjVPV2xaV0U1c1RGaEtiR0pIVm1nS1l6SlZkV1ZYUm5SaVJVSjVXbGRhZWt3elVtaGFNMDEyWkdwQmRVOVVaM1ZOUkVFMFFtZHZja0puUlVWQldVOHZUVUZGUzBKRGIwMUxSR3hzVFdwQ2FRcGFhbFpxVFZSUmVWcFVWWHBOUkdOM1dUSk9hVTlFVFhsTlIwVjVUVVJOZUU1WFdtMWFiVWt3VFZSUk1rOVhWWGRJVVZsTFMzZFpRa0pCUjBSMmVrRkNDa04zVVZCRVFURnVZVmhTYjJSWFNYUmhSemw2WkVkV2EwMUdTVWREYVhOSFFWRlJRbWMzT0hkQlVYZEZVa0Y0UTJGSVVqQmpTRTAyVEhrNWJtRllVbThLWkZkSmRWa3lPWFJNTWpsM1dsYzBkR1JIVm5OYVZ6RnNaRWhLTlV3eU9YZGFWelV3V2xkNGJHSlhWakJqYm10MFdUSTVjMkpIVm1wa1J6bDVURmhLYkFwaVIxWm9ZekpXZWsxRVowZERhWE5IUVZGUlFtYzNPSGRCVVRCRlMyZDNiMDlYVlhsTlIwcHRUbGROZUU1RVNteE9WRTEzVG5wQ2Fsa3lTVFJOZWtsM0NsbFVTWGROZWtVeFdtMWFiVmxxVVhoT1JGazFXbFJCYUVKbmIzSkNaMFZGUVZsUEwwMUJSVTlDUWsxTlJWaEtiRnB1VFhaa1IwWnVZM2s1TWsxRE5EVUtUME0wZDAxQ2EwZERhWE5IUVZGUlFtYzNPSGRCVVRoRlEzZDNTazVFUVhkTmFsVjZUbXBqTWsxRVJVZERhWE5IUVZGUlFtYzNPSGRCVWtGRlNYZDNhQXBoU0ZJd1kwaE5Oa3g1T1c1aFdGSnZaRmRKZFZreU9YUk1NamwzV2xjMGRHUkhWbk5hVnpGc1pFaEtOVTFDWjBkRGFYTkhRVkZSUW1jM09IZEJVa1ZGQ2tObmQwbE9SR3MxVDFSbmQwMUVTWGRuV1hOSFEybHpSMEZSVVVKbk56aDNRVkpKUldaUmVEZGhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkVmt5T1hRS1RESTVkMXBYTkhSa1IxWnpXbGN4YkdSSVNqVk1NamwzV2xjMU1GcFhlR3hpVjFZd1kyNXJkRmt5T1hOaVIxWnFaRWM1ZVV4WVNteGlSMVpvWXpKV2VncE1lVFZ1WVZoU2IyUlhTWFprTWpsNVlUSmFjMkl6WkhwTU0wcHNZa2RXYUdNeVZYUlpNamwxWkVoS2NGbHBOVFZaVnpGelVVaEtiRnB1VFhaa1IwWnVDbU41T1RKTlF6UTFUME0wZDAxRVowZERhWE5IUVZGUlFtYzNPSGRCVWsxRlMyZDNiMDlYVlhsTlIwcHRUbGROZUU1RVNteE9WRTEzVG5wQ2Fsa3lTVFFLVFhwSmQxbFVTWGROZWtVeFdtMWFiVmxxVVhoT1JGazFXbFJCVlVKbmIzSkNaMFZGUVZsUEwwMUJSVlZDUVZsTlFraENNV015WjNka1VWbExTM2RaUWdwQ1FVZEVkbnBCUWtaUlVtNUVSMVp2WkVoU2QyTjZiM1pNTW1Sd1pFZG9NVmxwTldwaU1qQjJZak5DYkdKcE1UQmFWM2hzWWxkV01HTnVhM1ppTTBKc0NtSnVVbXhpUjFaMFdsaFNlV1ZUTVdwaU1uaHpXbGRPTUdJelNYUmpiVlp6V2xkR2VscFlUWFpaVjA0d1lWYzVkV041T1hsa1Z6VjZUSHBuTWs1RVJYZ0tUbnBGTVU1cVkzWlpXRkl3V2xjeGQyUklUWFpOYWtGWFFtZHZja0puUlVWQldVOHZUVUZGVjBKQlowMUNia0l4V1cxNGNGbDZRMEpwWjFsTFMzZFpRZ3BDUVVoWFpWRkpSVUZuVWpoQ1NHOUJaVUZDTWtGT01EbE5SM0pIZUhoRmVWbDRhMlZJU214dVRuZExhVk5zTmpRemFubDBMelJsUzJOdlFYWkxaVFpQQ2tGQlFVSnFjM1JvUlVOUlFVRkJVVVJCUldOM1VsRkpaMWg2Y2xaME0xQjRkU3ROWVZKRkswUkdORzlGUldNMGVucHphSGR1VDJ4bGMwZGlla2xwYnpNS0wxWmpRMGxSUkZNelJ6QmlNemRhYUhRNGFITjJUSEozYkc1UFFXYzJWRXh1U1ZSS09HTjNkMVEzTW5sMVRVdFlUbFJCUzBKblozRm9hMnBQVUZGUlJBcEJkMDV1UVVSQ2EwRnFRWGxFUkZSYVFqQlRPVXBGYkZsSGJuTnZWVmhLYm04MU5Fc3ZUVUZUTlN0RFFVMU9lbWRqUWpWQ2JrRk5OMWhNUjBoV01HRnhDbVpaY21weFkyOXFia3RaUTAxSFRWRnFjalpUVGt0Q2NVaEtZVGwxTDBSTlQySlpNa0pKTVV0ME4yTnhOemhFT0VOcVMzQmFVblJoYnpadFVVMUVZMk1LUms5M2VYWnhWalJPVld0dlpsRTlQUW90TFMwdExVVk9SQ0JEUlZKVVNVWkpRMEZVUlMwdExTMHRDZz09In19fX0=","integratedTime":1712809120,"logIndex":84797936,"logID":"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}},"Issuer":"https://token.actions.githubusercontent.com","Subject":"https://github.com/open-telemetry/opentelemetry-collector-releases/.github/workflows/base-release.yaml@refs/tags/v0.98.0","githubWorkflowName":"Release Contrib","githubWorkflowRef":"refs/tags/v0.98.0","githubWorkflowRepository":"open-telemetry/opentelemetry-collector-releases","githubWorkflowSha":"9e20bf5c142e53070ccb8320a20315fffb41469e","githubWorkflowTrigger":"push"}},{"critical":{"identity":{"docker-reference":"ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector-contrib"},"image":{"docker-manifest-digest":"sha256:5cea85bcbc734a3c0a641368e5a4ea9d31b472997e9f2feca57eeb4a147fcf1a"},"type":"cosign container image signature"},"optional":{"1.3.6.1.4.1.57264.1.1":"https://token.actions.githubusercontent.com","1.3.6.1.4.1.57264.1.2":"push","1.3.6.1.4.1.57264.1.3":"9e20bf5c142e53070ccb8320a20315fffb41469e","1.3.6.1.4.1.57264.1.4":"Release Contrib","1.3.6.1.4.1.57264.1.5":"open-telemetry/opentelemetry-collector-releases","1.3.6.1.4.1.57264.1.6":"refs/tags/v0.98.0","Bundle":{"SignedEntryTimestamp":"MEUCIQD1ehDnPO6fzoPIpeQ3KFuYHHBiX7RcEbpo9B2r7JAlzwIgZ1bsuQz7gAXbNU1IEdsTQgfAnRk3xVXO16GnKXM2sAQ=","Payload":{"body":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiIxMzVjY2RlN2YzZTNhYjU2NmFmYzJhYWU3MDljYmJlNmFhMDZlZWMzNDA2MWNkZjMyNmRhYzM2MmY0NWM4Yjg4In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FUUNJRU92QXl0aE5RVGNvNHFMdG9GZUVOV0toNCtEK2I5SUxyYWhoa09WMmVBM0FpQjNEL2FpUGd1T05zUlB5alhaWk1hdnlCam0vMkVxNFNUMkZJWHozTnpyYWc9PSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVaHBSRU5EUW5jMlowRjNTVUpCWjBsVlZuRlRLMnd4WXpoMWVFUktOWEppZDAxMlVuaDBSR3hXVW1nMGQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFJkMDVFUlhoTlJGRjRUMFJSZVZkb1kwNU5hbEYzVGtSRmVFMUVVWGxQUkZGNVYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVYyWlRCdGJrRkdRVzl1TVZoUGRIVlRMMXBNT0djeE5YUlJkVmxPTmtRemVUUlBWM0FLT1ZSTFMwUlVkRkJHU2xST1ZrWlJkVTlKUWs1bVJqWk1ORTlGYkd4dlZuUndaSE5uYjB0NVZGTnlPR3hTV1c1S1JIRlBRMEpwTUhkbloxbHdUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZDSzFkSENuVmtlRE5IZUcxS1RWUkpUVVJyYW13clJtdzFXRzkzZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDJkWldVZEJNVlZrUlZGRlFpOTNVamhOU0hGSFpVZG9NR1JJUW5wUGFUaDJXakpzTUdGSVZtbE1iVTUyWWxNNWRtTkhWblZNV0ZKc1lrZFdkQXBhV0ZKNVpWTTVkbU5IVm5Wa1IxWnpXbGN4YkdSSVNqVk1WMDUyWWtkNGJGa3pVblpqYVRGNVdsZDRiRmxZVG14amVUaDFXakpzTUdGSVZtbE1NMlIyQ21OdGRHMWlSemt6WTNrNWFWbFlUbXhNV0Vwc1lrZFdhR015VlhWbFYwWjBZa1ZDZVZwWFducE1NMUpvV2pOTmRtUnFRWFZQVkdkMVRVUkJOVUpuYjNJS1FtZEZSVUZaVHk5TlFVVkNRa04wYjJSSVVuZGplbTkyVEROU2RtRXlWblZNYlVacVpFZHNkbUp1VFhWYU1td3dZVWhXYVdSWVRteGpiVTUyWW01U2JBcGlibEYxV1RJNWRFMUNTVWREYVhOSFFWRlJRbWMzT0hkQlVVbEZRa2hDTVdNeVozZE9aMWxMUzNkWlFrSkJSMFIyZWtGQ1FYZFJiMDlYVlhsTlIwcHRDazVYVFhoT1JFcHNUbFJOZDA1NlFtcFpNa2swVFhwSmQxbFVTWGROZWtVeFdtMWFiVmxxVVhoT1JGazFXbFJCWkVKbmIzSkNaMFZGUVZsUEwwMUJSVVVLUWtFNVUxcFhlR3haV0U1c1NVVk9kbUp1VW5saFYwbDNVRkZaUzB0M1dVSkNRVWRFZG5wQlFrSlJVWFppTTBKc1lta3hNRnBYZUd4aVYxWXdZMjVyZGdwaU0wSnNZbTVTYkdKSFZuUmFXRko1WlZNeGFtSXllSE5hVjA0d1lqTkpkR050Vm5OYVYwWjZXbGhOZDBoM1dVdExkMWxDUWtGSFJIWjZRVUpDWjFGU0NtTnRWbTFqZVRrd1dWZGtla3d6V1hkTWFtczBUR3BCZDA5M1dVdExkMWxDUWtGSFJIWjZRVUpEUVZGMFJFTjBiMlJJVW5kamVtOTJURE5TZG1FeVZuVUtURzFHYW1SSGJIWmliazExV2pKc01HRklWbWxrV0U1c1kyMU9kbUp1VW14aWJsRjFXVEk1ZEUxSlIwbENaMjl5UW1kRlJVRlpUeTlOUVVWS1FraHZUUXBsUjJnd1pFaENlazlwT0haYU1td3dZVWhXYVV4dFRuWmlVemwyWTBkV2RVeFlVbXhpUjFaMFdsaFNlV1ZUT1haalIxWjFaRWRXYzFwWE1XeGtTRW8xQ2t4WFRuWmlSM2hzV1ROU2RtTnBNWGxhVjNoc1dWaE9iR041T0hWYU1td3dZVWhXYVV3elpIWmpiWFJ0WWtjNU0yTjVPV2xaV0U1c1RGaEtiR0pIVm1nS1l6SlZkV1ZYUm5SaVJVSjVXbGRhZWt3elVtaGFNMDEyWkdwQmRVOVVaM1ZOUkVFMFFtZHZja0puUlVWQldVOHZUVUZGUzBKRGIwMUxSR3hzVFdwQ2FRcGFhbFpxVFZSUmVWcFVWWHBOUkdOM1dUSk9hVTlFVFhsTlIwVjVUVVJOZUU1WFdtMWFiVWt3VFZSUk1rOVhWWGRJVVZsTFMzZFpRa0pCUjBSMmVrRkNDa04zVVZCRVFURnVZVmhTYjJSWFNYUmhSemw2WkVkV2EwMUdTVWREYVhOSFFWRlJRbWMzT0hkQlVYZEZVa0Y0UTJGSVVqQmpTRTAyVEhrNWJtRllVbThLWkZkSmRWa3lPWFJNTWpsM1dsYzBkR1JIVm5OYVZ6RnNaRWhLTlV3eU9YZGFWelV3V2xkNGJHSlhWakJqYm10MFdUSTVjMkpIVm1wa1J6bDVURmhLYkFwaVIxWm9ZekpXZWsxRVowZERhWE5IUVZGUlFtYzNPSGRCVVRCRlMyZDNiMDlYVlhsTlIwcHRUbGROZUU1RVNteE9WRTEzVG5wQ2Fsa3lTVFJOZWtsM0NsbFVTWGROZWtVeFdtMWFiVmxxVVhoT1JGazFXbFJCYUVKbmIzSkNaMFZGUVZsUEwwMUJSVTlDUWsxTlJWaEtiRnB1VFhaa1IwWnVZM2s1TWsxRE5EVUtUME0wZDAxQ2EwZERhWE5IUVZGUlFtYzNPSGRCVVRoRlEzZDNTazVFUVhkTmFsVjZUbXBqTWsxRVJVZERhWE5IUVZGUlFtYzNPSGRCVWtGRlNYZDNhQXBoU0ZJd1kwaE5Oa3g1T1c1aFdGSnZaRmRKZFZreU9YUk1NamwzV2xjMGRHUkhWbk5hVnpGc1pFaEtOVTFDWjBkRGFYTkhRVkZSUW1jM09IZEJVa1ZGQ2tObmQwbE9SR3MxVDFSbmQwMUVTWGRuV1hOSFEybHpSMEZSVVVKbk56aDNRVkpKUldaUmVEZGhTRkl3WTBoTk5reDVPVzVoV0ZKdlpGZEpkVmt5T1hRS1RESTVkMXBYTkhSa1IxWnpXbGN4YkdSSVNqVk1NamwzV2xjMU1GcFhlR3hpVjFZd1kyNXJkRmt5T1hOaVIxWnFaRWM1ZVV4WVNteGlSMVpvWXpKV2VncE1lVFZ1WVZoU2IyUlhTWFprTWpsNVlUSmFjMkl6WkhwTU0wcHNZa2RXYUdNeVZYUlpNamwxWkVoS2NGbHBOVFZaVnpGelVVaEtiRnB1VFhaa1IwWnVDbU41T1RKTlF6UTFUME0wZDAxRVowZERhWE5IUVZGUlFtYzNPSGRCVWsxRlMyZDNiMDlYVlhsTlIwcHRUbGROZUU1RVNteE9WRTEzVG5wQ2Fsa3lTVFFLVFhwSmQxbFVTWGROZWtVeFdtMWFiVmxxVVhoT1JGazFXbFJCVlVKbmIzSkNaMFZGUVZsUEwwMUJSVlZDUVZsTlFraENNV015WjNka1VWbExTM2RaUWdwQ1FVZEVkbnBCUWtaUlVtNUVSMVp2WkVoU2QyTjZiM1pNTW1Sd1pFZG9NVmxwTldwaU1qQjJZak5DYkdKcE1UQmFWM2hzWWxkV01HTnVhM1ppTTBKc0NtSnVVbXhpUjFaMFdsaFNlV1ZUTVdwaU1uaHpXbGRPTUdJelNYUmpiVlp6V2xkR2VscFlUWFpaVjA0d1lWYzVkV041T1hsa1Z6VjZUSHBuTWs1RVJYZ0tUbnBGTVU1cVkzWlpXRkl3V2xjeGQyUklUWFpOYWtGWFFtZHZja0puUlVWQldVOHZUVUZGVjBKQlowMUNia0l4V1cxNGNGbDZRMEpwZDFsTFMzZFpRZ3BDUVVoWFpWRkpSVUZuVWpsQ1NITkJaVkZDTTBGT01EbE5SM0pIZUhoRmVWbDRhMlZJU214dVRuZExhVk5zTmpRemFubDBMelJsUzJOdlFYWkxaVFpQQ2tGQlFVSnFjM1JvUjJKSlFVRkJVVVJCUldkM1VtZEphRUZQZUZNM2RteDRjVzVGYTBKVVRtSlZVRUpsUkZSbk0waGtlRlkyY0cxWk9FdGliREV6TjNBS1lWUnViMEZwUlVFelMyMUxVbU5uYWxBeVQzSmxORVpyVm5vNU4xaENNWGRsUzBOeWFXazFTMWx2UTB0bVkxRktSREJSZDBObldVbExiMXBKZW1vd1JRcEJkMDFFWVVGQmQxcFJTWGhCUzNwcVpHMUZTV2gzV21Kb1lVSlNlalk1Y1N0MWVrNVZSMmxhYlRWVk4xcE5aWFJMUTFSM1VFTkljRkZQVldvdlVERkJDa2R0YWt3elJucFFObTVpYkRGblNYZFNUbXN6UkhkNWMwOUJUMHhoUVVoR09IaHhZV0ZzT0U5WGNGRmFhRGh4TTJVMVNVSmFXR0ZWVkhocFlWbGFTM29LUXpWS1RGVlNWbnBMTURsd04wVjBUd290TFMwdExVVk9SQ0JEUlZKVVNVWkpRMEZVUlMwdExTMHRDZz09In19fX0=","integratedTime":1712809122,"logIndex":84797940,"logID":"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}},"Issuer":"https://token.actions.githubusercontent.com","Subject":"https://github.com/open-telemetry/opentelemetry-collector-releases/.github/workflows/base-release.yaml@refs/tags/v0.98.0","githubWorkflowName":"Release Contrib","githubWorkflowRef":"refs/tags/v0.98.0","githubWorkflowRepository":"open-telemetry/opentelemetry-collector-releases","githubWorkflowSha":"9e20bf5c142e53070ccb8320a20315fffb41469e","githubWorkflowTrigger":"push"}}]
```
> [!NOTE]
> We started signing the images with release `v0.95.0`
## Contributing
See the [Contributing Guide](CONTRIBUTING.md) for details.
Here is a list of community roles with current and previous members:
### Maintainers
- [Alex Boten](https://github.com/codeboten), Honeycomb
- [Bogdan Drutu](https://github.com/bogdandrutu), Snowflake
- [Dmitrii Anoshin](https://github.com/dmitryax), Splunk
- [Pablo Baeyens](https://github.com/mx-psi), DataDog
For more information about the maintainer role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#maintainer).
### Approvers
- [Andrew Wilkins](https://github.com/axw), Elastic
- [Antoine Toulme](https://github.com/atoulme), Splunk
- [Damien Mathieu](https://github.com/dmathieu), Elastic
- [Evan Bradley](https://github.com/evan-bradley), Dynatrace
- [Jade Guiton](https://github.com/jade-guiton-dd), Datadog
- [Joshua MacDonald](https://github.com/jmacd), Microsoft
- [Tyler Helmuth](https://github.com/TylerHelmuth), Honeycomb
- [Yang Song](https://github.com/songy23), Datadog
For more information about the approver role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#approver).
In addition to what is described at the organization-level, the SIG Collector requires all core approvers to take part in rotating
the role of the [release manager](./docs/release.md#release-manager).
### Triagers
- [Andrzej Stencel](https://github.com/andrzej-stencel), Elastic
- [Arthur Silva Sens](https://github.com/ArthurSens), Grafana Labs
- [Chao Weng](https://github.com/sincejune), AppDynamics
- [Vihas Makwana](https://github.com/VihasMakwana), Elastic
- Actively seeking contributors to triage issues
For more information about the triager role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#triager).
### Emeritus Maintainers
- [Paulo Janotti](https://github.com/pjanotti)
- [Tigran Najaryan](https://github.com/tigrannajaryan)
For more information about the emeritus role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#emeritus-maintainerapprovertriager).
### Emeritus Approvers
- [Anthony Mirabella](https://github.com/Aneurysm9)
- [Daniel Jaglowski](https://github.com/djaglowski)
- [James Bebbington](https://github.com/james-bebbington)
- [Jay Camp](https://github.com/jrcamp)
- [Juraci Paixão Kröhling](https://github.com/jpkrohling)
- [Nail Islamov](https://github.com/nilebox)
- [Owais Lone](https://github.com/owais)
- [Rahul Patel](https://github.com/rghetia)
- [Steven Karis](https://github.com/sjkaris)
For more information about the emeritus role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#emeritus-maintainerapprovertriager).
### Emeritus Triagers
- [Alolita Sharma](https://github.com/alolita)
- [Andrew Hsu](https://github.com/andrewhsu)
- [Punya Biswal](https://github.com/punya)
- [Steve Flanders](https://github.com/flands)
For more information about the emeritus role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#emeritus-maintainerapprovertriager).
### Thanks to all of our contributors!
================================================
FILE: VERSIONING.md
================================================
# Versioning and stability
The OpenTelemetry Collector SIG produces several artifacts for [a variety of audiences](CONTRIBUTING.md#target-audiences). This document describes the versioning and support policy for these artifacts. These policies are designed so that the following goal can be achieved:
**Users are provided software artifacts of value that are stable and secure.**
The policies are divided depending on the artifact's target audience. While an artifact is supported, [critical bugs](docs/release.md#bugfix-release-criteria) and security vulnerabilities MUST be addressed. The main criteria for the length of support for an artifact is how easy it is for an artifact's target audience to adapt to disruptive changes.
These policies reflect the current consensus of the OpenTelemetry Collector SIG. They are subject to change as the project evolves.
## Software artifacts for end users
Software artifacts intended for [end users](CONTRIBUTING.md#end-users) of the OpenTelemetry Collector include
- Binary distributions of the OpenTelemetry Collector.
- Go modules that expose Collector components, such as receivers, processors, connectors, extensions and exporters.
These artifacts are versioned according to the [semantic versioning v2.0.0](https://semver.org/) specification.
### General considerations
Binary distributions produced by the Collector SIG contain components and features with varying [levels of stability](README.md#stability-levels). We abide by the following principles to relate the Collector's version to the stability of its components and features:
* The Collector's core framework behavior MUST be stable in order for a Collector distribution to be v1.0.0 or higher.
* Users can easily understand when they are opting in to use a component or feature that is not stable.
* The Collector MUST be configurable so that unstable components or features can be excluded ensuring that a fully stable configuration is possible.
* The Collector's telemetry (e.g. Collector logs) MUST provide the ability to identify usage of unstable components or features.
### Long-term support after v1
The OpenTelemetry Collector SIG provides long-term support for stable binary distributions of the OpenTelemetry Collector and its components. The following policies apply to long-term support for any major version starting on v1:
* A binary distribution of the OpenTelemetry Collector MUST be supported for a minimum of **one year** after the release of the next major version of said distribution.
* Components MUST be supported for a minimum of **6 months** after the release of the next major version of said component or after the component has been marked as deprecated. If a component has been deprecated for 6 months it MAY be removed from a binary distribution of the OpenTelemetry Collector. This does not imply a major version change in the Collector distribution.
## Go modules
Go modules are intended to be used by [component developers](CONTRIBUTING.md#component-developers) and [Collector library users](CONTRIBUTING.md#collector-library-users) of the OpenTelemetry Collector
Unless otherwise specified, the following public API expectations apply to all modules in opentelemetry-collector and opentelemetry-collector-contrib.
As a general rule, stability guarantees of modules versioned as `v1` or higher are aligned with [Go 1 compatibility promise](https://go.dev/doc/go1compat).
### General Go API considerations
OpenTelemetry authors reserve the right to introduce API changes breaking compatibility between minor versions in the following scenarios:
* **Struct literals.** It may be necessary to add new fields to exported structs in the API. Code that uses unkeyed
struct literals (such as pkg.T{3, "x"}) to create values of these types would fail to compile after such a change.
However, code that uses keyed literals (pkg.T{A: 3, B: "x"}) will continue to compile. We therefore recommend
using OpenTelemetry collector structs with the keyed literals only.
* **Methods.** As with struct fields, it may be necessary to add methods to types. Under some circumstances,
such as when the type is embedded in a struct along with another type, the addition of the new method may
break the struct by creating a conflict with an existing method of the other embedded type. We cannot protect
against this rare case and do not guarantee compatibility in such scenarios.
* **Dot imports.** If a program imports a package using `import .`, additional names defined in the imported package
in future releases may conflict with other names defined in the program. We do not recommend the use of
`import .` with OpenTelemetry Collector modules.
Unless otherwise specified in the documentation, the following may change in any way between minor versions:
* **String representation**. The `String` or `Error` method of any struct is intended to be human-readable and may
change its output in any way.
* **Go version compatibility**. Removing support for an unsupported Go version is not considered a breaking change.
* **OS version compatibility**. Removing support for an unsupported OS version is not considered a breaking change. Upgrading or downgrading OS version support per the [platform support](docs/platform-support.md) document is not considered a breaking change.
* **Protocol compatibility**. Changing the default minimum version of a supported protocol (e.g. TLS) or dropping support for protocols when there are security concerns is not considered a breaking change.
* **Dependency updates**. Updating dependencies is not considered a breaking change except when their types are part of the
public API or the update may change the behavior of applications in an incompatible way.
* **Underlying type for interfaces**. If a struct exported as an interface has an experimental
method, this method may change or be removed in a minor version. The method will be published in an
optional interface under an experimental module to signal it is experimental.
### Configuration structures
Configuration structures are part of the public API and backwards
compatibility should be maintained through any changes made to configuration structures.
Unless otherwise specified in the documentation, the following may change in any way between minor versions:
* **Adding new fields to configuration structures**. Because configuration structures are typically instantiated through
unmarshalling a serialized representation of the structure, and not through structure literals, additive changes to
the set of exported fields in a configuration structure are not considered to break backward compatibility.
* **Relaxing validation rules**. An invalid configuration struct as defined by its `Validate` method return value
may become valid after a change to the validation rules.
The following are explicitly considered to be breaking changes:
* **Modifying struct tags related to serialization**. Struct tags used to configure serialization mechanisms (`yaml:`,
`mapstructure:`, etc) are part of the structure definition and must maintain compatibility to the same extent as the
structure. However, changes are allowed when tag modifications produce a
functionally-equivalent result when serializing or deserializing the structure.
For example, adding a tag to a field so it will not be emitted during serialization
if it has a default value would not alter its value if the serialized representation
were again deserialized, so such a change would be permitted.
* **Making validation rules more strict**. A valid configuration struct as defined by its `Validate` method return value
must continue to be valid after a change to the validation rules, except when the configuration struct would cause an error
on its intended usage (e.g. when calling a method or when passed to any method or function in any module under opentelemetry-collector).
### Module versioning and schema
* Versioning of this project will be idiomatic of a Go project using [Go
modules](https://golang.org/ref/mod#versions).
* [Semantic import
versioning](https://github.com/golang/go/wiki/Modules#semantic-import-versioning)
will be used.
* Versions will comply with [semver 2.0](https://semver.org/spec/v2.0.0.html).
* If a module is version `v2` or higher, the major version of the module
must be included as a `/vN` at the end of the module paths used in
`go.mod` files (e.g., `module go.opentelemetry.io/collector/v2`, `require
go.opentelemetry.io/collector/v2 v2.0.1`) and in the package import path
(e.g., `import "go.opentelemetry.io/collector/v2/component"`). This includes the
paths used in `go get` commands (e.g., `go get
go.opentelemetry.io/collector/v2@v2.0.1`. Note there is both a `/v2` and a
`@v2.0.1` in that example. One way to think about it is that the module
name now includes the `/v2`, so include `/v2` whenever you are using the
module name).
* If a module is version `v0` or `v1`, do not include the major version in
either the module path or the import path.
* Semantic convention packages will contain a complete version identifier in their
import path to enable concurrent use of multiple convention versions in a single
application. This identifies the version of the specification used to generate
the package and is not related to the version of the module containing the package.
* A single module should exist, rooted at the top level of this repository,
that contains all packages provided for use outside this repository.
* Additional modules may be created in this repository to provide for
isolation of build-time tools, other commands or independent libraries. Such modules should be
versioned in sync with the `go.opentelemetry.io/collector` module.
* Experimental modules still under active development will be versioned with a major
version of `v0` to imply the stability guarantee defined by
[semver](https://semver.org/spec/v2.0.0.html#spec-item-4).
> Major version zero (0.y.z) is for initial development. Anything MAY
> change at any time. The public API SHOULD NOT be considered stable.
* Versioning of the associated [contrib
repository](https://github.com/open-telemetry/opentelemetry-collector-contrib) of
this project will be idiomatic of a Go project using [Go
modules](https://golang.org/ref/mod#versions).
* [Semantic import
versioning](https://github.com/golang/go/wiki/Modules#semantic-import-versioning)
will be used.
* Versions will comply with [semver 2.0](https://semver.org/spec/v2.0.0.html).
* If a module is version `v2` or higher, the
major version of the module must be included as a `/vN` at the end of the
module paths used in `go.mod` files (e.g., `module
github.com/open-telemetry/opentelemetry-collector-contrib/processor/k8sprocessor/v2`, `require
github.com/open-telemetry/opentelemetry-collector-contrib/processor/k8sprocessor/v2 v2.0.1`) and in the
package import path (e.g., `import
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/k8sprocessor/v2"`). This includes
the paths used in `go get` commands (e.g., `go get
github.com/open-telemetry/opentelemetry-collector-contrib/processor/k8sprocessor/v2@v2.0.1`. Note there
is both a `/v2` and a `@v2.0.1` in that example. One way to think about
it is that the module name now includes the `/v2`, so include `/v2`
whenever you are using the module name).
* If a module is version `v0` or `v1`, do not include the major version
in either the module path or the import path.
* Modules will be used to encapsulate receivers, processors, exporters,
extensions, connectors and any other independent sets of related components.
* Experimental modules still under active development will be versioned with a major
version of `v0` to imply the stability guarantee defined by
[semver](https://semver.org/spec/v2.0.0.html#spec-item-4).
> Major version zero (0.y.z) is for initial development. Anything MAY
> change at any time. The public API SHOULD NOT be considered stable.
* Experimental modules will start their versioning at `v0.0.0` and will
increment their minor version when backwards incompatible changes are
released and increment their patch version when backwards compatible
changes are released.
* Mature modules for which we guarantee a stable public API will
be versioned with a major version of `v1` or greater.
* All stable contrib modules of the same major version with this project
will use the same entire version.
* Stable modules may be released with an incremented minor or patch
version even though that module's code has not been changed. Instead
the only change that will have been included is to have updated that
modules dependency on this project's stable APIs.
* Contrib modules will be kept up to date with this project's releases.
* GitHub releases will be made for all releases.
* Go modules will be made available at Go package mirrors.
### Long-term support after v1
The OpenTelemetry Collector SIG provides long-term support for stable Go modules. Support for modules depend on the module's [target audiences](CONTRIBUTING.md#target-audiences). The following policies apply to long-term support for any major version starting on v1:
- Modules intended for **component developers** MUST be supported for a minimum of **1 year** after the release of the next major version of said module or after the module has been marked as deprecated.
- Modules intended for **Collector library users** MUST be supported for a minimum of **6 months** after the release of the next major version of said module or after the module has been marked as deprecated.
================================================
FILE: client/Makefile
================================================
include ../Makefile.Common
================================================
FILE: client/client.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package client contains generic representations of clients connecting to
// different receivers. Components, such as processors or exporters, can make
// use of this information to make decisions related to grouping of batches,
// tenancy, load balancing, tagging, among others.
//
// The structs defined here are typically used within the context that is
// propagated down the pipeline, with the values being produced by
// authenticators and/or receivers, and consumed by processors and exporters.
//
// # Producers
//
// Receivers are responsible for obtaining a client.Info from the current
// context and enhancing the client.Info with the net.Addr from the peer,
// storing a new client.Info into the context that it passes down. For HTTP
// requests, the net.Addr is typically the IP address of the client.
//
// Typically, however, receivers would delegate this processing to helpers such
// as the confighttp or configgrpc packages: both contain interceptors that will
// enhance the context with the client.Info, such that no actions are needed by
// receivers that are built using confighttp.HTTPServerSettings or
// configgrpc.GRPCServerSettings.
//
// Authenticators are responsible for obtaining a client.Info from the current
// context, enhancing the client.Info with an implementation of client.AuthData,
// and storing a new client.Info into the context that it passes down. The
// attribute names should be documented with their return types and considered
// part of the public API for the authenticator.
//
// # Consumers
//
// Provided that the pipeline does not contain processors that would discard or
// rewrite the context, such as the batch processor, processors and exporters
// have access to the client.Info via client.FromContext. Among other usages,
// this data can be used to:
//
// - annotate data points with authentication data (username, tenant, ...)
//
// - route data points based on authentication data
//
// - rate limit client calls based on IP addresses
//
// Processors and exporters relying on the existence of data from the
// client.Info, especially client.AuthData, should clearly document this as part
// of the component's README file. The expected pattern for consuming data is to
// allow users to specify the attribute name to use in the component. The
// expected data type should also be communicated to users, who should then
// compare this with the authenticators that are part of the pipeline. For
// example, assuming that the OIDC authenticator pushes a "subject" string
// attribute and that we have a hypothetical "authprinter" processor that prints
// the "username" to the console, this is how an OpenTelemetry Collector
// configuration would look like:
//
// extensions:
// oidc:
// issuer_url: http://localhost:8080/auth/realms/opentelemetry
// audience: collector
// receivers:
// otlp:
// protocols:
// grpc:
// auth:
// authenticator: oidc
// processors:
// authprinter:
// attribute: subject
// exporters:
// debug:
// service:
// extensions: [oidc]
// pipelines:
// traces:
// receivers: [otlp]
// processors: [authprinter]
// exporters: [debug]
package client // import "go.opentelemetry.io/collector/client"
import (
"context"
"iter"
"maps"
"net"
"strings"
)
type ctxKey struct{}
// Info contains data related to the clients connecting to receivers.
type Info struct {
// Addr for the client connecting to this collector. Available in a
// best-effort basis, and generally reliable for receivers making use of
// confighttp.ToServer and configgrpc.ToServerOption.
Addr net.Addr
// Auth information from the incoming request as provided by
// configauth.ServerAuthenticator implementations tied to the receiver for
// this connection.
Auth AuthData
// Metadata is the request metadata from the client connecting to this connector.
Metadata Metadata
// prevent unkeyed literal initialization
_ struct{}
}
// AuthData represents the authentication data as seen by authenticators tied to
// the receivers.
type AuthData interface {
// GetAttribute returns the value for the given attribute. Authenticator
// implementations might define different data types for different
// attributes. While "string" is used most of the time, a key named
// "membership" might return a list of strings.
GetAttribute(string) any
// GetAttributeNames returns the names of all attributes in this authentication data.
GetAttributeNames() []string
}
const MetadataHostName = "Host"
// NewContext takes an existing context and derives a new context with the
// client.Info value stored on it.
func NewContext(ctx context.Context, c Info) context.Context {
return context.WithValue(ctx, ctxKey{}, c)
}
// FromContext takes a context and returns a ClientInfo from it.
// When a ClientInfo isn't present, a new empty one is returned.
func FromContext(ctx context.Context) Info {
c, ok := ctx.Value(ctxKey{}).(Info)
if !ok {
c = Info{}
}
return c
}
// Metadata is an immutable map, meant to contain request metadata.
type Metadata struct {
data map[string][]string
}
// NewMetadata creates a new Metadata object to use in Info.
func NewMetadata(md map[string][]string) Metadata {
c := make(map[string][]string, len(md))
for k, v := range md {
c[strings.ToLower(k)] = v
}
return Metadata{
data: c,
}
}
// Keys returns an iterator for the metadata keys.
func (m Metadata) Keys() iter.Seq[string] {
return maps.Keys(m.data)
}
// Get gets the value of the key from metadata, returning a copy.
// The key lookup is case-insensitive.
func (m Metadata) Get(key string) []string {
if len(m.data) == 0 {
return nil
}
vals := m.data[strings.ToLower(key)]
if len(vals) == 0 {
return nil
}
ret := make([]string, len(vals))
copy(ret, vals)
return ret
}
================================================
FILE: client/client_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package client contains generic representations of clients connecting to
// different receivers
package client
import (
"context"
"net"
"slices"
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewContext(t *testing.T) {
testCases := []struct {
name string
cl Info
}{
{
name: "valid client",
cl: Info{
Addr: &net.IPAddr{
IP: net.IPv4(1, 2, 3, 4),
},
},
},
{
name: "nil client",
cl: Info{},
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
ctx := NewContext(context.Background(), tt.cl)
assert.Equal(t, ctx.Value(ctxKey{}), tt.cl)
})
}
}
func TestFromContext(t *testing.T) {
testCases := []struct {
name string
input context.Context
expected Info
}{
{
name: "context with client",
input: context.WithValue(context.Background(), ctxKey{}, Info{
Addr: &net.IPAddr{
IP: net.IPv4(1, 2, 3, 4),
},
}),
expected: Info{
Addr: &net.IPAddr{
IP: net.IPv4(1, 2, 3, 4),
},
},
},
{
name: "context without client",
input: context.Background(),
expected: Info{},
},
{
name: "context with something else in the key",
input: context.WithValue(context.Background(), ctxKey{}, "unexpected!"),
expected: Info{},
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, FromContext(tt.input))
})
}
}
func TestMetadata(t *testing.T) {
source := map[string][]string{"test-key": {"test-val"}, "TEST-KEY-2": {"test-val"}}
md := NewMetadata(source)
assert.Equal(t, []string{"test-key", "test-key-2"}, slices.Sorted(md.Keys()))
assert.Equal(t, []string{"test-val"}, md.Get("test-key"))
assert.Equal(t, []string{"test-val"}, md.Get("test-KEY")) // case insensitive lookup
assert.Equal(t, []string{"test-val"}, md.Get("test-key-2")) // case insensitive lookup
// test if copy. In regular use, source cannot change
val := md.Get("test-key")
source["test-key"][0] = "abc"
assert.Equal(t, []string{"test-val"}, val)
assert.Empty(t, md.Get("non-existent-key"))
}
func TestUninstantiatedMetadata(t *testing.T) {
i := Info{}
assert.Empty(t, slices.Collect(i.Metadata.Keys()))
assert.Empty(t, i.Metadata.Get("test"))
}
================================================
FILE: client/doc_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package client_test
import (
"context"
"fmt"
"net"
"go.opentelemetry.io/collector/client"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/pdata/ptrace"
)
func Example_receiver() {
// Your receiver get a next consumer when it's constructed
next, err := consumer.NewTraces(func(_ context.Context, _ ptrace.Traces) error {
return nil
})
if err != nil {
panic(err)
}
// You'll convert the incoming data into pipeline data
td := ptrace.NewTraces()
// You probably have a context with client metadata from your listener or
// scraper
ctx := context.Background()
// Get the client from the context: if it doesn't exist, FromContext will
// create one
cl := client.FromContext(ctx)
// Extract the client information based on your original context and set it
// to Addr
//nolint:govet
cl.Addr = &net.IPAddr{
IP: net.IPv4(1, 2, 3, 4),
}
// When you are done, propagate the context down the pipeline to the next
// consumer and handle error.
if err = next.ConsumeTraces(ctx, td); err != nil {
panic(err)
}
}
func Example_processor() {
// Your processor or exporter will receive a context, from which you get the
// client information
ctx := context.Background()
cl := client.FromContext(ctx)
// And use the information from the client as you need
fmt.Println(cl.Addr)
}
func Example_authenticator() {
// Your configauth.AuthenticateFunc receives a context
ctx := context.Background()
// Get the client from the context: if it doesn't exist, FromContext will
// create one
cl := client.FromContext(ctx)
// After a successful authentication, place the data you want to propagate
// as part of an AuthData implementation of your own
cl.Auth = &exampleAuthData{
username: "jdoe",
}
// Your configauth.AuthenticateFunc should return this new context
_ = client.NewContext(ctx, cl)
}
type exampleAuthData struct {
username string
}
func (e *exampleAuthData) GetAttribute(key string) any {
if key == "username" {
return e.username
}
return nil
}
func (e *exampleAuthData) GetAttributeNames() []string {
return []string{"username"}
}
================================================
FILE: client/go.mod
================================================
module go.opentelemetry.io/collector/client
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/consumer v1.54.0
go.opentelemetry.io/collector/pdata v1.54.0
go.uber.org/goleak v1.3.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/consumer => ../consumer
replace go.opentelemetry.io/collector/pdata => ../pdata
replace go.opentelemetry.io/collector/featuregate => ../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../internal/testutil
================================================
FILE: client/go.sum
================================================
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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: client/metadata.yaml
================================================
type: client
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: client/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package client
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: cmd/builder/Makefile
================================================
include ../../Makefile.Common
.PHONY: ocb
ocb:
CGO_ENABLED=0 $(GOCMD) build -trimpath -o ../../bin/ocb_$(GOOS)_$(GOARCH) .
# Generate the default build config from otelcorecol, by removing the
# "replaces" stanza, which is assumed to be at the end of the file.
#
# The default config file is checked in so that `go install` will work
# and so that non-unix builds don't need sed to be installed.
.PHONY: config
config: internal/config/default.yaml
sed '-e/replaces:/,$$d' <../otelcorecol/builder-config.yaml > internal/config/default.yaml
================================================
FILE: cmd/builder/README.md
================================================
# OpenTelemetry Collector Builder (ocb)
This program generates a custom OpenTelemetry Collector binary based on a given configuration.
## TL;DR
```console
$ go install go.opentelemetry.io/collector/cmd/builder@v0.129.0
$ cat > otelcol-builder.yaml < /tmp/otelcol.yaml < github.com/open-telemetry/opentelemetry-collector-contrib/internal/common v0.128.0
```
The builder also allows setting the scheme to use as the default URI scheme via `conf_resolver.default_uri_scheme`:
```yaml
conf_resolver:
default_uri_scheme: "env"
```
This tells the builder to produce a Collector that uses the `env` scheme when expanding configuration that does not
provide a scheme, such as `${HOST}` (instead of doing `${env:HOST}`).
## Steps
The builder has 3 steps:
* Generate: generates the golang source code
* Get modules: generates the go.mod file based on the imported modules in the generated golang source code
* Compilation: builds the OpenTelemetry Collector executable
Each step can be skipped independently: `--skip-generate`, `--skip-get-modules` and `--skip-compilation`.
For instance, a code generation step could execute
```console
ocb --skip-compilation --config=config.yaml
```
then commit the code in a git repo. A CI can sync the code and execute
```console
ocb --skip-generate --skip-get-modules --config=config.yaml
```
to only execute the compilation step.
### Strict versioning checks
The builder checks the relevant `go.mod`
file for the following things after `go get`ing all components and calling
`go mod tidy`:
1. The `dist::otelcol_version` field in the build configuration must have
matching major and minor versions as the core library version calculated by
the Go toolchain, considering all components. A mismatch could happen, for
example, when the builder or one of the components depends on a newer release
of the core collector library.
2. For each component in the build configuration, the major and minor versions
included in the `gomod` module specifier must match the one calculated by
the Go toolchain, considering all components. A mismatch could
happen, for example, when the enclosing Go module uses a newer
release of the core collector library.
The `--skip-strict-versioning` flag disables these versioning checks.
This flag is available temporarily and
**will be removed in a future minor version**.
### Cgo disabled by default
By default, the OpenTelemetry Collector binary is built with `CGO_ENABLED=0` in accordance with
how the official OpenTelemetry Collector releases are built. This can be overridden by adding
the following configuration option to the `dist` section of the builder configuration file:
```yaml
dist:
cgo_enabled: true
```
================================================
FILE: cmd/builder/RELEASE.md
================================================
# Releasing the OpenTelemetry Collector Builder
This project uses [`goreleaser`](https://github.com/goreleaser/goreleaser) to manage the release of new versions.
To release a new version, simply add a tag named `vX.Y.Z`, like:
```
git tag -a v0.1.1 -m "Release v0.1.1"
git push upstream v0.1.1
```
A new GitHub workflow should be started, and at the end, a GitHub release should have been created, similar with the ["Release v0.56.0"](https://github.com/open-telemetry/opentelemetry-collector/releases/tag/cmd%2Fbuilder%2Fv0.56.0).
================================================
FILE: cmd/builder/go.mod
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
module go.opentelemetry.io/collector/cmd/builder
go 1.25.0
require (
github.com/knadh/koanf/parsers/yaml v1.1.0
github.com/knadh/koanf/providers/env/v2 v2.0.0
github.com/knadh/koanf/providers/file v1.2.1
github.com/knadh/koanf/providers/fs v1.0.0
github.com/knadh/koanf/v2 v2.3.3
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
github.com/stretchr/testify v1.11.1
go.uber.org/goleak v1.3.0
go.uber.org/multierr v1.11.0
go.uber.org/zap v1.27.1
golang.org/x/mod v0.33.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/sys v0.41.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
retract (
v0.76.0 // Depends on retracted pdata v1.0.0-rc10 module, use v0.76.1
v0.69.0 // Release failed, use v0.69.1
v0.57.1 // Release failed, use v0.57.2
v0.57.0 // Release failed, use v0.57.2
)
================================================
FILE: cmd/builder/go.sum
================================================
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/parsers/yaml v1.1.0 h1:3ltfm9ljprAHt4jxgeYLlFPmUaunuCgu1yILuTXRdM4=
github.com/knadh/koanf/parsers/yaml v1.1.0/go.mod h1:HHmcHXUrp9cOPcuC+2wrr44GTUB0EC+PyfN3HZD9tFg=
github.com/knadh/koanf/providers/env/v2 v2.0.0 h1:Ad5H3eun722u+FvchiIcEIJZsZ2M6oxCkgZfWN5B5KY=
github.com/knadh/koanf/providers/env/v2 v2.0.0/go.mod h1:1g01PE+Ve1gBfWNNw2wmULRP0tc8RJrjn5p2N/jNCIc=
github.com/knadh/koanf/providers/file v1.2.1 h1:bEWbtQwYrA+W2DtdBrQWyXqJaJSG3KrP3AESOJYp9wM=
github.com/knadh/koanf/providers/file v1.2.1/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd9uIzg+n9UgthfY+nRA=
github.com/knadh/koanf/providers/fs v1.0.0 h1:tvn4MrduLgdOSUqqEHULUuIcELXf6xDOpH8GUErpYaY=
github.com/knadh/koanf/providers/fs v1.0.0/go.mod h1:FksHET+xXFNDozvj8ZCdom54OnZ6eGKJtC5FhZJKx/8=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
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/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: cmd/builder/header.txt
================================================
Copyright The OpenTelemetry Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: cmd/builder/internal/.gitignore
================================================
# tmp folder used by tests
tmp/
================================================
FILE: cmd/builder/internal/builder/config.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package builder // import "go.opentelemetry.io/collector/cmd/builder/internal/builder"
import (
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"slices"
"strings"
"time"
"go.uber.org/multierr"
"go.uber.org/zap"
)
const (
DefaultBetaOtelColVersion = "v0.148.0"
DefaultStableOtelColVersion = "v1.54.0"
)
// errMissingGoMod indicates an empty gomod field
var errMissingGoMod = errors.New("missing gomod specification for module")
// Config holds the builder's configuration
type Config struct {
Logger *zap.Logger
OtelColVersion string `mapstructure:"-"` // only used be the go.mod template
SkipGenerate bool `mapstructure:"-"`
SkipCompilation bool `mapstructure:"-"`
SkipGetModules bool `mapstructure:"-"`
SkipStrictVersioning bool `mapstructure:"-"`
LDFlags string `mapstructure:"-"`
LDSet bool `mapstructure:"-"` // only used to override LDFlags
GCFlags string `mapstructure:"-"`
GCSet bool `mapstructure:"-"` // only used to override GCFlags
Verbose bool `mapstructure:"-"`
Distribution Distribution `mapstructure:"dist"`
Exporters []Module `mapstructure:"exporters"`
Extensions []Module `mapstructure:"extensions"`
Receivers []Module `mapstructure:"receivers"`
Processors []Module `mapstructure:"processors"`
Connectors []Module `mapstructure:"connectors"`
Telemetry Module `mapstructure:"telemetry"`
ConfmapProviders []Module `mapstructure:"providers"`
ConfmapConverters []Module `mapstructure:"converters"`
Replaces []string `mapstructure:"replaces"`
Excludes []string `mapstructure:"excludes"`
ConfResolver ConfResolver `mapstructure:"conf_resolver"`
downloadModules retry `mapstructure:"-"`
}
type ConfResolver struct {
// When set, will be used to set the CollectorSettings.ConfResolver.DefaultScheme value,
// which determines how the Collector interprets URIs that have no scheme, such as ${ENV}.
// See https://pkg.go.dev/go.opentelemetry.io/collector/confmap#ResolverSettings for more details.
DefaultURIScheme string `mapstructure:"default_uri_scheme"`
}
// Distribution holds the parameters for the final binary
type Distribution struct {
Module string `mapstructure:"module"`
Name string `mapstructure:"name"`
Go string `mapstructure:"go"`
Description string `mapstructure:"description"`
OutputPath string `mapstructure:"output_path"`
Version string `mapstructure:"version"`
BuildTags string `mapstructure:"build_tags"`
DebugCompilation bool `mapstructure:"debug_compilation"`
CGoEnabled bool `mapstructure:"cgo_enabled"`
}
// Module represents a receiver, exporter, processor or extension for the distribution
type Module struct {
Name string `mapstructure:"name"` // if not specified, this is package part of the go mod (last part of the path)
Import string `mapstructure:"import"` // if not specified, this is the path part of the go mods
GoMod string `mapstructure:"gomod"` // a gomod-compatible spec for the module
Path string `mapstructure:"path"` // an optional path to the local version of this module
}
type retry struct {
numRetries int
wait time.Duration
}
// NewDefaultConfig creates a new config, with default values
func NewDefaultConfig() (*Config, error) {
log, err := zap.NewDevelopment()
if err != nil {
panic(fmt.Sprintf("failed to obtain a logger instance: %v", err))
}
outputDir, err := os.MkdirTemp("", "otelcol-distribution")
if err != nil {
return nil, err
}
return &Config{
OtelColVersion: DefaultBetaOtelColVersion,
Logger: log,
Distribution: Distribution{
OutputPath: outputDir,
Module: "go.opentelemetry.io/collector/cmd/builder",
},
// basic retry if error from go mod command (in case of transient network error).
// retry 3 times with 5 second spacing interval
downloadModules: retry{
numRetries: 3,
wait: 5 * time.Second,
},
ConfmapProviders: []Module{
{
GoMod: "go.opentelemetry.io/collector/confmap/provider/envprovider " + DefaultStableOtelColVersion,
},
{
GoMod: "go.opentelemetry.io/collector/confmap/provider/fileprovider " + DefaultStableOtelColVersion,
},
{
GoMod: "go.opentelemetry.io/collector/confmap/provider/httpprovider " + DefaultStableOtelColVersion,
},
{
GoMod: "go.opentelemetry.io/collector/confmap/provider/httpsprovider " + DefaultStableOtelColVersion,
},
{
GoMod: "go.opentelemetry.io/collector/confmap/provider/yamlprovider " + DefaultStableOtelColVersion,
},
},
}, nil
}
// Validate checks whether the current configuration is valid
func (c *Config) Validate() error {
return multierr.Combine(
validateModules("extension", c.Extensions),
validateModules("receiver", c.Receivers),
validateModules("exporter", c.Exporters),
validateModules("processor", c.Processors),
validateModules("connector", c.Connectors),
validateModules("provider", c.ConfmapProviders),
validateModules("converter", c.ConfmapConverters),
validateTelemetry(c),
)
}
// SetGoPath sets go path
func (c *Config) SetGoPath() error {
if !c.SkipCompilation || !c.SkipGetModules {
//nolint:gosec // #nosec G204
if _, err := exec.Command(c.Distribution.Go, "env").CombinedOutput(); err != nil {
path, err := exec.LookPath("go")
if err != nil {
return ErrGoNotFound
}
c.Distribution.Go = path
}
c.Logger.Info("Using go", zap.String("go-executable", c.Distribution.Go))
}
return nil
}
// ParseModules will parse the Modules entries and populate the missing values
func (c *Config) ParseModules() error {
var err error
usedNames := make(map[string]int)
c.Extensions, err = parseModules(c.Extensions, usedNames)
if err != nil {
return err
}
c.Receivers, err = parseModules(c.Receivers, usedNames)
if err != nil {
return err
}
c.Exporters, err = parseModules(c.Exporters, usedNames)
if err != nil {
return err
}
c.Processors, err = parseModules(c.Processors, usedNames)
if err != nil {
return err
}
c.Connectors, err = parseModules(c.Connectors, usedNames)
if err != nil {
return err
}
telemetry, err := parseModules([]Module{c.Telemetry}, usedNames)
if err != nil {
return err
}
c.Telemetry = telemetry[0]
c.ConfmapProviders, err = parseModules(c.ConfmapProviders, usedNames)
if err != nil {
return err
}
c.ConfmapConverters, err = parseModules(c.ConfmapConverters, usedNames)
if err != nil {
return err
}
return nil
}
func (c *Config) allComponents() []Module {
return slices.Concat(c.Exporters, c.Receivers, c.Processors, c.Extensions, c.Connectors, []Module{c.Telemetry}, c.ConfmapProviders, c.ConfmapConverters)
}
func validateModules(name string, mods []Module) error {
for i, mod := range mods {
if mod.GoMod == "" {
return fmt.Errorf("%s module at index %v: %w", name, i, errMissingGoMod)
}
}
return nil
}
// validateTelemetry ensures there is a valid telemetry module specified.
// If the field is not set, it is defaulted to otelconftelemetry.
func validateTelemetry(c *Config) error {
// We cannot set this in createDefaultConfig, since koanf merges maps and we
// would get a blend of this value and user-provided values. Once
// otelconftelemetry is its own module (that is, the `Import` field is not
// set), we can likely move the default to createDefaultConfig.
if c.Telemetry.Name == "" && c.Telemetry.Import == "" && c.Telemetry.GoMod == "" && c.Telemetry.Path == "" {
c.Telemetry = Module{
GoMod: "go.opentelemetry.io/collector/service " + DefaultBetaOtelColVersion,
Import: "go.opentelemetry.io/collector/service/telemetry/otelconftelemetry",
}
} else if c.Telemetry.GoMod == "" {
return fmt.Errorf("telemetry module: %w", errMissingGoMod)
}
return nil
}
func parseModules(mods []Module, usedNames map[string]int) ([]Module, error) {
var parsedModules []Module
for _, mod := range mods {
if mod.Import == "" {
mod.Import = strings.Split(mod.GoMod, " ")[0]
}
if mod.Name == "" {
parts := strings.Split(mod.Import, "/")
mod.Name = parts[len(parts)-1]
}
originalModName := mod.Name
if count, exists := usedNames[mod.Name]; exists {
var newName string
for {
newName = fmt.Sprintf("%s%d", mod.Name, count+1)
if _, transformedExists := usedNames[newName]; !transformedExists {
break
}
count++
}
mod.Name = newName
usedNames[newName] = 1
}
usedNames[originalModName] = 1
// Check if path is empty, otherwise filepath.Abs replaces it with current path ".".
if mod.Path != "" {
var err error
mod.Path, err = filepath.Abs(mod.Path)
if err != nil {
return mods, fmt.Errorf("module has a relative \"path\" element, but we couldn't resolve the current working dir: %w", err)
}
// Check if the path exists
if _, err := os.Stat(mod.Path); os.IsNotExist(err) {
return mods, fmt.Errorf("filepath does not exist: %s", mod.Path)
}
}
parsedModules = append(parsedModules, mod)
}
return parsedModules, nil
}
================================================
FILE: cmd/builder/internal/builder/config_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package builder
import (
"os"
"strings"
"testing"
"github.com/knadh/koanf/parsers/yaml"
"github.com/knadh/koanf/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"go.uber.org/zap/zaptest"
"go.opentelemetry.io/collector/cmd/builder/internal/config"
)
func TestAliases(t *testing.T) {
// prepare
cfg := Config{
Extensions: []Module{
{
GoMod: "github.com/org/repo/impl v0.1.2",
},
{
GoMod: "github.com/org/repo2/impl v0.1.2",
},
{
GoMod: "github.com/org/repo3/impl v0.1.2",
},
},
Receivers: []Module{
{
GoMod: "github.com/org/repo v0.1.2",
},
{
GoMod: "github.com/org2/repo v0.1.2",
},
{
GoMod: "github.com/org/repo4/impl v0.1.2",
},
},
Exporters: []Module{
{
GoMod: "github.com/another/module v0.1.2",
},
{
GoMod: "github.com/org/repo5/impl v0.1.2",
},
},
Processors: []Module{
{
GoMod: "github.com/another/module2 v0.1.2",
},
{
GoMod: "github.com/another2/module v0.1.2",
},
},
Connectors: []Module{
{
GoMod: "github.com/another/module3 v0.1.2",
},
{
GoMod: "github.com/another2/module4 v0.1.2",
},
{
GoMod: "github.com/another3/module v0.1.2",
},
},
Telemetry: Module{
GoMod: "github.com/another3/module v0.1.2",
},
}
// test
err := cfg.ParseModules()
require.NoError(t, err)
// verify
assert.Equal(t, "github.com/org/repo/impl v0.1.2", cfg.Extensions[0].GoMod)
assert.Equal(t, "github.com/org/repo/impl", cfg.Extensions[0].Import)
assert.Equal(t, "impl", cfg.Extensions[0].Name)
assert.Equal(t, "github.com/org/repo2/impl v0.1.2", cfg.Extensions[1].GoMod)
assert.Equal(t, "github.com/org/repo2/impl", cfg.Extensions[1].Import)
assert.Equal(t, "impl2", cfg.Extensions[1].Name)
assert.Equal(t, "github.com/org/repo3/impl v0.1.2", cfg.Extensions[2].GoMod)
assert.Equal(t, "github.com/org/repo3/impl", cfg.Extensions[2].Import)
assert.Equal(t, "impl3", cfg.Extensions[2].Name)
assert.Equal(t, "github.com/org/repo v0.1.2", cfg.Receivers[0].GoMod)
assert.Equal(t, "github.com/org/repo", cfg.Receivers[0].Import)
assert.Equal(t, "repo", cfg.Receivers[0].Name)
assert.Equal(t, "github.com/org2/repo v0.1.2", cfg.Receivers[1].GoMod)
assert.Equal(t, "github.com/org2/repo", cfg.Receivers[1].Import)
assert.Equal(t, "repo2", cfg.Receivers[1].Name)
assert.Equal(t, "github.com/org/repo4/impl v0.1.2", cfg.Receivers[2].GoMod)
assert.Equal(t, "github.com/org/repo4/impl", cfg.Receivers[2].Import)
assert.Equal(t, "impl4", cfg.Receivers[2].Name)
assert.Equal(t, "github.com/another/module v0.1.2", cfg.Exporters[0].GoMod)
assert.Equal(t, "github.com/another/module", cfg.Exporters[0].Import)
assert.Equal(t, "module", cfg.Exporters[0].Name)
assert.Equal(t, "github.com/org/repo5/impl v0.1.2", cfg.Exporters[1].GoMod)
assert.Equal(t, "github.com/org/repo5/impl", cfg.Exporters[1].Import)
assert.Equal(t, "impl5", cfg.Exporters[1].Name)
assert.Equal(t, "github.com/another/module2 v0.1.2", cfg.Processors[0].GoMod)
assert.Equal(t, "github.com/another/module2", cfg.Processors[0].Import)
assert.Equal(t, "module2", cfg.Processors[0].Name)
assert.Equal(t, "github.com/another2/module v0.1.2", cfg.Processors[1].GoMod)
assert.Equal(t, "github.com/another2/module", cfg.Processors[1].Import)
assert.Equal(t, "module3", cfg.Processors[1].Name)
assert.Equal(t, "github.com/another/module3 v0.1.2", cfg.Connectors[0].GoMod)
assert.Equal(t, "github.com/another/module3", cfg.Connectors[0].Import)
assert.Equal(t, "module32", cfg.Connectors[0].Name)
assert.Equal(t, "github.com/another2/module4 v0.1.2", cfg.Connectors[1].GoMod)
assert.Equal(t, "github.com/another2/module4", cfg.Connectors[1].Import)
assert.Equal(t, "module4", cfg.Connectors[1].Name)
assert.Equal(t, "github.com/another3/module v0.1.2", cfg.Connectors[2].GoMod)
assert.Equal(t, "github.com/another3/module", cfg.Connectors[2].Import)
assert.Equal(t, "module5", cfg.Connectors[2].Name)
assert.Equal(t, "github.com/another3/module v0.1.2", cfg.Telemetry.GoMod)
assert.Equal(t, "github.com/another3/module", cfg.Telemetry.Import)
assert.Equal(t, "module6", cfg.Telemetry.Name)
}
func TestParseModules(t *testing.T) {
// prepare
cfg := Config{
Extensions: []Module{{
GoMod: "github.com/org/repo v0.1.2",
}},
}
// test
err := cfg.ParseModules()
require.NoError(t, err)
// verify
assert.Equal(t, "github.com/org/repo v0.1.2", cfg.Extensions[0].GoMod)
assert.Equal(t, "github.com/org/repo", cfg.Extensions[0].Import)
assert.Equal(t, "repo", cfg.Extensions[0].Name)
}
func TestInvalidConverter(t *testing.T) {
// Create a Config instance with invalid Converters
config := &Config{
ConfmapConverters: []Module{
{
Path: "./invalid/module/path", // Invalid module path to trigger an error
},
},
}
// Call the method and expect an error
err := config.ParseModules()
require.Error(t, err, "expected an error when parsing invalid modules")
}
func TestRelativePath(t *testing.T) {
// prepare
cfg := Config{
Extensions: []Module{{
GoMod: "some-module",
Path: "./templates",
}},
}
// test
err := cfg.ParseModules()
require.NoError(t, err)
// verify
cwd, err := os.Getwd()
require.NoError(t, err)
assert.True(t, strings.HasPrefix(cfg.Extensions[0].Path, cwd))
}
func TestModuleFromCore(t *testing.T) {
// prepare
cfg := Config{
Extensions: []Module{ // see issue-12
{
Import: "go.opentelemetry.io/collector/receiver/otlpreceiver",
GoMod: "go.opentelemetry.io/collector v0.0.0",
},
{
Import: "go.opentelemetry.io/collector/receiver/otlpreceiver",
GoMod: "go.opentelemetry.io/collector v0.0.0",
},
},
}
// test
err := cfg.ParseModules()
require.NoError(t, err)
// verify
assert.True(t, strings.HasPrefix(cfg.Extensions[0].Name, "otlpreceiver"))
}
func TestMissingModule(t *testing.T) {
type invalidModuleTest struct {
cfg Config
err error
}
// prepare
configurations := []invalidModuleTest{
{
cfg: Config{
Logger: zap.NewNop(),
ConfmapProviders: []Module{{
Import: "invalid",
}},
},
err: errMissingGoMod,
},
{
cfg: Config{
Logger: zap.NewNop(),
Extensions: []Module{{
Import: "invalid",
}},
},
err: errMissingGoMod,
},
{
cfg: Config{
Logger: zap.NewNop(),
Receivers: []Module{{
Import: "invalid",
}},
},
err: errMissingGoMod,
},
{
cfg: Config{
Logger: zap.NewNop(),
Exporters: []Module{{
Import: "invalid",
}},
},
err: errMissingGoMod,
},
{
cfg: Config{
Logger: zap.NewNop(),
Processors: []Module{{
Import: "invalid",
}},
},
err: errMissingGoMod,
},
{
cfg: Config{
Logger: zap.NewNop(),
Connectors: []Module{{
Import: "invalid",
}},
},
err: errMissingGoMod,
},
{
cfg: Config{
Logger: zap.NewNop(),
ConfmapConverters: []Module{{
Import: "invalid",
}},
},
err: errMissingGoMod,
},
{
cfg: Config{
Logger: zap.NewNop(),
Telemetry: Module{
Import: "go.opentelemetry.io/collector/service/telemetry/otelconftelemetry",
},
},
err: errMissingGoMod,
},
}
for _, test := range configurations {
assert.ErrorIs(t, test.cfg.Validate(), test.err)
}
}
func TestNewDefaultConfig(t *testing.T) {
cfg, err := NewDefaultConfig()
require.NoError(t, err)
assert.Empty(t, cfg.Telemetry.GoMod)
require.NoError(t, cfg.Validate())
require.NoError(t, cfg.SetGoPath())
require.NoError(t, cfg.ParseModules())
assert.NotEmpty(t, cfg.Telemetry.GoMod)
assert.False(t, cfg.Distribution.DebugCompilation)
assert.Empty(t, cfg.Distribution.BuildTags)
assert.False(t, cfg.LDSet)
assert.Empty(t, cfg.LDFlags)
assert.False(t, cfg.GCSet)
assert.Empty(t, cfg.GCFlags)
}
func TestNewBuiltinConfig(t *testing.T) {
k := koanf.New(".")
require.NoError(t, k.Load(config.DefaultProvider(), yaml.Parser()))
cfg := Config{Logger: zaptest.NewLogger(t)}
require.NoError(t, k.UnmarshalWithConf("", &cfg, koanf.UnmarshalConf{Tag: "mapstructure"}))
require.NoError(t, cfg.Validate())
require.NoError(t, cfg.SetGoPath())
require.NoError(t, cfg.ParseModules())
// Unlike the config initialized in NewDefaultConfig(), we expect
// the builtin default to be practically useful, so there must be
// a set of modules present.
assert.NotEmpty(t, cfg.Receivers)
assert.NotEmpty(t, cfg.Exporters)
assert.NotEmpty(t, cfg.Extensions)
assert.NotEmpty(t, cfg.Processors)
}
func TestSkipGoValidation(t *testing.T) {
cfg := Config{
Distribution: Distribution{
Go: "invalid/go/binary/path",
},
SkipCompilation: true,
SkipGetModules: true,
}
assert.NoError(t, cfg.Validate())
assert.NoError(t, cfg.SetGoPath())
}
func TestSkipGoInitialization(t *testing.T) {
cfg := Config{
SkipCompilation: true,
SkipGetModules: true,
}
assert.NoError(t, cfg.Validate())
assert.NoError(t, cfg.SetGoPath())
assert.Empty(t, cfg.Distribution.Go)
}
func TestBuildTagConfig(t *testing.T) {
cfg := Config{
Distribution: Distribution{
BuildTags: "customTag",
},
SkipCompilation: true,
SkipGetModules: true,
}
require.NoError(t, cfg.Validate())
assert.Equal(t, "customTag", cfg.Distribution.BuildTags)
}
func TestDebugOptionSetConfig(t *testing.T) {
cfg := Config{
Distribution: Distribution{
DebugCompilation: true,
},
SkipCompilation: true,
SkipGetModules: true,
}
require.NoError(t, cfg.Validate())
assert.True(t, cfg.Distribution.DebugCompilation)
}
func TestAddsDefaultProviders(t *testing.T) {
cfg, err := NewDefaultConfig()
require.NoError(t, err)
require.NoError(t, cfg.ParseModules())
assert.Len(t, cfg.ConfmapProviders, 5)
}
func TestSkipsNilFieldValidation(t *testing.T) {
cfg, err := NewDefaultConfig()
require.NoError(t, err)
cfg.ConfmapProviders = nil
cfg.ConfmapConverters = nil
assert.NoError(t, cfg.Validate())
}
================================================
FILE: cmd/builder/internal/builder/main.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package builder // import "go.opentelemetry.io/collector/cmd/builder/internal/builder"
import (
"bytes"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"text/template"
"time"
"go.uber.org/zap"
"golang.org/x/mod/modfile"
"golang.org/x/mod/semver"
)
var (
// ErrGoNotFound is returned when a Go binary hasn't been found
ErrGoNotFound = errors.New("go binary not found")
ErrDepNotFound = errors.New("dependency not found in go mod file")
ErrVersionMismatch = errors.New("mismatch in go.mod and builder configuration versions")
errDownloadFailed = errors.New("failed to download go modules")
errCompileFailed = errors.New("failed to compile the OpenTelemetry Collector distribution")
skipStrictMsg = "Use --skip-strict-versioning to temporarily disable this check. This flag will be removed in a future minor version"
)
const otelcolPath = "go.opentelemetry.io/collector/otelcol"
func runGoCommand(cfg *Config, args ...string) ([]byte, error) {
if cfg.Verbose {
cfg.Logger.Info("Running go subcommand.", zap.Any("arguments", args))
}
//nolint:gosec // #nosec G204 -- cfg.Distribution.Go is trusted to be a safe path and the caller is assumed to have carried out necessary input validation
cmd := exec.Command(cfg.Distribution.Go, args...)
cmd.Dir = cfg.Distribution.OutputPath
cmd.Env = os.Environ()
if cfg.Distribution.CGoEnabled {
cmd.Env = append(cmd.Env, "CGO_ENABLED=1")
} else {
cmd.Env = append(cmd.Env, "CGO_ENABLED=0")
}
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("go subcommand failed with args '%v': %w, error message: %s", args, err, stderr.String())
}
if cfg.Verbose && stderr.Len() != 0 {
cfg.Logger.Info("go subcommand error", zap.String("message", stderr.String()))
}
return stdout.Bytes(), nil
}
// GenerateAndCompile will generate the source files based on the given configuration, update go mod, and will compile into a binary
func GenerateAndCompile(cfg *Config) error {
if err := Generate(cfg); err != nil {
return err
}
// run go get to update go.mod and go.sum files
if err := GetModules(cfg); err != nil {
return err
}
return Compile(cfg)
}
// Generate assembles a new distribution based on the given configuration
func Generate(cfg *Config) error {
if cfg.SkipGenerate {
cfg.Logger.Info("Skipping generating source codes.")
return nil
}
// if the file does not exist, try to create it
if _, err := os.Stat(cfg.Distribution.OutputPath); os.IsNotExist(err) {
if err = os.Mkdir(cfg.Distribution.OutputPath, 0o750); err != nil {
return fmt.Errorf("failed to create output path: %w", err)
}
} else if err != nil {
return fmt.Errorf("failed to create output path: %w", err)
}
for _, tmpl := range []*template.Template{
mainTemplate,
mainOthersTemplate,
mainWindowsTemplate,
componentsTemplate,
goModTemplate,
} {
if err := processAndWrite(cfg, tmpl, tmpl.Name(), cfg); err != nil {
return fmt.Errorf("failed to generate source file %q: %w", tmpl.Name(), err)
}
}
cfg.Logger.Info("Sources created", zap.String("path", cfg.Distribution.OutputPath))
return nil
}
// Compile generates a binary from the sources based on the configuration
func Compile(cfg *Config) error {
if cfg.SkipCompilation {
cfg.Logger.Info("Generating source codes only, the distribution will not be compiled.")
return nil
}
cfg.Logger.Info("Compiling")
ldflags := "-s -w" // we strip the symbols by default for smaller binaries
gcflags := ""
binaryName := outputBinaryName(cfg.Distribution.Name)
args := []string{"build", "-trimpath", "-o", binaryName}
if cfg.Distribution.DebugCompilation {
cfg.Logger.Info("Debug compilation is enabled, the debug symbols will be left on the resulting binary")
ldflags = cfg.LDFlags
gcflags = "all=-N -l"
} else {
if cfg.LDSet {
cfg.Logger.Info("Using custom ldflags", zap.String("ldflags", cfg.LDFlags))
ldflags = cfg.LDFlags
}
if cfg.GCSet {
cfg.Logger.Info("Using custom gcflags", zap.String("gcflags", cfg.GCFlags))
gcflags = cfg.GCFlags
}
}
if cfg.Distribution.CGoEnabled {
cfg.Logger.Info("Building with cgo enabled")
}
args = append(args, "-ldflags="+ldflags, "-gcflags="+gcflags)
if cfg.Distribution.BuildTags != "" {
args = append(args, "-tags", cfg.Distribution.BuildTags)
}
if _, err := runGoCommand(cfg, args...); err != nil {
return fmt.Errorf("%w: %s", errCompileFailed, err.Error())
}
cfg.Logger.Info("Compiled", zap.String("binary", fmt.Sprintf("%s/%s", cfg.Distribution.OutputPath, binaryName)))
return nil
}
func outputBinaryName(name string) string {
goos, ok := os.LookupEnv("GOOS")
if !ok || goos == "" {
goos = runtime.GOOS
}
if !strings.EqualFold(goos, "windows") {
return name
}
if strings.EqualFold(filepath.Ext(name), ".exe") {
return name
}
return name + ".exe"
}
// GetModules retrieves the go modules, updating go.mod and go.sum in the process
func GetModules(cfg *Config) error {
if cfg.SkipGetModules {
cfg.Logger.Info("Generating source codes only, will not update go.mod and retrieve Go modules.")
return nil
}
if _, err := runGoCommand(cfg, "mod", "tidy", "-compat=1.25"); err != nil {
return fmt.Errorf("failed to update go.mod: %w", err)
}
if cfg.SkipStrictVersioning {
return downloadModules(cfg)
}
// Perform strict version checking. For each component listed and the
// otelcol core dependency, check that the enclosing go module matches.
modulePath, dependencyVersions, err := readGoModFile(cfg)
if err != nil {
return err
}
coreDepVersion, ok := dependencyVersions[otelcolPath]
betaVersion := semver.MajorMinor(DefaultBetaOtelColVersion)
if !ok {
return fmt.Errorf("core collector %w: '%s'. %s", ErrDepNotFound, otelcolPath, skipStrictMsg)
}
if semver.MajorMinor(coreDepVersion) != betaVersion {
return fmt.Errorf(
"%w: core collector version calculated by component dependencies %q does not match configured version %q. %s",
ErrVersionMismatch, coreDepVersion, betaVersion, skipStrictMsg)
}
for _, mod := range cfg.allComponents() {
module, version, _ := strings.Cut(mod.GoMod, " ")
if module == modulePath {
// No need to check the version of components that are part of the
// module we're building from.
continue
}
moduleDepVersion, ok := dependencyVersions[module]
if !ok {
return fmt.Errorf("component %w: '%s'. %s", ErrDepNotFound, module, skipStrictMsg)
}
if semver.MajorMinor(moduleDepVersion) != semver.MajorMinor(version) {
return fmt.Errorf(
"%w: component %q version calculated by dependencies %q does not match configured version %q. %s",
ErrVersionMismatch, module, moduleDepVersion, version, skipStrictMsg)
}
}
return downloadModules(cfg)
}
func downloadModules(cfg *Config) error {
cfg.Logger.Info("Getting go modules")
failReason := "unknown"
for i := 1; i <= cfg.downloadModules.numRetries; i++ {
if _, err := runGoCommand(cfg, "mod", "download"); err != nil {
failReason = err.Error()
cfg.Logger.Info("Failed modules download", zap.String("retry", fmt.Sprintf("%d/%d", i, cfg.downloadModules.numRetries)))
time.Sleep(cfg.downloadModules.wait)
continue
}
return nil
}
return fmt.Errorf("%w: %s", errDownloadFailed, failReason)
}
func processAndWrite(cfg *Config, tmpl *template.Template, outFile string, tmplParams any) error {
out, err := os.Create(filepath.Clean(filepath.Join(cfg.Distribution.OutputPath, outFile)))
if err != nil {
return err
}
defer out.Close()
return tmpl.Execute(out, tmplParams)
}
func readGoModFile(cfg *Config) (string, map[string]string, error) {
var modPath string
stdout, err := runGoCommand(cfg, "mod", "edit", "-print")
if err != nil {
return modPath, nil, err
}
parsedFile, err := modfile.Parse("go.mod", stdout, nil)
if err != nil {
return modPath, nil, err
}
if parsedFile.Module != nil {
modPath = parsedFile.Module.Mod.Path
}
dependencies := map[string]string{}
for _, req := range parsedFile.Require {
if req == nil {
continue
}
dependencies[req.Mod.Path] = req.Mod.Version
}
return modPath, dependencies, nil
}
================================================
FILE: cmd/builder/internal/builder/main_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package builder
import (
"fmt"
"io"
"os"
"path"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"golang.org/x/mod/modfile"
)
const (
goModTestFile = `// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
module go.opentelemetry.io/collector/cmd/builder/internal/tester
go 1.20
require (
go.opentelemetry.io/collector/component v0.96.0
go.opentelemetry.io/collector/connector v0.94.1
go.opentelemetry.io/collector/exporter v0.94.1
go.opentelemetry.io/collector/extension v0.94.1
go.opentelemetry.io/collector/otelcol v0.94.1
go.opentelemetry.io/collector/processor v0.94.1
go.opentelemetry.io/collector/receiver v0.94.1
go.opentelemetry.io/collector v0.96.0
)`
modulePrefix = "go.opentelemetry.io/collector"
)
var replaceModules = []string{
"",
"/component",
"/component/componentstatus",
"/component/componenttest",
"/client",
"/config/configauth",
"/config/configcompression",
"/config/configgrpc",
"/config/confighttp",
"/config/configmiddleware",
"/config/confignet",
"/config/configopaque",
"/config/configoptional",
"/config/configretry",
"/config/configtelemetry",
"/config/configtls",
"/confmap",
"/confmap/xconfmap",
"/confmap/provider/envprovider",
"/confmap/provider/fileprovider",
"/confmap/provider/httpprovider",
"/confmap/provider/httpsprovider",
"/confmap/provider/yamlprovider",
"/consumer",
"/consumer/consumererror",
"/consumer/consumererror/xconsumererror",
"/consumer/xconsumer",
"/consumer/consumertest",
"/connector",
"/connector/connectortest",
"/connector/xconnector",
"/exporter",
"/exporter/debugexporter",
"/exporter/xexporter",
"/exporter/exportertest",
"/exporter/exporterhelper",
"/exporter/exporterhelper/xexporterhelper",
"/exporter/nopexporter",
"/exporter/otlpexporter",
"/exporter/otlphttpexporter",
"/extension",
"/extension/extensionauth",
"/extension/extensionauth/extensionauthtest",
"/extension/extensioncapabilities",
"/extension/extensionmiddleware",
"/extension/extensionmiddleware/extensionmiddlewaretest",
"/extension/extensiontest",
"/extension/zpagesextension",
"/extension/xextension",
"/featuregate",
"/internal/componentalias",
"/internal/memorylimiter",
"/internal/fanoutconsumer",
"/internal/sharedcomponent",
"/internal/telemetry",
"/internal/testutil",
"/otelcol",
"/pdata",
"/pdata/testdata",
"/pdata/pprofile",
"/pdata/xpdata",
"/pipeline",
"/pipeline/xpipeline",
"/processor",
"/processor/processortest",
"/processor/batchprocessor",
"/processor/memorylimiterprocessor",
"/processor/processorhelper",
"/processor/processorhelper/xprocessorhelper",
"/processor/xprocessor",
"/receiver",
"/receiver/nopreceiver",
"/receiver/otlpreceiver",
"/receiver/receivertest",
"/receiver/receiverhelper",
"/receiver/xreceiver",
"/service",
"/service/hostcapabilities",
"/service/telemetry/telemetrytest",
}
func newTestConfig(tb testing.TB) *Config {
cfg, err := NewDefaultConfig()
require.NoError(tb, err)
cfg.downloadModules.wait = 0
cfg.downloadModules.numRetries = 1
return cfg
}
func newInitializedConfig(t *testing.T) *Config {
cfg := newTestConfig(t)
// Validate and ParseModules will be called before the config is
// given to Generate.
assert.NoError(t, cfg.Validate())
assert.NoError(t, cfg.ParseModules())
return cfg
}
func TestGenerateDefault(t *testing.T) {
require.NoError(t, Generate(newInitializedConfig(t)))
}
func TestGenerateInvalidOutputPath(t *testing.T) {
cfg := newInitializedConfig(t)
cfg.Distribution.OutputPath = ":/invalid"
err := Generate(cfg)
require.ErrorContains(t, err, "failed to create output path")
}
func TestOutputBinaryName(t *testing.T) {
for _, tt := range []struct {
name string
goos string
want string
}{
{
name: "on windows",
goos: "windows",
want: "otelcorecol.exe",
},
{
name: "on other OSes",
goos: "linux",
want: "otelcorecol",
},
} {
t.Run(tt.name, func(t *testing.T) {
t.Setenv("GOOS", tt.goos)
assert.Equal(t, tt.want, outputBinaryName("otelcorecol"))
assert.Equal(t, "otelcorecol.exe", outputBinaryName("otelcorecol.exe"))
})
}
}
func TestVersioning(t *testing.T) {
replaces := generateReplaces()
tests := []struct {
name string
cfgBuilder func() *Config
expectedErr error
}{
{
name: "defaults",
cfgBuilder: func() *Config {
cfg := newTestConfig(t)
cfg.Distribution.Go = "go"
cfg.Replaces = append(cfg.Replaces, replaces...)
return cfg
},
expectedErr: nil,
},
{
name: "only gomod file, skip generate",
cfgBuilder: func() *Config {
cfg := newTestConfig(t)
tempDir := t.TempDir()
err := makeModule(tempDir, []byte(goModTestFile))
require.NoError(t, err)
cfg.Distribution.OutputPath = tempDir
cfg.SkipGenerate = true
cfg.Distribution.Go = "go"
return cfg
},
expectedErr: ErrDepNotFound,
},
{
name: "old component version",
cfgBuilder: func() *Config {
cfg := newTestConfig(t)
cfg.Distribution.Go = "go"
cfg.Exporters = []Module{
{
GoMod: "go.opentelemetry.io/collector/exporter/otlpexporter v0.112.0",
},
}
cfg.ConfmapProviders = []Module{}
cfg.Replaces = append(cfg.Replaces, replaces...)
return cfg
},
expectedErr: nil,
},
{
name: "old component version without strict mode",
cfgBuilder: func() *Config {
cfg := newTestConfig(t)
cfg.Distribution.Go = "go"
cfg.SkipStrictVersioning = true
cfg.Exporters = []Module{
{
GoMod: "go.opentelemetry.io/collector/exporter/otlpexporter v0.112.0",
},
}
cfg.ConfmapProviders = []Module{}
cfg.Replaces = append(cfg.Replaces, replaces...)
return cfg
},
expectedErr: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// X25519 curves are not supported when GODEBUG=fips140=only is set, so we
// detect if it is and conditionally also add the tlsmklem=0 flag to disable
// these curves. See: https://pkg.go.dev/crypto/tls#Config.CurvePreferences
if strings.Contains(os.Getenv("GODEBUG"), "fips140=only") {
t.Setenv("GODEBUG", os.Getenv("GODEBUG")+",tlsmlkem=0")
}
cfg := tt.cfgBuilder()
require.NoError(t, cfg.Validate())
require.NoError(t, cfg.ParseModules())
err := GenerateAndCompile(cfg)
require.ErrorIs(t, err, tt.expectedErr)
})
}
}
func TestSkipGenerate(t *testing.T) {
cfg := newInitializedConfig(t)
cfg.Distribution.OutputPath = t.TempDir()
cfg.SkipGenerate = true
err := Generate(cfg)
require.NoError(t, err)
outputFile, err := os.Open(cfg.Distribution.OutputPath)
defer func() {
require.NoError(t, outputFile.Close())
}()
require.NoError(t, err)
_, err = outputFile.Readdirnames(1)
require.ErrorIs(t, err, io.EOF, "skip generate should leave output directory empty")
}
func TestGenerateAndCompile(t *testing.T) {
replaces := generateReplaces()
testCases := []struct {
name string
cfgBuilder func(t *testing.T) *Config
}{
{
name: "Default Configuration Compilation",
cfgBuilder: func(t *testing.T) *Config {
cfg := newTestConfig(t)
cfg.Distribution.OutputPath = t.TempDir()
cfg.Replaces = append(cfg.Replaces, replaces...)
return cfg
},
},
{
name: "LDFlags Compilation",
cfgBuilder: func(t *testing.T) *Config {
cfg := newTestConfig(t)
cfg.Distribution.OutputPath = t.TempDir()
cfg.Replaces = append(cfg.Replaces, replaces...)
cfg.LDSet = true
cfg.LDFlags = `-X "test.gitVersion=0743dc6c6411272b98494a9b32a63378e84c34da" -X "test.gitTag=local-testing" -X "test.goVersion=go version go1.20.7 darwin/amd64"`
return cfg
},
},
{
name: "GCFlags Compilation",
cfgBuilder: func(t *testing.T) *Config {
cfg := newTestConfig(t)
cfg.Distribution.OutputPath = t.TempDir()
cfg.Replaces = append(cfg.Replaces, replaces...)
cfg.GCSet = true
cfg.GCFlags = `all=-N -l`
return cfg
},
},
{
name: "Build Tags Compilation",
cfgBuilder: func(t *testing.T) *Config {
cfg := newTestConfig(t)
cfg.Distribution.OutputPath = t.TempDir()
cfg.Replaces = append(cfg.Replaces, replaces...)
cfg.Distribution.BuildTags = "customTag"
return cfg
},
},
{
name: "Debug Compilation",
cfgBuilder: func(t *testing.T) *Config {
cfg := newTestConfig(t)
cfg.Distribution.OutputPath = t.TempDir()
cfg.Replaces = append(cfg.Replaces, replaces...)
cfg.Logger = zap.NewNop()
cfg.Distribution.DebugCompilation = true
return cfg
},
},
{
name: "No providers",
cfgBuilder: func(t *testing.T) *Config {
cfg := newTestConfig(t)
cfg.Distribution.OutputPath = t.TempDir()
cfg.Replaces = append(cfg.Replaces, replaces...)
cfg.ConfmapProviders = []Module{}
return cfg
},
},
{
name: "With confmap factories",
cfgBuilder: func(t *testing.T) *Config {
cfg := newTestConfig(t)
cfg.Distribution.OutputPath = t.TempDir()
cfg.Replaces = append(cfg.Replaces, replaces...)
cfg.SkipStrictVersioning = true
return cfg
},
},
{
name: "ConfResolverDefaultURIScheme set",
cfgBuilder: func(t *testing.T) *Config {
cfg := newTestConfig(t)
cfg.ConfResolver = ConfResolver{
DefaultURIScheme: "env",
}
cfg.Distribution.OutputPath = t.TempDir()
cfg.Replaces = append(cfg.Replaces, replaces...)
return cfg
},
},
{
name: "CGoEnabled set to true",
cfgBuilder: func(t *testing.T) *Config {
cfg := newTestConfig(t)
cfg.Distribution.OutputPath = t.TempDir()
cfg.Replaces = append(cfg.Replaces, replaces...)
cfg.Distribution.CGoEnabled = true
return cfg
},
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
cfg := tt.cfgBuilder(t)
assert.NoError(t, cfg.Validate())
assert.NoError(t, cfg.SetGoPath())
assert.NoError(t, cfg.ParseModules())
require.NoError(t, GenerateAndCompile(cfg))
})
}
}
// Test that the go.mod files that other tests in this file
// may generate have all their modules covered by our
// "replace" statements created in `generateReplaces`.
//
// An incomplete set of replace statements in these tests
// may cause them to fail during the release process, when
// the local version of modules in the release branch is
// not yet available on the Go package repository.
// Unless the replace statements route all modules to the
// local copy, `go get` will try to fetch the unreleased
// version remotely and some tests will fail.
func TestReplaceStatementsAreComplete(t *testing.T) {
workspaceDir := getWorkspaceDir()
replaceMods := map[string]bool{}
for _, suffix := range replaceModules {
replaceMods[modulePrefix+suffix] = false
}
for _, mod := range replaceModules {
verifyGoMod(t, workspaceDir+mod, replaceMods)
}
var err error
dir := t.TempDir()
cfg, err := NewDefaultConfig()
require.NoError(t, err)
cfg.Distribution.Go = "go"
cfg.Distribution.OutputPath = dir
cfg.Replaces = append(cfg.Replaces, generateReplaces()...)
// Configure all components that we want to use elsewhere in these tests.
// This ensures the resulting go.mod file has maximum coverage of modules
// that exist in the Core repository.
usedNames := make(map[string]int)
cfg.Exporters, err = parseModules([]Module{
{
GoMod: "go.opentelemetry.io/collector/exporter/debugexporter v1.9999.9999",
},
{
GoMod: "go.opentelemetry.io/collector/exporter/nopexporter v1.9999.9999",
},
{
GoMod: "go.opentelemetry.io/collector/exporter/otlpexporter v1.9999.9999",
},
{
GoMod: "go.opentelemetry.io/collector/exporter/otlphttpexporter v1.9999.9999",
},
}, usedNames)
require.NoError(t, err)
cfg.Receivers, err = parseModules([]Module{
{
GoMod: "go.opentelemetry.io/collector/receiver/nopreceiver v1.9999.9999",
},
{
GoMod: "go.opentelemetry.io/collector/receiver/otlpreceiver v1.9999.9999",
},
}, usedNames)
require.NoError(t, err)
cfg.Extensions, err = parseModules([]Module{
{
GoMod: "go.opentelemetry.io/collector/extension/zpagesextension v1.9999.9999",
},
}, usedNames)
require.NoError(t, err)
cfg.Processors, err = parseModules([]Module{
{
GoMod: "go.opentelemetry.io/collector/processor/batchprocessor v1.9999.9999",
},
{
GoMod: "go.opentelemetry.io/collector/processor/memorylimiterprocessor v1.9999.9999",
},
}, usedNames)
require.NoError(t, err)
require.NoError(t, cfg.Validate())
require.NoError(t, cfg.ParseModules())
err = GenerateAndCompile(cfg)
require.NoError(t, err)
verifyGoMod(t, dir, replaceMods)
for k, v := range replaceMods {
assert.Truef(t, v, "Module not used: %s", k)
}
}
func verifyGoMod(t *testing.T, dir string, replaceMods map[string]bool) {
gomodpath := path.Join(dir, "go.mod")
gomod, err := os.ReadFile(filepath.Clean(gomodpath))
require.NoError(t, err)
mod, err := modfile.Parse(gomodpath, gomod, nil)
require.NoError(t, err)
for _, req := range mod.Require {
if !strings.HasPrefix(req.Mod.Path, modulePrefix) {
continue
}
_, ok := replaceMods[req.Mod.Path]
assert.Truef(t, ok, "Module missing from replace statements list: %s", req.Mod.Path)
replaceMods[req.Mod.Path] = true
}
}
func makeModule(dir string, fileContents []byte) error {
// if the file does not exist, try to create it
if _, err := os.Stat(dir); os.IsNotExist(err) {
if err = os.Mkdir(dir, 0o750); err != nil {
return fmt.Errorf("failed to create output path: %w", err)
}
} else if err != nil {
return fmt.Errorf("failed to create output path: %w", err)
}
err := os.WriteFile(filepath.Clean(filepath.Join(dir, "go.mod")), fileContents, 0o600)
if err != nil {
return fmt.Errorf("failed to write go.mod file: %w", err)
}
return nil
}
func generateReplaces() []string {
workspaceDir := getWorkspaceDir()
modules := replaceModules
replaces := make([]string, len(modules))
for i, mod := range modules {
replaces[i] = fmt.Sprintf("%s%s => %s%s", modulePrefix, mod, workspaceDir, mod)
}
return replaces
}
func getWorkspaceDir() string {
// This is dependent on the current file structure.
// The goal is find the root of the repo so we can replace the root module.
_, thisFile, _, _ := runtime.Caller(0)
return filepath.Dir(filepath.Dir(filepath.Dir(filepath.Dir(filepath.Dir(thisFile)))))
}
================================================
FILE: cmd/builder/internal/builder/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package builder
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: cmd/builder/internal/builder/templates/components.go.tmpl
================================================
// Code generated by "go.opentelemetry.io/collector/cmd/builder". DO NOT EDIT.
package main
import (
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/connector"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/extension"
"go.opentelemetry.io/collector/otelcol"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/receiver"
{{.Telemetry.Name}} "{{.Telemetry.Import}}"
{{- range .Connectors}}
{{.Name}} "{{.Import}}"
{{- end}}
{{- range .Exporters}}
{{.Name}} "{{.Import}}"
{{- end}}
{{- range .Extensions}}
{{.Name}} "{{.Import}}"
{{- end}}
{{- range .Processors}}
{{.Name}} "{{.Import}}"
{{- end}}
{{- range .Receivers}}
{{.Name}} "{{.Import}}"
{{- end}}
)
type aliasProvider interface{ DeprecatedAlias() component.Type }
func makeModulesMap[T component.Factory](factories map[component.Type]T, modules map[component.Type]string) map[component.Type]string {
for compType, factory := range factories {
if ap, ok := any(factory).(aliasProvider); ok {
alias := ap.DeprecatedAlias()
if alias.String() != "" {
modules[alias] = modules[compType]
}
}
}
return modules
}
func components() (otelcol.Factories, error) {
var err error
factories := otelcol.Factories{
Telemetry: {{.Telemetry.Name}}.NewFactory(),
}
factories.Extensions, err = otelcol.MakeFactoryMap[extension.Factory](
{{- range .Extensions}}
{{.Name}}.NewFactory(),
{{- end}}
)
if err != nil {
return otelcol.Factories{}, err
}
factories.ExtensionModules = makeModulesMap(factories.Extensions, map[component.Type]string{
{{- range .Extensions}}
{{.Name}}.NewFactory().Type(): "{{.GoMod}}",
{{- end}}
})
factories.Receivers, err = otelcol.MakeFactoryMap[receiver.Factory](
{{- range .Receivers}}
{{.Name}}.NewFactory(),
{{- end}}
)
if err != nil {
return otelcol.Factories{}, err
}
factories.ReceiverModules = makeModulesMap(factories.Receivers, map[component.Type]string{
{{- range .Receivers}}
{{.Name}}.NewFactory().Type(): "{{.GoMod}}",
{{- end}}
})
factories.Exporters, err = otelcol.MakeFactoryMap[exporter.Factory](
{{- range .Exporters}}
{{.Name}}.NewFactory(),
{{- end}}
)
if err != nil {
return otelcol.Factories{}, err
}
factories.ExporterModules = makeModulesMap(factories.Exporters, map[component.Type]string{
{{- range .Exporters}}
{{.Name}}.NewFactory().Type(): "{{.GoMod}}",
{{- end}}
})
factories.Processors, err = otelcol.MakeFactoryMap[processor.Factory](
{{- range .Processors}}
{{.Name}}.NewFactory(),
{{- end}}
)
if err != nil {
return otelcol.Factories{}, err
}
factories.ProcessorModules = makeModulesMap(factories.Processors, map[component.Type]string{
{{- range .Processors}}
{{.Name}}.NewFactory().Type(): "{{.GoMod}}",
{{- end}}
})
factories.Connectors, err = otelcol.MakeFactoryMap[connector.Factory](
{{- range .Connectors}}
{{.Name}}.NewFactory(),
{{- end}}
)
if err != nil {
return otelcol.Factories{}, err
}
factories.ConnectorModules = makeModulesMap(factories.Connectors, map[component.Type]string{
{{- range .Connectors}}
{{.Name}}.NewFactory().Type(): "{{.GoMod}}",
{{- end}}
})
return factories, nil
}
================================================
FILE: cmd/builder/internal/builder/templates/go.mod.tmpl
================================================
// Code generated by "go.opentelemetry.io/collector/cmd/builder". DO NOT EDIT.
module {{.Distribution.Module}}
go 1.25
require (
{{- range .ConfmapConverters}}
{{if .GoMod}}{{.GoMod}}{{end}}
{{- end}}
{{- range .ConfmapProviders}}
{{if .GoMod}}{{.GoMod}}{{end}}
{{- end}}
{{- range .Connectors}}
{{if .GoMod}}{{.GoMod}}{{end}}
{{- end}}
{{- range .Extensions}}
{{if .GoMod}}{{.GoMod}}{{end}}
{{- end}}
{{- range .Receivers}}
{{if .GoMod}}{{.GoMod}}{{end}}
{{- end}}
{{- range .Exporters}}
{{if .GoMod}}{{.GoMod}}{{end}}
{{- end}}
{{- range .Processors}}
{{if .GoMod}}{{.GoMod}}{{end}}
{{- end}}
{{if .Telemetry.GoMod}}{{.Telemetry.GoMod}}{{end}}
go.opentelemetry.io/collector/otelcol {{.OtelColVersion}}
)
require (
github.com/knadh/koanf/maps v0.1.1 // indirect
github.com/knadh/koanf/providers/confmap v0.1.0 // indirect
)
{{- range .ConfmapConverters}}
{{if ne .Path ""}}replace {{.GoMod}} => {{.Path}}{{end}}
{{- end}}
{{- range .ConfmapProviders}}
{{if ne .Path ""}}replace {{.GoMod}} => {{.Path}}{{end}}
{{- end}}
{{- range .Connectors}}
{{if ne .Path ""}}replace {{.GoMod}} => {{.Path}}{{end}}
{{- end}}
{{- range .Extensions}}
{{if ne .Path ""}}replace {{.GoMod}} => {{.Path}}{{end}}
{{- end}}
{{- range .Receivers}}
{{if ne .Path ""}}replace {{.GoMod}} => {{.Path}}{{end}}
{{- end}}
{{- range .Exporters}}
{{if ne .Path ""}}replace {{.GoMod}} => {{.Path}}{{end}}
{{- end}}
{{- range .Processors}}
{{if ne .Path ""}}replace {{.GoMod}} => {{.Path}}{{end}}
{{- end}}
{{if ne .Telemetry.Path ""}}replace {{.Telemetry.GoMod}} => {{.Telemetry.Path}}{{end}}
{{- range .Replaces}}
replace {{.}}
{{- end}}
{{- range .Excludes}}
exclude {{.}}
{{- end}}
================================================
FILE: cmd/builder/internal/builder/templates/main.go.tmpl
================================================
// Code generated by "go.opentelemetry.io/collector/cmd/builder". DO NOT EDIT.
// Program {{ .Distribution.Name }} is an OpenTelemetry Collector binary.
package main
import (
"os"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/confmap"
{{- range .ConfmapConverters}}
{{.Name}} "{{.Import}}"
{{- end}}
{{- range .ConfmapProviders}}
{{.Name}} "{{.Import}}"
{{- end}}
"go.opentelemetry.io/collector/otelcol"
)
func main() {
info := component.BuildInfo{
Command: "{{ .Distribution.Name }}",
Description: "{{ .Distribution.Description }}",
Version: "{{ .Distribution.Version }}",
}
set := otelcol.CollectorSettings{
BuildInfo: info,
Factories: components,
ConfigProviderSettings: otelcol.ConfigProviderSettings{
ResolverSettings: confmap.ResolverSettings{
ProviderFactories: []confmap.ProviderFactory{
{{- range .ConfmapProviders}}
{{.Name}}.NewFactory(),
{{- end}}
},
{{- if .ConfmapConverters }}
ConverterFactories: []confmap.ConverterFactory{
{{- range .ConfmapConverters}}
{{.Name}}.NewFactory(),
{{- end}}
},
{{- end }}
{{- if .ConfResolver.DefaultURIScheme }}
DefaultScheme: "{{ .ConfResolver.DefaultURIScheme }}",
{{- end }}
},
},
ProviderModules: map[string]string{
{{- range .ConfmapProviders}}
{{.Name}}.NewFactory().Create(confmap.ProviderSettings{}).Scheme(): "{{.GoMod}}",
{{- end}}
},
ConverterModules: []string{
{{- range .ConfmapConverters}}
"{{.GoMod}}",
{{- end}}
},
}
if err := run(set); err != nil {
// The error message is logged by cobra, so we intentionally
// avoid logging it again here to prevent duplicate output.
os.Exit(1)
}
}
func runInteractive(params otelcol.CollectorSettings) error {
cmd := otelcol.NewCommand(params)
if err := cmd.Execute(); err != nil {
return err
}
return nil
}
================================================
FILE: cmd/builder/internal/builder/templates/main_others.go.tmpl
================================================
// Code generated by "go.opentelemetry.io/collector/cmd/builder". DO NOT EDIT.
//go:build !windows
package main
import "go.opentelemetry.io/collector/otelcol"
func run(params otelcol.CollectorSettings) error {
return runInteractive(params)
}
================================================
FILE: cmd/builder/internal/builder/templates/main_windows.go.tmpl
================================================
// Code generated by "go.opentelemetry.io/collector/cmd/builder". DO NOT EDIT.
//go:build windows
package main
import (
"errors"
"fmt"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/svc"
"go.opentelemetry.io/collector/otelcol"
)
func run(params otelcol.CollectorSettings) error {
// No need to supply service name when startup is invoked through
// the Service Control Manager directly.
if err := svc.Run("", otelcol.NewSvcHandler(params)); err != nil {
if errors.Is(err, windows.ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) {
// Per https://learn.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-startservicectrldispatchera#return-value
// this means that the process is not running as a service, so run interactively.
return runInteractive(params)
}
return fmt.Errorf("failed to start collector server: %w", err)
}
return nil
}
================================================
FILE: cmd/builder/internal/builder/templates.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package builder // import "go.opentelemetry.io/collector/cmd/builder/internal/builder"
import (
_ "embed"
"text/template"
)
var (
//go:embed templates/components.go.tmpl
componentsBytes []byte
componentsTemplate = parseTemplate("components.go", componentsBytes)
//go:embed templates/main.go.tmpl
mainBytes []byte
mainTemplate = parseTemplate("main.go", mainBytes)
//go:embed templates/main_others.go.tmpl
mainOthersBytes []byte
mainOthersTemplate = parseTemplate("main_others.go", mainOthersBytes)
//go:embed templates/main_windows.go.tmpl
mainWindowsBytes []byte
mainWindowsTemplate = parseTemplate("main_windows.go", mainWindowsBytes)
//go:embed templates/go.mod.tmpl
goModBytes []byte
goModTemplate = parseTemplate("go.mod", goModBytes)
)
func parseTemplate(name string, bytes []byte) *template.Template {
return template.Must(template.New(name).Parse(string(bytes)))
}
================================================
FILE: cmd/builder/internal/command.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/cmd/builder/internal"
import (
"fmt"
"github.com/knadh/koanf/parsers/yaml"
"github.com/knadh/koanf/providers/env/v2"
"github.com/knadh/koanf/providers/file"
"github.com/knadh/koanf/v2"
"github.com/spf13/cobra"
flag "github.com/spf13/pflag"
"go.uber.org/multierr"
"go.uber.org/zap"
"go.opentelemetry.io/collector/cmd/builder/internal/builder"
"go.opentelemetry.io/collector/cmd/builder/internal/config"
)
const (
configFlag = "config"
skipGenerateFlag = "skip-generate"
skipCompilationFlag = "skip-compilation"
skipGetModulesFlag = "skip-get-modules"
skipStrictVersioningFlag = "skip-strict-versioning"
ldflagsFlag = "ldflags"
gcflagsFlag = "gcflags"
distributionOutputPathFlag = "output-path"
verboseFlag = "verbose"
)
// Command is the main entrypoint for this application
func Command() (*cobra.Command, error) {
cmd := &cobra.Command{
SilenceUsage: true, // Don't print usage on Run error.
Use: "ocb",
Long: fmt.Sprintf("OpenTelemetry Collector Builder (%s)", version) + `
ocb generates a custom OpenTelemetry Collector binary using the
build configuration given by the "--config" argument. If no build
configuration is provided, ocb will generate a default Collector.
`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
cfg, err := initConfig(cmd.Flags())
if err != nil {
return err
}
if err = cfg.Validate(); err != nil {
return fmt.Errorf("invalid configuration: %w", err)
}
if err = cfg.SetGoPath(); err != nil {
return fmt.Errorf("go not found: %w", err)
}
if err = cfg.ParseModules(); err != nil {
return fmt.Errorf("invalid module configuration: %w", err)
}
return builder.GenerateAndCompile(cfg)
},
}
if err := initFlags(cmd.Flags()); err != nil {
return nil, err
}
cmd.AddCommand(initCommand())
cmd.AddCommand(versionCommand())
return cmd, nil
}
func initFlags(flags *flag.FlagSet) error {
flags.String(configFlag, "", "build configuration file")
// the distribution parameters, which we accept as CLI flags as well
flags.Bool(skipGenerateFlag, false, "Whether builder should skip generating go code (default false)")
flags.Bool(skipCompilationFlag, false, "Whether builder should only generate go code with no compile of the collector (default false)")
flags.Bool(skipGetModulesFlag, false, "Whether builder should skip updating go.mod and retrieve Go module list (default false)")
flags.Bool(skipStrictVersioningFlag, true, "Whether builder should skip strictly checking the calculated versions following dependency resolution")
flags.Bool(verboseFlag, false, "Whether builder should print verbose output (default false)")
flags.String(ldflagsFlag, "", `ldflags to include in the "go build" command`)
flags.String(gcflagsFlag, "", `gcflags to include in the "go build" command`)
flags.String(distributionOutputPathFlag, "", "Where to write the resulting files")
return flags.MarkDeprecated(distributionOutputPathFlag, "use config distribution::output_path")
}
func initConfig(flags *flag.FlagSet) (*builder.Config, error) {
cfg, err := builder.NewDefaultConfig()
if err != nil {
return nil, err
}
cfg.Logger.Info("OpenTelemetry Collector Builder", zap.String("version", version))
var provider koanf.Provider
cfgFile, _ := flags.GetString(configFlag)
if cfgFile != "" {
cfg.Logger.Info("Using config file", zap.String("path", cfgFile))
// load the config file
provider = file.Provider(cfgFile)
} else {
cfg.Logger.Info("Using default build configuration")
// or the default if the config isn't provided
provider = config.DefaultProvider()
}
k := koanf.New(".")
if err = k.Load(provider, yaml.Parser()); err != nil {
return nil, fmt.Errorf("failed to load configuration file: %w", err)
}
// handle env variables
if err = k.Load(env.Provider(".", env.Opt{}), nil); err != nil {
return nil, fmt.Errorf("failed to load environment variables: %w", err)
}
if err = k.UnmarshalWithConf("", cfg, koanf.UnmarshalConf{Tag: "mapstructure"}); err != nil {
return nil, fmt.Errorf("failed to unmarshal configuration: %w", err)
}
if err = applyFlags(flags, cfg); err != nil {
return nil, fmt.Errorf("failed to apply flags configuration: %w", err)
}
return cfg, nil
}
func applyFlags(flags *flag.FlagSet, cfg *builder.Config) error {
var errs, err error
cfg.SkipGenerate, err = flags.GetBool(skipGenerateFlag)
errs = multierr.Append(errs, err)
cfg.SkipCompilation, err = flags.GetBool(skipCompilationFlag)
errs = multierr.Append(errs, err)
cfg.SkipGetModules, err = flags.GetBool(skipGetModulesFlag)
errs = multierr.Append(errs, err)
cfg.SkipStrictVersioning, err = flags.GetBool(skipStrictVersioningFlag)
errs = multierr.Append(errs, err)
if flags.Changed(ldflagsFlag) {
cfg.LDSet = true
cfg.LDFlags, err = flags.GetString(ldflagsFlag)
errs = multierr.Append(errs, err)
}
if flags.Changed(gcflagsFlag) {
cfg.GCSet = true
cfg.GCFlags, err = flags.GetString(gcflagsFlag)
errs = multierr.Append(errs, err)
}
cfg.Verbose, err = flags.GetBool(verboseFlag)
errs = multierr.Append(errs, err)
if flags.Changed(distributionOutputPathFlag) {
cfg.Distribution.OutputPath, err = flags.GetString(distributionOutputPathFlag)
errs = multierr.Append(errs, err)
}
return errs
}
================================================
FILE: cmd/builder/internal/command_init.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/cmd/builder/internal"
import (
"bytes"
"embed"
"errors"
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"text/template"
"github.com/spf13/cobra"
"go.opentelemetry.io/collector/cmd/builder/internal/builder"
)
const defaultDescription = "Custom OpenTelemetry Collector"
//go:embed init/templates/*.tmpl
var templatesFS embed.FS
type metadata struct {
Name string
Description string
StableVersion string
BetaVersion string
}
func initCommand() *cobra.Command {
var outputPath string
cmd := &cobra.Command{
Use: "init",
Short: "[EXPERIMENTAL] Initializes a new custom collector repository in the provided folder",
Long: `ocb init initializes a new repository in the provided folder with a manifest to start building a custom Collector. This command is experimental and very likely to change.`,
Args: cobra.NoArgs,
RunE: func(_ *cobra.Command, _ []string) error {
return run(outputPath)
},
}
cmd.Flags().StringVar(&outputPath, "path", ".", "Output path where the collector repository will be initialized")
return cmd
}
func run(path string) error {
if path == "" {
return errors.New("argument must be a folder")
}
path, err := filepath.Abs(path)
if err != nil {
return fmt.Errorf("failed to get absolute path for %v: %w", path, err)
}
err = os.MkdirAll(path, 0o750)
if err != nil {
return fmt.Errorf("failed creating folder %v: %w", path, err)
}
meta := metadata{
Name: filepath.Base(path),
Description: defaultDescription,
StableVersion: builder.DefaultStableOtelColVersion,
BetaVersion: builder.DefaultBetaOtelColVersion,
}
err = writeTemplate(path, "manifest.yaml", meta)
if err != nil {
return fmt.Errorf("failed writing manifest: %w", err)
}
err = writeTemplate(path, ".gitignore", meta)
if err != nil {
return fmt.Errorf("failed writing gitignore: %w", err)
}
err = writeTemplate(path, "README.md", meta)
if err != nil {
return fmt.Errorf("failed writing README: %w", err)
}
err = writeTemplate(path, "go.mod", meta)
if err != nil {
return fmt.Errorf("failed writing go.mod: %w", err)
}
err = writeTemplate(path, "Makefile", meta)
if err != nil {
return fmt.Errorf("failed writing Makefile: %w", err)
}
err = writeTemplate(path, "config.yaml", meta)
if err != nil {
return fmt.Errorf("failed writing config.yaml: %w", err)
}
err = os.MkdirAll(filepath.Join(path, "build"), 0o750)
if err != nil {
return fmt.Errorf("failed creating build folder: %w", err)
}
err = runTidy(path)
if err != nil {
return fmt.Errorf("failed running go mod tidy: %w", err)
}
return nil
}
func writeTemplate(path, fn string, m metadata) error {
outputFile := filepath.Join(path, fn)
content, err := executeTemplate(fn+".tmpl", m)
if err != nil {
return err
}
return os.WriteFile(outputFile, content, 0o600)
}
func executeTemplate(tmplFile string, m metadata) ([]byte, error) {
tmplPath := path.Join("init/templates", tmplFile)
tmpl := template.Must(template.New(tmplFile).ParseFS(templatesFS, tmplPath))
buf := bytes.Buffer{}
if err := tmpl.Execute(&buf, m); err != nil {
return []byte{}, fmt.Errorf("failed executing template: %w", err)
}
return buf.Bytes(), nil
}
func runTidy(path string) error {
cmd := exec.Command("go", "mod", "tidy")
cmd.Dir = path
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("%w (%s)", err, string(output))
}
return nil
}
================================================
FILE: cmd/builder/internal/command_init_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/cmd/builder/internal"
import (
"path/filepath"
"testing"
"github.com/knadh/koanf/parsers/yaml"
"github.com/knadh/koanf/providers/file"
"github.com/knadh/koanf/v2"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/cmd/builder/internal/builder"
)
func TestInitCommand(t *testing.T) {
cmd := initCommand()
assert.NotNil(t, cmd)
assert.IsType(t, &cobra.Command{}, cmd)
assert.Equal(t, "init", cmd.Use)
}
func TestRunInit(t *testing.T) {
for _, tt := range []struct {
name string
buildPath func(string) string
wantErr string
}{
{
name: "without a path",
buildPath: func(string) string { return "" },
wantErr: "argument must be a folder",
},
{
name: "with a relative path",
buildPath: func(string) string { return "./tmp/init" },
wantErr: "",
},
{
name: "with an absolute path",
buildPath: func(dir string) string { return dir },
wantErr: "",
},
} {
t.Run(tt.name, func(t *testing.T) {
tmpdir := filepath.Join(t.TempDir(), "init")
path := tt.buildPath(tmpdir)
err := run(path)
if tt.wantErr == "" {
require.NoError(t, err)
validateCollector(t, path)
} else {
require.ErrorContains(t, err, tt.wantErr)
}
})
}
}
func validateCollector(t *testing.T, path string) {
require.FileExists(t, filepath.Join(path, ".gitignore"))
require.FileExists(t, filepath.Join(path, "README.md"))
require.FileExists(t, filepath.Join(path, "manifest.yaml"))
require.FileExists(t, filepath.Join(path, "go.mod"))
require.FileExists(t, filepath.Join(path, "go.sum"))
require.FileExists(t, filepath.Join(path, "Makefile"))
require.FileExists(t, filepath.Join(path, "config.yaml"))
k := koanf.New(".")
err := k.Load(file.Provider(filepath.Join(path, "manifest.yaml")), yaml.Parser())
require.NoError(t, err)
cfg := builder.Config{}
err = k.UnmarshalWithConf("", &cfg, koanf.UnmarshalConf{
Tag: "mapstructure",
})
require.NoError(t, err)
assert.Equal(t, "init", cfg.Distribution.Name)
assert.Equal(t, defaultDescription, cfg.Distribution.Description)
}
================================================
FILE: cmd/builder/internal/command_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal
import (
"bytes"
"strings"
"testing"
"github.com/spf13/cobra"
flag "github.com/spf13/pflag"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/cmd/builder/internal/builder"
)
func TestCommand(t *testing.T) {
tests := []struct {
name string
want *cobra.Command
wantErr bool
}{
{
name: "command created",
want: &cobra.Command{
SilenceUsage: true, // Don't print usage on Run error.
Use: "ocb",
Long: "OpenTelemetry Collector Builder",
Args: cobra.NoArgs,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Command()
if !tt.wantErr {
require.NoErrorf(t, err, "Command()")
assert.Equal(t, tt.want.Aliases, got.Aliases)
assert.Equal(t, tt.want.Annotations, got.Annotations)
assert.Equal(t, tt.want.ValidArgs, got.ValidArgs)
assert.Equal(t, tt.want.ArgAliases, got.ArgAliases)
assert.Equal(t, tt.want.Use, got.Use)
assert.Equal(t, tt.want.SilenceUsage, got.SilenceUsage)
assert.Equal(t, tt.want.SilenceErrors, got.SilenceErrors)
assert.True(t, strings.HasPrefix(got.Long, tt.want.Long))
assert.Empty(t, got.Short)
assert.NotEqual(t, tt.want.HasFlags(), got.Flags().HasFlags())
} else {
require.Error(t, err)
}
})
}
}
func TestCommandErrorOutputOnce(t *testing.T) {
cmd, err := Command()
require.NoError(t, err)
var stderr bytes.Buffer
cmd.SetErr(&stderr)
cmd.SetArgs([]string{"/nonexistent/path/metadata.yaml"})
err = cmd.Execute()
require.Error(t, err)
out := stderr.String()
require.NotEmpty(t, out)
msg := err.Error()
assert.Equal(t, 1, strings.Count(out, msg), out)
}
func TestApplyFlags(t *testing.T) {
tests := []struct {
name string
flags []string
want *builder.Config
}{
{
name: "Default flag values",
want: &builder.Config{
SkipStrictVersioning: true,
},
},
{
name: "All flag values",
flags: []string{"--skip-generate=true", "--skip-compilation=true", "--skip-get-modules=true", "--skip-strict-versioning=true", "--ldflags=test", "--gcflags=test", "--verbose=true"},
want: &builder.Config{
SkipGenerate: true,
SkipCompilation: true,
SkipGetModules: true,
SkipStrictVersioning: true,
LDFlags: "test",
GCFlags: "test",
Verbose: true,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
flags := flag.NewFlagSet("version=1.0.0", 1)
require.NoError(t, initFlags(flags))
require.NoError(t, flags.Parse(tt.flags))
cfg, err := builder.NewDefaultConfig()
require.NoError(t, err)
require.NoError(t, applyFlags(flags, cfg))
assert.Equal(t, tt.want.SkipGenerate, cfg.SkipGenerate)
assert.Equal(t, tt.want.SkipCompilation, cfg.SkipCompilation)
assert.Equal(t, tt.want.SkipGetModules, cfg.SkipGetModules)
assert.Equal(t, tt.want.SkipStrictVersioning, cfg.SkipStrictVersioning)
assert.Equal(t, tt.want.LDFlags, cfg.LDFlags)
assert.Equal(t, tt.want.Verbose, cfg.Verbose)
})
}
}
func TestInitConfig(t *testing.T) {
tests := []struct {
name string
flags *flag.FlagSet
wantErr bool
}{
{
name: "initConfig created correctly",
flags: flag.NewFlagSet("version=1.0.0", 1),
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.NoError(t, initFlags(tt.flags))
_, err := initConfig(tt.flags)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
})
}
}
================================================
FILE: cmd/builder/internal/config/default.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package config // import "go.opentelemetry.io/collector/cmd/builder/internal/config"
import (
"embed"
"github.com/knadh/koanf/providers/fs"
"github.com/knadh/koanf/v2"
)
//go:embed *.yaml
var configs embed.FS
// DefaultProvider returns a koanf.Provider that provides the default build
// configuration file. This is the same configuration that otelcorecol is
// built with.
func DefaultProvider() koanf.Provider {
return fs.Provider(configs, "default.yaml")
}
================================================
FILE: cmd/builder/internal/config/default.yaml
================================================
# NOTE:
# This builder configuration is NOT used to build any official binary.
# To see the builder manifests used for official binaries,
# check https://github.com/open-telemetry/opentelemetry-collector-releases
#
# For the OpenTelemetry Collector Core official distribution sources, check
# https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol
dist:
module: go.opentelemetry.io/collector/cmd/otelcorecol
name: otelcorecol
description: Local OpenTelemetry Collector binary, testing only.
version: 0.148.0-dev
receivers:
- gomod: go.opentelemetry.io/collector/receiver/nopreceiver v0.148.0
- gomod: go.opentelemetry.io/collector/receiver/otlpreceiver v0.148.0
exporters:
- gomod: go.opentelemetry.io/collector/exporter/debugexporter v0.148.0
- gomod: go.opentelemetry.io/collector/exporter/nopexporter v0.148.0
- gomod: go.opentelemetry.io/collector/exporter/otlpexporter v0.148.0
- gomod: go.opentelemetry.io/collector/exporter/otlphttpexporter v0.148.0
extensions:
- gomod: go.opentelemetry.io/collector/extension/memorylimiterextension v0.148.0
- gomod: go.opentelemetry.io/collector/extension/zpagesextension v0.148.0
processors:
- gomod: go.opentelemetry.io/collector/processor/batchprocessor v0.148.0
- gomod: go.opentelemetry.io/collector/processor/memorylimiterprocessor v0.148.0
connectors:
- gomod: go.opentelemetry.io/collector/connector/forwardconnector v0.148.0
providers:
- gomod: go.opentelemetry.io/collector/confmap/provider/envprovider v1.54.0
- gomod: go.opentelemetry.io/collector/confmap/provider/fileprovider v1.54.0
- gomod: go.opentelemetry.io/collector/confmap/provider/httpprovider v1.54.0
- gomod: go.opentelemetry.io/collector/confmap/provider/httpsprovider v1.54.0
- gomod: go.opentelemetry.io/collector/confmap/provider/yamlprovider v1.54.0
telemetry:
gomod: go.opentelemetry.io/collector/service v0.148.0
import: go.opentelemetry.io/collector/service/telemetry/otelconftelemetry
================================================
FILE: cmd/builder/internal/init/templates/.gitignore.tmpl
================================================
build/
================================================
FILE: cmd/builder/internal/init/templates/Makefile.tmpl
================================================
GOMODULES := $(shell find . -mindepth 2 \
-type f \
-name "go.mod" \
-not -path "./internal/tools/*" \
-exec dirname {} \; | sort )
.PHONY: generate
generate:
go tool go.opentelemetry.io/collector/cmd/builder --verbose --config manifest.yaml
cd build/collector && go mod tidy
.PHONY: run
run: generate
@cd build/collector && \
./{{.Name}} --config ../../config.yaml
================================================
FILE: cmd/builder/internal/init/templates/README.md.tmpl
================================================
# {{.Name}} Collector
Welcome to your new custom OpenTelemetry Collector!
This Collector allows you to configure the components you wish to use, and only those.
Components can come from the ones provided by the OpenTelemetry organization in
the
[opentelemetry-collector-contrib](https://github.com/open-telemetry/opentelemetry-collector-contrib)
repository, or from any other source, including your [custom, possibly private
code](https://opentelemetry.io/docs/collector/extend/custom-component/). The
only requirement is that they match the Go interface for that component, so the
Collector can run them.
## Important files
Edit these two files to customize your Collector:
* `manifest.yaml` - The manifest file configures the OpenTelemetry Collector Builder and tells it what modules it needs to enable within your custom collector.
See [Configure the OpenTelemetry Collector Builder](https://opentelemetry.io/docs/collector/extend/ocb/#configure-the-opentelemetry-collector-builder) for more information.
* `config.yaml` - The Collector configuration allows you to configure your components and their pipelines. This is where you tell your Collector where it needs to export data.
See [Collector Configuration](https://opentelemetry.io/docs/collector/configuration/) for more information.
## Running your custom Collector
You can already run your custom Collector, though it only includes the OTLP
receiver and exporter.
Use the following make command:
```
make run
```
This will build your collector inside the `build/collector` folder, and then
run the compiled binary.
With the `make generate` command, you can also build the collector but not run it.
## Next
To learn more, read the [Extend the
Collector](https://opentelemetry.io/docs/collector/extend/) documentation,
which will give you pointers to configure your new custom Collector, and to
build custom components.
================================================
FILE: cmd/builder/internal/init/templates/config.yaml.tmpl
================================================
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
exporters:
otlp:
endpoint: https://localhost:4318
service:
pipelines:
traces:
receivers: [otlp]
exporters: [otlp]
metrics:
receivers: [otlp]
exporters: [otlp]
logs:
receivers: [otlp]
exporters: [otlp]
================================================
FILE: cmd/builder/internal/init/templates/go.mod.tmpl
================================================
module {{.Name}}
go 1.25
tool go.opentelemetry.io/collector/cmd/builder
================================================
FILE: cmd/builder/internal/init/templates/manifest.yaml.tmpl
================================================
dist:
name: {{.Name}}
description: {{.Description}}
output_path: ./build/collector
exporters:
- gomod: go.opentelemetry.io/collector/exporter/otlpexporter {{.BetaVersion}}
receivers:
- gomod: go.opentelemetry.io/collector/receiver/otlpreceiver {{.BetaVersion}}
================================================
FILE: cmd/builder/internal/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: cmd/builder/internal/version.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/cmd/builder/internal"
import (
"fmt"
"runtime/debug"
"github.com/spf13/cobra"
)
var version = ""
func init() {
// the second returned value is a boolean, which is true if the binaries are built with module support.
if version != "" {
return
}
info, ok := debug.ReadBuildInfo()
if ok {
version = info.Main.Version
}
}
func versionCommand() *cobra.Command {
return &cobra.Command{
Use: "version",
Short: "Version of ocb",
Long: "Prints the version of the ocb binary",
RunE: func(cmd *cobra.Command, _ []string) error {
cmd.Println(fmt.Sprintf("%s version %s", cmd.Parent().Name(), version))
return nil
},
}
}
================================================
FILE: cmd/builder/main.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package main
import (
"os"
"github.com/spf13/cobra"
"go.opentelemetry.io/collector/cmd/builder/internal"
)
func main() {
cmd, err := internal.Command()
cobra.CheckErr(err)
if err := cmd.Execute(); err != nil {
os.Exit(1)
}
}
================================================
FILE: cmd/builder/metadata.yaml
================================================
type: builder
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: cmd
stability:
alpha: [metrics]
codeowners:
active: [ArthurSens, dmathieu]
================================================
FILE: cmd/builder/test/README.md
================================================
# Testing for the OpenTelemetry Collector Builder
This is a set of end-to-end tests for the builder. As such, it includes only positive tests, based on the manifest files in this directory. Each manifest is expected to be in a working state and should yield an OpenTelemetry Collector instance that is ready within a time interval. "Ready" is defined by calling its healthcheck endpoint.
================================================
FILE: cmd/builder/test/core.builder.yaml
================================================
dist:
name: core
module: go.opentelemetry.io/collector/builder/test/core
go: ${GOBIN}
extensions:
- gomod: go.opentelemetry.io/collector/extension/zpagesextension v0.148.0
receivers:
- gomod: go.opentelemetry.io/collector/receiver/otlpreceiver v0.148.0
exporters:
- gomod: go.opentelemetry.io/collector/exporter/debugexporter v0.148.0
================================================
FILE: cmd/builder/test/core.otel.yaml
================================================
extensions:
zpages:
receivers:
otlp:
protocols:
grpc:
processors:
exporters:
debug:
service:
extensions: [zpages]
pipelines:
traces:
receivers:
- otlp
processors: []
exporters:
- debug
================================================
FILE: cmd/builder/test/test.sh
================================================
#!/bin/bash
#
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
WORKSPACE_DIR=$( cd -- "$( dirname "$(dirname "$(dirname -- "${SCRIPT_DIR}")")" )" &> /dev/null && pwd )
export WORKSPACE_DIR
GOBIN=$(go env GOBIN)
if [[ "$GO" == "" ]]; then
GOBIN=$(which go)
fi
export GOBIN
if [[ "$GOBIN" == "" ]]; then
echo "Could not determine which Go binary to use."
exit 1
fi
echo "Using ${GOBIN} to compile the distributions."
test_build_config() {
local test="$1"
local build_config="$2"
out="${base}/${test}"
if ! mkdir -p "${out}"; then
echo "❌ FAIL ${test}. Failed to create test directory for the test. Aborting tests."
exit 2
fi
echo "Starting test '${test}' at $(date)" >> "${out}/test.log"
final_build_config=$(basename "${build_config}")
go tool -modfile "${WORKSPACE_DIR}/internal/tools/go.mod" envsubst \
-o "${out}/${final_build_config}" -i <(cat "$build_config" "$replaces")
if ! go run . --config "${out}/${final_build_config}" --output-path "${out}" > "${out}/builder.log" 2>&1; then
echo "❌ FAIL ${test}. Failed to compile the test ${test}. Build logs:"
cat "${out}/builder.log"
failed=true
return
fi
if [ ! -f "${out}/${test}" ]; then
echo "❌ FAIL ${test}. Binary not found for ${test} at '${out}/${test}'. Build logs:"
cat "${out}/builder.log"
failed=true
return
fi
# start the distribution
if [ ! -f "./test/${test}.otel.yaml" ]; then
echo "❌ FAIL ${test}. Config file for ${test} not found at './test/${test}.otel.yaml'"
failed=true
return
fi
"${out}/${test}" --config "./test/${test}.otel.yaml" > "${out}/otelcol.log" 2>&1 &
pid=$!
# each attempt pauses for 100ms before retrying
max_retries=50
retries=0
while true
do
if ! kill -0 "${pid}" >/dev/null 2>&1; then
echo "❌ FAIL ${test}. The OpenTelemetry Collector isn't running. Startup log:"
cat "${out}/otelcol.log"
failed=true
break
fi
# Since the content of the servicez page depend on which extensions are
# built into the collector, we depend only on the zpages extension
# being present and serving something.
if curl --fail --silent --output /dev/null http://localhost:55679/debug/servicez; then
echo "✅ PASS ${test}"
kill "${pid}"
ret=$?
if [ $ret -ne 0 ]; then
echo "Failed to stop the running instance for test ${test}. Return code: ${ret} . Skipping tests."
exit 4
fi
break
fi
echo "Server still unavailable for test '${test}'" >> "${out}/test.log"
((retries++))
if [ "$retries" -gt "$max_retries" ]; then
echo "❌ FAIL ${test}. Server wasn't up after about 5s."
failed=true
kill "${pid}"
ret=$?
if [ $ret -ne 0 ]; then
echo "Failed to stop the running instance for test ${test}. Return code: ${ret} . Skipping tests."
exit 8
fi
break
fi
sleep 0.1s
done
echo "Stopping server for '${test}' (pid: ${pid})" >> "${out}/test.log"
}
test_init() {
out="${base}/init"
if ! mkdir -p "${out}"; then
echo "❌ FAIL ${test}. Failed to create test directory for the test. Aborting tests."
exit 2
fi
echo "Starting init test at $(date)" >> "${out}/test.log"
if ! go run . init --path "${out}" > "${out}/builder.log" 2>&1; then
echo "❌ FAIL ${test}. Failed to compile the test. Build logs:"
cat "${out}/builder.log"
failed=true
return
fi
go tool -modfile "${WORKSPACE_DIR}/internal/tools/go.mod" envsubst \
-o "${out}/manifest.yaml" -i <(cat "${out}/manifest.yaml" "$replaces")
cd "${out}" || exit 1
make run > "${out}/otelcol.log" 2>&1 &
pid=$!
# each attempt pauses for 100ms before retrying
max_retries=15000
retries=0
while true
do
if ! kill -0 "${pid}" >/dev/null 2>&1; then
echo "❌ FAIL ${test}. The OpenTelemetry Collector isn't running. Startup log:"
cat "${out}/otelcol.log"
failed=true
break
fi
if cat "${out}/otelcol.log" | grep "Everything is ready."; then
echo "✅ PASS init"
kill "${pid}"
ret=$?
if [ $ret -ne 0 ]; then
echo "Failed to stop the running instance for test ${test}. Return code: ${ret} . Skipping tests."
exit 4
fi
break
fi
echo "Server still unavailable for test '${test}'" >> "${out}/test.log"
((retries++))
if [ "$retries" -gt "$max_retries" ]; then
echo "❌ FAIL ${test}. Server wasn't up after about 5m."
failed=true
kill "${pid}"
ret=$?
if [ $ret -ne 0 ]; then
echo "Failed to stop the running instance for test ${test}. Return code: ${ret} . Skipping tests."
exit 8
fi
break
fi
sleep 0.1s
done
echo "Stopping server for '${test}' (pid: ${pid})" >> "${out}/test.log"
}
tests="core"
base=$(mktemp -d)
echo "Running the tests in ${base}"
replaces="$base/replaces"
# Get path of all core modules, in sorted order
core_mods=$(cd ../.. && find . -type f -name "go.mod" -exec dirname {} \; | sort)
echo "replaces:" >> "$replaces"
for mod_path in $core_mods; do
mod=${mod_path#"."} # remove initial dot
echo " - go.opentelemetry.io/collector$mod => \${WORKSPACE_DIR}$mod" >> "$replaces"
done
echo "Wrote replace statements to $replaces"
failed=false
for test in $tests
do
test_build_config "$test" "./test/${test}.builder.yaml"
done
test_init
if [[ "$failed" == "true" ]]; then
exit 1
fi
================================================
FILE: cmd/githubgen/allowlist.txt
================================================
================================================
FILE: cmd/mdatagen/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: cmd/mdatagen/README.md
================================================
# Metadata Generator
| Status | |
| ------------- |-----------|
| Stability | [alpha]: metrics |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Acmd%2Fmdatagen) [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aclosed+is%3Aissue+label%3Acmd%2Fmdatagen) |
| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@dmitryax](https://www.github.com/dmitryax) |
[alpha]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#alpha
Every component's documentation should include a brief description of the component and guidance on how to use it.
There is also some information about the component (or metadata) that should be included to help end-users understand the current state of the component and whether it is right for their use case.
Examples of this metadata about a component are:
- its stability level
- the distributions containing it
- the types of pipelines it supports
- metrics emitted in the case of a scraping receiver, a scraper, or a connector
The metadata generator defines a schema for specifying this information to ensure it is complete and well-formed.
The metadata generator is then able to ingest the metadata, validate it against the schema and produce documentation in a standardized format.
An example of how this generated documentation looks can be found in [documentation.md](https://github.com/open-telemetry/opentelemetry-collector/blob/main/cmd/mdatagen/internal/samplereceiver/documentation.md).
## Using the Metadata Generator
In order for a component to benefit from the metadata generator (`mdatagen`) these requirements need to be met:
1. A yaml file containing the metadata that needs to be included in the component
2. The component should declare a `go:generate mdatagen` directive which tells `mdatagen` what to generate
As an example, here is a minimal `metadata.yaml` for the [OTLP receiver](https://github.com/open-telemetry/opentelemetry-collector/tree/main/receiver/otlpreceiver):
```yaml
type: otlp
status:
class: receiver
stability:
beta: [logs]
stable: [metrics, traces]
```
Detailed information about the schema of `metadata.yaml` can be found in [metadata-schema.yaml](./metadata-schema.yaml).
The `go:generate mdatagen` directive is usually defined in a `doc.go` file in the same package as the component, for example:
```go
//go:generate mdatagen metadata.yaml
package main
```
Below are some more examples that can be used for reference:
- The ElasticSearch receiver has an extensive [metadata.yaml](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/elasticsearchreceiver/metadata.yaml)
- The host metrics receiver has internal subcomponents, each with their own `metadata.yaml` and `doc.go`. See [cpuscraper](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/hostmetricsreceiver/internal/scraper/cpuscraper) for example.
You can run `cd cmd/mdatagen && $(GOCMD) install .` to install the `mdatagen` tool in `GOBIN` and then run `mdatagen metadata.yaml` to generate documentation for a specific component or you can run `make generate` to generate documentation for all components.
### Component Config Documentation
The metadata generator supports automatic generation of configuration schemas for components.
This generates JSON Schema files that enable IDE autocompletion, validation, and documentation for component configuration.
In the future it will also generate Go config structs and human-readable documentation for configuration options
To define a configuration schema, add a `config` section to your `metadata.yaml`:
```yaml
type: myreceiver
status:
class: receiver
stability:
beta: [metrics, traces]
config:
type: object
properties:
endpoint:
type: string
description: The endpoint to listen on
default: "localhost:4317"
timeout:
type: string
format: duration
description: Request timeout duration
default: "30s"
tls:
$ref: go.opentelemetry.io/collector/config/configtls.server_config
required: [endpoint]
```
The `config` section is based on [JSON Schema standard](https://json-schema.org/) (draft 2020-12) and supports:
- **Standard JSON Schema types**: string, number, integer, boolean, object, array, null
- **Validation constraints**: minLength, maxLength, pattern, minimum, maximum, enum, etc.
- **References**: Internal (`$ref: definition_name`), external (`$ref: package.path.type`), or relative (`$ref: ./internal/config.type`)
- **Reusable definitions**: Define common schemas in `$defs` and reference them with `$ref`
- **Schema composition**: Use `allOf` for complex configurations
### Metrics Builder Configuration
For receivers, scrapers, and other components that emit metrics, `mdatagen` can generate metrics builder
configuration from `metadata.yaml`.
```yaml
type: myreceiver
status:
class: receiver
stability:
beta: [metrics]
resource_attributes:
transport:
description: Transport used by the request.
type: string
enabled: true
attributes:
status_code:
description: Response status code.
type: int
requirement_level: opt_in
metrics:
http.server.request.count:
enabled: true
description: Number of received requests.
unit: "{request}"
sum:
value_type: int
monotonic: true
aggregation_temporality: cumulative
attributes: [status_code]
```
This lets users:
- enable or disable individual metrics
- enable or disable resource attributes
- use `metrics_include` and `metrics_exclude` on resource attributes to only emit metrics with matching resource attribute values
### Metric Reaggregation Configuration
Set `reaggregation_enabled: true` to let users reduce metric cardinality by dropping selected metric
attributes and aggregating the resulting datapoints.
```yaml
reaggregation_enabled: true
attributes:
transport:
description: Transport used by the request.
type: string
requirement_level: recommended
status_code:
description: Response status code.
type: int
requirement_level: opt_in
```
This adds two per-metric settings for metrics that declare attributes:
- `attributes`: the subset of metric attributes to keep in the emitted metric stream
- `aggregation_strategy`: how collapsed datapoints are merged, using `sum`, `avg`, `min`, or `max`
Defaults:
- sum metrics use `sum`; gauge metrics use `avg`
- `required` attributes are always kept
- `recommended` and `conditionally_required` attributes are kept by default, but users can remove them
- `opt_in` attributes are omitted by default, so that dimension is aggregated unless the user adds it
Example user configuration:
```yaml
receivers:
myreceiver:
metrics:
http.server.request.count:
enabled: true
aggregation_strategy: sum
attributes: [transport]
```
In this example, datapoints that only differ by `status_code` are aggregated together, while
`transport` remains part of the output identity.
### Feature Gates Documentation
The metadata generator supports automatic documentation generation for feature gates used by components. Feature gates are documented by adding a `feature_gates` section to your `metadata.yaml`:
```yaml
type: mycomponent
status:
class: receiver
stability:
beta: [metrics, traces]
feature_gates:
- id: mycomponent.newFeature
description: 'Enables new feature functionality that improves performance'
stage: alpha
from_version: 'v0.100.0'
reference_url: 'https://github.com/open-telemetry/opentelemetry-collector/issues/12345'
- id: mycomponent.stableFeature
description: 'A feature that has reached stability'
stage: stable
from_version: 'v0.90.0'
to_version: 'v0.95.0'
reference_url: 'https://github.com/open-telemetry/opentelemetry-collector/issues/11111'
```
This will generate a "Feature Gates" section in the component's `documentation.md` file with a table containing:
- **Feature Gate**: The gate identifier
- **Stage**: The lifecycle stage (alpha, beta, stable, deprecated)
- **Description**: Brief description of what the gate controls
- **From Version**: Version when the gate was introduced
- **To Version**: Version when stable/deprecated gates will be removed (if applicable)
- **Reference**: Link to additional contextual information
The feature gate definitions should correspond to actual gates registered in your component code using the [Feature Gates API](../../featuregate/README.md).
### Generate multiple metadata packages
By default, `mdatagen` will generate a package called `metadata` in the `internal` directory. If you want to generate a package with a different name, you can use the `generated_package_name` configuration field to provide an alternate name.
```yaml
type: otlp
generated_package_name: customname
status:
class: receiver
stability:
beta: [logs]
stable: [metrics, traces]
```
The most common scenario for this would be making major changes to a receiver's metadata without breaking what exists. In this scenario, `mdatagen` could produce separate packages for different metadata specs in the same receiver:
```go
//go:generate mdatagen metadata.yaml
//go:generate mdatagen custom.yaml
package main
```
With two different packages generated, the behaviour for which metadata is used can be easily controlled via featuregate or a similar mechanism.
## Contributing to the Metadata Generator
The code for generating the documentation can be found in [loader.go](./internal/loader.go) and the templates for rendering the documentation can be found in [templates](./internal/templates).
When making updates to the metadata generator or introducing support for new functionality:
1. Ensure the [metadata-schema.yaml](./metadata-schema.yaml) and [metadata.yaml](./metadata.yaml) files reflect the changes.
2. Run `make mdatagen-test`.
3. Make sure all tests are passing including [generated tests](./internal/samplereceiver/internal/metadata/generated_metrics_test.go).
4. Run `make generate`.
================================================
FILE: cmd/mdatagen/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package main
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: cmd/mdatagen/go.mod
================================================
module go.opentelemetry.io/collector/cmd/mdatagen
go 1.25.0
require (
github.com/google/go-cmp v0.7.0
github.com/spf13/cobra v1.10.2
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.opentelemetry.io/collector/config/confighttp v0.148.0
go.opentelemetry.io/collector/config/configoptional v1.54.0
go.opentelemetry.io/collector/confmap v1.54.0
go.opentelemetry.io/collector/confmap/provider/fileprovider v1.54.0
go.opentelemetry.io/collector/connector v0.148.0
go.opentelemetry.io/collector/connector/connectortest v0.148.0
go.opentelemetry.io/collector/connector/xconnector v0.148.0
go.opentelemetry.io/collector/consumer v1.54.0
go.opentelemetry.io/collector/consumer/consumertest v0.148.0
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0
go.opentelemetry.io/collector/featuregate v1.54.0
go.opentelemetry.io/collector/filter v0.148.0
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/collector/pdata/pprofile v0.148.0
go.opentelemetry.io/collector/pdata/xpdata v0.148.0
go.opentelemetry.io/collector/pipeline v1.54.0
go.opentelemetry.io/collector/pipeline/xpipeline v0.148.0
go.opentelemetry.io/collector/processor v1.54.0
go.opentelemetry.io/collector/processor/processortest v0.148.0
go.opentelemetry.io/collector/processor/xprocessor v0.148.0
go.opentelemetry.io/collector/receiver v1.54.0
go.opentelemetry.io/collector/receiver/receivertest v0.148.0
go.opentelemetry.io/collector/receiver/xreceiver v0.148.0
go.opentelemetry.io/collector/scraper v0.148.0
go.opentelemetry.io/collector/scraper/scraperhelper v0.148.0
go.opentelemetry.io/collector/scraper/scrapertest v0.148.0
go.opentelemetry.io/collector/scraper/xscraper v0.148.0
go.opentelemetry.io/collector/service/hostcapabilities v0.148.0
go.opentelemetry.io/otel v1.42.0
go.opentelemetry.io/otel/metric v1.42.0
go.opentelemetry.io/otel/sdk/metric v1.42.0
go.opentelemetry.io/otel/trace v1.42.0
go.uber.org/goleak v1.3.0
go.uber.org/zap v1.27.1
go.yaml.in/yaml/v3 v3.0.4
golang.org/x/text v0.34.0
golang.org/x/tools v0.42.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/go-tpm v0.9.8 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.18.4 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pierrec/lz4/v4 v4.1.26 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rs/cors v1.11.1 // indirect
github.com/spf13/pflag v1.0.10 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/client v1.54.0 // indirect
go.opentelemetry.io/collector/component/componentstatus v0.148.0 // indirect
go.opentelemetry.io/collector/config/configauth v1.54.0 // indirect
go.opentelemetry.io/collector/config/configcompression v1.54.0 // indirect
go.opentelemetry.io/collector/config/configmiddleware v1.54.0 // indirect
go.opentelemetry.io/collector/config/confignet v1.54.0 // indirect
go.opentelemetry.io/collector/config/configopaque v1.54.0 // indirect
go.opentelemetry.io/collector/config/configtls v1.54.0 // indirect
go.opentelemetry.io/collector/confmap/xconfmap v0.148.0 // indirect
go.opentelemetry.io/collector/consumer/consumererror v0.148.0 // indirect
go.opentelemetry.io/collector/extension/extensionauth v1.54.0 // indirect
go.opentelemetry.io/collector/extension/extensionmiddleware v0.148.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
go.opentelemetry.io/collector/internal/fanoutconsumer v0.148.0 // indirect
go.opentelemetry.io/collector/pdata/testdata v0.148.0 // indirect
go.opentelemetry.io/collector/receiver/receiverhelper v0.148.0 // indirect
go.opentelemetry.io/collector/service v0.148.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/mod v0.33.0 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.42.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest
replace go.opentelemetry.io/collector/confmap => ../../confmap
replace go.opentelemetry.io/collector/confmap/provider/fileprovider => ../../confmap/provider/fileprovider
replace go.opentelemetry.io/collector/filter => ../../filter
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/pdata/testdata => ../../pdata/testdata
replace go.opentelemetry.io/collector/receiver => ../../receiver
replace go.opentelemetry.io/collector/consumer => ../../consumer
replace go.opentelemetry.io/collector/receiver/receivertest => ../../receiver/receivertest
retract (
v0.76.2
v0.76.1
v0.65.0
)
replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile
replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer
replace go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest
replace go.opentelemetry.io/collector/receiver/xreceiver => ../../receiver/xreceiver
replace go.opentelemetry.io/collector/pipeline => ../../pipeline
replace go.opentelemetry.io/collector/processor/xprocessor => ../../processor/xprocessor
replace go.opentelemetry.io/collector/processor/processortest => ../../processor/processortest
replace go.opentelemetry.io/collector/component/componentstatus => ../../component/componentstatus
replace go.opentelemetry.io/collector/processor => ../../processor
replace go.opentelemetry.io/collector/consumer/consumererror => ../../consumer/consumererror
replace go.opentelemetry.io/collector/scraper => ../../scraper
replace go.opentelemetry.io/collector/scraper/scrapertest => ../../scraper/scrapertest
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/connector => ../../connector
replace go.opentelemetry.io/collector/connector/connectortest => ../../connector/connectortest
replace go.opentelemetry.io/collector/pipeline/xpipeline => ../../pipeline/xpipeline
replace go.opentelemetry.io/collector/internal/fanoutconsumer => ../../internal/fanoutconsumer
replace go.opentelemetry.io/collector/connector/xconnector => ../../connector/xconnector
replace go.opentelemetry.io/collector/internal/telemetry => ../../internal/telemetry
replace go.opentelemetry.io/collector/extension/extensiontest => ../../extension/extensiontest
replace go.opentelemetry.io/collector/exporter => ../../exporter
replace go.opentelemetry.io/collector/confmap/xconfmap => ../../confmap/xconfmap
replace go.opentelemetry.io/collector/extension/extensionmiddleware => ../../extension/extensionmiddleware
replace go.opentelemetry.io/collector/config/configmiddleware => ../../config/configmiddleware
replace go.opentelemetry.io/collector/exporter/exportertest => ../../exporter/exportertest
replace go.opentelemetry.io/collector/client => ../../client
replace go.opentelemetry.io/collector/extension => ../../extension
replace go.opentelemetry.io/collector/config/configauth => ../../config/configauth
replace go.opentelemetry.io/collector/otelcol => ../../otelcol
replace go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest => ../../extension/extensionmiddleware/extensionmiddlewaretest
replace go.opentelemetry.io/collector/extension/xextension => ../../extension/xextension
replace go.opentelemetry.io/collector/extension/zpagesextension => ../../extension/zpagesextension
replace go.opentelemetry.io/collector/config/configretry => ../../config/configretry
replace go.opentelemetry.io/collector/config/configoptional => ../../config/configoptional
replace go.opentelemetry.io/collector/config/configopaque => ../../config/configopaque
replace go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest => ../../extension/extensionauth/extensionauthtest
replace go.opentelemetry.io/collector/service => ../../service
replace go.opentelemetry.io/collector/extension/extensionauth => ../../extension/extensionauth
replace go.opentelemetry.io/collector/service/hostcapabilities => ../../service/hostcapabilities
replace go.opentelemetry.io/collector/config/configtls => ../../config/configtls
replace go.opentelemetry.io/collector/config/configcompression => ../../config/configcompression
replace go.opentelemetry.io/collector/exporter/xexporter => ../../exporter/xexporter
replace go.opentelemetry.io/collector/config/configtelemetry => ../../config/configtelemetry
replace go.opentelemetry.io/collector/config/confighttp => ../../config/confighttp
replace go.opentelemetry.io/collector/extension/extensioncapabilities => ../../extension/extensioncapabilities
replace go.opentelemetry.io/collector/pdata/xpdata => ../../pdata/xpdata
replace go.opentelemetry.io/collector/exporter/exporterhelper => ../../exporter/exporterhelper
replace go.opentelemetry.io/collector/service/telemetry/telemetrytest => ../../service/telemetry/telemetrytest
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
replace go.opentelemetry.io/collector/config/confignet => ../../config/confignet
replace go.opentelemetry.io/collector/receiver/receiverhelper => ../../receiver/receiverhelper
replace go.opentelemetry.io/collector/scraper/scraperhelper => ../../scraper/scraperhelper
replace go.opentelemetry.io/collector/scraper/xscraper => ../../scraper/xscraper
================================================
FILE: cmd/mdatagen/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f h1:RJ+BDPLSHQO7cSjKBqjPJSbi1qfk9WcsjQDtZiw3dZw=
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f/go.mod h1:VHbbch/X4roIY22jL1s3qRbZhCiRIgUAF/PdSUcx2io=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/go-tpm-tools v0.4.7 h1:J3ycC8umYxM9A4eF73EofRZu4BxY0jjQnUnkhIBbvws=
github.com/google/go-tpm-tools v0.4.7/go.mod h1:gSyXTZHe3fgbzb6WEGd90QucmsnT1SRdlye82gH8QjQ=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY=
github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: cmd/mdatagen/internal/cfggen/generation.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package cfggen // import "go.opentelemetry.io/collector/cmd/mdatagen/internal/cfggen"
import (
"errors"
"fmt"
"maps"
"slices"
"go.opentelemetry.io/collector/cmd/mdatagen/internal/helpers"
)
// NewCfgFns returns template functions for config generation with rootPackage and componentPackage
// baked into closures. This way the template itself never needs to pass these context values around.
func NewCfgFns(rootPackage, componentPackage string) map[string]any {
return map[string]any{
"extractImports": func(cfg *ConfigMetadata) []string {
if cfg == nil {
return nil
}
imports, err := ExtractImports(cfg, rootPackage, componentPackage)
if err != nil {
return []string{}
}
return imports
},
"extractDefs": func(cfg *ConfigMetadata) map[string]*ConfigMetadata {
if cfg == nil {
return nil
}
return ExtractDefs(cfg)
},
"mapGoType": func(cfg *ConfigMetadata, propName string) string {
if cfg == nil {
return "any"
}
goType, err := MapGoType(cfg, propName, rootPackage, componentPackage)
if err != nil {
panic(err)
}
return goType
},
"publicType": func(ref string) string {
typeName, err := FormatTypeName(ref, rootPackage, componentPackage)
if err != nil {
panic(err)
}
return typeName
},
}
}
// WithCfgFns merges config generation template functions into the given function map.
// The rootPackage and componentPackage are captured in closures so the template doesn't need to thread them through.
func WithCfgFns(fns map[string]any, rootPackage, componentPackage string) map[string]any {
cfgFns := NewCfgFns(rootPackage, componentPackage)
maps.Copy(fns, cfgFns)
return fns
}
var goBasicTypes = []string{
"rune", "byte",
"uint", "int8", "uint8", "int16", "uint16", "int32", "uint32", "int64", "uint64",
"float32", "float64",
}
// MapGoType maps a ConfigMetadata to its corresponding Go type as a string.
func MapGoType(md *ConfigMetadata, propName, rootPackage, componentPackage string) (string, error) {
if md == nil {
return "", errors.New("nil ConfigMetadata")
}
goType, err := resolveGoType(md, propName, rootPackage, componentPackage)
if err != nil {
return "", fmt.Errorf("failed to resolve Go type for property %q: %w", propName, err)
}
if md.IsPointer {
goType = "*" + goType
}
if md.IsOptional {
return "configoptional.Optional[" + goType + "]", nil
}
return goType, nil
}
func resolveGoType(md *ConfigMetadata, propName, rootPackage, componentPackage string) (string, error) {
if md.GoType != "" {
if slices.Contains(goBasicTypes, md.GoType) {
return md.GoType, nil
}
typeName, err := FormatTypeName(md.GoType, rootPackage, componentPackage)
if err != nil {
return "", fmt.Errorf("failed to format custom type %q: %w", md.GoType, err)
}
return typeName, nil
}
if md.Ref != "" {
typeName, err := FormatTypeName(md.Ref, rootPackage, componentPackage)
if err != nil {
return "", fmt.Errorf("failed to format reference type %q: %w", md.Ref, err)
}
return typeName, nil
}
switch md.Type {
case "string":
switch md.Format {
case "date-time":
return "time.Time", nil
case "duration":
return "time.Duration", nil
default:
return "string", nil
}
case "integer":
return "int", nil
case "number":
return "float64", nil
case "boolean":
return "bool", nil
case "array":
if md.Items == nil {
return "[]any", nil
}
itemType, err := MapGoType(md.Items, propName+"_item", rootPackage, componentPackage)
if err != nil {
return "", fmt.Errorf("failed to map array item type: %w", err)
}
return "[]" + itemType, nil
case "object":
if md.AdditionalProperties != nil {
valueType, err := MapGoType(md.AdditionalProperties, propName, rootPackage, componentPackage)
if err != nil {
return "", fmt.Errorf("failed to map additionalProperties type: %w", err)
}
return "map[string]" + valueType, nil
}
if md.Properties != nil {
formatted, err := helpers.FormatIdentifier(propName, true)
if err != nil {
return "", fmt.Errorf("failed to format embedded object type name %q: %w", propName, err)
}
return formatted, nil
}
return "map[string]any", nil
case "":
return "any", nil
default:
return "", fmt.Errorf("unsupported type: %q", md.Type)
}
}
// ExtractImports recursively scans the ConfigMetadata and collects all unique import paths needed for the generated Go code.
func ExtractImports(md *ConfigMetadata, rootPackage, componentPackage string) ([]string, error) {
if md == nil {
return nil, nil
}
imports := make(map[string]bool)
if err := collectImports(md, imports, rootPackage, componentPackage); err != nil {
return nil, err
}
return slices.Collect(maps.Keys(imports)), nil
}
func collectImports(md *ConfigMetadata, imports map[string]bool, rootPackage, componentPackage string) error {
if md == nil {
return nil
}
if md.GoType != "" {
ref, err := ResolveGoTypeRef(md.GoType, rootPackage, componentPackage)
if err == nil && ref.ImportPath != "" {
imports[ref.ImportPath] = true
}
}
if md.Ref != "" {
ref, err := ResolveGoTypeRef(md.Ref, rootPackage, componentPackage)
if err == nil && ref.ImportPath != "" {
imports[ref.ImportPath] = true
}
}
if md.Type == "string" && (md.Format == "date-time" || md.Format == "duration") {
imports["time"] = true
}
if md.IsOptional {
imports["go.opentelemetry.io/collector/config/configoptional"] = true
}
for _, prop := range md.Properties {
if err := collectImports(prop, imports, rootPackage, componentPackage); err != nil {
return err
}
}
if md.Items != nil {
if err := collectImports(md.Items, imports, rootPackage, componentPackage); err != nil {
return err
}
}
for _, schema := range md.AllOf {
if err := collectImports(schema, imports, rootPackage, componentPackage); err != nil {
return err
}
}
for _, def := range md.Defs {
if err := collectImports(def, imports, rootPackage, componentPackage); err != nil {
return err
}
}
if err := collectImports(md.AdditionalProperties, imports, rootPackage, componentPackage); err != nil {
return err
}
if md.ContentSchema != nil {
if err := collectImports(md.ContentSchema, imports, rootPackage, componentPackage); err != nil {
return err
}
}
return nil
}
// FormatTypeName resolves a reference string to a Go type expression using GoTypeRef.
func FormatTypeName(ref, rootPackage, componentPackage string) (string, error) {
tr, err := ResolveGoTypeRef(ref, rootPackage, componentPackage)
if err != nil {
return "", err
}
return tr.String(), nil
}
// ExtractDefs recursively collects all definitions from the ConfigMetadata, including nested ones,
// and returns a flat map of definition names to their corresponding ConfigMetadata.
func ExtractDefs(md *ConfigMetadata) map[string]*ConfigMetadata {
defs := make(map[string]*ConfigMetadata)
collectDefs(md, defs)
return defs
}
func collectDefs(md *ConfigMetadata, defs map[string]*ConfigMetadata) {
if md == nil {
return
}
for name, def := range md.Defs {
defs[name] = def
collectDefs(def, defs)
}
for propName, prop := range md.Properties {
// if is embedded object
if prop.Type == "object" {
if len(prop.Properties) > 0 {
defs[propName] = prop
collectDefs(prop, defs)
}
ap := md.AdditionalProperties
if ap != nil && ap.Type == "object" && len(ap.Properties) > 0 {
defs[propName] = ap
collectDefs(ap, defs)
}
}
if prop.Type == "array" {
if prop.Items != nil && prop.Items.Type == "object" && len(prop.Items.Properties) > 0 {
defName := propName + "_item"
defs[defName] = prop.Items
collectDefs(prop.Items, defs)
}
}
}
}
================================================
FILE: cmd/mdatagen/internal/cfggen/generation_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package cfggen
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestMapGoType_BasicTypes(t *testing.T) {
tests := []struct {
name string
metadata *ConfigMetadata
propName string
expected string
}{
{
name: "string type",
metadata: &ConfigMetadata{Type: "string"},
propName: "field",
expected: "string",
},
{
name: "integer type",
metadata: &ConfigMetadata{Type: "integer"},
propName: "field",
expected: "int",
},
{
name: "number type",
metadata: &ConfigMetadata{Type: "number"},
propName: "field",
expected: "float64",
},
{
name: "boolean type",
metadata: &ConfigMetadata{Type: "boolean"},
propName: "field",
expected: "bool",
},
{
name: "empty type defaults to any",
metadata: &ConfigMetadata{Type: ""},
propName: "field",
expected: "any",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := MapGoType(tt.metadata, tt.propName, "", "")
require.NoError(t, err)
require.Equal(t, tt.expected, result)
})
}
}
func TestMapGoType_FormattedStrings(t *testing.T) {
tests := []struct {
name string
format string
expected string
}{
{
name: "date-time format",
format: "date-time",
expected: "time.Time",
},
{
name: "duration format",
format: "duration",
expected: "time.Duration",
},
{
name: "no format",
format: "",
expected: "string",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
md := &ConfigMetadata{
Type: "string",
Format: tt.format,
}
result, err := MapGoType(md, "field", "", "")
require.NoError(t, err)
require.Equal(t, tt.expected, result)
})
}
}
func TestMapGoType_Arrays(t *testing.T) {
compPkg := "go.opentelemetry.io/collector/cmd/mdatagen/internal/samplescraper"
tests := []struct {
name string
metadata *ConfigMetadata
expected string
}{
{
name: "array with string items",
metadata: &ConfigMetadata{
Type: "array",
Items: &ConfigMetadata{Type: "string"},
},
expected: "[]string",
},
{
name: "array with int items",
metadata: &ConfigMetadata{
Type: "array",
Items: &ConfigMetadata{Type: "integer"},
},
expected: "[]int",
},
{
name: "array with ref items",
metadata: &ConfigMetadata{
Type: "array",
Items: &ConfigMetadata{Ref: "./internal/metadata.custom_type"},
},
expected: "[]metadata.CustomType",
},
{
name: "array with nested object items ",
metadata: &ConfigMetadata{
Type: "array",
Items: &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"name": {Type: "string"},
},
},
},
expected: "[]FieldItem",
},
{
name: "array without items defaults to any",
metadata: &ConfigMetadata{
Type: "array",
Items: nil,
},
expected: "[]any",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := MapGoType(tt.metadata, "field", "", compPkg)
require.NoError(t, err)
require.Equal(t, tt.expected, result)
})
}
}
func TestMapGoType_Objects(t *testing.T) {
compPkg := "go.opentelemetry.io/collector/cmd/mdatagen/internal/samplescraper"
tests := []struct {
name string
metadata *ConfigMetadata
propName string
expected string
}{
{
name: "object with additionalProperties string",
metadata: &ConfigMetadata{
Type: "object",
AdditionalProperties: &ConfigMetadata{Type: "string"},
},
propName: "field",
expected: "map[string]string",
},
{
name: "object with additionalProperties int",
metadata: &ConfigMetadata{
Type: "object",
AdditionalProperties: &ConfigMetadata{Type: "integer"},
},
propName: "field",
expected: "map[string]int",
},
{
name: "object with additionalProperties ref",
metadata: &ConfigMetadata{
Type: "object",
AdditionalProperties: &ConfigMetadata{Ref: "./internal/metadata.custom_type"},
},
propName: "field",
expected: "map[string]metadata.CustomType",
},
{
name: "object without additionalProperties or properties",
metadata: &ConfigMetadata{
Type: "object",
},
propName: "field",
expected: "map[string]any",
},
{
name: "object with properties",
metadata: &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"name": {Type: "string"},
},
},
propName: "my_config",
expected: "MyConfig",
},
{
name: "map of arrays of objects",
metadata: &ConfigMetadata{
Type: "object",
AdditionalProperties: &ConfigMetadata{
Type: "array",
Items: &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"id": {Type: "integer"},
},
},
},
},
propName: "field",
expected: "map[string][]FieldItem",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := MapGoType(tt.metadata, tt.propName, "", compPkg)
require.NoError(t, err)
require.Equal(t, tt.expected, result)
})
}
}
func TestMapGoType_CustomTypes(t *testing.T) {
tests := []struct {
name string
metadata *ConfigMetadata
expected string
}{
{
name: "custom type with basic type",
metadata: &ConfigMetadata{
Type: "string",
GoType: "rune",
},
expected: "rune",
},
{
name: "custom type with external package",
metadata: &ConfigMetadata{
Type: "object",
GoType: "github.com/example/pkg.CustomType",
},
expected: "pkg.CustomType",
},
{
name: "custom type without package",
metadata: &ConfigMetadata{
Type: "object",
GoType: "my_custom_type",
},
expected: "MyCustomType",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := MapGoType(tt.metadata, "field", "", "")
require.NoError(t, err)
require.Equal(t, tt.expected, result)
})
}
}
func TestMapGoType_References(t *testing.T) {
tests := []struct {
name string
ref string
expected string
}{
{
name: "internal reference",
ref: "my_type",
expected: "MyType",
},
{
name: "external reference",
ref: "go.opentelemetry.io/collector/component.Config",
expected: "component.Config",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
md := &ConfigMetadata{
Ref: tt.ref,
}
result, err := MapGoType(md, "field", "", "")
require.NoError(t, err)
require.Equal(t, tt.expected, result)
})
}
}
func TestMapGoType_Modifiers(t *testing.T) {
tests := []struct {
name string
metadata *ConfigMetadata
expected string
}{
{
name: "optional type",
metadata: &ConfigMetadata{
Type: "string",
IsOptional: true,
},
expected: "configoptional.Optional[string]",
},
{
name: "pointer type",
metadata: &ConfigMetadata{
Type: "string",
IsPointer: true,
},
expected: "*string",
},
{
name: "optional pointer",
metadata: &ConfigMetadata{
Type: "string",
IsOptional: true,
IsPointer: true,
},
expected: "configoptional.Optional[*string]",
},
{
name: "array of pointers",
metadata: &ConfigMetadata{
Type: "array",
Items: &ConfigMetadata{
Type: "string",
IsPointer: true,
},
},
expected: "[]*string",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := MapGoType(tt.metadata, "field", "", "")
require.NoError(t, err)
require.Equal(t, tt.expected, result)
})
}
}
func TestMapGoType_NilInput(t *testing.T) {
_, err := MapGoType(nil, "field", "", "")
require.Error(t, err)
require.Contains(t, err.Error(), "nil ConfigMetadata")
}
func TestMapGoType_UnsupportedType(t *testing.T) {
md := &ConfigMetadata{
Type: "unsupported_type",
}
_, err := MapGoType(md, "field", "", "")
require.Error(t, err)
require.Contains(t, err.Error(), "unsupported type")
}
func TestExtractImports_BasicTypes(t *testing.T) {
tests := []struct {
name string
metadata *ConfigMetadata
expected []string
}{
{
name: "no imports for basic types",
metadata: &ConfigMetadata{Type: "string"},
expected: []string{},
},
{
name: "time import for date-time format",
metadata: &ConfigMetadata{
Type: "string",
Format: "date-time",
},
expected: []string{"time"},
},
{
name: "time import for duration format",
metadata: &ConfigMetadata{
Type: "string",
Format: "duration",
},
expected: []string{"time"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := ExtractImports(tt.metadata, "", "")
require.NoError(t, err)
require.ElementsMatch(t, tt.expected, result)
})
}
}
func TestExtractImports_CustomTypes(t *testing.T) {
tests := []struct {
name string
metadata *ConfigMetadata
expected []string
}{
{
name: "external custom type",
metadata: &ConfigMetadata{
Type: "object",
GoType: "github.com/example/pkg.CustomType",
},
expected: []string{"github.com/example/pkg"},
},
{
name: "external reference",
metadata: &ConfigMetadata{
Ref: "go.opentelemetry.io/collector/component.Config",
},
expected: []string{"go.opentelemetry.io/collector/component"},
},
{
name: "no import for internal reference",
metadata: &ConfigMetadata{
Ref: "my_type",
},
expected: []string{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := ExtractImports(tt.metadata, "", "")
require.NoError(t, err)
require.ElementsMatch(t, tt.expected, result)
})
}
}
func TestExtractImports_LocalRef(t *testing.T) {
rootPkg := "go.opentelemetry.io/collector"
compPkg := "go.opentelemetry.io/collector/scraper/scraperhelper"
tests := []struct {
name string
metadata *ConfigMetadata
expected []string
}{
{
name: "local absolute reference",
metadata: &ConfigMetadata{
Ref: "/config/confighttp.client_config",
},
expected: []string{"go.opentelemetry.io/collector/config/confighttp"},
},
{
name: "local relative reference",
metadata: &ConfigMetadata{
Ref: "./internal/metadata.custom_type",
},
expected: []string{"go.opentelemetry.io/collector/scraper/scraperhelper/internal/metadata"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := ExtractImports(tt.metadata, rootPkg, compPkg)
require.NoError(t, err)
require.ElementsMatch(t, tt.expected, result)
})
}
}
func TestExtractImports_Optional(t *testing.T) {
md := &ConfigMetadata{
Type: "string",
IsOptional: true,
}
result, err := ExtractImports(md, "", "")
require.NoError(t, err)
require.Contains(t, result, "go.opentelemetry.io/collector/config/configoptional")
}
func TestExtractImports_Nested(t *testing.T) {
md := &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"timeout": {
Type: "string",
Format: "duration",
},
"nested": {
Type: "object",
Properties: map[string]*ConfigMetadata{
"timestamp": {
Type: "string",
Format: "date-time",
},
},
},
},
}
result, err := ExtractImports(md, "", "")
require.NoError(t, err)
require.Contains(t, result, "time")
}
func TestExtractImports_AllOf(t *testing.T) {
md := &ConfigMetadata{
Type: "object",
AllOf: []*ConfigMetadata{
{
Type: "string",
Format: "duration",
},
},
}
result, err := ExtractImports(md, "", "")
require.NoError(t, err)
require.Contains(t, result, "time")
}
func TestExtractImports_ArrayItems(t *testing.T) {
md := &ConfigMetadata{
Type: "array",
Items: &ConfigMetadata{
Type: "string",
Format: "date-time",
},
}
result, err := ExtractImports(md, "", "")
require.NoError(t, err)
require.Contains(t, result, "time")
}
func TestExtractImports_AdditionalProperties(t *testing.T) {
md := &ConfigMetadata{
Type: "object",
AdditionalProperties: &ConfigMetadata{
Type: "string",
Format: "duration",
},
}
result, err := ExtractImports(md, "", "")
require.NoError(t, err)
require.Contains(t, result, "time")
}
func TestExtractImports_Defs(t *testing.T) {
md := &ConfigMetadata{
Type: "object",
Defs: map[string]*ConfigMetadata{
"CustomType": {
Type: "string",
Format: "date-time",
},
},
}
result, err := ExtractImports(md, "", "")
require.NoError(t, err)
require.Contains(t, result, "time")
}
func TestExtractImports_NilInput(t *testing.T) {
result, err := ExtractImports(nil, "", "")
require.NoError(t, err)
require.Nil(t, result)
}
func TestFormatTypeName_InternalReferences(t *testing.T) {
tests := []struct {
name string
ref string
expected string
}{
{
name: "simple name",
ref: "my_type",
expected: "MyType",
},
{
name: "snake case",
ref: "my_custom_type",
expected: "MyCustomType",
},
{
name: "already formatted",
ref: "MyType",
expected: "MyType",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := FormatTypeName(tt.ref, "", "")
require.NoError(t, err)
require.Equal(t, tt.expected, result)
})
}
}
func TestFormatTypeName_ExternalReferences(t *testing.T) {
tests := []struct {
name string
ref string
expected string
}{
{
name: "full package path",
ref: "go.opentelemetry.io/collector/component.Config",
expected: "component.Config",
},
{
name: "nested package",
ref: "github.com/example/pkg/subpkg.Type",
expected: "subpkg.Type",
},
{
name: "type name needs formatting",
ref: "github.com/example/pkg.my_type",
expected: "pkg.MyType",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := FormatTypeName(tt.ref, "", "")
require.NoError(t, err)
require.Equal(t, tt.expected, result)
})
}
}
func TestFormatTypeName_LocalReferences(t *testing.T) {
rootPkg := "go.opentelemetry.io/collector"
compPkg := "go.opentelemetry.io/collector/scraper/scraperhelper"
tests := []struct {
name string
ref string
expected string
}{
{
name: "local absolute",
ref: "/config/confighttp.client_config",
expected: "confighttp.ClientConfig",
},
{
name: "local relative",
ref: "./internal/metadata.metrics_builder",
expected: "metadata.MetricsBuilder",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := FormatTypeName(tt.ref, rootPkg, compPkg)
require.NoError(t, err)
require.Equal(t, tt.expected, result)
})
}
}
func TestFormatTypeName_InvalidInput(t *testing.T) {
tests := []struct {
name string
ref string
}{
{
name: "empty type name after dot",
ref: "github.com/example/pkg.",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := FormatTypeName(tt.ref, "", "")
require.Error(t, err)
})
}
}
func TestExtractDefs_Basic(t *testing.T) {
md := &ConfigMetadata{
Type: "object",
Defs: map[string]*ConfigMetadata{
"CustomType": {
Type: "string",
},
"AnotherType": {
Type: "integer",
},
},
}
result := ExtractDefs(md)
require.Len(t, result, 2)
require.Contains(t, result, "CustomType")
require.Contains(t, result, "AnotherType")
}
func TestExtractDefs_NestedDefs(t *testing.T) {
md := &ConfigMetadata{
Type: "object",
Defs: map[string]*ConfigMetadata{
"OuterType": {
Type: "object",
Defs: map[string]*ConfigMetadata{
"InnerType": {
Type: "string",
},
},
},
},
}
result := ExtractDefs(md)
require.Len(t, result, 2)
require.Contains(t, result, "OuterType")
require.Contains(t, result, "InnerType")
}
func TestExtractDefs_EmbeddedObjects(t *testing.T) {
md := &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"config": {
Type: "object",
Properties: map[string]*ConfigMetadata{
"name": {Type: "string"},
},
},
},
}
result := ExtractDefs(md)
require.Len(t, result, 1)
require.Contains(t, result, "config")
require.Equal(t, "object", result["config"].Type)
}
func TestExtractDefs_ArrayItems(t *testing.T) {
md := &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"servers": {
Type: "array",
Items: &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"host": {Type: "string"},
},
},
},
},
}
result := ExtractDefs(md)
require.Len(t, result, 1)
require.Contains(t, result, "servers_item")
require.Equal(t, "object", result["servers_item"].Type)
}
func TestExtractDefs_NilInput(t *testing.T) {
result := ExtractDefs(nil)
require.Empty(t, result)
}
func TestExtractDefs_EmptyInput(t *testing.T) {
md := &ConfigMetadata{
Type: "object",
}
result := ExtractDefs(md)
require.Empty(t, result)
}
func TestExtractDefs_AdditionalPropertiesObject(t *testing.T) {
md := &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
// A property of type "object" with no sub-properties triggers the ap check
"extra": {Type: "object"},
},
AdditionalProperties: &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"value": {Type: "integer"},
},
},
}
result := ExtractDefs(md)
require.Contains(t, result, "extra")
}
func TestNewCfgFns_ExtractImports(t *testing.T) {
fns := NewCfgFns("go.opentelemetry.io/collector", "go.opentelemetry.io/collector/comp")
extractImports := fns["extractImports"].(func(*ConfigMetadata) []string)
// nil input returns nil
require.Nil(t, extractImports(nil))
// valid input returns imports
md := &ConfigMetadata{Type: "string", Format: "duration"}
result := extractImports(md)
require.Contains(t, result, "time")
// input with unresolvable GoType: collectImports swallows the error, returns empty slice
errMd := &ConfigMetadata{GoType: "github.com/pkg."}
result = extractImports(errMd)
require.Empty(t, result)
}
func TestNewCfgFns_ExtractDefs(t *testing.T) {
fns := NewCfgFns("", "")
extractDefs := fns["extractDefs"].(func(*ConfigMetadata) map[string]*ConfigMetadata)
// nil input returns nil
require.Nil(t, extractDefs(nil))
// valid input
md := &ConfigMetadata{
Type: "object",
Defs: map[string]*ConfigMetadata{"MyType": {Type: "string"}},
}
result := extractDefs(md)
require.Contains(t, result, "MyType")
}
func TestNewCfgFns_MapGoType(t *testing.T) {
fns := NewCfgFns("", "")
mapGoType := fns["mapGoType"].(func(*ConfigMetadata, string) string)
// nil input returns "any"
require.Equal(t, "any", mapGoType(nil, "field"))
// valid input
require.Equal(t, "string", mapGoType(&ConfigMetadata{Type: "string"}, "field"))
}
func TestNewCfgFns_PublicType(t *testing.T) {
fns := NewCfgFns("", "")
publicType := fns["publicType"].(func(string) string)
require.Equal(t, "MyType", publicType("my_type"))
require.Equal(t, "component.Config", publicType("go.opentelemetry.io/collector/component.Config"))
}
func TestWithCfgFns(t *testing.T) {
base := map[string]any{"existing": "value"}
result := WithCfgFns(base, "", "")
require.Equal(t, "value", result["existing"])
require.Contains(t, result, "mapGoType")
require.Contains(t, result, "extractImports")
require.Contains(t, result, "extractDefs")
require.Contains(t, result, "publicType")
}
func TestResolveGoType_CustomTypeFormatError(t *testing.T) {
// GoType with invalid empty type name after dot triggers FormatTypeName error
md := &ConfigMetadata{GoType: "github.com/pkg."}
_, err := MapGoType(md, "field", "", "")
require.Error(t, err)
require.Contains(t, err.Error(), "failed to format custom type")
}
func TestResolveGoType_RefFormatError(t *testing.T) {
// Ref with invalid empty type name after dot triggers FormatTypeName error
md := &ConfigMetadata{Ref: "github.com/pkg."}
_, err := MapGoType(md, "field", "", "")
require.Error(t, err)
require.Contains(t, err.Error(), "failed to format reference type")
}
func TestResolveGoType_ArrayItemError(t *testing.T) {
// Array whose item type fails to resolve
md := &ConfigMetadata{
Type: "array",
Items: &ConfigMetadata{Type: "unsupported_array_item"},
}
_, err := MapGoType(md, "field", "", "")
require.Error(t, err)
require.Contains(t, err.Error(), "failed to map array item type")
}
func TestResolveGoType_AdditionalPropertiesError(t *testing.T) {
// Object with additionalProperties whose type fails to resolve
md := &ConfigMetadata{
Type: "object",
AdditionalProperties: &ConfigMetadata{Type: "unsupported_value"},
}
_, err := MapGoType(md, "field", "", "")
require.Error(t, err)
require.Contains(t, err.Error(), "failed to map additionalProperties type")
}
func TestResolveGoType_EmbeddedObjectNameError(t *testing.T) {
// Object with properties but propName that cannot be formatted as an identifier
md := &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"x": {Type: "string"},
},
}
_, err := MapGoType(md, "", "", "")
require.Error(t, err)
require.Contains(t, err.Error(), "failed to format embedded object type name")
}
func TestExtractImports_PropError(t *testing.T) {
// A property with an invalid GoType propagates the error through collectImports
md := &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"bad": {GoType: "github.com/pkg.", Type: "object"},
},
}
// collectImports swallows ResolveGoTypeRef errors (err == nil check), so no error expected;
// this exercises the properties loop path
_, err := ExtractImports(md, "", "")
require.NoError(t, err)
}
func TestExtractImports_ItemsPath(t *testing.T) {
md := &ConfigMetadata{
Type: "array",
Items: &ConfigMetadata{
Type: "string",
IsOptional: true,
},
}
result, err := ExtractImports(md, "", "")
require.NoError(t, err)
require.Contains(t, result, "go.opentelemetry.io/collector/config/configoptional")
}
func TestExtractImports_DefsPath(t *testing.T) {
md := &ConfigMetadata{
Defs: map[string]*ConfigMetadata{
"T": {
Type: "string",
IsOptional: true,
},
},
}
result, err := ExtractImports(md, "", "")
require.NoError(t, err)
require.Contains(t, result, "go.opentelemetry.io/collector/config/configoptional")
}
func TestExtractImports_ContentSchema(t *testing.T) {
md := &ConfigMetadata{
ContentSchema: &ConfigMetadata{
Type: "string",
Format: "duration",
},
}
result, err := ExtractImports(md, "", "")
require.NoError(t, err)
require.Contains(t, result, "time")
}
================================================
FILE: cmd/mdatagen/internal/cfggen/loader.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package cfggen // import "go.opentelemetry.io/collector/cmd/mdatagen/internal/cfggen"
import (
"errors"
"fmt"
"io"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"go.yaml.in/yaml/v3"
"golang.org/x/tools/go/packages"
)
const (
schemaFileName = "config.schema.yaml"
)
// ErrNotFound indicates a schema was not found by any source.
var ErrNotFound = errors.New("schema not found")
// Loader loads configuration metadata from various sources (file, HTTP).
type Loader interface {
Load(ref Ref) (*ConfigMetadata, error)
}
type schemaLoader struct {
cache map[string]*ConfigMetadata
cd string
rootDir string
httpClient *http.Client
}
// NewLoader creates a fully configured loader. Takes current component's directory to determine where to look for local schema files.
func NewLoader(cwd string) Loader {
return &schemaLoader{
cache: make(map[string]*ConfigMetadata),
cd: cwd,
httpClient: &http.Client{Timeout: 30 * time.Second},
}
}
func (sl *schemaLoader) Load(ref Ref) (*ConfigMetadata, error) {
if cached, ok := sl.cache[ref.CacheKey()]; ok {
return cached, nil
}
metadata, err := sl.load(ref)
if err != nil {
return nil, err
}
sl.cache[ref.CacheKey()] = metadata
return metadata, nil
}
func (sl *schemaLoader) load(ref Ref) (*ConfigMetadata, error) {
repoRoot, err := sl.repoRoot(sl.cd)
if err != nil {
return nil, fmt.Errorf("failed to determine repo root: %w", err)
}
if ref.isLocal() {
var filePath string
if strings.HasPrefix(ref.schemaID, "/") {
filePath = filepath.Join(repoRoot, ref.SchemaID(), schemaFileName)
} else {
filePath = filepath.Join(sl.cd, ref.SchemaID(), schemaFileName)
}
return sl.loadFromFile(filePath)
}
return sl.loadFromHTTP(ref, filepath.Join(repoRoot, ".schemas"))
}
func (sl *schemaLoader) loadFromFile(filePath string) (*ConfigMetadata, error) {
body, err := os.ReadFile(filePath) // #nosec G304
if err != nil {
if os.IsNotExist(err) {
return nil, ErrNotFound
}
return nil, fmt.Errorf("failed to read schema from %s: %w", filePath, err)
}
var metadata ConfigMetadata
if err := yaml.Unmarshal(body, &metadata); err != nil {
return nil, fmt.Errorf("failed to parse schema from %s: %w", filePath, err)
}
return &metadata, nil
}
func (sl *schemaLoader) loadFromHTTP(ref Ref, fileCacheDir string) (*ConfigMetadata, error) {
version, err := sl.refVersion(&ref)
if err != nil {
return nil, err
}
filePath := filepath.Join(fileCacheDir, version, filepath.FromSlash(ref.SchemaID()), schemaFileName)
// check fs cache first
metadata, err := sl.loadFromFile(filePath)
if err == nil {
return metadata, nil
}
if !errors.Is(err, ErrNotFound) {
log.Printf("warning: failed to load schema from file cache at %s: %v", filePath, err)
}
metadata, err = sl.tryLoad(ref, version)
if err != nil {
return nil, err
}
if err := sl.persistToFile(filePath, metadata); err != nil {
log.Printf("warning: failed to persist schema to file cache at %s: %v", filePath, err)
}
return metadata, nil
}
func (sl *schemaLoader) tryLoad(ref Ref, version string) (*ConfigMetadata, error) {
url, err := ref.URL(version)
if err != nil {
return nil, fmt.Errorf("failed to construct URL for %s: %w", ref.CacheKey(), err)
}
resp, err := sl.httpClient.Get(url)
if err != nil {
return nil, fmt.Errorf("failed to fetch schema from %s: %w", url, err)
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusNotFound {
return nil, ErrNotFound
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to fetch schema from %s: HTTP %d", url, resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body from %s: %w", url, err)
}
var metadata ConfigMetadata
if err := yaml.Unmarshal(body, &metadata); err != nil {
return nil, fmt.Errorf("failed to parse schema from %s: %w", url, err)
}
return &metadata, nil
}
func (sl *schemaLoader) persistToFile(filePath string, md *ConfigMetadata) error {
if err := os.MkdirAll(filepath.Dir(filePath), 0o750); err != nil {
return fmt.Errorf("failed to create directory: %w", err)
}
data, err := yaml.Marshal(md)
if err != nil {
return fmt.Errorf("failed to marshal metadata: %w", err)
}
if err := os.WriteFile(filePath, data, 0o600); err != nil {
return fmt.Errorf("failed to write file: %w", err)
}
return nil
}
func (sl *schemaLoader) refVersion(ref *Ref) (string, error) {
// Try to resolve version via packages.Load (respects replace directives)
modulePath := ref.Module()
if modulePath != "" {
if version := sl.resolveModuleVersion(modulePath); version != "" {
return version, nil
}
// attempt to "go get" the module to resolve version if not found locally
cmd := exec.Command("go", "get", modulePath) // #nosec G204
cmd.Dir = sl.cd
if err := cmd.Run(); err != nil {
return "", fmt.Errorf("failed to run `go get` for module %s: %w", modulePath, err)
}
if version := sl.resolveModuleVersion(modulePath); version != "" {
return version, nil
}
return "", fmt.Errorf("unable to resolve version for module %s after `go get`: %w", modulePath, ErrNotFound)
}
return "", fmt.Errorf("unknown module path for `%s` ref", ref)
}
func (sl *schemaLoader) resolveModuleVersion(importPath string) string {
cfg := &packages.Config{
Mode: packages.NeedModule,
Dir: sl.cd,
}
pkgs, err := packages.Load(cfg, importPath)
if err != nil || len(pkgs) == 0 {
return ""
}
pkg := pkgs[0]
if pkg.Module == nil {
return ""
}
return pkg.Module.Version
}
func (sl *schemaLoader) repoRoot(componentDir string) (string, error) {
if sl.rootDir != "" {
return sl.rootDir, nil
}
cmd := exec.Command("git", "rev-parse", "--show-toplevel")
cmd.Dir = componentDir
output, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("failed to determine repo root: %w", err)
}
sl.rootDir = strings.TrimSpace(string(output))
return sl.rootDir, nil
}
================================================
FILE: cmd/mdatagen/internal/cfggen/loader_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package cfggen
import (
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestLoader_LoadFromFile_Success(t *testing.T) {
tempDir := t.TempDir()
// Create schema file - for a local ref
schemaPath := filepath.Join(tempDir, "internal/metadata")
require.NoError(t, os.MkdirAll(schemaPath, 0o750))
schemaFile := filepath.Join(schemaPath, schemaFileName)
schemaContent := `title: "Test Schema"
description: "A test schema"
type: object
`
require.NoError(t, os.WriteFile(schemaFile, []byte(schemaContent), 0o600))
loader := NewLoader(tempDir).(*schemaLoader)
// For local refs, the loadFromFile method takes a file path
result, err := loader.loadFromFile(schemaFile)
require.NoError(t, err)
require.Equal(t, "Test Schema", result.Title)
require.Equal(t, "A test schema", result.Description)
require.Equal(t, "object", result.Type)
}
func TestLoader_LoadFromFile_NotFound(t *testing.T) {
tempDir := t.TempDir()
loader := NewLoader(tempDir).(*schemaLoader)
// Try to load from non-existent file
result, err := loader.loadFromFile(filepath.Join(tempDir, "nonexistent", schemaFileName))
require.ErrorIs(t, err, ErrNotFound)
require.Nil(t, result)
}
func TestLoader_LoadFromFile_ParseError(t *testing.T) {
tempDir := t.TempDir()
// Create invalid YAML file
schemaPath := filepath.Join(tempDir, "test/path")
require.NoError(t, os.MkdirAll(schemaPath, 0o750))
schemaFile := filepath.Join(schemaPath, schemaFileName)
invalidYAML := `title: "Test"
invalid: yaml: content:
`
require.NoError(t, os.WriteFile(schemaFile, []byte(invalidYAML), 0o600))
loader := NewLoader(tempDir).(*schemaLoader)
result, err := loader.loadFromFile(schemaFile)
require.Error(t, err)
require.Contains(t, err.Error(), "failed to parse schema")
require.Nil(t, result)
}
func TestLoader_LoadFromHTTP_Success(t *testing.T) {
// Create test HTTP server
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`title: "HTTP Schema"
type: string
`))
}))
defer server.Close()
// Override namespaceToURL for testing
originalURL := namespaceToURL["go.opentelemetry.io/collector"]
namespaceToURL["go.opentelemetry.io/collector"] = server.URL
defer func() { namespaceToURL["go.opentelemetry.io/collector"] = originalURL }()
tempDir := t.TempDir()
// Use mdatagenDir so resolveModuleVersion can find the module in go.mod
loader := NewLoader(mdatagenDir(t)).(*schemaLoader)
ref := *NewRef("go.opentelemetry.io/collector/scraper/scraperhelper.controller_config")
result, err := loader.loadFromHTTP(ref, filepath.Join(tempDir, ".schemas"))
require.NoError(t, err)
require.Equal(t, "HTTP Schema", result.Title)
require.Equal(t, "string", result.Type)
}
func TestLoader_LoadFromHTTP_NotFound(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusNotFound)
}))
defer server.Close()
originalURL := namespaceToURL["go.opentelemetry.io/collector"]
namespaceToURL["go.opentelemetry.io/collector"] = server.URL
defer func() { namespaceToURL["go.opentelemetry.io/collector"] = originalURL }()
tempDir := t.TempDir()
loader := NewLoader(mdatagenDir(t)).(*schemaLoader)
ref := *NewRef("go.opentelemetry.io/collector/scraper/scraperhelper.controller_config")
result, err := loader.loadFromHTTP(ref, filepath.Join(tempDir, ".schemas"))
require.ErrorIs(t, err, ErrNotFound)
require.Nil(t, result)
}
func TestLoader_LoadFromHTTP_ServerError(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
}))
defer server.Close()
originalURL := namespaceToURL["go.opentelemetry.io/collector"]
namespaceToURL["go.opentelemetry.io/collector"] = server.URL
defer func() { namespaceToURL["go.opentelemetry.io/collector"] = originalURL }()
tempDir := t.TempDir()
loader := NewLoader(mdatagenDir(t)).(*schemaLoader)
ref := *NewRef("go.opentelemetry.io/collector/scraper/scraperhelper.controller_config")
result, err := loader.loadFromHTTP(ref, filepath.Join(tempDir, ".schemas"))
require.Error(t, err)
require.Contains(t, err.Error(), "HTTP 500")
require.Nil(t, result)
}
func TestLoader_TryLoad_WithVersion(t *testing.T) {
// Create test HTTP server that returns different content for different versions
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
if strings.Contains(r.URL.Path, "v1.0.0") {
_, _ = w.Write([]byte(`title: "Versioned Schema"`))
} else {
_, _ = w.Write([]byte(`title: "Main Version Schema"`))
}
}))
defer server.Close()
originalURL := namespaceToURL["go.opentelemetry.io/collector"]
namespaceToURL["go.opentelemetry.io/collector"] = server.URL
defer func() { namespaceToURL["go.opentelemetry.io/collector"] = originalURL }()
tempDir := t.TempDir()
loader := NewLoader(tempDir).(*schemaLoader)
ref := *NewRef("go.opentelemetry.io/collector/test/path.config")
// Try to load with version
result, err := loader.tryLoad(ref, "v1.0.0")
require.NoError(t, err)
require.Equal(t, "Versioned Schema", result.Title)
}
func TestLoader_PersistToFile_Success(t *testing.T) {
tempDir := t.TempDir()
loader := NewLoader(tempDir).(*schemaLoader)
metadata := &ConfigMetadata{
Title: "Persisted Schema",
Description: "Test persistence",
}
filePath := filepath.Join(tempDir, "test", "persisted.yaml")
err := loader.persistToFile(filePath, metadata)
require.NoError(t, err)
// Verify file was created
require.FileExists(t, filePath)
content, err := os.ReadFile(filePath) // #nosec G304
require.NoError(t, err)
require.Contains(t, string(content), "Persisted Schema")
require.Contains(t, string(content), "Test persistence")
}
func TestLoader_Load_CacheInteraction(t *testing.T) {
tempDir := t.TempDir()
loader := NewLoader(tempDir).(*schemaLoader)
ref := *NewRef("go.opentelemetry.io/collector/test/path.config")
expected := &ConfigMetadata{Title: "Pre-cached Schema"}
loader.cache[ref.CacheKey()] = expected
result, err := loader.Load(ref)
require.NoError(t, err)
require.Equal(t, expected, result)
require.Same(t, expected, result)
}
func TestLoader_Integration_MemoryCachePeristence(t *testing.T) {
tempDir := t.TempDir()
loader := &schemaLoader{
cache: make(map[string]*ConfigMetadata),
cd: tempDir,
httpClient: &http.Client{Timeout: 30 * time.Second},
}
ref := *NewRef("go.opentelemetry.io/collector/test/path.config")
expected := &ConfigMetadata{Title: "Integration Test"}
loader.cache[ref.CacheKey()] = expected
result1, err := loader.Load(ref)
require.NoError(t, err)
require.Equal(t, "Integration Test", result1.Title)
result2, err := loader.Load(ref)
require.NoError(t, err)
require.Equal(t, "Integration Test", result2.Title)
require.Same(t, result1, result2)
}
func TestLoader_Load_CachesOnFirstLoad(t *testing.T) {
tempDir := t.TempDir()
schemaPath := filepath.Join(tempDir, "subdir")
require.NoError(t, os.MkdirAll(schemaPath, 0o750))
schemaFile := filepath.Join(schemaPath, schemaFileName)
require.NoError(t, os.WriteFile(schemaFile, []byte("title: Cached\ntype: object\n"), 0o600))
loader := NewLoader(tempDir).(*schemaLoader)
loader.rootDir = tempDir // bypass git
ref := Ref{schemaID: "subdir", kind: Local}
result1, err := loader.Load(ref)
require.NoError(t, err)
require.Equal(t, "Cached", result1.Title)
result2, err := loader.Load(ref)
require.NoError(t, err)
require.Same(t, result1, result2)
}
func TestLoader_Load_RepoRootError(t *testing.T) {
tempDir := t.TempDir()
loader := NewLoader(tempDir).(*schemaLoader)
ref := Ref{schemaID: "subdir", kind: Local}
_, err := loader.load(ref)
require.Error(t, err)
require.Contains(t, err.Error(), "failed to determine repo root")
}
func TestLoader_Load_LocalAbsolutePath(t *testing.T) {
tempDir := t.TempDir()
// Create schema at /somepackage/config.schema.yaml
schemaDir := filepath.Join(tempDir, "somepackage")
require.NoError(t, os.MkdirAll(schemaDir, 0o750))
require.NoError(t, os.WriteFile(filepath.Join(schemaDir, schemaFileName), []byte("title: AbsoluteLocal\ntype: object\n"), 0o600))
loader := NewLoader(tempDir).(*schemaLoader)
loader.rootDir = tempDir // bypass git
// absolute local ref (starts with /)
ref := Ref{schemaID: "/somepackage", kind: Local}
result, err := loader.load(ref)
require.NoError(t, err)
require.Equal(t, "AbsoluteLocal", result.Title)
}
func TestLoader_Load_LocalRelativePath(t *testing.T) {
tempDir := t.TempDir()
subDir := filepath.Join(tempDir, "subdir")
require.NoError(t, os.MkdirAll(subDir, 0o750))
require.NoError(t, os.WriteFile(filepath.Join(subDir, schemaFileName), []byte("title: RelativeLocal\ntype: object\n"), 0o600))
loader := NewLoader(tempDir).(*schemaLoader)
loader.rootDir = tempDir // bypass git
// relative local ref (does not start with /)
ref := Ref{schemaID: "subdir", kind: Local}
result, err := loader.load(ref)
require.NoError(t, err)
require.Equal(t, "RelativeLocal", result.Title)
}
func TestLoader_LoadFromFile_ReadError(t *testing.T) {
// Create a directory where the file is expected — os.ReadFile on a directory fails
tempDir := t.TempDir()
dirPath := filepath.Join(tempDir, schemaFileName)
require.NoError(t, os.MkdirAll(dirPath, 0o750))
loader := NewLoader(tempDir).(*schemaLoader)
result, err := loader.loadFromFile(dirPath)
require.Error(t, err)
require.Nil(t, result)
require.NotErrorIs(t, err, ErrNotFound)
require.Contains(t, err.Error(), "failed to read schema")
}
func TestLoader_LoadFromHTTP_FileCacheHit(t *testing.T) {
// Test that loadFromHTTP returns from the file cache when a schema is already persisted,
// without making any HTTP requests.
ref := *NewRef("go.opentelemetry.io/collector/scraper/scraperhelper.controller_config")
// Resolve the real module version so we can place the file at the right path.
mdDir := mdatagenDir(t)
helper := NewLoader(mdDir).(*schemaLoader)
version := helper.resolveModuleVersion(ref.Module())
if version == "" {
t.Skip("could not resolve module version, skipping file cache hit test")
}
tempDir := t.TempDir()
fileCacheDir := filepath.Join(tempDir, ".schemas")
filePath := filepath.Join(fileCacheDir, version, ref.SchemaID(), schemaFileName)
require.NoError(t, os.MkdirAll(filepath.Dir(filePath), 0o750))
require.NoError(t, os.WriteFile(filePath, []byte("title: FileCached\ntype: object\n"), 0o600))
// Use an httpClient that always panics to confirm no HTTP call is made.
loader := &schemaLoader{
cache: make(map[string]*ConfigMetadata),
cd: mdDir,
httpClient: &http.Client{},
}
result, err := loader.loadFromHTTP(ref, fileCacheDir)
require.NoError(t, err)
require.Equal(t, "FileCached", result.Title)
}
func TestLoader_LoadFromHTTP_PersistWarning(t *testing.T) {
// Test the warning path when persistToFile fails (non-writable dir).
// We create a read-only file cache dir so persistToFile fails.
if runtime.GOOS == "windows" {
t.Skip("file permission test not reliable on Windows")
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("title: PersistFail\ntype: object\n"))
}))
defer server.Close()
originalURL := namespaceToURL["go.opentelemetry.io/collector"]
namespaceToURL["go.opentelemetry.io/collector"] = server.URL
defer func() { namespaceToURL["go.opentelemetry.io/collector"] = originalURL }()
tempDir := t.TempDir()
// Make the file cache directory read-only so persistToFile fails
cacheDir := filepath.Join(tempDir, ".schemas")
require.NoError(t, os.MkdirAll(cacheDir, 0o750))
loader := NewLoader(mdatagenDir(t)).(*schemaLoader)
ref := *NewRef("go.opentelemetry.io/collector/scraper/scraperhelper.controller_config")
// Should still return the result even though persist fails (warning only)
result, err := loader.loadFromHTTP(ref, cacheDir)
require.NoError(t, err)
require.Equal(t, "PersistFail", result.Title)
}
func TestLoader_LoadFromHTTP_NonNotFoundFileError(t *testing.T) {
// Test the log.Printf warning path in loadFromHTTP when loadFromFile on the cached path
// fails with a non-ErrNotFound error (directory placed where file is expected).
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("title: AfterWarning\ntype: object\n"))
}))
defer server.Close()
originalURL := namespaceToURL["go.opentelemetry.io/collector"]
namespaceToURL["go.opentelemetry.io/collector"] = server.URL
defer func() { namespaceToURL["go.opentelemetry.io/collector"] = originalURL }()
mdDir := mdatagenDir(t)
ref := *NewRef("go.opentelemetry.io/collector/scraper/scraperhelper.controller_config")
// Resolve the real version so we can poison the file cache at the correct path
helper := NewLoader(mdDir).(*schemaLoader)
version := helper.resolveModuleVersion(ref.Module())
if version == "" {
t.Skip("could not resolve module version, skipping non-ErrNotFound warning test")
}
tempDir := t.TempDir()
cacheDir := filepath.Join(tempDir, ".schemas")
filePath := filepath.Join(cacheDir, version, ref.SchemaID(), schemaFileName)
// Place a directory at the expected file path → loadFromFile returns a non-ErrNotFound error
require.NoError(t, os.MkdirAll(filePath, 0o750))
loader := &schemaLoader{
cache: make(map[string]*ConfigMetadata),
cd: mdDir,
httpClient: &http.Client{},
}
result, err := loader.loadFromHTTP(ref, cacheDir)
require.NoError(t, err)
require.Equal(t, "AfterWarning", result.Title)
}
func TestLoader_TryLoad_URLError(t *testing.T) {
// Ref with unsupported namespace → URL() returns an error
loader := NewLoader("").(*schemaLoader)
ref := *NewRef("unknownns/path.type")
// namespace is set so Module() returns non-empty, but Namespace() returns false
// Actually NewRef sets it as external; let's manually set up a ref with no URL support
ref2 := Ref{namespace: "unsupported.example.com", schemaID: "pkg", defName: "t", kind: External}
_, err := loader.tryLoad(ref2, "v1.0.0")
require.Error(t, err)
require.Contains(t, err.Error(), "failed to construct URL")
_ = ref
}
func TestLoader_TryLoad_HTTPError(t *testing.T) {
// Use a server that closes connections immediately to simulate a network error
// The simplest approach: use an invalid URL
loader := NewLoader("").(*schemaLoader)
ref := *NewRef("go.opentelemetry.io/collector/test/path.config")
// Override the URL to point to an invalid host
originalURL := namespaceToURL["go.opentelemetry.io/collector"]
namespaceToURL["go.opentelemetry.io/collector"] = "http://127.0.0.1:0" // port 0 → connection refused
defer func() { namespaceToURL["go.opentelemetry.io/collector"] = originalURL }()
_, err := loader.tryLoad(ref, "v1.0.0")
require.Error(t, err)
require.Contains(t, err.Error(), "failed to fetch schema")
}
func TestLoader_TryLoad_ParseError(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("title: bad\n invalid: yaml: :\n"))
}))
defer server.Close()
originalURL := namespaceToURL["go.opentelemetry.io/collector"]
namespaceToURL["go.opentelemetry.io/collector"] = server.URL
defer func() { namespaceToURL["go.opentelemetry.io/collector"] = originalURL }()
loader := NewLoader("").(*schemaLoader)
ref := *NewRef("go.opentelemetry.io/collector/test/path.config")
_, err := loader.tryLoad(ref, "v1.0.0")
require.Error(t, err)
require.Contains(t, err.Error(), "failed to parse schema")
}
func TestLoader_RefVersion_UnknownModulePath(t *testing.T) {
tempDir := t.TempDir()
loader := NewLoader(tempDir).(*schemaLoader)
ref := Ref{namespace: "", schemaID: "pkg", defName: "t", kind: Internal}
_, err := loader.refVersion(&ref)
require.Error(t, err)
require.Contains(t, err.Error(), "unknown module path")
}
func TestLoader_ResolveModuleVersion_NilModule(t *testing.T) {
loader := NewLoader(mdatagenDir(t)).(*schemaLoader)
version := loader.resolveModuleVersion("fmt")
require.Empty(t, version)
}
func TestLoader_ResolveModuleVersion_LoadError(t *testing.T) {
loader := NewLoader("/nonexistent/path/xyz").(*schemaLoader)
version := loader.resolveModuleVersion("somemodule/that/doesnt/exist")
require.Empty(t, version)
}
func TestLoader_RepoRoot_CachedValue(t *testing.T) {
loader := NewLoader("").(*schemaLoader)
loader.rootDir = "/cached/root"
root, err := loader.repoRoot("/any/dir")
require.NoError(t, err)
require.Equal(t, "/cached/root", root)
}
func TestLoader_RepoRoot_GitError(t *testing.T) {
tempDir := t.TempDir()
loader := NewLoader(tempDir).(*schemaLoader)
_, err := loader.repoRoot(tempDir)
require.Error(t, err)
require.Contains(t, err.Error(), "failed to determine repo root")
}
func TestLoader_PersistToFile_MkdirAllError(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("file permission test not reliable on Windows")
}
tempDir := t.TempDir()
t.Cleanup(func() {
_ = os.Chmod(tempDir, 0o700) // #nosec G302 -- restore so t.TempDir cleanup can remove it
})
require.NoError(t, os.Chmod(tempDir, 0o500)) // #nosec G302
loader := NewLoader(tempDir).(*schemaLoader)
err := loader.persistToFile(filepath.Join(tempDir, "newdir", "schema.yaml"), &ConfigMetadata{Title: "X"})
require.Error(t, err)
require.Contains(t, err.Error(), "failed to create directory")
}
func TestLoader_PersistToFile_WriteFileError(t *testing.T) {
tempDir := t.TempDir()
filePath := filepath.Join(tempDir, "schema.yaml")
require.NoError(t, os.MkdirAll(filePath, 0o750))
loader := NewLoader(tempDir).(*schemaLoader)
err := loader.persistToFile(filePath, &ConfigMetadata{Title: "X"})
require.Error(t, err)
require.Contains(t, err.Error(), "failed to write file")
}
func TestLoader_TryLoad_ReadBodyError(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Length", "1000")
w.WriteHeader(http.StatusOK)
hj, ok := w.(http.Hijacker)
if !ok {
return
}
conn, _, _ := hj.Hijack()
_ = conn.Close()
}))
defer server.Close()
originalURL := namespaceToURL["go.opentelemetry.io/collector"]
namespaceToURL["go.opentelemetry.io/collector"] = server.URL
defer func() { namespaceToURL["go.opentelemetry.io/collector"] = originalURL }()
loader := NewLoader("").(*schemaLoader)
ref := *NewRef("go.opentelemetry.io/collector/test/path.config")
_, err := loader.tryLoad(ref, "v1.0.0")
require.Error(t, err)
require.Contains(t, err.Error(), "failed to read response body")
}
func mdatagenDir(t *testing.T) string {
t.Helper()
_, file, _, ok := runtime.Caller(0)
require.True(t, ok, "could not determine caller file")
return filepath.Join(filepath.Dir(file), "../..")
}
================================================
FILE: cmd/mdatagen/internal/cfggen/model.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package cfggen // import "go.opentelemetry.io/collector/cmd/mdatagen/internal/cfggen"
import (
"encoding/json"
"errors"
"fmt"
)
// ConfigMetadata represents a JSON schema object, draft 2020-12 (limited), with additional custom fields.
type ConfigMetadata struct {
Schema string `mapstructure:"$schema,omitempty" json:"$schema,omitempty" yaml:"$schema,omitempty"`
ID string `mapstructure:"$id,omitempty" json:"$id,omitempty" yaml:"$id,omitempty"`
Title string `mapstructure:"title,omitempty" json:"title,omitempty" yaml:"title,omitempty"`
Description string `mapstructure:"description,omitempty" json:"description,omitempty" yaml:"description,omitempty"`
Comment string `mapstructure:"$comment,omitempty" json:"$comment,omitempty" yaml:"$comment,omitempty"`
Type any `mapstructure:"type,omitempty" json:"type,omitempty" yaml:"type,omitempty"`
Ref string `mapstructure:"$ref,omitempty" json:"-" yaml:"$ref,omitempty"`
Default any `mapstructure:"default,omitempty" json:"default,omitempty" yaml:"default,omitempty"`
Examples []any `mapstructure:"examples,omitempty" json:"examples,omitempty" yaml:"examples,omitempty"`
Deprecated bool `mapstructure:"deprecated,omitempty" json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
Enum []any `mapstructure:"enum,omitempty" json:"enum,omitempty" yaml:"enum,omitempty"`
Const any `mapstructure:"const,omitempty" json:"const,omitempty" yaml:"const,omitempty"`
AllOf []*ConfigMetadata `mapstructure:"allOf,omitempty" json:"allOf,omitempty" yaml:"allOf,omitempty"`
Properties map[string]*ConfigMetadata `mapstructure:"properties,omitempty" json:"properties,omitempty" yaml:"properties,omitempty"`
AdditionalProperties *ConfigMetadata `mapstructure:"additionalProperties,omitempty" json:"additionalProperties,omitempty" yaml:"additionalProperties,omitempty"`
Required []string `mapstructure:"required,omitempty" json:"required,omitempty" yaml:"required,omitempty"`
MinProperties *int `mapstructure:"minProperties,omitempty" json:"minProperties,omitempty" yaml:"minProperties,omitempty"`
MaxProperties *int `mapstructure:"maxProperties,omitempty" json:"maxProperties,omitempty" yaml:"maxProperties,omitempty"`
Items *ConfigMetadata `mapstructure:"items,omitempty" json:"items,omitempty" yaml:"items,omitempty"`
MinItems *int `mapstructure:"minItems,omitempty" json:"minItems,omitempty" yaml:"minItems,omitempty"`
MaxItems *int `mapstructure:"maxItems,omitempty" json:"maxItems,omitempty" yaml:"maxItems,omitempty"`
UniqueItems bool `mapstructure:"uniqueItems,omitempty" json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"`
MaxLength *int `mapstructure:"maxLength,omitempty" json:"maxLength,omitempty" yaml:"maxLength,omitempty"`
MinLength *int `mapstructure:"minLength,omitempty" json:"minLength,omitempty" yaml:"minLength,omitempty"`
Pattern string `mapstructure:"pattern,omitempty" json:"pattern,omitempty" yaml:"pattern,omitempty"`
Format string `mapstructure:"format,omitempty" json:"format,omitempty" yaml:"format,omitempty"`
ContentMediaType string `mapstructure:"contentMediaType,omitempty" json:"contentMediaType,omitempty" yaml:"contentMediaType,omitempty"`
ContentEncoding string `mapstructure:"contentEncoding,omitempty" json:"contentEncoding,omitempty" yaml:"contentEncoding,omitempty"`
ContentSchema *ConfigMetadata `mapstructure:"contentSchema,omitempty" json:"contentSchema,omitempty" yaml:"contentSchema,omitempty"`
MultipleOf *float64 `mapstructure:"multipleOf,omitempty" json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"`
Maximum *float64 `mapstructure:"maximum,omitempty" json:"maximum,omitempty" yaml:"maximum,omitempty"`
ExclusiveMaximum *float64 `mapstructure:"exclusiveMaximum,omitempty" json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"`
Minimum *float64 `mapstructure:"minimum,omitempty" json:"minimum,omitempty" yaml:"minimum,omitempty"`
ExclusiveMinimum *float64 `mapstructure:"exclusiveMinimum,omitempty" json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"`
Defs map[string]*ConfigMetadata `mapstructure:"$defs,omitempty" json:"-" yaml:"$defs,omitempty"`
// Additional custom fields
GoType string `mapstructure:"x-customType,omitempty" json:"-" yaml:"x-customType,omitempty"`
IsPointer bool `mapstructure:"x-pointer,omitempty" json:"-" yaml:"x-pointer,omitempty"`
IsOptional bool `mapstructure:"x-optional,omitempty" json:"-" yaml:"x-optional,omitempty"`
}
func (md *ConfigMetadata) ToJSON() ([]byte, error) {
return json.MarshalIndent(md, "", " ")
}
func (md *ConfigMetadata) Validate() error {
var errs error
if md.Type != "object" {
errs = errors.Join(errs, fmt.Errorf("config type must be \"object\", got %q", md.Type))
}
if len(md.Properties) == 0 && len(md.AllOf) == 0 {
errs = errors.Join(errs, errors.New("config must not be empty"))
}
return errs
}
================================================
FILE: cmd/mdatagen/internal/cfggen/model_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package cfggen
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestConfigMetadata_ToJSON(t *testing.T) {
md := &ConfigMetadata{
Schema: schemaVersion,
Type: "object",
Properties: map[string]*ConfigMetadata{
"endpoint": {Type: "string", Description: "The endpoint"},
},
}
data, err := md.ToJSON()
require.NoError(t, err)
assert.Contains(t, string(data), `"$schema"`)
assert.Contains(t, string(data), `"endpoint"`)
assert.Contains(t, string(data), `"The endpoint"`)
}
func TestConfigMetadata_Validate_Valid(t *testing.T) {
tests := []struct {
name string
md *ConfigMetadata
}{
{
name: "valid with properties",
md: &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"endpoint": {Type: "string"},
},
},
},
{
name: "valid with allOf",
md: &ConfigMetadata{
Type: "object",
AllOf: []*ConfigMetadata{
{Ref: "some_ref"},
},
},
},
{
name: "valid with both properties and allOf",
md: &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"endpoint": {Type: "string"},
},
AllOf: []*ConfigMetadata{
{Ref: "some_ref"},
},
},
},
{
name: "valid with multiple properties",
md: &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"endpoint": {Type: "string"},
"timeout": {Type: "string", GoType: "time.Duration"},
"port": {Type: "integer"},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.md.Validate()
assert.NoError(t, err)
})
}
}
func TestConfigMetadata_Validate_InvalidType(t *testing.T) {
tests := []struct {
name string
md *ConfigMetadata
wantErr string
}{
{
name: "type is string instead of object",
md: &ConfigMetadata{
Type: "string",
Properties: map[string]*ConfigMetadata{
"endpoint": {Type: "string"},
},
},
wantErr: `config type must be "object", got "string"`,
},
{
name: "type is empty string",
md: &ConfigMetadata{
Type: "",
Properties: map[string]*ConfigMetadata{
"endpoint": {Type: "string"},
},
},
wantErr: `config type must be "object", got ""`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.md.Validate()
require.Error(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
})
}
}
func TestConfigMetadata_Validate_EmptyConfig(t *testing.T) {
tests := []struct {
name string
md *ConfigMetadata
wantErr string
}{
{
name: "no properties and no allOf",
md: &ConfigMetadata{
Type: "object",
},
wantErr: "config must not be empty",
},
{
name: "empty properties map and no allOf",
md: &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{},
},
wantErr: "config must not be empty",
},
{
name: "empty allOf slice and no properties",
md: &ConfigMetadata{
Type: "object",
AllOf: []*ConfigMetadata{},
},
wantErr: "config must not be empty",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.md.Validate()
require.Error(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
})
}
}
func TestConfigMetadata_Validate_MultipleErrors(t *testing.T) {
tests := []struct {
name string
md *ConfigMetadata
wantErrCount int
wantErrContains []string
}{
{
name: "invalid type and empty config",
md: &ConfigMetadata{
Type: "string",
},
wantErrCount: 2,
wantErrContains: []string{
`config type must be "object", got "string"`,
"config must not be empty",
},
},
{
name: "invalid type with empty properties and empty allOf",
md: &ConfigMetadata{
Type: "array",
Properties: map[string]*ConfigMetadata{},
AllOf: []*ConfigMetadata{},
},
wantErrCount: 2,
wantErrContains: []string{
`config type must be "object", got "array"`,
"config must not be empty",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.md.Validate()
require.Error(t, err)
// Check that error contains all expected substrings
for _, expectedErr := range tt.wantErrContains {
assert.Contains(t, err.Error(), expectedErr)
}
})
}
}
func TestConfigMetadata_Validate_NilMetadata(t *testing.T) {
var md *ConfigMetadata
// The current implementation panics on nil receiver
// This test documents that behavior
assert.Panics(t, func() {
_ = md.Validate()
}, "Validate() should panic when called on nil ConfigMetadata")
}
func TestConfigMetadata_Validate_TypeAsInterface(t *testing.T) {
// Test when Type field is set as interface{} instead of string
// This tests the real-world scenario where YAML/JSON unmarshaling
// might produce different types
tests := []struct {
name string
typeVal any
wantErr bool
}{
{
name: "type as string 'object'",
typeVal: "object",
wantErr: false,
},
{
name: "type as string 'string'",
typeVal: "string",
wantErr: true,
},
{
name: "type as array of strings (union type) - not supported",
typeVal: []any{"object", "null"},
wantErr: true, // Current implementation doesn't handle union types, treats as invalid
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
md := &ConfigMetadata{
Type: tt.typeVal,
Properties: map[string]*ConfigMetadata{
"field": {Type: "string"},
},
}
err := md.Validate()
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
func TestConfigMetadata_Validate_EdgeCases(t *testing.T) {
tests := []struct {
name string
md *ConfigMetadata
wantErr bool
}{
{
name: "single property is sufficient",
md: &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"only_field": {Type: "string"},
},
},
wantErr: false,
},
{
name: "single allOf entry is sufficient",
md: &ConfigMetadata{
Type: "object",
AllOf: []*ConfigMetadata{
{Ref: "base_config"},
},
},
wantErr: false,
},
{
name: "properties with nested objects",
md: &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"server": {
Type: "object",
Properties: map[string]*ConfigMetadata{
"host": {Type: "string"},
"port": {Type: "integer"},
},
},
},
},
wantErr: false,
},
{
name: "allOf with nil entries",
md: &ConfigMetadata{
Type: "object",
AllOf: []*ConfigMetadata{
nil,
{Ref: "base_config"},
},
},
wantErr: false, // At least one non-nil entry exists
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.md.Validate()
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
================================================
FILE: cmd/mdatagen/internal/cfggen/namespace.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package cfggen // import "go.opentelemetry.io/collector/cmd/mdatagen/internal/cfggen"
import (
"errors"
"fmt"
"maps"
"path"
"regexp"
"slices"
"strings"
)
var namespaceToURL = map[string]string{
"go.opentelemetry.io/collector": "https://raw.githubusercontent.com/open-telemetry/opentelemetry-collector",
"github.com/open-telemetry/opentelemetry-collector-contrib": "https://raw.githubusercontent.com/open-telemetry/opentelemetry-collector-contrib",
}
var supportedNamespaces = slices.Collect(maps.Keys(namespaceToURL))
type RefKind int
const (
External RefKind = iota
Internal
Local
)
type Ref struct {
namespace string
schemaID string
defName string
kind RefKind
}
var localRefPattern = regexp.MustCompile(`^((?:/|\.\.?/).*?)(?:\.([^./]+))?$`)
func NewRef(refPath string) *Ref {
var namespace, schemaID, defName string
var kind RefKind
switch {
case localRefPattern.MatchString(refPath):
matches := localRefPattern.FindStringSubmatch(refPath)
schemaID = matches[1]
defName = matches[2]
kind = Local
case !strings.ContainsRune(refPath, '/'):
defName = refPath
kind = Internal
default:
namespace = namespaceOf(refPath)
rest, _ := strings.CutPrefix(refPath, namespace)
schemaID, defName, _ = strings.Cut(rest, ".")
schemaID = strings.Trim(schemaID, "/")
kind = External
}
return &Ref{
namespace,
schemaID,
defName,
kind,
}
}
func WithOrigin(refPath string, origin *Ref) *Ref {
ref := NewRef(refPath)
if origin == nil {
return ref
}
if origin.isExternal() {
ref.namespace = origin.namespace
ref.kind = External
if strings.HasPrefix(ref.schemaID, "/") {
ref.schemaID = strings.Trim(ref.schemaID, "/")
return ref
}
ref.schemaID = path.Join(origin.schemaID, ref.schemaID)
return ref
}
// check if it's a local ref with relative path, if so, resolve it against the origin schema ID
if ref.isLocal() && !strings.HasPrefix(ref.schemaID, "/") {
ref.schemaID = path.Join(origin.schemaID, ref.schemaID)
}
return ref
}
func namespaceOf(path string) string {
if ns, ok := matchSupportedNamespace(path); ok {
return ns
}
if idx := strings.LastIndex(path, "/"); idx != -1 {
return path[:idx]
}
return ""
}
func matchSupportedNamespace(path string) (string, bool) {
for _, ns := range supportedNamespaces {
if strings.HasPrefix(path, ns) {
return ns, true
}
}
return "", false
}
func (r *Ref) Namespace() (string, bool) {
_, ok := matchSupportedNamespace(r.namespace)
return r.namespace, ok
}
func (r *Ref) Module() string {
if r.namespace != "" {
return r.namespace + "/" + r.schemaID
}
return ""
}
func (r *Ref) SchemaID() string {
return r.schemaID
}
func (r *Ref) DefName() string {
return r.defName
}
func (r *Ref) URL(version string) (string, error) {
ns, ok := r.Namespace()
if !ok {
return "", errors.New("unsupported namespace")
}
baseURL := namespaceToURL[ns]
return fmt.Sprintf("%s/%s/%s/%s",
baseURL,
version,
r.SchemaID(),
schemaFileName),
nil
}
func (r *Ref) isInternal() bool {
return r.kind == Internal
}
func (r *Ref) isLocal() bool {
return r.kind == Local
}
func (r *Ref) isExternal() bool {
return r.kind == External
}
func (r *Ref) Validate() error {
if r.String() == "" {
return errors.New("empty path")
}
if r.defName == "" {
return errors.New("missing definition name")
}
if r.isLocal() && r.schemaID == "" {
return errors.New("missing schema ID in local reference")
}
return nil
}
func (r *Ref) String() string {
var sb strings.Builder
if r.namespace != "" {
sb.WriteString(r.namespace)
}
if r.schemaID != "" {
if sb.Len() > 0 {
sb.WriteRune('/')
}
sb.WriteString(r.schemaID)
}
if r.defName != "" {
if sb.Len() > 0 {
sb.WriteRune('.')
}
sb.WriteString(r.defName)
}
return sb.String()
}
func (r *Ref) CacheKey() string {
return r.String()
}
func LocalizeRef(refPath, importRootPath string) string {
if importRootPath == "" || !strings.HasPrefix(refPath, importRootPath+"/") {
return refPath
}
return strings.TrimPrefix(refPath, importRootPath)
}
================================================
FILE: cmd/mdatagen/internal/cfggen/namespace_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package cfggen
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestNewRef(t *testing.T) {
tests := []struct {
name string
refPath string
expected *Ref
}{
{
name: "empty ref",
refPath: "",
expected: &Ref{
namespace: "",
schemaID: "",
defName: "",
kind: Internal,
},
},
{
name: "internal ref",
refPath: "target_type",
expected: &Ref{
namespace: "",
schemaID: "",
defName: "target_type",
kind: Internal,
},
},
{
name: "local ref with absolute path",
refPath: "/config/configauth.config",
expected: &Ref{
namespace: "",
schemaID: "/config/configauth",
defName: "config",
kind: Local,
},
},
{
name: "local ref with relative path (./)",
refPath: "./internal/metadata.config",
expected: &Ref{
namespace: "",
schemaID: "./internal/metadata",
defName: "config",
kind: Local,
},
},
{
name: "local ref with relative path (../)",
refPath: "../other.config",
expected: &Ref{
namespace: "",
schemaID: "../other",
defName: "config",
kind: Local,
},
},
{
name: "local ref without def name",
refPath: "/config/configauth",
expected: &Ref{
namespace: "",
schemaID: "/config/configauth",
defName: "",
kind: Local,
},
},
{
name: "local ref empty",
refPath: "../",
expected: &Ref{
namespace: "",
schemaID: "../",
defName: "",
kind: Local,
},
},
{
name: "local ref empty short schemaId",
refPath: "../.test",
expected: &Ref{
namespace: "",
schemaID: "../",
defName: "test",
kind: Local,
},
},
{
name: "external ref without version",
refPath: "go.opentelemetry.io/collector/config/confighttp.client_config",
expected: &Ref{
namespace: "go.opentelemetry.io/collector",
schemaID: "config/confighttp",
defName: "client_config",
kind: External,
},
},
{
name: "external ref without def name",
refPath: "go.opentelemetry.io/collector/config/confighttp",
expected: &Ref{
namespace: "go.opentelemetry.io/collector",
schemaID: "config/confighttp",
defName: "",
kind: External,
},
},
{
name: "external ref without schema ID",
refPath: "go.opentelemetry.io/collector",
expected: &Ref{
namespace: "go.opentelemetry.io/collector",
schemaID: "",
defName: "",
kind: External,
},
},
{
name: "unknown namespace",
refPath: "com.github.example/custom.xyz",
expected: &Ref{
namespace: "com.github.example",
schemaID: "custom",
defName: "xyz",
kind: External,
},
},
{
name: "ref with wrong format",
refPath: "some/path.with.dots",
expected: &Ref{
namespace: "some",
schemaID: "path",
defName: "with.dots",
kind: External,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := NewRef(tt.refPath)
require.Equal(t, tt.expected, result)
})
}
}
func TestWithOrigin(t *testing.T) {
tests := []struct {
name string
refPath string
origin string
expected string
}{
{
name: "absolute ref resolves against namespace",
refPath: "/config/configauth.config",
origin: "go.opentelemetry.io/collector/config/confighttp",
expected: "go.opentelemetry.io/collector/config/configauth.config",
},
{
name: "absolute ref with empty origin unchanged",
refPath: "/config/configauth.config",
origin: "",
expected: "/config/configauth.config",
},
{
name: "relative ref resolves against module path",
refPath: "./internal/metadata.config",
origin: "go.opentelemetry.io/collector/config/confighttp",
expected: "go.opentelemetry.io/collector/config/confighttp/internal/metadata.config",
},
{
name: "parent relative ref resolves against parent module",
refPath: "../configtls.tls_config",
origin: "go.opentelemetry.io/collector/config/confighttp",
expected: "go.opentelemetry.io/collector/config/configtls.tls_config",
},
{
name: "external ref with same-namespace origin joins schema IDs",
refPath: "go.opentelemetry.io/collector/config/confighttp.client_config",
origin: "go.opentelemetry.io/collector/config/confighttp",
expected: "go.opentelemetry.io/collector/config/confighttp/config/confighttp.client_config",
},
{
name: "internal ref with origin unchanged",
refPath: "target_type",
origin: "go.opentelemetry.io/collector/config/confighttp",
expected: "go.opentelemetry.io/collector/config/confighttp.target_type",
},
{
name: "local relative ref with local absolute resolves to local absolute schema ID",
refPath: "./internal.timeout_config",
origin: "/config/confighttp",
expected: "/config/confighttp/internal.timeout_config",
},
{
name: "local relative ref with external origin resolves to external schema ID",
refPath: "./internal.timeout_config",
origin: "go.opentelemetry.io/collector/config/confighttp",
expected: "go.opentelemetry.io/collector/config/confighttp/internal.timeout_config",
},
{
name: "local absolute ref with local absolute origin keeps original schema ID",
refPath: "/config/confighttp.timeout_config",
origin: "/config/confignet.connection_config",
expected: "/config/confighttp.timeout_config",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ref := WithOrigin(tt.refPath, NewRef(tt.origin))
require.Equal(t, tt.expected, ref.CacheKey())
})
}
}
func TestRef_Validate(t *testing.T) {
tests := []struct {
name string
refPath string
wantErr bool
}{
{
name: "valid collector reference",
refPath: "go.opentelemetry.io/collector/scraper/scraperhelper.controller_config",
wantErr: false,
},
{
name: "valid contrib reference",
refPath: "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mysqlreceiver.config",
wantErr: false,
},
{
name: "valid internal reference",
refPath: "target_type",
wantErr: false,
},
{
name: "valid local absolute reference",
refPath: "/config/configauth.config",
wantErr: false,
},
{
name: "empty path",
refPath: "",
wantErr: true,
},
{
name: "missing def name",
refPath: "/config/configauth",
wantErr: true,
},
{
name: "missing schema ID with external ref",
refPath: "go.opentelemetry.io/collector",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ref := NewRef(tt.refPath)
err := ref.Validate()
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}
func TestRef_URL(t *testing.T) {
tests := []struct {
name string
refPath string
version string
expected string
}{
{
name: "collector reference",
refPath: "go.opentelemetry.io/collector/scraper/scraperhelper.controller_config",
version: "v1.0.0",
expected: "https://raw.githubusercontent.com/open-telemetry/opentelemetry-collector/v1.0.0/scraper/scraperhelper/config.schema.yaml",
},
{
name: "contrib reference",
refPath: "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mysqlreceiver.config",
version: "v0.95.0",
expected: "https://raw.githubusercontent.com/open-telemetry/opentelemetry-collector-contrib/v0.95.0/receiver/mysqlreceiver/config.schema.yaml",
},
{
name: "main version",
refPath: "go.opentelemetry.io/collector/processor/batchprocessor.config",
version: "main",
expected: "https://raw.githubusercontent.com/open-telemetry/opentelemetry-collector/main/processor/batchprocessor/config.schema.yaml",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ref := NewRef(tt.refPath)
result, err := ref.URL(tt.version)
require.NoError(t, err)
require.Equal(t, tt.expected, result)
})
}
}
func TestRef_String(t *testing.T) {
ref := NewRef("go.opentelemetry.io/collector/config/confighttp.client_config")
require.Equal(t, "go.opentelemetry.io/collector/config/confighttp.client_config", ref.String())
}
func TestRef_Module_EmptyNamespace(t *testing.T) {
ref := NewRef("target_type")
require.Empty(t, ref.Module())
}
func TestRef_URL_UnsupportedNamespace(t *testing.T) {
ref := NewRef("unsupported.example.com/pkg/sub.config")
_, err := ref.URL("v1.0.0")
require.Error(t, err)
require.Contains(t, err.Error(), "unsupported namespace")
}
func TestRef_Validate_LocalRefMissingSchemaID(t *testing.T) {
ref := &Ref{schemaID: "", defName: "config", kind: Local}
err := ref.Validate()
require.Error(t, err)
require.Contains(t, err.Error(), "missing schema ID in local reference")
}
func TestNamespaceOf_FallbackLastSlash(t *testing.T) {
result := namespaceOf("com.example/some/path.type")
require.Equal(t, "com.example/some", result)
}
func TestNamespaceOf_NoSlash(t *testing.T) {
result := namespaceOf("noslash")
require.Empty(t, result)
}
func TestLocalizeRef(t *testing.T) {
tests := []struct {
name string
refPath string
importRootPath string
expected string
}{
{
name: "same root collector ref becomes local absolute",
refPath: "go.opentelemetry.io/collector/filter.config",
importRootPath: "go.opentelemetry.io/collector",
expected: "/filter.config",
},
{
name: "different root ref stays external",
refPath: "go.opentelemetry.io/collector/filter.config",
importRootPath: "github.com/open-telemetry/opentelemetry-collector-contrib",
expected: "go.opentelemetry.io/collector/filter.config",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t, tt.expected, LocalizeRef(tt.refPath, tt.importRootPath))
})
}
}
================================================
FILE: cmd/mdatagen/internal/cfggen/resolver.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package cfggen // import "go.opentelemetry.io/collector/cmd/mdatagen/internal/cfggen"
import (
"fmt"
"reflect"
"strings"
)
const (
schemaVersion = "https://json-schema.org/draft/2020-12/schema"
// goDurationPattern matches Go duration strings (e.g., "30s", "1h30m", "500ms")
goDurationPattern = `^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$`
)
type Resolver struct {
pkgID string
class string
name string
loader Loader
}
func NewResolver(pkgID, class, name, dir string) *Resolver {
loader := NewLoader(dir)
return &Resolver{
loader: loader,
pkgID: pkgID,
class: class,
name: name,
}
}
// ResolveSchema takes a source configuration metadata schema and resolves all references ($ref)
// to produce a fully resolved schema. It handles both internal references (within the same schema) and external references
// (pointing to other schemas, either locally or remotely). The resolver uses registered loaders to fetch external schemas as needed.
//
// Returns a new ConfigMetadata with all references resolved, or an error if resolution fails.
func (r *Resolver) ResolveSchema(src *ConfigMetadata) (*ConfigMetadata, error) {
target := &ConfigMetadata{}
err := r.resolveSchema(src, src, target, nil)
if err != nil {
return nil, err
}
target.Schema = schemaVersion
target.ID = r.pkgID
target.Title = fmt.Sprintf("%s/%s", r.class, r.name)
return target, nil
}
// transformDurationFormat converts JSON Schema format: duration to Go duration pattern.
// JSON Schema duration format expects ISO 8601 (e.g., "PT30S"), but Go uses a different
// format (e.g., "30s", "1h30m"). This function replaces the format with a pattern that
// validates Go duration strings.
func transformDurationFormat(md *ConfigMetadata) {
if md.Type == "string" && md.Format == "duration" {
md.Format = ""
md.Pattern = goDurationPattern
if md.Description != "" && !strings.Contains(md.Description, "duration") {
md.Description += " (duration format, e.g., \"30s\", \"1h30m\")"
}
}
}
func (r *Resolver) resolveSchema(root, current, target *ConfigMetadata, origin *Ref) error {
if current.Ref != "" {
resolved, err := r.resolveRef(root, current, origin)
if err != nil {
return fmt.Errorf("failed to resolve $ref %q: %w", current.Ref, err)
}
// Preserve custom extensions defined on the reference node
customGoType := current.GoType
customIsPointer := current.IsPointer
customIsOptional := current.IsOptional
customDescription := current.Description
customDefault := current.Default
customEnum := current.Enum
// Copy the resolved node
newCurrent := *resolved
// Restore custom extensions if they were explicitly set on the reference
if customGoType != "" {
newCurrent.GoType = customGoType
}
if customIsPointer {
newCurrent.IsPointer = customIsPointer
}
if customIsOptional {
newCurrent.IsOptional = customIsOptional
}
if customDescription != "" {
newCurrent.Description = customDescription
}
if customDefault != nil {
newCurrent.Default = customDefault
}
if len(customEnum) > 0 {
newCurrent.Enum = customEnum
}
current = &newCurrent
}
currRef := reflect.ValueOf(current).Elem()
targetRef := reflect.ValueOf(target).Elem()
for i := 0; i < currRef.NumField(); i++ {
field := currRef.Field(i)
targetField := targetRef.Field(i)
if !targetField.CanSet() {
continue
}
switch field.Kind() {
case reflect.Ptr:
if !field.IsNil() && field.Elem().Kind() == reflect.Struct {
if field.Type() == reflect.TypeFor[*ConfigMetadata]() {
newMeta := &ConfigMetadata{}
if err := r.resolveSchema(root, field.Interface().(*ConfigMetadata), newMeta, origin); err != nil {
return err
}
targetField.Set(reflect.ValueOf(newMeta))
}
}
case reflect.Map:
if field.Type().Elem() == reflect.TypeFor[*ConfigMetadata]() {
newMap := reflect.MakeMap(field.Type())
iter := field.MapRange()
for iter.Next() {
key := iter.Key()
value := iter.Value()
if !value.IsNil() {
newMeta := &ConfigMetadata{}
if err := r.resolveSchema(root, value.Interface().(*ConfigMetadata), newMeta, origin); err != nil {
return err
}
newMap.SetMapIndex(key, reflect.ValueOf(newMeta))
}
}
targetField.Set(newMap)
} else {
targetField.Set(field)
}
case reflect.Slice:
if field.Type().Elem() == reflect.TypeFor[*ConfigMetadata]() {
newSlice := reflect.MakeSlice(field.Type(), field.Len(), field.Len())
for j := 0; j < field.Len(); j++ {
elem := field.Index(j)
if !elem.IsNil() {
newMeta := &ConfigMetadata{}
if err := r.resolveSchema(root, elem.Interface().(*ConfigMetadata), newMeta, origin); err != nil {
return err
}
newSlice.Index(j).Set(reflect.ValueOf(newMeta))
}
}
targetField.Set(newSlice)
} else {
targetField.Set(field)
}
default:
targetField.Set(field)
}
}
transformDurationFormat(target)
target.Defs = nil // Clear defs after resolution to avoid confusion
return nil
}
// resolveRef resolves a JSON Schema $ref, handling both internal and external references.
// The origin parameter tracks which namespace the current schema was loaded from,
// enabling local refs in remotely-fetched schemas to be converted to external refs.
func (r *Resolver) resolveRef(root, current *ConfigMetadata, origin *Ref) (*ConfigMetadata, error) {
ref := WithOrigin(current.Ref, origin)
if err := ref.Validate(); err != nil {
return nil, fmt.Errorf("invalid reference format %q: %w", current.Ref, err)
}
if ref.isInternal() {
if root.Defs != nil {
if val, ok := root.Defs[ref.DefName()]; ok {
return val, nil
}
}
}
if ref.isLocal() {
return r.loadExternalRef(ref)
}
// check if it's in known namespace
if _, ok := ref.Namespace(); ok {
return r.loadExternalRef(ref)
}
// fallback to type "any"
current.GoType = current.Ref
current.Comment = "Uses `any` type."
return current, nil
}
// loadExternalRef uses SchemaLoader to load external references
func (r *Resolver) loadExternalRef(ref *Ref) (*ConfigMetadata, error) {
md, err := r.loader.Load(*ref)
if err != nil {
return nil, err
}
if md == nil {
return nil, fmt.Errorf("no loader could resolve external reference: %s", ref)
}
if md.Defs != nil {
if def, ok := md.Defs[ref.DefName()]; ok {
resolved := &ConfigMetadata{}
if err := r.resolveSchema(md, def, resolved, ref); err != nil {
return nil, fmt.Errorf("failed to resolve internal references in external schema %s: %w", ref, err)
}
return resolved, nil
}
}
return nil, fmt.Errorf("type %q not found in loaded schema for reference %s", ref.DefName(), ref)
}
================================================
FILE: cmd/mdatagen/internal/cfggen/resolver_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package cfggen
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
)
func TestResolver_ResolveSchema_BasicMetadata(t *testing.T) {
resolver := &Resolver{
pkgID: "go.opentelemetry.io/collector/receiver/otlpreceiver",
class: "receiver",
name: "otlp",
loader: NewLoader(""),
}
src := &ConfigMetadata{
Description: "OTLP receiver configuration",
Type: "object",
}
result, err := resolver.ResolveSchema(src)
require.NoError(t, err)
require.Equal(t, schemaVersion, result.Schema)
require.Equal(t, "go.opentelemetry.io/collector/receiver/otlpreceiver", result.ID)
require.Equal(t, "receiver/otlp", result.Title)
require.Equal(t, "OTLP receiver configuration", result.Description)
require.Equal(t, "object", result.Type)
}
func TestResolver_ResolveSchema_InternalReference(t *testing.T) {
resolver := &Resolver{
pkgID: "go.opentelemetry.io/collector/test/component",
class: "receiver",
name: "test",
loader: NewLoader(""),
}
src := &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"config": {
Ref: "target_type",
},
},
Defs: map[string]*ConfigMetadata{
"target_type": {
Type: "string",
Description: "Target type description",
},
},
}
result, err := resolver.ResolveSchema(src)
require.NoError(t, err)
require.Equal(t, "object", result.Type)
require.NotNil(t, result.Properties["config"])
require.Equal(t, "string", result.Properties["config"].Type)
require.Equal(t, "Target type description", result.Properties["config"].Description)
}
func TestResolver_ResolveSchema_UnknownInternalReference(t *testing.T) {
resolver := &Resolver{
pkgID: "go.opentelemetry.io/collector/test/component",
class: "receiver",
name: "test",
loader: NewLoader(""),
}
src := &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"config": {
Ref: "unknown_type",
},
},
}
// Should use "any" type because the internal reference doesn't exist
result, err := resolver.ResolveSchema(src)
require.NoError(t, err)
require.Nil(t, result.Properties["config"].Type)
}
func TestResolver_ResolveSchema_NestedStructures(t *testing.T) {
resolver := &Resolver{
pkgID: "go.opentelemetry.io/collector/test/component",
class: "receiver",
name: "test",
loader: NewLoader(""),
}
src := &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"nested": {
Type: "object",
Properties: map[string]*ConfigMetadata{
"field1": {
Type: "string",
},
"field2": {
Type: "integer",
},
},
},
},
}
result, err := resolver.ResolveSchema(src)
require.NoError(t, err)
require.Equal(t, "object", result.Type)
require.NotNil(t, result.Properties["nested"])
require.Equal(t, "object", result.Properties["nested"].Type)
require.NotNil(t, result.Properties["nested"].Properties["field1"])
require.Equal(t, "string", result.Properties["nested"].Properties["field1"].Type)
require.NotNil(t, result.Properties["nested"].Properties["field2"])
require.Equal(t, "integer", result.Properties["nested"].Properties["field2"].Type)
}
func TestResolver_ResolveSchema_AllOf(t *testing.T) {
resolver := &Resolver{
pkgID: "go.opentelemetry.io/collector/test/component",
class: "receiver",
name: "test",
loader: NewLoader(""),
}
src := &ConfigMetadata{
Type: "object",
AllOf: []*ConfigMetadata{
{
Type: "object",
Properties: map[string]*ConfigMetadata{
"field1": {Type: "string"},
},
},
{
Type: "object",
Properties: map[string]*ConfigMetadata{
"field2": {Type: "integer"},
},
},
},
}
result, err := resolver.ResolveSchema(src)
require.NoError(t, err)
require.Len(t, result.AllOf, 2)
require.NotNil(t, result.AllOf[0].Properties["field1"])
require.NotNil(t, result.AllOf[1].Properties["field2"])
}
func TestResolver_ResolveSchema_ArrayItems(t *testing.T) {
resolver := &Resolver{
pkgID: "go.opentelemetry.io/collector/test/component",
class: "receiver",
name: "test",
loader: NewLoader(""),
}
src := &ConfigMetadata{
Type: "array",
Items: &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"name": {Type: "string"},
},
},
}
result, err := resolver.ResolveSchema(src)
require.NoError(t, err)
require.Equal(t, "array", result.Type)
require.NotNil(t, result.Items)
require.Equal(t, "object", result.Items.Type)
require.NotNil(t, result.Items.Properties["name"])
}
func TestResolver_LoadExternalRef_Success(t *testing.T) {
ml := &mockLoader{
schemas: map[string]*ConfigMetadata{
"go.opentelemetry.io/collector/scraper/scraperhelper.controller_config": {
Type: "object",
Defs: map[string]*ConfigMetadata{
"controller_config": {
Type: "object",
Properties: map[string]*ConfigMetadata{
"timeout": {
Type: "string",
},
},
},
},
},
},
}
resolver := &Resolver{
pkgID: "go.opentelemetry.io/collector/test/component",
class: "receiver",
name: "test",
loader: ml,
}
ref := NewRef("go.opentelemetry.io/collector/scraper/scraperhelper.controller_config")
result, err := resolver.loadExternalRef(ref)
require.NoError(t, err)
require.Equal(t, "object", result.Type)
require.NotNil(t, result.Properties["timeout"])
require.Equal(t, "string", result.Properties["timeout"].Type)
}
func TestResolver_LoadExternalRef_InvalidPath(t *testing.T) {
resolver := &Resolver{
pkgID: "go.opentelemetry.io/collector/test/component",
class: "receiver",
name: "test",
loader: NewLoader(""),
}
ref := NewRef("invalid/path/without/namespace")
result, err := resolver.loadExternalRef(ref)
require.Error(t, err)
require.Nil(t, result)
}
func TestResolver_LoadExternalRef_TypeNotFound(t *testing.T) {
ml := &mockLoader{
schemas: map[string]*ConfigMetadata{
"go.opentelemetry.io/collector/scraper/scraperhelper.controller_config": {
Type: "object",
Defs: map[string]*ConfigMetadata{
"other_type": {
Type: "string",
},
},
},
},
}
resolver := &Resolver{
pkgID: "go.opentelemetry.io/collector/test/component",
class: "receiver",
name: "test",
loader: ml,
}
ref := NewRef("go.opentelemetry.io/collector/scraper/scraperhelper.controller_config")
result, err := resolver.loadExternalRef(ref)
require.Error(t, err)
require.Contains(t, err.Error(), "type \"controller_config\" not found")
require.Nil(t, result)
}
func TestResolver_IsExternalRef(t *testing.T) {
tests := []struct {
name string
ref string
expected bool
}{
{
name: "collector external ref - known namespace",
ref: "go.opentelemetry.io/collector/scraper/scraperhelper.controller_config",
expected: true,
},
{
name: "contrib external ref - known namespace",
ref: "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mysqlreceiver.config",
expected: true,
},
{
name: "relative path - local not external",
ref: "./internal/metadata.metrics_builder_config",
expected: false,
},
{
name: "internal ref - simple name",
ref: "target_type",
expected: false,
},
{
name: "unsupported namespace - still external (not internal/local)",
ref: "github.com/example/custom.config",
expected: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := NewRef(tt.ref)
result := r.isExternal()
require.Equal(t, tt.expected, result)
})
}
}
func TestResolver_ResolveSchema_ExternalReference_Integration(t *testing.T) {
// Use mockLoader instead of real file loading to avoid repo root dependency
confighttpSchema := &ConfigMetadata{
Type: "object",
Defs: map[string]*ConfigMetadata{
"client_config": {
Type: "object",
Properties: map[string]*ConfigMetadata{
"endpoint": {
Type: "string",
Description: "HTTP endpoint",
},
"timeout": {
Type: "string",
Description: "Request timeout",
},
},
},
},
}
ml := &mockLoader{
schemas: map[string]*ConfigMetadata{
"go.opentelemetry.io/collector/config/confighttp.client_config": confighttpSchema,
},
}
resolver := &Resolver{
pkgID: "go.opentelemetry.io/collector/receiver/otlpreceiver",
class: "receiver",
name: "otlp",
loader: ml,
}
src := &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"http": {
Ref: "go.opentelemetry.io/collector/config/confighttp.client_config",
},
},
}
result, err := resolver.ResolveSchema(src)
require.NoError(t, err)
require.Equal(t, "object", result.Type)
require.NotNil(t, result.Properties["http"])
require.Equal(t, "object", result.Properties["http"].Type)
require.NotNil(t, result.Properties["http"].Properties["endpoint"])
require.Equal(t, "HTTP endpoint", result.Properties["http"].Properties["endpoint"].Description)
require.NotNil(t, result.Properties["http"].Properties["timeout"])
require.Equal(t, "Request timeout", result.Properties["http"].Properties["timeout"].Description)
}
func TestResolver_ResolveSchema_DurationFormat(t *testing.T) {
resolver := &Resolver{
pkgID: "go.opentelemetry.io/collector/test/component",
class: "receiver",
name: "test",
loader: NewLoader(""),
}
src := &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"timeout": {
Type: "string",
Format: "duration",
Description: "Request timeout",
},
"interval": {
Type: "string",
Format: "duration",
},
},
}
result, err := resolver.ResolveSchema(src)
require.NoError(t, err)
// Check timeout field - should have pattern instead of format
require.NotNil(t, result.Properties["timeout"])
require.Equal(t, "string", result.Properties["timeout"].Type)
require.Empty(t, result.Properties["timeout"].Format, "format should be cleared")
require.Equal(t, `^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$`, result.Properties["timeout"].Pattern)
require.Contains(t, result.Properties["timeout"].Description, "duration format")
require.Contains(t, result.Properties["timeout"].Description, "Request timeout")
// Check interval field - should have pattern and auto-generated description hint
require.NotNil(t, result.Properties["interval"])
require.Equal(t, "string", result.Properties["interval"].Type)
require.Empty(t, result.Properties["interval"].Format)
require.Equal(t, `^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$`, result.Properties["interval"].Pattern)
}
// mockLoader is a test helper that returns pre-configured schemas keyed by cache key.
type mockLoader struct {
schemas map[string]*ConfigMetadata
}
func (m *mockLoader) Load(ref Ref) (*ConfigMetadata, error) {
cacheKey := ref.CacheKey()
if md, ok := m.schemas[cacheKey]; ok {
return md, nil
}
return nil, fmt.Errorf("schema not found for ref: %s", cacheKey)
}
func TestResolver_ResolveSchema_OriginConvertsLocalRefToExternal(t *testing.T) {
// confighttp schema contains a local absolute ref to /config/configauth.config
// When loaded as an external ref from the collector namespace, the local ref
// should be converted to go.opentelemetry.io/collector/config/configauth.config
configauthSchema := &ConfigMetadata{
Type: "object",
Defs: map[string]*ConfigMetadata{
"config": {
Type: "object",
Description: "Auth configuration",
Properties: map[string]*ConfigMetadata{
"token": {Type: "string"},
},
},
},
}
confighttpSchema := &ConfigMetadata{
Type: "object",
Defs: map[string]*ConfigMetadata{
"client_config": {
Type: "object",
Properties: map[string]*ConfigMetadata{
"endpoint": {Type: "string"},
"auth": {
// This is the key: a local absolute ref inside an externally-loaded schema
Ref: "/config/configauth.config",
},
},
},
},
}
ml := &mockLoader{
schemas: map[string]*ConfigMetadata{
// When loading the reference to confighttp.client_config, we get the whole schema with all defs
"go.opentelemetry.io/collector/config/confighttp.client_config": confighttpSchema,
// When resolving the local ref /config/configauth.config -> go.opentelemetry.io/collector/config/configauth.config
"go.opentelemetry.io/collector/config/configauth.config": configauthSchema,
},
}
resolver := &Resolver{
pkgID: "go.opentelemetry.io/collector/receiver/otlpreceiver",
class: "receiver",
name: "otlp",
loader: ml,
}
src := &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"http": {
Ref: "go.opentelemetry.io/collector/config/confighttp.client_config",
},
},
}
result, err := resolver.ResolveSchema(src)
require.NoError(t, err)
require.NotNil(t, result.Properties["http"])
require.Equal(t, "object", result.Properties["http"].Type)
require.NotNil(t, result.Properties["http"].Properties["endpoint"])
require.Equal(t, "string", result.Properties["http"].Properties["endpoint"].Type)
// The auth property should have been resolved through origin-aware conversion
require.NotNil(t, result.Properties["http"].Properties["auth"])
require.Equal(t, "object", result.Properties["http"].Properties["auth"].Type)
require.Equal(t, "Auth configuration", result.Properties["http"].Properties["auth"].Description)
require.NotNil(t, result.Properties["http"].Properties["auth"].Properties["token"])
}
func TestResolver_ResolveSchema_LocalRefWithOriginConversion(t *testing.T) {
// When a local ref is encountered in an externally-loaded schema, it should be converted
// using the origin namespace
configauthSchema := &ConfigMetadata{
Type: "object",
Defs: map[string]*ConfigMetadata{
"config": {
Type: "object",
Description: "Auth config",
},
},
}
confighttpSchema := &ConfigMetadata{
Type: "object",
Defs: map[string]*ConfigMetadata{
"client_config": {
Type: "object",
Properties: map[string]*ConfigMetadata{
"auth": {
// This is a local absolute ref inside an externally-loaded schema
Ref: "/config/configauth.config",
},
},
},
},
}
ml := &mockLoader{
schemas: map[string]*ConfigMetadata{
"go.opentelemetry.io/collector/config/confighttp.client_config": confighttpSchema,
// After origin conversion, /config/configauth.config becomes:
"go.opentelemetry.io/collector/config/configauth.config": configauthSchema,
},
}
resolver := &Resolver{
pkgID: "go.opentelemetry.io/collector/receiver/otlpreceiver",
class: "receiver",
name: "otlp",
loader: ml,
}
src := &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"http": {
Ref: "go.opentelemetry.io/collector/config/confighttp.client_config",
},
},
}
result, err := resolver.ResolveSchema(src)
require.NoError(t, err)
require.NotNil(t, result.Properties["http"])
require.Equal(t, "object", result.Properties["http"].Type)
// auth should be resolved through origin-aware conversion
require.NotNil(t, result.Properties["http"].Properties["auth"])
require.Equal(t, "object", result.Properties["http"].Properties["auth"].Type)
require.Equal(t, "Auth config", result.Properties["http"].Properties["auth"].Description)
}
func TestResolver_ResolveSchema_NestedOriginPropagation(t *testing.T) {
// Schema A (remote) → local ref → Schema B (also remote) → local ref → Schema C
// Verify the origin propagates through all levels.
schemaC := &ConfigMetadata{
Type: "object",
Defs: map[string]*ConfigMetadata{
"tls_config": {
Type: "object",
Description: "TLS configuration",
},
},
}
schemaB := &ConfigMetadata{
Type: "object",
Defs: map[string]*ConfigMetadata{
"auth_config": {
Type: "object",
Properties: map[string]*ConfigMetadata{
"tls": {
// Nested local ref — should also be converted using origin
Ref: "/config/configtls.tls_config",
},
},
},
},
}
schemaA := &ConfigMetadata{
Type: "object",
Defs: map[string]*ConfigMetadata{
"client_config": {
Type: "object",
Properties: map[string]*ConfigMetadata{
"auth": {
Ref: "/config/configauth.auth_config",
},
},
},
},
}
ml := &mockLoader{
schemas: map[string]*ConfigMetadata{
"go.opentelemetry.io/collector/config/confighttp.client_config": schemaA,
"go.opentelemetry.io/collector/config/configauth.auth_config": schemaB,
"go.opentelemetry.io/collector/config/configtls.tls_config": schemaC,
},
}
resolver := &Resolver{
pkgID: "go.opentelemetry.io/collector/receiver/otlpreceiver",
class: "receiver",
name: "otlp",
loader: ml,
}
src := &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"http": {
Ref: "go.opentelemetry.io/collector/config/confighttp.client_config",
},
},
}
result, err := resolver.ResolveSchema(src)
require.NoError(t, err)
require.NotNil(t, result.Properties["http"])
// auth should be resolved through origin-aware conversion
auth := result.Properties["http"].Properties["auth"]
require.NotNil(t, auth)
require.Equal(t, "object", auth.Type)
// tls inside auth should also be resolved through origin propagation
tls := auth.Properties["tls"]
require.NotNil(t, tls)
require.Equal(t, "object", tls.Type)
require.Equal(t, "TLS configuration", tls.Description)
}
func TestResolver_ResolveSchema_RelativeRefWithOrigin(t *testing.T) {
metadataSchema := &ConfigMetadata{
Type: "object",
Defs: map[string]*ConfigMetadata{
"metrics_config": {
Type: "object",
Description: "Metrics configuration",
},
},
}
confighttpSchema := &ConfigMetadata{
Type: "object",
Defs: map[string]*ConfigMetadata{
"client_config": {
Type: "object",
Properties: map[string]*ConfigMetadata{
"metrics": {
Ref: "./internal/metadata.metrics_config",
},
},
},
},
}
ml := &mockLoader{
schemas: map[string]*ConfigMetadata{
"go.opentelemetry.io/collector/config/confighttp.client_config": confighttpSchema,
"go.opentelemetry.io/collector/config/confighttp/internal/metadata.metrics_config": metadataSchema,
},
}
resolver := &Resolver{
pkgID: "go.opentelemetry.io/collector/receiver/otlpreceiver",
class: "receiver",
name: "otlp",
loader: ml,
}
src := &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"http": {
Ref: "go.opentelemetry.io/collector/config/confighttp.client_config",
},
},
}
result, err := resolver.ResolveSchema(src)
require.NoError(t, err)
require.NotNil(t, result.Properties["http"])
metrics := result.Properties["http"].Properties["metrics"]
require.NotNil(t, metrics)
require.Equal(t, "object", metrics.Type)
require.Equal(t, "Metrics configuration", metrics.Description)
}
func TestResolver_ResolveSchema_ParentRelativeRefWithOrigin(t *testing.T) {
configtlsSchema := &ConfigMetadata{
Type: "object",
Defs: map[string]*ConfigMetadata{
"tls_config": {
Type: "object",
Description: "TLS settings",
},
},
}
confighttpSchema := &ConfigMetadata{
Type: "object",
Defs: map[string]*ConfigMetadata{
"client_config": {
Type: "object",
Properties: map[string]*ConfigMetadata{
"tls": {
Ref: "../configtls.tls_config",
},
},
},
},
}
ml := &mockLoader{
schemas: map[string]*ConfigMetadata{
"go.opentelemetry.io/collector/config/confighttp.client_config": confighttpSchema,
"go.opentelemetry.io/collector/config/configtls.tls_config": configtlsSchema,
},
}
resolver := &Resolver{
pkgID: "go.opentelemetry.io/collector/receiver/otlpreceiver",
class: "receiver",
name: "otlp",
loader: ml,
}
src := &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"http": {
Ref: "go.opentelemetry.io/collector/config/confighttp.client_config",
},
},
}
result, err := resolver.ResolveSchema(src)
require.NoError(t, err)
require.NotNil(t, result.Properties["http"])
tls := result.Properties["http"].Properties["tls"]
require.NotNil(t, tls)
require.Equal(t, "object", tls.Type)
require.Equal(t, "TLS settings", tls.Description)
}
func TestNewResolver(t *testing.T) {
dir := t.TempDir()
r := NewResolver("go.opentelemetry.io/collector/receiver/otlp", "receiver", "otlp", dir)
require.NotNil(t, r)
require.Equal(t, "go.opentelemetry.io/collector/receiver/otlp", r.pkgID)
require.Equal(t, "receiver", r.class)
require.Equal(t, "otlp", r.name)
require.NotNil(t, r.loader)
}
func TestResolver_ResolveSchema_UnknownNamespaceFallback(t *testing.T) {
// An external ref with an unsupported namespace should fall back to "any" type
resolver := &Resolver{
pkgID: "go.opentelemetry.io/collector/test/component",
class: "receiver",
name: "test",
loader: NewLoader(""),
}
src := &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"custom": {
Ref: "github.com/example/custom.config",
},
},
}
result, err := resolver.ResolveSchema(src)
require.NoError(t, err)
require.NotNil(t, result.Properties["custom"])
require.Equal(t, "github.com/example/custom.config", result.Properties["custom"].GoType)
require.Contains(t, result.Properties["custom"].Comment, "any")
}
func TestResolver_ResolveSchema_LoaderError(t *testing.T) {
ml := &mockLoader{schemas: map[string]*ConfigMetadata{}}
resolver := &Resolver{
pkgID: "go.opentelemetry.io/collector/test/component",
class: "receiver",
name: "test",
loader: ml,
}
src := &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"http": {
Ref: "go.opentelemetry.io/collector/config/confighttp.client_config",
},
},
}
result, err := resolver.ResolveSchema(src)
require.Error(t, err)
require.Nil(t, result)
}
func TestResolver_ResolveRef_InvalidRefFormat(t *testing.T) {
resolver := &Resolver{
pkgID: "go.opentelemetry.io/collector/test/component",
class: "receiver",
name: "test",
loader: NewLoader(""),
}
src := &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"bad": {
Ref: "/",
},
},
}
_, err := resolver.ResolveSchema(src)
require.Error(t, err)
require.Contains(t, err.Error(), "invalid reference format")
}
func TestResolver_LoadExternalRef_NilResult(t *testing.T) {
ml := &nilResultLoader{}
resolver := &Resolver{
pkgID: "go.opentelemetry.io/collector/test/component",
class: "receiver",
name: "test",
loader: ml,
}
ref := NewRef("go.opentelemetry.io/collector/config/confighttp.client_config")
result, err := resolver.loadExternalRef(ref)
require.Error(t, err)
require.Contains(t, err.Error(), "no loader could resolve external reference")
require.Nil(t, result)
}
// nilResultLoader returns (nil, nil) for any ref.
type nilResultLoader struct{}
func (n *nilResultLoader) Load(_ Ref) (*ConfigMetadata, error) { return nil, nil }
func TestResolver_LoadExternalRef_InternalResolutionError(t *testing.T) {
brokenSchema := &ConfigMetadata{
Type: "object",
Defs: map[string]*ConfigMetadata{
"config": {
Type: "object",
Properties: map[string]*ConfigMetadata{
"field": {
Ref: "/",
},
},
},
},
}
ml := &mockLoader{
schemas: map[string]*ConfigMetadata{
"go.opentelemetry.io/collector/config/confighttp.config": brokenSchema,
},
}
resolver := &Resolver{
pkgID: "go.opentelemetry.io/collector/test/component",
class: "receiver",
name: "test",
loader: ml,
}
ref := NewRef("go.opentelemetry.io/collector/config/confighttp.config")
result, err := resolver.loadExternalRef(ref)
require.Error(t, err)
require.Contains(t, err.Error(), "failed to resolve internal references in external schema")
require.Nil(t, result)
}
func TestResolver_ResolveSchema_LocalRef(t *testing.T) {
localSchema := &ConfigMetadata{
Type: "object",
Defs: map[string]*ConfigMetadata{
"target": {
Type: "object",
Description: "Local target",
},
},
}
ml := &mockLoader{
schemas: map[string]*ConfigMetadata{
"/config/localtype.target": localSchema,
},
}
resolver := &Resolver{
pkgID: "go.opentelemetry.io/collector/test/component",
class: "receiver",
name: "test",
loader: ml,
}
src := &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"local": {
Ref: "/config/localtype.target",
},
},
}
result, err := resolver.ResolveSchema(src)
require.NoError(t, err)
require.NotNil(t, result.Properties["local"])
require.Equal(t, "object", result.Properties["local"].Type)
require.Equal(t, "Local target", result.Properties["local"].Description)
}
func TestResolver_ResolveSchema_MapValueError(t *testing.T) {
resolver := &Resolver{
pkgID: "go.opentelemetry.io/collector/test/component",
class: "receiver",
name: "test",
loader: &mockLoader{schemas: map[string]*ConfigMetadata{}},
}
src := &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"field": {
// Ref to an unknown external schema → loader returns error
Ref: "go.opentelemetry.io/collector/missing/pkg.config",
},
},
}
_, err := resolver.ResolveSchema(src)
require.Error(t, err)
}
func TestResolver_ResolveSchema_AllOfError(t *testing.T) {
resolver := &Resolver{
pkgID: "go.opentelemetry.io/collector/test/component",
class: "receiver",
name: "test",
loader: &mockLoader{schemas: map[string]*ConfigMetadata{}},
}
src := &ConfigMetadata{
Type: "object",
AllOf: []*ConfigMetadata{
{
Ref: "go.opentelemetry.io/collector/missing/pkg.config",
},
},
}
_, err := resolver.ResolveSchema(src)
require.Error(t, err)
}
func TestResolver_ResolveSchema_PtrFieldError(t *testing.T) {
resolver := &Resolver{
pkgID: "go.opentelemetry.io/collector/test/component",
class: "receiver",
name: "test",
loader: &mockLoader{schemas: map[string]*ConfigMetadata{}},
}
src := &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"body": {
Type: "string",
ContentSchema: &ConfigMetadata{
Ref: "/",
},
},
},
}
_, err := resolver.ResolveSchema(src)
require.Error(t, err)
}
func TestResolver_ResolveSchema_PointerFields(t *testing.T) {
resolver := &Resolver{
pkgID: "go.opentelemetry.io/collector/test/component",
class: "receiver",
name: "test",
loader: NewLoader(""),
}
minItems := 1
maxItems := 10
src := &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"tags": {
Type: "array",
Items: &ConfigMetadata{Type: "string"},
MinItems: &minItems,
MaxItems: &maxItems,
},
},
}
result, err := resolver.ResolveSchema(src)
require.NoError(t, err)
require.NotNil(t, result.Properties["tags"])
require.Equal(t, "array", result.Properties["tags"].Type)
require.NotNil(t, result.Properties["tags"].Items)
require.Equal(t, "string", result.Properties["tags"].Items.Type)
}
func TestResolver_ResolveSchema_ContentSchema(t *testing.T) {
resolver := &Resolver{
pkgID: "go.opentelemetry.io/collector/test/component",
class: "receiver",
name: "test",
loader: NewLoader(""),
}
src := &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"body": {
Type: "string",
ContentMediaType: "application/json",
ContentSchema: &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"name": {Type: "string"},
},
},
},
},
}
result, err := resolver.ResolveSchema(src)
require.NoError(t, err)
require.NotNil(t, result.Properties["body"])
require.NotNil(t, result.Properties["body"].ContentSchema)
require.Equal(t, "object", result.Properties["body"].ContentSchema.Type)
}
func TestResolver_ResolveSchema_PreservesCustomExtensions(t *testing.T) {
// When a node has both a $ref and custom extensions (GoType, IsPointer,
// IsOptional, Description, Default, Enum), the custom extensions should
// be preserved after resolution instead of being overwritten by the
// resolved schema's values.
targetSchema := &ConfigMetadata{
Type: "object",
Defs: map[string]*ConfigMetadata{
"duration_type": {
Type: "string",
Description: "A generic duration type",
},
},
}
ml := &mockLoader{
schemas: map[string]*ConfigMetadata{
"go.opentelemetry.io/collector/config/configbase.duration_type": targetSchema,
},
}
resolver := &Resolver{
pkgID: "go.opentelemetry.io/collector/test/component",
class: "receiver",
name: "test",
loader: ml,
}
src := &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"timeout": {
Ref: "go.opentelemetry.io/collector/config/configbase.duration_type",
GoType: "time.Duration",
IsPointer: true,
IsOptional: true,
Description: "Request timeout for the endpoint",
Default: "30s",
Enum: []any{"10s", "30s", "60s"},
},
},
}
result, err := resolver.ResolveSchema(src)
require.NoError(t, err)
require.NotNil(t, result.Properties["timeout"])
timeout := result.Properties["timeout"]
// GoType should be preserved from the referencing node
require.Equal(t, "time.Duration", timeout.GoType)
// IsPointer should be preserved
require.True(t, timeout.IsPointer)
// IsOptional should be preserved
require.True(t, timeout.IsOptional)
// Description should come from the referencing node, not the target
require.Equal(t, "Request timeout for the endpoint", timeout.Description)
// Default should be preserved
require.NotNil(t, timeout.Default)
require.Equal(t, "30s", timeout.Default)
// Enum should be preserved
require.Equal(t, []any{"10s", "30s", "60s"}, timeout.Enum)
// Type should come from the resolved schema
require.Equal(t, "string", timeout.Type)
}
func TestResolver_ResolveSchema_RefWithoutCustomExtensions(t *testing.T) {
// When a node has a $ref but NO custom extensions, the resolved schema's
// values should be used as-is (no overriding).
targetSchema := &ConfigMetadata{
Type: "object",
Defs: map[string]*ConfigMetadata{
"base_config": {
Type: "object",
Description: "Base configuration from the target schema",
GoType: "BaseConfig",
},
},
}
ml := &mockLoader{
schemas: map[string]*ConfigMetadata{
"go.opentelemetry.io/collector/config/configbase.base_config": targetSchema,
},
}
resolver := &Resolver{
pkgID: "go.opentelemetry.io/collector/test/component",
class: "receiver",
name: "test",
loader: ml,
}
src := &ConfigMetadata{
Type: "object",
Properties: map[string]*ConfigMetadata{
"base": {
Ref: "go.opentelemetry.io/collector/config/configbase.base_config",
// No custom extensions set
},
},
}
result, err := resolver.ResolveSchema(src)
require.NoError(t, err)
require.NotNil(t, result.Properties["base"])
base := result.Properties["base"]
// Values should come from the resolved target
require.Equal(t, "object", base.Type)
require.Equal(t, "Base configuration from the target schema", base.Description)
require.Equal(t, "BaseConfig", base.GoType)
}
================================================
FILE: cmd/mdatagen/internal/cfggen/type_ref.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package cfggen // import "go.opentelemetry.io/collector/cmd/mdatagen/internal/cfggen"
import (
"errors"
"fmt"
"path"
"strings"
"go.opentelemetry.io/collector/cmd/mdatagen/internal/helpers"
)
// GoTypeRef represents a fully resolved Go type reference for code generation.
// It holds the import path and exported type name needed to render a type in generated Go source code.
type GoTypeRef struct {
// ImportPath is the full Go import path
// Empty for internal (local $defs) references that need no import.
ImportPath string
// TypeName is the exported Go type name
TypeName string
}
// Qualifier returns the short package name used as a qualifier in Go source.
// Returns "" for internal references (no import needed).
func (r GoTypeRef) Qualifier() string {
if r.ImportPath == "" {
return ""
}
return path.Base(r.ImportPath)
}
// String returns the Go type expression as it would appear in source code,
func (r GoTypeRef) String() string {
if q := r.Qualifier(); q != "" {
return q + "." + r.TypeName
}
return r.TypeName
}
// ResolveGoTypeRef converts a raw metadata reference string into a GoTypeRef.
//
// Parameters:
// - ref: raw reference string from metadata
// - rootPackage: module path from the repo-root go.mod
// - componentPackage: full Go import path of the component
func ResolveGoTypeRef(ref, rootPackage, componentPackage string) (GoTypeRef, error) {
if ref == "" {
return GoTypeRef{}, errors.New("empty reference string")
}
cleanRef, _, _ := strings.Cut(ref, "@")
switch {
case strings.HasPrefix(cleanRef, "/"):
return resolveLocalAbsolute(cleanRef, rootPackage)
case strings.HasPrefix(cleanRef, "./") || strings.HasPrefix(cleanRef, "../"):
return resolveLocalRelative(cleanRef, componentPackage)
case strings.Contains(cleanRef, "/"):
return resolveExternal(cleanRef)
default:
return resolveInternal(cleanRef)
}
}
func resolveInternal(ref string) (GoTypeRef, error) {
typeName, err := helpers.FormatIdentifier(ref, true)
if err != nil {
return GoTypeRef{}, fmt.Errorf("failed to format internal type %q: %w", ref, err)
}
return GoTypeRef{ImportPath: "", TypeName: typeName}, nil
}
func resolveExternal(ref string) (GoTypeRef, error) {
sepIndex := strings.LastIndex(ref, ".")
if sepIndex == -1 || sepIndex == len(ref)-1 {
return GoTypeRef{}, fmt.Errorf("invalid external reference %q: missing type name after last dot", ref)
}
pkgPath := ref[:sepIndex]
rawType := ref[sepIndex+1:]
typeName, err := helpers.FormatIdentifier(rawType, true)
if err != nil {
return GoTypeRef{}, fmt.Errorf("failed to format external type %q: %w", rawType, err)
}
return GoTypeRef{ImportPath: pkgPath, TypeName: typeName}, nil
}
func resolveLocalAbsolute(ref, rootPackage string) (GoTypeRef, error) {
sepIndex := strings.LastIndex(ref, ".")
if sepIndex == -1 || sepIndex == len(ref)-1 {
return GoTypeRef{}, fmt.Errorf("invalid local absolute reference %q: missing type name after last dot", ref)
}
localPath := ref[:sepIndex]
rawType := ref[sepIndex+1:]
typeName, err := helpers.FormatIdentifier(rawType, true)
if err != nil {
return GoTypeRef{}, fmt.Errorf("failed to format local type %q: %w", rawType, err)
}
importPath := rootPackage + localPath
return GoTypeRef{ImportPath: importPath, TypeName: typeName}, nil
}
func resolveLocalRelative(ref, componentPackage string) (GoTypeRef, error) {
sepIndex := strings.LastIndex(ref, ".")
if sepIndex == -1 || sepIndex == len(ref)-1 {
return GoTypeRef{}, fmt.Errorf("invalid local relative reference %q: missing type name after last dot", ref)
}
relPath := ref[:sepIndex]
rawType := ref[sepIndex+1:]
typeName, err := helpers.FormatIdentifier(rawType, true)
if err != nil {
return GoTypeRef{}, fmt.Errorf("failed to format local type %q: %w", rawType, err)
}
importPath := path.Join(componentPackage, relPath)
return GoTypeRef{ImportPath: importPath, TypeName: typeName}, nil
}
================================================
FILE: cmd/mdatagen/internal/cfggen/type_ref_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package cfggen
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestGoTypeRef_String(t *testing.T) {
tests := []struct {
name string
ref GoTypeRef
expected string
}{
{
name: "internal type (no import)",
ref: GoTypeRef{ImportPath: "", TypeName: "Target"},
expected: "Target",
},
{
name: "external type with package qualifier",
ref: GoTypeRef{ImportPath: "go.opentelemetry.io/collector/config/confighttp", TypeName: "ClientConfig"},
expected: "confighttp.ClientConfig",
},
{
name: "standard library type",
ref: GoTypeRef{ImportPath: "time", TypeName: "Duration"},
expected: "time.Duration",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t, tt.expected, tt.ref.String())
})
}
}
func TestGoTypeRef_Qualifier(t *testing.T) {
tests := []struct {
name string
ref GoTypeRef
expected string
}{
{
name: "no import path",
ref: GoTypeRef{ImportPath: ""},
expected: "",
},
{
name: "nested package",
ref: GoTypeRef{ImportPath: "go.opentelemetry.io/collector/config/confighttp"},
expected: "confighttp",
},
{
name: "single segment",
ref: GoTypeRef{ImportPath: "time"},
expected: "time",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t, tt.expected, tt.ref.Qualifier())
})
}
}
func TestResolveGoTypeRef_Internal(t *testing.T) {
tests := []struct {
name string
ref string
expected GoTypeRef
}{
{
name: "simple snake_case name",
ref: "target",
expected: GoTypeRef{ImportPath: "", TypeName: "Target"},
},
{
name: "multi-word snake_case",
ref: "my_custom_type",
expected: GoTypeRef{ImportPath: "", TypeName: "MyCustomType"},
},
{
name: "already formatted",
ref: "MyType",
expected: GoTypeRef{ImportPath: "", TypeName: "MyType"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := ResolveGoTypeRef(tt.ref, "", "")
require.NoError(t, err)
require.Equal(t, tt.expected, result)
})
}
}
func TestResolveGoTypeRef_External(t *testing.T) {
tests := []struct {
name string
ref string
expected GoTypeRef
}{
{
name: "full module path",
ref: "go.opentelemetry.io/collector/config/confighttp.client_config",
expected: GoTypeRef{
ImportPath: "go.opentelemetry.io/collector/config/confighttp",
TypeName: "ClientConfig",
},
},
{
name: "with version suffix",
ref: "go.opentelemetry.io/collector/scraper/scraperhelper.controller_config@v0.146.0",
expected: GoTypeRef{
ImportPath: "go.opentelemetry.io/collector/scraper/scraperhelper",
TypeName: "ControllerConfig",
},
},
{
name: "github package",
ref: "github.com/example/pkg/subpkg.MyType",
expected: GoTypeRef{
ImportPath: "github.com/example/pkg/subpkg",
TypeName: "MyType",
},
},
{
name: "type name needs formatting",
ref: "github.com/example/pkg.my_type",
expected: GoTypeRef{
ImportPath: "github.com/example/pkg",
TypeName: "MyType",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := ResolveGoTypeRef(tt.ref, "", "")
require.NoError(t, err)
require.Equal(t, tt.expected, result)
})
}
}
func TestResolveGoTypeRef_LocalAbsolute(t *testing.T) {
rootPkg := "go.opentelemetry.io/collector"
tests := []struct {
name string
ref string
expected GoTypeRef
}{
{
name: "repo-relative path",
ref: "/config/confighttp.client_config",
expected: GoTypeRef{
ImportPath: "go.opentelemetry.io/collector/config/confighttp",
TypeName: "ClientConfig",
},
},
{
name: "nested path",
ref: "/scraper/scraperhelper.controller_config",
expected: GoTypeRef{
ImportPath: "go.opentelemetry.io/collector/scraper/scraperhelper",
TypeName: "ControllerConfig",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := ResolveGoTypeRef(tt.ref, rootPkg, "")
require.NoError(t, err)
require.Equal(t, tt.expected, result)
})
}
}
func TestResolveGoTypeRef_LocalAbsolute_DifferentRoot(t *testing.T) {
rootPkg := "github.com/open-telemetry/opentelemetry-collector-contrib"
ref := "/receiver/hostmetricsreceiver/internal.scraper_config"
result, err := ResolveGoTypeRef(ref, rootPkg, "")
require.NoError(t, err)
require.Equal(t, GoTypeRef{
ImportPath: "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal",
TypeName: "ScraperConfig",
}, result)
}
func TestResolveGoTypeRef_LocalRelative(t *testing.T) {
compPkg := "go.opentelemetry.io/collector/scraper/scraperhelper"
tests := []struct {
name string
ref string
expected GoTypeRef
}{
{
name: "relative to component",
ref: "./internal/metadata.metrics_builder",
expected: GoTypeRef{
ImportPath: "go.opentelemetry.io/collector/scraper/scraperhelper/internal/metadata",
TypeName: "MetricsBuilder",
},
},
{
name: "parent directory reference",
ref: "../otherpackage.some_type",
expected: GoTypeRef{
ImportPath: "go.opentelemetry.io/collector/scraper/otherpackage",
TypeName: "SomeType",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := ResolveGoTypeRef(tt.ref, "", compPkg)
require.NoError(t, err)
require.Equal(t, tt.expected, result)
})
}
}
func TestResolveGoTypeRef_Errors(t *testing.T) {
tests := []struct {
name string
ref string
}{
{
name: "empty reference",
ref: "",
},
{
name: "external missing type after dot",
ref: "github.com/example/pkg.",
},
{
name: "local absolute missing type after dot",
ref: "/config/confighttp.",
},
{
name: "local relative missing type after dot",
ref: "./internal/metadata.",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := ResolveGoTypeRef(tt.ref, "root", "comp")
require.Error(t, err)
})
}
}
func TestResolveGoTypeRef_VersionStripped(t *testing.T) {
ref := "go.opentelemetry.io/collector/config/confighttp.client_config@v0.146.0"
result, err := ResolveGoTypeRef(ref, "", "")
require.NoError(t, err)
require.Equal(t, "go.opentelemetry.io/collector/config/confighttp", result.ImportPath)
require.Equal(t, "ClientConfig", result.TypeName)
}
================================================
FILE: cmd/mdatagen/internal/cfggen/writer.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package cfggen // import "go.opentelemetry.io/collector/cmd/mdatagen/internal/cfggen"
import (
"os"
"path/filepath"
)
const fileName = "config.schema.json"
// WriteJSONSchema writes the given ConfigMetadata as a JSON Schema file
// named "config.schema.json" in the specified directory.
func WriteJSONSchema(dir string, md *ConfigMetadata) error {
filePath := filepath.Join(dir, fileName)
data, err := md.ToJSON()
if err != nil {
return err
}
return os.WriteFile(filePath, data, 0o600)
}
================================================
FILE: cmd/mdatagen/internal/cfggen/writer_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package cfggen
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
)
func TestWriteJSONSchema(t *testing.T) {
dir := t.TempDir()
md := &ConfigMetadata{
Schema: schemaVersion,
Type: "object",
Properties: map[string]*ConfigMetadata{
"endpoint": {Type: "string"},
},
}
err := WriteJSONSchema(dir, md)
require.NoError(t, err)
content, err := os.ReadFile(filepath.Join(dir, fileName)) // #nosec G304
require.NoError(t, err)
require.Contains(t, string(content), `"$schema"`)
require.Contains(t, string(content), `"endpoint"`)
}
func TestWriteJSONSchema_InvalidDir(t *testing.T) {
md := &ConfigMetadata{Type: "object"}
err := WriteJSONSchema("/nonexistent/path/that/does/not/exist", md)
require.Error(t, err)
}
================================================
FILE: cmd/mdatagen/internal/command.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/cmd/mdatagen/internal"
import (
"bytes"
"errors"
"fmt"
"go/format"
"io/fs"
"os"
"path/filepath"
"regexp"
"runtime/debug"
"slices"
"sort"
"strings"
"text/template"
"github.com/spf13/cobra"
"go.yaml.in/yaml/v3"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"go.opentelemetry.io/collector/cmd/mdatagen/internal/cfggen"
"go.opentelemetry.io/collector/cmd/mdatagen/internal/helpers"
)
const (
statusStart = ""
statusEnd = ""
)
var nonComponents = []string{
"cmd",
"converter",
"pkg",
"provider",
}
func getVersion() (string, error) {
// the second returned value is a boolean, which is true if the binaries are built with module support.
info, ok := debug.ReadBuildInfo()
if !ok {
return "", errors.New("could not read build info")
}
return info.Main.Version, nil
}
// NewCommand constructs a new cobra.Command using the given Settings.
// Any URIs specified in CollectorSettings.ConfigProviderSettings.ResolverSettings.URIs
// are considered defaults and will be overwritten by config flags passed as
// command-line arguments to the executable.
// At least one Provider must be set.
func NewCommand() (*cobra.Command, error) {
ver, err := getVersion()
if err != nil {
return nil, err
}
rootCmd := &cobra.Command{
Use: "mdatagen",
Version: ver,
SilenceUsage: true,
Args: cobra.ExactArgs(1),
RunE: func(_ *cobra.Command, args []string) error {
return run(args[0])
},
}
return rootCmd, nil
}
func run(ymlPath string) error {
if ymlPath == "" {
return errors.New("argument must be metadata.yaml file")
}
ymlPath, err := filepath.Abs(ymlPath)
if err != nil {
return fmt.Errorf("failed to get absolute path for %v: %w", ymlPath, err)
}
ymlDir := filepath.Dir(ymlPath)
packageName := filepath.Base(ymlDir)
importRootPath, err := helpers.RootPackage(ymlDir)
if err != nil {
return fmt.Errorf("unable to determine import root path: %w", err)
}
raw, readErr := os.ReadFile(filepath.Clean(ymlPath))
if readErr != nil {
return fmt.Errorf("failed reading %v: %w", ymlPath, readErr)
}
if err = validateYAMLKeyOrder(raw); err != nil {
return fmt.Errorf("metadata.yaml ordering check failed: %w", err)
}
md, err := LoadMetadata(ymlPath)
if err != nil {
return fmt.Errorf("failed loading %v: %w", ymlPath, err)
}
tmplDir := "templates"
codeDir := filepath.Join(ymlDir, "internal", md.GeneratedPackageName)
toGenerate := map[string]string{}
if md.Status != nil {
if !slices.Contains(nonComponents, md.Status.Class) {
toGenerate[filepath.Join(tmplDir, "status.go.tmpl")] = filepath.Join(codeDir, "generated_status.go")
err = generateFile(filepath.Join(tmplDir, "component_test.go.tmpl"),
filepath.Join(ymlDir, "generated_component_test.go"), md, packageName, importRootPath)
if err != nil {
return err
}
} else {
if _, err = os.Stat(filepath.Join(codeDir, "generated_status.go")); err == nil {
err = os.Remove(filepath.Join(codeDir, "generated_status.go"))
if err != nil {
return err
}
}
if _, err = os.Stat(filepath.Join(ymlDir, "generated_component_test.go")); err == nil {
err = os.Remove(filepath.Join(ymlDir, "generated_component_test.go"))
if err != nil {
return err
}
}
}
err = generateFile(filepath.Join(tmplDir, "package_test.go.tmpl"),
filepath.Join(ymlDir, "generated_package_test.go"), md, packageName, importRootPath)
if err != nil {
return err
}
if _, err = os.Stat(filepath.Join(ymlDir, "README.md")); err == nil {
err = inlineReplace(
filepath.Join(tmplDir, "readme.md.tmpl"),
filepath.Join(ymlDir, "README.md"),
md, statusStart, statusEnd, md.GeneratedPackageName, importRootPath)
if err != nil {
return err
}
}
}
if len(md.Telemetry.Metrics) != 0 { // if there are telemetry metrics, generate telemetry specific files
testDir := filepath.Join(ymlDir, "internal", md.GeneratedPackageName+"test")
if err = os.MkdirAll(testDir, 0o700); err != nil {
return fmt.Errorf("unable to create output test directory %q: %w", codeDir, err)
}
toGenerate[filepath.Join(tmplDir, "telemetry.go.tmpl")] = filepath.Join(codeDir, "generated_telemetry.go")
toGenerate[filepath.Join(tmplDir, "telemetry_test.go.tmpl")] = filepath.Join(codeDir, "generated_telemetry_test.go")
toGenerate[filepath.Join(tmplDir, "telemetrytest.go.tmpl")] = filepath.Join(testDir, "generated_telemetrytest.go")
toGenerate[filepath.Join(tmplDir, "telemetrytest_test.go.tmpl")] = filepath.Join(testDir, "generated_telemetrytest_test.go")
} else {
if _, err = os.Stat(filepath.Join(ymlDir, "generated_telemetry.go")); err == nil {
err = os.Remove(filepath.Join(ymlDir, "generated_telemetry.go"))
if err != nil {
return err
}
}
if _, err = os.Stat(filepath.Join(ymlDir, "generated_telemetry_test.go")); err == nil {
err = os.Remove(filepath.Join(ymlDir, "generated_telemetry_test.go"))
if err != nil {
return err
}
}
if _, err = os.Stat(filepath.Join(ymlDir, "generated_telemetrytest.go")); err == nil {
err = os.Remove(filepath.Join(ymlDir, "generated_telemetrytest.go"))
if err != nil {
return err
}
}
if _, err = os.Stat(filepath.Join(ymlDir, "generated_telemetrytest_test.go")); err == nil {
err = os.Remove(filepath.Join(ymlDir, "generated_telemetrytest_test.go"))
if err != nil {
return err
}
}
}
if len(md.Metrics) != 0 || len(md.Telemetry.Metrics) != 0 || len(md.ResourceAttributes) != 0 || len(md.Events) != 0 || len(md.FeatureGates) != 0 { // if there's metrics or internal metrics or events or feature gates, generate documentation for them
toGenerate[filepath.Join(tmplDir, "documentation.md.tmpl")] = filepath.Join(ymlDir, "documentation.md")
}
if len(md.Metrics) > 0 || len(md.Events) > 0 || len(md.ResourceAttributes) > 0 {
testdataDir := filepath.Join(codeDir, "testdata")
if err = os.MkdirAll(filepath.Join(codeDir, "testdata"), 0o700); err != nil {
return fmt.Errorf("unable to create output directory %q: %w", testdataDir, err)
}
toGenerate[filepath.Join(tmplDir, "testdata", "config.yaml.tmpl")] = filepath.Join(testdataDir, "config.yaml")
toGenerate[filepath.Join(tmplDir, "config.go.tmpl")] = filepath.Join(codeDir, "generated_config.go")
toGenerate[filepath.Join(tmplDir, "config_test.go.tmpl")] = filepath.Join(codeDir, "generated_config_test.go")
toGenerate[filepath.Join(tmplDir, "config.schema.yaml.tmpl")] = filepath.Join(codeDir, "config.schema.yaml")
}
if len(md.ResourceAttributes) > 0 { // only generate resource files if resource attributes are configured
toGenerate[filepath.Join(tmplDir, "resource.go.tmpl")] = filepath.Join(codeDir, "generated_resource.go")
toGenerate[filepath.Join(tmplDir, "resource_test.go.tmpl")] = filepath.Join(codeDir, "generated_resource_test.go")
}
if len(md.Metrics) > 0 { // only generate metrics if metrics are present
toGenerate[filepath.Join(tmplDir, "metrics.go.tmpl")] = filepath.Join(codeDir, "generated_metrics.go")
toGenerate[filepath.Join(tmplDir, "metrics_test.go.tmpl")] = filepath.Join(codeDir, "generated_metrics_test.go")
}
if md.supportsSignal("logs") &&
(md.Status.Class == "receiver" || md.Status.Class == "scraper") {
toGenerate[filepath.Join(tmplDir, "logs.go.tmpl")] = filepath.Join(codeDir, "generated_logs.go")
toGenerate[filepath.Join(tmplDir, "logs_test.go.tmpl")] = filepath.Join(codeDir, "generated_logs_test.go")
}
if len(md.FeatureGates) > 0 { // only generate feature gates if feature gates are present
toGenerate[filepath.Join(tmplDir, "feature_gates.go.tmpl")] = filepath.Join(codeDir, "generated_feature_gates.go")
}
if len(md.Entities) > 0 && len(md.Metrics) > 0 { // only generate entity metrics if entities are defined
toGenerate[filepath.Join(tmplDir, "entity_metrics.go.tmpl")] = filepath.Join(codeDir, "generated_entity_metrics.go")
toGenerate[filepath.Join(tmplDir, "entity_metrics_test.go.tmpl")] = filepath.Join(codeDir, "generated_entity_metrics_test.go")
}
// If at least one file to generate, will need the codeDir
if len(toGenerate) > 0 {
if err = os.MkdirAll(codeDir, 0o700); err != nil {
return fmt.Errorf("unable to create output directory %q: %w", codeDir, err)
}
}
for tmpl, dst := range toGenerate {
if err := generateFile(tmpl, dst, md, md.GeneratedPackageName, importRootPath); err != nil {
return err
}
}
if err := generateConfigFiles(md, ymlDir, importRootPath); err != nil {
return fmt.Errorf("failed to generate config files: %w", err)
}
return nil
}
func getTemplateFuncMap(md Metadata, importRootPath string) template.FuncMap {
return template.FuncMap{
"publicVar": func(s string) (string, error) {
return helpers.FormatIdentifier(s, true)
},
"attributeInfo": func(an AttributeName) Attribute {
return md.Attributes[an]
},
"defaultAttributes": func(ans []AttributeName) []string {
var atts []string
for _, an := range ans {
if md.Attributes[an].IsNotOptIn() {
atts = append(atts, string(md.Attributes[an].Name()))
}
}
return atts
},
"requiredAttributes": func(ans []AttributeName) []string {
var atts []string
for _, an := range ans {
if md.Attributes[an].IsRequired() {
atts = append(atts, string(md.Attributes[an].Name()))
}
}
return atts
},
"hasAggregatableAttributes": func(ans []AttributeName) bool {
for _, an := range ans {
if md.Attributes[an].RequirementLevel == AttributeRequirementLevelRecommended ||
md.Attributes[an].RequirementLevel == AttributeRequirementLevelOptIn {
return true
}
}
return false
},
"getEventConditionalAttributes": func(attrs map[AttributeName]Attribute) []AttributeName {
seen := make(map[AttributeName]bool)
used := make([]AttributeName, 0)
for _, event := range md.Events {
for _, attribute := range event.Attributes {
v, exists := attrs[attribute]
if exists && v.IsConditional() && !seen[attribute] {
used = append(used, attribute)
seen[attribute] = true
}
}
}
sort.Slice(used, func(i, j int) bool { return string(used[i]) < string(used[j]) })
return used
},
"getMetricConditionalAttributes": func(attrs map[AttributeName]Attribute) []AttributeName {
seen := make(map[AttributeName]bool)
used := make([]AttributeName, 0)
for _, event := range md.Metrics {
for _, attribute := range event.Attributes {
v, exists := attrs[attribute]
if exists && v.IsConditional() && !seen[attribute] {
used = append(used, attribute)
seen[attribute] = true
}
}
}
sort.Slice(used, func(i, j int) bool { return string(used[i]) < string(used[j]) })
return used
},
"metricInfo": func(mn MetricName) Metric {
return md.Metrics[mn]
},
"eventInfo": func(en EventName) Event {
return md.Events[en]
},
"telemetryInfo": func(mn MetricName) Metric {
return md.Telemetry.Metrics[mn]
},
"parseImportsRequired": func(metrics map[MetricName]Metric) bool {
for _, m := range metrics {
if m.Data().HasMetricInputType() {
return true
}
}
return false
},
"stringsJoin": strings.Join,
"stringsSplit": strings.Split,
"userLinks": func(elems []string) []string {
result := make([]string, len(elems))
for i, elem := range elems {
if elem == "open-telemetry/collector-approvers" {
result[i] = "[@open-telemetry/collector-approvers](https://github.com/orgs/open-telemetry/teams/collector-approvers)"
} else {
result[i] = fmt.Sprintf("[@%s](https://www.github.com/%s)", elem, elem)
}
}
return result
},
"casesTitle": cases.Title(language.English).String,
"toLowerCase": strings.ToLower,
"toCamelCase": func(s string) string {
return joinCamelCase(strings.Split(s, "_"), true)
},
"toLowerCamelCase": func(s string) string {
return joinCamelCase(strings.Split(s, "_"), false)
},
"schemaRef": func(ref string) string {
return cfggen.LocalizeRef(ref, importRootPath)
},
"inc": func(i int) int { return i + 1 },
"distroURL": distroURL,
"isExporter": func() bool {
return md.Status.Class == "exporter"
},
"isProcessor": func() bool {
return md.Status.Class == "processor"
},
"isReceiver": func() bool {
return md.Status.Class == "receiver"
},
"isExtension": func() bool {
return md.Status.Class == "extension"
},
"isConnector": func() bool {
return md.Status.Class == "connector"
},
"isScraper": func() bool {
return md.Status.Class == "scraper"
},
"isCommand": func() bool {
return md.Status.Class == "cmd"
},
"supportsLogs": func() bool { return md.supportsSignal("logs") },
"supportsMetrics": func() bool { return md.supportsSignal("metrics") },
"supportsTraces": func() bool { return md.supportsSignal("traces") },
"supportsProfiles": func() bool { return md.supportsSignal("profiles") },
"supportsLogsToLogs": func() bool { return md.supportsSignal("logs_to_logs") },
"supportsLogsToMetrics": func() bool { return md.supportsSignal("logs_to_metrics") },
"supportsLogsToTraces": func() bool { return md.supportsSignal("logs_to_traces") },
"supportsLogsToProfiles": func() bool { return md.supportsSignal("logs_to_profiles") },
"supportsMetricsToLogs": func() bool { return md.supportsSignal("metrics_to_logs") },
"supportsMetricsToMetrics": func() bool { return md.supportsSignal("metrics_to_metrics") },
"supportsMetricsToTraces": func() bool { return md.supportsSignal("metrics_to_traces") },
"supportsMetricsToProfiles": func() bool { return md.supportsSignal("metrics_to_profiles") },
"supportsTracesToLogs": func() bool { return md.supportsSignal("traces_to_logs") },
"supportsTracesToMetrics": func() bool { return md.supportsSignal("traces_to_metrics") },
"supportsTracesToTraces": func() bool { return md.supportsSignal("traces_to_traces") },
"supportsTracesToProfiles": func() bool { return md.supportsSignal("traces_to_profiles") },
"supportsProfilesToLogs": func() bool { return md.supportsSignal("profiles_to_logs") },
"supportsProfilesToMetrics": func() bool { return md.supportsSignal("profiles_to_metrics") },
"supportsProfilesToTraces": func() bool { return md.supportsSignal("profiles_to_traces") },
"supportsProfilesToProfiles": func() bool { return md.supportsSignal("profiles_to_profiles") },
"expectConsumerError": func() bool {
return md.Tests.ExpectConsumerError
},
// ParseFS delegates the parsing of the files to `Glob`
// which uses the `\` as a special character.
// Meaning on windows based machines, the `\` needs to be replaced
// with a `/` for it to find the file.
}
}
func templatize(tmplFile string, funcMap template.FuncMap) *template.Template {
return template.Must(
template.
New(filepath.Base(tmplFile)).
Option("missingkey=error").
Funcs(funcMap).
ParseFS(TemplateFS, "templates/helper.tmpl", strings.ReplaceAll(tmplFile, "\\", "/")))
}
func executeTemplate(tmplFile string, md Metadata, goPackage, importRootPath string, fns template.FuncMap) ([]byte, error) {
tmpl := templatize(tmplFile, fns)
buf := bytes.Buffer{}
if err := tmpl.Execute(&buf, TemplateContext{Metadata: md, Package: goPackage, ImportRootPath: importRootPath}); err != nil {
return []byte{}, fmt.Errorf("failed executing template: %w", err)
}
return buf.Bytes(), nil
}
func generateFile(tmplFile, outputFile string, md Metadata, goPackage, importRootPath string) error {
return generateFileWithFns(tmplFile, outputFile, md, goPackage, importRootPath, getTemplateFuncMap(md, importRootPath))
}
func inlineReplace(tmplFile, outputFile string, md Metadata, start, end, goPackage, importRootPath string) error {
var readmeContents []byte
var err error
if readmeContents, err = os.ReadFile(filepath.Clean(outputFile)); err != nil {
return err
}
re := regexp.MustCompile(fmt.Sprintf("%s[\\s\\S]*%s", start, end))
if !re.Match(readmeContents) {
return nil
}
if md.GithubProject == "" {
md.GithubProject = "open-telemetry/opentelemetry-collector-contrib"
}
buf, err := executeTemplate(tmplFile, md, goPackage, importRootPath, getTemplateFuncMap(md, importRootPath))
if err != nil {
return err
}
s := re.ReplaceAllString(string(readmeContents), string(buf))
if err := os.WriteFile(outputFile, []byte(s), 0o600); err != nil {
return fmt.Errorf("failed writing %q: %w", outputFile, err)
}
return nil
}
func generateFileWithFns(tmplFile, outputFile string, md Metadata, goPackage, importRootPath string, fns template.FuncMap) error {
if err := os.Remove(outputFile); err != nil && !errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf("unable to remove generated file %q: %w", outputFile, err)
}
result, err := executeTemplate(tmplFile, md, goPackage, importRootPath, fns)
if err != nil {
return err
}
var formatErr error
if strings.HasSuffix(outputFile, ".go") {
if formatted, err := format.Source(result); err == nil {
result = formatted
} else {
formatErr = fmt.Errorf("failed formatting %s:%w", outputFile, err)
}
}
if err := os.WriteFile(outputFile, result, 0o600); err != nil {
return fmt.Errorf("failed writing %q: %w", outputFile, err)
}
return formatErr
}
func validateMappingKeysSorted(root *yaml.Node, path ...string) error {
// unwrap doc
n := root
if n.Kind == yaml.DocumentNode && len(n.Content) > 0 {
n = n.Content[0]
}
// follow path
for _, seg := range path {
if n.Kind != yaml.MappingNode {
return nil
}
var next *yaml.Node
for i := 0; i < len(n.Content); i += 2 {
if n.Content[i].Value == seg {
next = n.Content[i+1]
break
}
}
if next == nil {
return nil
}
n = next
}
if n.Kind != yaml.MappingNode {
return nil
}
// collect keys
keys := make([]string, 0, len(n.Content)/2)
for i := 0; i < len(n.Content); i += 2 {
keys = append(keys, n.Content[i].Value)
}
if !slices.IsSorted(keys) {
return fmt.Errorf("%v keys are not sorted: %v", path, keys)
}
return nil
}
// ValidateYAMLKeyOrder checks the sections we care about.
func validateYAMLKeyOrder(raw []byte) error {
var doc yaml.Node
if err := yaml.Unmarshal(raw, &doc); err != nil {
return err
}
for _, p := range [][]string{
{"resource_attributes"},
{"entities"},
{"attributes"},
{"metrics"},
{"events"},
{"telemetry", "metrics"},
} {
if err := validateMappingKeysSorted(&doc, p...); err != nil {
return err
}
}
return nil
}
func generateConfigFiles(md Metadata, mdDir, _ string) error {
if md.Config != nil {
resolver := cfggen.NewResolver(md.PackageName, md.Status.Class, md.Type, mdDir)
resolvedSchema, err := resolver.ResolveSchema(md.Config)
if err != nil {
return fmt.Errorf("failed to resolve config schema: %w", err)
}
err = cfggen.WriteJSONSchema(mdDir, resolvedSchema)
if err != nil {
return fmt.Errorf("failed to write config schema: %w", err)
}
if err := generateConfigGoStruct(md, mdDir); err != nil {
return fmt.Errorf("failed to generate config Go struct: %w", err)
}
}
return nil
}
func generateConfigGoStruct(md Metadata, outputDir string) error {
rootPkg, err := helpers.RootPackage(outputDir)
if err != nil {
return fmt.Errorf("unable to determine root package: %w", err)
}
packageName := filepath.Base(outputDir)
tmplFile := filepath.Join("templates", "config_from_cfggen.go.tmpl")
dstFile := filepath.Join(outputDir, "generated_config.go")
fns := cfggen.WithCfgFns(getTemplateFuncMap(md, rootPkg), rootPkg, md.PackageName)
return generateFileWithFns(tmplFile, dstFile, md, packageName, rootPkg, fns)
}
func joinCamelCase(parts []string, exported bool) string {
caser := cases.Title(language.English).String
var result strings.Builder
for i, part := range parts {
if i == 0 && !exported {
fmt.Fprintf(&result, "%s", strings.ToLower(part))
} else {
fmt.Fprintf(&result, "%s", caser(part))
}
}
return result.String()
}
================================================
FILE: cmd/mdatagen/internal/command_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal
import (
"bytes"
"fmt"
"go/parser"
"go/token"
"os"
"path/filepath"
"strings"
"testing"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/cmd/mdatagen/internal/cfggen"
"go.opentelemetry.io/collector/component"
)
func TestNewCommand(t *testing.T) {
cmd, err := NewCommand()
require.NoError(t, err)
assert.NotNil(t, cmd)
assert.IsType(t, &cobra.Command{}, cmd)
assert.Equal(t, "mdatagen", cmd.Use)
assert.True(t, cmd.SilenceUsage)
}
func TestCommandNoArgs(t *testing.T) {
cmd, err := NewCommand()
require.NoError(t, err)
cmd.SetArgs([]string{})
err = cmd.Execute()
require.Error(t, err)
}
func TestCommandErrorOutputOnce(t *testing.T) {
cmd, err := NewCommand()
require.NoError(t, err)
var stderr bytes.Buffer
cmd.SetErr(&stderr)
cmd.SetArgs([]string{"/nonexistent/path/metadata.yaml"})
err = cmd.Execute()
require.Error(t, err)
out := stderr.String()
require.NotEmpty(t, out)
msg := err.Error()
assert.Equal(t, 1, strings.Count(out, msg), out)
}
func TestRunContents(t *testing.T) {
tests := []struct {
yml string
wantMetricsGenerated bool
// TODO: we should add one more flag for logs builder
wantEventsGenerated bool
wantMetricsContext bool
wantLogsGenerated bool
wantConfigGenerated bool
wantTelemetryGenerated bool
wantResourceAttributesGenerated bool
wantReadmeGenerated bool
wantStatusGenerated bool
wantComponentTestGenerated bool
wantGoleakIgnore bool
wantGoleakSkip bool
wantGoleakSetup bool
wantGoleakTeardown bool
wantFeatureGatesGenerated bool
wantConfigSchemaGenerated bool
wantMetricsSchemaYamlGenerated bool
wantErr bool
wantOrderErr bool
wantRunErr bool
wantAttributes []string
}{
{
yml: "invalid.yaml",
wantErr: true,
},
{
yml: "unsorted_rattr.yaml",
wantOrderErr: true,
},
{
yml: "basic_connector.yaml",
wantErr: false,
wantStatusGenerated: true,
wantReadmeGenerated: true,
wantComponentTestGenerated: true,
},
{
yml: "basic_receiver.yaml",
wantErr: false,
wantStatusGenerated: true,
wantReadmeGenerated: true,
wantComponentTestGenerated: true,
wantLogsGenerated: true,
},
{
yml: "basic_pkg.yaml",
wantErr: false,
wantStatusGenerated: false,
wantReadmeGenerated: true,
},
{
yml: "metrics_and_type.yaml",
wantMetricsGenerated: true,
wantConfigGenerated: true,
wantStatusGenerated: true,
wantReadmeGenerated: true,
wantComponentTestGenerated: true,
wantMetricsSchemaYamlGenerated: true,
},
{
yml: "resource_attributes_only.yaml",
wantConfigGenerated: true,
wantStatusGenerated: true,
wantResourceAttributesGenerated: true,
wantReadmeGenerated: true,
wantComponentTestGenerated: true,
wantLogsGenerated: true,
wantMetricsSchemaYamlGenerated: true,
},
{
yml: "status_only.yaml",
wantStatusGenerated: true,
wantReadmeGenerated: true,
wantComponentTestGenerated: true,
},
{
yml: "with_tests_receiver.yaml",
wantStatusGenerated: true,
wantReadmeGenerated: true,
wantComponentTestGenerated: true,
wantLogsGenerated: true,
},
{
yml: "with_tests_exporter.yaml",
wantStatusGenerated: true,
wantReadmeGenerated: true,
wantComponentTestGenerated: true,
},
{
yml: "with_tests_processor.yaml",
wantStatusGenerated: true,
wantReadmeGenerated: true,
wantComponentTestGenerated: true,
},
{
yml: "with_tests_extension.yaml",
wantStatusGenerated: true,
wantReadmeGenerated: true,
wantComponentTestGenerated: true,
},
{
yml: "with_tests_connector.yaml",
wantStatusGenerated: true,
wantReadmeGenerated: true,
wantComponentTestGenerated: true,
},
{
yml: "with_tests_profiles_connector.yaml",
wantStatusGenerated: true,
wantReadmeGenerated: true,
wantComponentTestGenerated: true,
},
{
yml: "with_goleak_ignores.yaml",
wantStatusGenerated: true,
wantGoleakIgnore: true,
wantReadmeGenerated: true,
wantComponentTestGenerated: true,
},
{
yml: "with_goleak_skip.yaml",
wantStatusGenerated: true,
wantGoleakSkip: true,
wantReadmeGenerated: true,
wantComponentTestGenerated: true,
},
{
yml: "with_goleak_setup.yaml",
wantStatusGenerated: true,
wantGoleakSetup: true,
wantReadmeGenerated: true,
wantComponentTestGenerated: true,
},
{
yml: "with_goleak_teardown.yaml",
wantStatusGenerated: true,
wantGoleakTeardown: true,
wantReadmeGenerated: true,
wantComponentTestGenerated: true,
},
{
yml: "with_telemetry.yaml",
wantStatusGenerated: true,
wantTelemetryGenerated: true,
wantReadmeGenerated: true,
wantComponentTestGenerated: true,
wantAttributes: []string{"name"},
wantLogsGenerated: true,
},
{
yml: "invalid_telemetry_missing_value_type_for_histogram.yaml",
wantErr: true,
wantReadmeGenerated: true,
wantComponentTestGenerated: true,
},
{
yml: "async_metric.yaml",
wantMetricsGenerated: true,
wantConfigGenerated: true,
wantStatusGenerated: true,
wantReadmeGenerated: true,
wantComponentTestGenerated: true,
wantMetricsSchemaYamlGenerated: true,
},
{
yml: "custom_generated_package_name.yaml",
wantStatusGenerated: true,
wantReadmeGenerated: true,
wantComponentTestGenerated: true,
wantLogsGenerated: true,
},
{
yml: "feature_gates.yaml",
wantStatusGenerated: true,
wantReadmeGenerated: true,
wantComponentTestGenerated: true,
wantFeatureGatesGenerated: true,
},
{
yml: "with_conditional_attribute.yaml",
wantStatusGenerated: true,
wantReadmeGenerated: true,
wantMetricsGenerated: true,
wantLogsGenerated: true,
wantConfigGenerated: true,
wantComponentTestGenerated: true,
wantMetricsSchemaYamlGenerated: true,
},
{
yml: "events/basic_event.yaml",
wantStatusGenerated: true,
wantReadmeGenerated: true,
wantComponentTestGenerated: true,
wantConfigGenerated: true,
wantEventsGenerated: true,
wantLogsGenerated: true,
wantMetricsSchemaYamlGenerated: true,
},
{
yml: "with_config.yaml",
wantStatusGenerated: true,
wantReadmeGenerated: true,
wantLogsGenerated: true,
wantComponentTestGenerated: true,
wantConfigSchemaGenerated: true,
},
{
yml: "with_invalid_config_ref.yaml",
wantRunErr: true,
},
}
for _, tt := range tests {
t.Run(tt.yml, func(t *testing.T) {
tmpdir := filepath.Join(t.TempDir(), "shortname")
err := os.MkdirAll(tmpdir, 0o750)
require.NoError(t, err)
ymlContent, err := os.ReadFile(filepath.Join("testdata", tt.yml))
require.NoError(t, err)
metadataFile := filepath.Join(tmpdir, "metadata.yaml")
require.NoError(t, os.WriteFile(metadataFile, ymlContent, 0o600))
require.NoError(t, os.WriteFile(filepath.Join(tmpdir, "empty.go"), []byte("package shortname"), 0o600))
require.NoError(t, os.WriteFile(filepath.Join(tmpdir, "go.mod"), []byte("module shortname"), 0o600))
require.NoError(t, os.WriteFile(filepath.Join(tmpdir, "README.md"), []byte(`
foo
`), 0o600))
md, err := LoadMetadata(metadataFile)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
generatedPackageDir := filepath.Join("internal", md.GeneratedPackageName)
require.NoError(t, os.MkdirAll(filepath.Join(tmpdir, generatedPackageDir), 0o700))
require.NoError(t, os.WriteFile(filepath.Join(tmpdir, generatedPackageDir, "generated_status.go"), []byte("status"), 0o600))
require.NoError(t, os.WriteFile(filepath.Join(tmpdir, generatedPackageDir, "generated_telemetry_test.go"), []byte("test"), 0o600))
require.NoError(t, os.WriteFile(filepath.Join(tmpdir, generatedPackageDir, "generated_component_test.go"), []byte("test"), 0o600))
err = run(metadataFile)
if tt.wantOrderErr {
require.Error(t, err)
require.Contains(t, err.Error(), "metadata.yaml ordering check failed")
return
}
if tt.wantRunErr {
require.Error(t, err)
require.Contains(t, err.Error(), "failed to generate config files")
return
}
require.NoError(t, err)
// Documentation is generated when any of these features are present
wantDocumentationGenerated := tt.wantFeatureGatesGenerated || tt.wantMetricsGenerated || tt.wantTelemetryGenerated || tt.wantResourceAttributesGenerated || tt.wantEventsGenerated
var contents []byte
if tt.wantMetricsGenerated {
require.FileExists(t, filepath.Join(tmpdir, generatedPackageDir, "generated_metrics.go"))
require.FileExists(t, filepath.Join(tmpdir, generatedPackageDir, "generated_metrics_test.go"))
require.FileExists(t, filepath.Join(tmpdir, "documentation.md"))
if len(tt.wantAttributes) > 0 {
contents, err = os.ReadFile(filepath.Clean(filepath.Join(tmpdir, "documentation.md")))
require.NoError(t, err)
for _, attr := range tt.wantAttributes {
require.Contains(t, string(contents), attr)
}
}
contents, err = os.ReadFile(filepath.Clean(filepath.Join(tmpdir, generatedPackageDir, "generated_metrics.go")))
require.NoError(t, err)
if tt.wantMetricsContext {
require.Contains(t, string(contents), "\"context\"")
} else {
require.NotContains(t, string(contents), "\"context\"")
}
} else {
require.NoFileExists(t, filepath.Join(tmpdir, generatedPackageDir, "generated_metrics.go"))
require.NoFileExists(t, filepath.Join(tmpdir, generatedPackageDir, "generated_metrics_test.go"))
}
if tt.wantLogsGenerated {
require.FileExists(t, filepath.Join(tmpdir, generatedPackageDir, "generated_logs.go"))
require.FileExists(t, filepath.Join(tmpdir, generatedPackageDir, "generated_logs_test.go"))
} else {
require.NoFileExists(t, filepath.Join(tmpdir, generatedPackageDir, "generated_logs.go"))
require.NoFileExists(t, filepath.Join(tmpdir, generatedPackageDir, "generated_logs_test.go"))
}
if tt.wantConfigGenerated {
require.FileExists(t, filepath.Join(tmpdir, generatedPackageDir, "generated_config.go"))
require.FileExists(t, filepath.Join(tmpdir, generatedPackageDir, "generated_config_test.go"))
} else {
require.NoFileExists(t, filepath.Join(tmpdir, generatedPackageDir, "generated_config.go"))
require.NoFileExists(t, filepath.Join(tmpdir, generatedPackageDir, "generated_config_test.go"))
}
if tt.wantTelemetryGenerated {
require.FileExists(t, filepath.Join(tmpdir, generatedPackageDir, "generated_telemetry.go"))
require.FileExists(t, filepath.Join(tmpdir, generatedPackageDir, "generated_telemetry_test.go"))
require.FileExists(t, filepath.Join(tmpdir, "documentation.md"))
contents, err = os.ReadFile(filepath.Clean(filepath.Join(tmpdir, generatedPackageDir, "generated_telemetry.go")))
require.NoError(t, err)
if tt.wantMetricsContext {
require.Contains(t, string(contents), "\"context\"")
} else {
require.NotContains(t, string(contents), "\"context\"")
}
} else {
require.NoFileExists(t, filepath.Join(tmpdir, generatedPackageDir, "generated_telemetry.go"))
}
if wantDocumentationGenerated {
require.FileExists(t, filepath.Join(tmpdir, "documentation.md"))
} else {
require.NoFileExists(t, filepath.Join(tmpdir, "documentation.md"))
}
if tt.wantStatusGenerated {
require.FileExists(t, filepath.Join(tmpdir, generatedPackageDir, "generated_status.go"))
} else {
require.NoFileExists(t, filepath.Join(tmpdir, generatedPackageDir, "generated_status.go"))
}
contents, err = os.ReadFile(filepath.Clean(filepath.Join(tmpdir, "README.md")))
require.NoError(t, err)
if tt.wantReadmeGenerated {
require.NotContains(t, string(contents), "foo")
} else {
require.Contains(t, string(contents), "foo")
}
if tt.wantComponentTestGenerated {
require.FileExists(t, filepath.Join(tmpdir, "generated_component_test.go"))
contents, err = os.ReadFile(filepath.Clean(filepath.Join(tmpdir, "generated_component_test.go")))
require.NoError(t, err)
require.Contains(t, string(contents), "func Test")
_, err = parser.ParseFile(token.NewFileSet(), "", contents, parser.DeclarationErrors)
require.NoError(t, err)
} else {
require.NoFileExists(t, filepath.Join(tmpdir, "generated_component_test.go"))
}
require.FileExists(t, filepath.Join(tmpdir, "generated_package_test.go"))
contents, err = os.ReadFile(filepath.Clean(filepath.Join(tmpdir, "generated_package_test.go")))
require.NoError(t, err)
require.Contains(t, string(contents), "func TestMain")
_, err = parser.ParseFile(token.NewFileSet(), "", contents, parser.DeclarationErrors)
require.NoError(t, err)
if tt.wantGoleakSkip {
require.Contains(t, string(contents), "skipping goleak test")
} else {
require.NotContains(t, string(contents), "skipping goleak test")
}
if tt.wantGoleakIgnore {
require.Contains(t, string(contents), "IgnoreTopFunction")
require.Contains(t, string(contents), "IgnoreAnyFunction")
} else {
require.NotContains(t, string(contents), "IgnoreTopFunction")
require.NotContains(t, string(contents), "IgnoreAnyFunction")
}
if tt.wantGoleakSetup {
require.Contains(t, string(contents), "setupFunc")
} else {
require.NotContains(t, string(contents), "setupFunc")
}
if tt.wantGoleakTeardown {
require.Contains(t, string(contents), "teardownFunc")
} else {
require.NotContains(t, string(contents), "teardownFunc")
}
if tt.wantConfigSchemaGenerated {
require.FileExists(t, filepath.Join(tmpdir, "config.schema.json"))
} else {
require.NoFileExists(t, filepath.Join(tmpdir, "config.schema.json"))
}
schemaYamlPath := filepath.Join(tmpdir, generatedPackageDir, "config.schema.yaml")
if tt.wantMetricsSchemaYamlGenerated {
require.FileExists(t, schemaYamlPath)
contents, err = os.ReadFile(filepath.Clean(schemaYamlPath))
require.NoError(t, err)
require.Contains(t, string(contents), "# Code generated by mdatagen. DO NOT EDIT.")
require.Contains(t, string(contents), "$defs:")
if tt.wantMetricsGenerated {
require.Contains(t, string(contents), "metrics_config:")
require.Contains(t, string(contents), "metrics_builder_config:")
}
if tt.wantEventsGenerated {
require.Contains(t, string(contents), "events_config:")
require.Contains(t, string(contents), "logs_builder_config:")
}
if tt.wantResourceAttributesGenerated {
require.Contains(t, string(contents), "resource_attributes_config:")
}
} else {
require.NoFileExists(t, schemaYamlPath)
}
})
}
}
func TestGenerateConfigFiles(t *testing.T) {
tests := []struct {
name string
md Metadata
wantErr bool
wantGen bool
}{
{
name: "nil config skips generation",
md: Metadata{
Type: "test",
Status: &Status{
Class: "receiver",
},
Config: nil,
},
wantGen: false,
},
{
name: "valid config generates schema file",
md: Metadata{
Type: "test",
PackageName: "shortname",
Status: &Status{
Class: "receiver",
},
Config: &cfggen.ConfigMetadata{
Type: "object",
},
},
wantGen: true,
},
{
name: "invalid ref in config causes resolve error",
md: Metadata{
Type: "test",
PackageName: "shortname",
Status: &Status{
Class: "receiver",
},
// A local ref without a definition name fails Validate() inside ResolveSchema
Config: &cfggen.ConfigMetadata{
Ref: "/config/configauth",
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
root := t.TempDir()
tmpdir := filepath.Join(root, "shortname")
require.NoError(t, os.MkdirAll(tmpdir, 0o700))
require.NoError(t, os.WriteFile(filepath.Join(root, "go.mod"), []byte("module testmodule\n"), 0o600))
err := generateConfigFiles(tt.md, tmpdir, "testmodule")
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
if tt.wantGen {
require.FileExists(t, filepath.Join(tmpdir, "config.schema.json"))
} else {
require.NoFileExists(t, filepath.Join(tmpdir, "config.schema.json"))
}
})
}
}
func TestGenerateConfigGoStruct_RootPackageError(t *testing.T) {
// tmpdir has no go.mod in any ancestor, so helpers.RootPackage fails
md := Metadata{
Type: "test",
PackageName: "shortname",
Status: &Status{Class: "receiver"},
Config: &cfggen.ConfigMetadata{Type: "object"},
}
err := generateConfigGoStruct(md, t.TempDir())
require.Error(t, err)
require.Contains(t, err.Error(), "unable to determine root package")
}
func TestGenerateConfigFiles_GoStructError(t *testing.T) {
// generateConfigGoStruct fails because tmpdir has no go.mod in any ancestor
md := Metadata{
Type: "test",
PackageName: "shortname",
Status: &Status{Class: "receiver"},
Config: &cfggen.ConfigMetadata{Type: "object"},
}
err := generateConfigFiles(md, t.TempDir(), "testmodule")
require.Error(t, err)
require.Contains(t, err.Error(), "failed to generate config Go struct")
}
func TestGenerateConfigFiles_WriteError(t *testing.T) {
md := Metadata{
Type: "test",
PackageName: "shortname",
Status: &Status{
Class: "receiver",
},
Config: &cfggen.ConfigMetadata{
Type: "object",
},
}
err := generateConfigFiles(md, "/nonexistent/path/that/does/not/exist", "testmodule")
require.Error(t, err)
require.Contains(t, err.Error(), "failed to write config schema")
}
func TestRun(t *testing.T) {
type args struct {
ymlPath string
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "no argument",
args: args{""},
wantErr: true,
},
{
name: "no such file",
args: args{"/no/such/file"},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := run(tt.args.ymlPath)
if !tt.wantErr {
require.NoError(t, err, "run()")
} else {
require.Error(t, err)
}
})
}
}
func TestInlineReplace(t *testing.T) {
tests := []struct {
name string
markdown string
outputFile string
componentClass string
warnings []string
stability map[component.StabilityLevel][]string
deprecation map[string]DeprecationInfo
distros []string
codeowners *Codeowners
githubProject string
}{
{
name: "readme with empty status",
markdown: `# Some component
Some info about a component
`,
outputFile: "readme_with_status.md",
componentClass: "receiver",
distros: []string{"contrib"},
githubProject: "open-telemetry/opentelemetry-collector",
},
{
name: "readme with status for extension",
markdown: `# Some component
Some info about a component
`,
outputFile: "readme_with_status_extension.md",
componentClass: "extension",
distros: []string{"contrib"},
},
{
name: "readme with status for converter",
markdown: `# Some component
Some info about a component
`,
outputFile: "readme_with_status_converter.md",
componentClass: "converter",
distros: []string{"contrib"},
},
{
name: "readme with status for provider",
markdown: `# Some component
Some info about a component
`,
outputFile: "readme_with_status_provider.md",
componentClass: "provider",
distros: []string{"contrib"},
},
{
name: "readme with status with codeowners and seeking new",
markdown: `# Some component
Some info about a component
`,
outputFile: "readme_with_status_codeowners_and_seeking_new.md",
componentClass: "receiver",
distros: []string{"contrib"},
codeowners: &Codeowners{
Active: []string{"foo"},
SeekingNew: true,
},
},
{
name: "readme with status with codeowners and emeritus",
markdown: `# Some component
Some info about a component
`,
outputFile: "readme_with_status_codeowners_and_emeritus.md",
componentClass: "receiver",
distros: []string{"contrib"},
codeowners: &Codeowners{
Active: []string{"foo"},
Emeritus: []string{"bar"},
},
},
{
name: "readme with status with codeowners",
markdown: `# Some component
Some info about a component
`,
outputFile: "readme_with_status_codeowners.md",
componentClass: "receiver",
distros: []string{"contrib"},
codeowners: &Codeowners{
Active: []string{"open-telemetry/collector-approvers"},
},
},
{
name: "readme with status table",
markdown: `# Some component
| Status | |
| ------------------------ |-----------|
Some info about a component
`,
outputFile: "readme_with_status.md",
componentClass: "receiver",
distros: []string{"contrib"},
githubProject: "open-telemetry/opentelemetry-collector",
},
{
name: "readme with no status",
markdown: `# Some component
Some info about a component
`,
outputFile: "readme_without_status.md",
distros: []string{"contrib"},
},
{
name: "component with warnings",
markdown: `# Some component
Some info about a component
### warnings
Some warning there.
`,
outputFile: "readme_with_warnings.md",
warnings: []string{"warning1"},
distros: []string{"contrib"},
},
{
name: "readme with multiple signals",
markdown: `# Some component
Some info about a component
`,
outputFile: "readme_with_multiple_signals.md",
stability: map[component.StabilityLevel][]string{
component.StabilityLevelBeta: {"metrics"},
component.StabilityLevelAlpha: {"logs"},
},
distros: []string{"contrib"},
},
{
name: "readme with multiple signals and deprecation",
markdown: `# Some component
Some info about a component
`,
outputFile: "readme_with_multiple_signals_and_deprecation.md",
stability: map[component.StabilityLevel][]string{
component.StabilityLevelBeta: {"metrics"},
component.StabilityLevelAlpha: {"logs"},
component.StabilityLevelDeprecated: {"traces"},
},
deprecation: DeprecationMap{
"traces": DeprecationInfo{
Date: "2025-02-05",
Migration: "no migration needed",
},
},
distros: []string{"contrib"},
},
{
name: "readme with cmd class",
markdown: `# Some component
Some info about a component
`,
outputFile: "readme_with_cmd_class.md",
stability: map[component.StabilityLevel][]string{
component.StabilityLevelBeta: {"metrics"},
component.StabilityLevelAlpha: {"logs"},
},
componentClass: "cmd",
distros: []string{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
stability := map[component.StabilityLevel][]string{component.StabilityLevelBeta: {"metrics"}}
if len(tt.stability) > 0 {
stability = tt.stability
}
md := Metadata{
GithubProject: tt.githubProject,
Type: "foo",
ShortFolderName: "foo",
Status: &Status{
DisableCodeCov: true,
Stability: stability,
Distributions: tt.distros,
Class: tt.componentClass,
Warnings: tt.warnings,
Codeowners: tt.codeowners,
Deprecation: tt.deprecation,
},
}
tmpdir := t.TempDir()
readmeFile := filepath.Join(tmpdir, "README.md")
require.NoError(t, os.WriteFile(readmeFile, []byte(tt.markdown), 0o600))
err := inlineReplace("templates/readme.md.tmpl", readmeFile, md, statusStart, statusEnd, "metadata", "go.opentelemetry.io/collector")
require.NoError(t, err)
require.FileExists(t, filepath.Join(tmpdir, "README.md"))
got, err := os.ReadFile(filepath.Clean(filepath.Join(tmpdir, "README.md")))
require.NoError(t, err)
got = bytes.ReplaceAll(got, []byte("\r\n"), []byte("\n"))
expected, err := os.ReadFile(filepath.Join("testdata", tt.outputFile))
require.NoError(t, err)
expected = bytes.ReplaceAll(expected, []byte("\r\n"), []byte("\n"))
fmt.Println(string(got))
fmt.Println(string(expected))
require.Equal(t, string(expected), string(got))
})
}
}
func TestGenerateStatusMetadata(t *testing.T) {
tests := []struct {
name string
output string
md Metadata
expected string
}{
{
name: "foo component with beta status",
md: Metadata{
Type: "foo",
Status: &Status{
Stability: map[component.StabilityLevel][]string{
component.StabilityLevelBeta: {"metrics"},
},
Distributions: []string{"contrib"},
Class: "receiver",
},
},
expected: `// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/component"
)
var (
Type = component.MustNewType("foo")
ScopeName = ""
)
const (
MetricsStability = component.StabilityLevelBeta
)
`,
},
{
name: "foo component with alpha status",
md: Metadata{
Type: "foo",
Status: &Status{
Stability: map[component.StabilityLevel][]string{
component.StabilityLevelAlpha: {"metrics"},
},
Distributions: []string{"contrib"},
Class: "receiver",
},
},
expected: `// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/component"
)
var (
Type = component.MustNewType("foo")
ScopeName = ""
)
const (
MetricsStability = component.StabilityLevelAlpha
)
`,
},
{
name: "foo component with deprecated type",
md: Metadata{
Type: "foo",
DeprecatedType: "old_foo",
Status: &Status{
Stability: map[component.StabilityLevel][]string{
component.StabilityLevelBeta: {"metrics"},
},
Distributions: []string{"contrib"},
Class: "receiver",
},
},
expected: `// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/component"
)
var (
Type = component.MustNewType("foo")
DeprecatedType = component.MustNewType("old_foo")
ScopeName = ""
)
const (
MetricsStability = component.StabilityLevelBeta
)
`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpdir := t.TempDir()
err := generateFile("templates/status.go.tmpl",
filepath.Join(tmpdir, "generated_status.go"), tt.md, "metadata", "go.opentelemetry.io/collector")
require.NoError(t, err)
actual, err := os.ReadFile(filepath.Clean(filepath.Join(tmpdir, "generated_status.go")))
require.NoError(t, err)
require.Equal(t, tt.expected, string(actual))
})
}
}
func TestGenerateTelemetryMetadata(t *testing.T) {
tests := []struct {
name string
output string
md Metadata
expected string
}{
{
name: "foo component with beta status",
md: Metadata{
Type: "foo",
Status: &Status{
Stability: map[component.StabilityLevel][]string{
component.StabilityLevelBeta: {"metrics"},
},
Distributions: []string{"contrib"},
Class: "receiver",
},
},
expected: `// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/collector/component"
)
func Meter(settings component.TelemetrySettings) metric.Meter {
return settings.MeterProvider.Meter("")
}
func Tracer(settings component.TelemetrySettings) trace.Tracer {
return settings.TracerProvider.Tracer("")
}
`,
},
{
name: "foo component with alpha status",
md: Metadata{
Type: "foo",
Status: &Status{
Stability: map[component.StabilityLevel][]string{
component.StabilityLevelAlpha: {"metrics"},
},
Distributions: []string{"contrib"},
Class: "receiver",
},
},
expected: `// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/collector/component"
)
func Meter(settings component.TelemetrySettings) metric.Meter {
return settings.MeterProvider.Meter("")
}
func Tracer(settings component.TelemetrySettings) trace.Tracer {
return settings.TracerProvider.Tracer("")
}
`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpdir := t.TempDir()
err := generateFile("templates/telemetry.go.tmpl",
filepath.Join(tmpdir, "generated_telemetry.go"), tt.md, "metadata", "go.opentelemetry.io/collector")
require.NoError(t, err)
actual, err := os.ReadFile(filepath.Clean(filepath.Join(tmpdir, "generated_telemetry.go")))
require.NoError(t, err)
require.Equal(t, tt.expected, string(actual))
})
}
}
func TestGenerateConfigSchema_LocalizesSameRootRefs(t *testing.T) {
enabled := true
md := Metadata{
Type: "foo",
ResourceAttributes: map[AttributeName]Attribute{
"resource.attr": {
Description: "resource attr",
EnabledPtr: &enabled,
FullName: "resource.attr",
},
},
Events: map[EventName]Event{
"default.event": {
Signal: Signal{
Enabled: true,
Description: "event description",
},
},
},
}
tmpdir := t.TempDir()
outputFile := filepath.Join(tmpdir, "config.schema.yaml")
err := generateFile("templates/config.schema.yaml.tmpl", outputFile, md, "metadata", "go.opentelemetry.io/collector")
require.NoError(t, err)
actual, err := os.ReadFile(filepath.Clean(outputFile))
require.NoError(t, err)
require.Contains(t, string(actual), "$ref: /filter.config")
require.NotContains(t, string(actual), "$ref: go.opentelemetry.io/collector/filter.config")
}
================================================
FILE: cmd/mdatagen/internal/embedded_templates.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/cmd/mdatagen/internal"
import "embed"
// TemplateFS ensures that the files needed
// to generate metadata as an embedded filesystem since
// `go get` doesn't require these files to be downloaded.
//
//go:embed templates/*.tmpl templates/testdata/*.tmpl
var TemplateFS embed.FS
================================================
FILE: cmd/mdatagen/internal/embedded_templates_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal
import (
"io/fs"
"path"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestEnsureTemplatesLoaded(t *testing.T) {
t.Parallel()
const (
rootDir = "templates"
)
var (
templateFiles = map[string]struct{}{
path.Join(rootDir, "component_test.go.tmpl"): {},
path.Join(rootDir, "documentation.md.tmpl"): {},
path.Join(rootDir, "metrics.go.tmpl"): {},
path.Join(rootDir, "metrics_test.go.tmpl"): {},
path.Join(rootDir, "logs.go.tmpl"): {},
path.Join(rootDir, "logs_test.go.tmpl"): {},
path.Join(rootDir, "resource.go.tmpl"): {},
path.Join(rootDir, "resource_test.go.tmpl"): {},
path.Join(rootDir, "config.go.tmpl"): {},
path.Join(rootDir, "config_test.go.tmpl"): {},
path.Join(rootDir, "config.schema.yaml.tmpl"): {},
path.Join(rootDir, "package_test.go.tmpl"): {},
path.Join(rootDir, "readme.md.tmpl"): {},
path.Join(rootDir, "status.go.tmpl"): {},
path.Join(rootDir, "telemetry.go.tmpl"): {},
path.Join(rootDir, "telemetry_test.go.tmpl"): {},
path.Join(rootDir, "testdata", "config.yaml.tmpl"): {},
path.Join(rootDir, "telemetrytest.go.tmpl"): {},
path.Join(rootDir, "telemetrytest_test.go.tmpl"): {},
path.Join(rootDir, "helper.tmpl"): {},
path.Join(rootDir, "feature_gates.md.tmpl"): {},
path.Join(rootDir, "feature_gates.go.tmpl"): {},
path.Join(rootDir, "config_from_cfggen.go.tmpl"): {},
path.Join(rootDir, "entity_metrics.go.tmpl"): {},
path.Join(rootDir, "entity_metrics_test.go.tmpl"): {},
}
count = 0
)
require.NoError(t, fs.WalkDir(TemplateFS, ".", func(path string, d fs.DirEntry, _ error) error {
if d != nil && d.IsDir() {
return nil
}
count++
assert.Contains(t, templateFiles, path)
return nil
}))
assert.Equal(t, len(templateFiles), count, "Must match the expected number of calls")
}
================================================
FILE: cmd/mdatagen/internal/event.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/cmd/mdatagen/internal"
import (
"errors"
"go.opentelemetry.io/collector/cmd/mdatagen/internal/helpers"
"go.opentelemetry.io/collector/confmap"
)
type (
EventName string
)
func (ln EventName) Render() (string, error) {
return helpers.FormatIdentifier(string(ln), true)
}
func (ln EventName) RenderUnexported() (string, error) {
return helpers.FormatIdentifier(string(ln), false)
}
type Event struct {
Signal `mapstructure:",squash"`
}
func (l *Event) validate() error {
var errs error
if l.Description == "" {
errs = errors.Join(errs, errors.New(`missing event description`))
}
return errs
}
func (l *Event) Unmarshal(parser *confmap.Conf) error {
if !parser.IsSet("enabled") {
return errors.New("missing required field: `enabled`")
}
return parser.Unmarshal(l)
}
================================================
FILE: cmd/mdatagen/internal/event_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestEventNameRender(t *testing.T) {
for _, tt := range []struct {
name EventName
success bool
expectedExported string
expectedUnExported string
}{
{"", false, "", ""},
{"otel.val", true, "OtelVal", "otelVal"},
{"otel_val_2", true, "OtelVal2", "otelVal2"},
} {
exported, err := tt.name.Render()
if tt.success {
require.NoError(t, err)
assert.Equal(t, tt.expectedExported, exported)
} else {
require.Error(t, err)
}
unexported, err := tt.name.RenderUnexported()
if tt.success {
require.NoError(t, err)
assert.Equal(t, tt.expectedUnExported, unexported)
} else {
require.Error(t, err)
}
}
}
================================================
FILE: cmd/mdatagen/internal/helpers/lint.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package helpers // import "go.opentelemetry.io/collector/cmd/mdatagen/internal/helpers"
import (
"errors"
"strings"
"unicode"
"go.opentelemetry.io/collector/cmd/mdatagen/third_party/golint"
)
// FormatIdentifier variable in a go-safe way
func FormatIdentifier(s string, exported bool) (string, error) {
if s == "" {
return "", errors.New("string cannot be empty")
}
// Convert various characters to . for strings.Title to operate on.
replace := strings.NewReplacer("_", ".", "-", ".", "<", ".", ">", ".", "/", ".", ":", ".")
str := replace.Replace(s)
str = strings.Title(str) //nolint:staticcheck // SA1019
str = strings.ReplaceAll(str, ".", "")
var word string
var output string
// Fixup acronyms to make lint happy.
for idx, r := range str {
if idx == 0 {
if exported {
r = unicode.ToUpper(r)
} else {
r = unicode.ToLower(r)
}
}
if unicode.IsUpper(r) || unicode.IsNumber(r) {
// If the current word is an acronym and it's either exported or it's not the
// beginning of an unexported variable then upper case it.
if golint.Acronyms[strings.ToUpper(word)] && (exported || output != "") {
output += strings.ToUpper(word)
word = string(r)
} else {
output += word
word = string(r)
}
} else {
word += string(r)
}
}
if golint.Acronyms[strings.ToUpper(word)] && output != "" {
output += strings.ToUpper(word)
} else {
output += word
}
// Remove white spaces
output = strings.Join(strings.Fields(output), "")
return output, nil
}
================================================
FILE: cmd/mdatagen/internal/helpers/lint_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package helpers
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestFormatIdentifier(t *testing.T) {
tests := []struct {
input string
want string
exported bool
wantErr string
}{
// Unexported.
{input: "max.cpu", want: "maxCPU"},
{input: "max.foo", want: "maxFoo"},
{input: "cpu.utilization", want: "cpuUtilization"},
{input: "cpu", want: "cpu"},
{input: "max.ip.addr", want: "maxIPAddr"},
{input: "some_metric", want: "someMetric"},
{input: "some-metric", want: "someMetric"},
{input: "Upper.Case", want: "upperCase"},
{input: "max.ip6", want: "maxIP6"},
{input: "max.ip6.idle", want: "maxIP6Idle"},
{input: "node_netstat_IpExt_OutOctets", want: "nodeNetstatIPExtOutOctets"},
// Exported.
{input: "cpu.state", want: "CPUState", exported: true},
// Errors
{input: "", want: "", wantErr: "string cannot be empty"},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
got, err := FormatIdentifier(tt.input, tt.exported)
if tt.wantErr != "" {
require.EqualError(t, err, tt.wantErr)
} else {
require.NoError(t, err)
require.Equal(t, tt.want, got)
}
})
}
}
================================================
FILE: cmd/mdatagen/internal/helpers/packages.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package helpers // import "go.opentelemetry.io/collector/cmd/mdatagen/internal/helpers"
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
)
// RootPackage determines the root Go module path by finding the highest go.mod above componentDir
// and running "go list -m" in that directory. This is used to resolve local absolute references
// (e.g., "/config/confighttp.client_config") into full Go import paths.
func RootPackage(componentDir string) (string, error) {
rootModDir, err := rootModuleDir(componentDir)
if err != nil {
return "", err
}
cmd := exec.Command("go", "list", "-m")
cmd.Dir = rootModDir
output, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("failed to resolve root module path: %w", err)
}
return strings.TrimSpace(string(output)), nil
}
func rootModuleDir(componentDir string) (string, error) {
absDir, err := filepath.Abs(componentDir)
if err != nil {
return "", fmt.Errorf("failed to get absolute path: %w", err)
}
var found string
dir := absDir
for {
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
found = dir
}
parent := filepath.Dir(dir)
if parent == dir {
break
}
dir = parent
}
if found == "" {
return "", fmt.Errorf("no go.mod found in any parent of %s", componentDir)
}
return found, nil
}
================================================
FILE: cmd/mdatagen/internal/helpers/packages_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package helpers
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRootModuleDir(t *testing.T) {
t.Run("finds_go_mod_in_parent", func(t *testing.T) {
tmp := t.TempDir()
subDir := filepath.Join(tmp, "sub")
require.NoError(t, os.MkdirAll(subDir, 0o700))
require.NoError(t, os.WriteFile(filepath.Join(tmp, "go.mod"), []byte("module example.com/root\n"), 0o600))
dir, err := rootModuleDir(subDir)
require.NoError(t, err)
assert.Equal(t, tmp, dir)
})
t.Run("finds_go_mod_in_intermediate_directory", func(t *testing.T) {
tmp := t.TempDir()
projectDir := filepath.Join(tmp, "project")
componentDir := filepath.Join(projectDir, "receiver", "foo")
require.NoError(t, os.MkdirAll(componentDir, 0o700))
// No go.mod at tmp root; go.mod is in project/ subdirectory
require.NoError(t, os.WriteFile(filepath.Join(projectDir, "go.mod"), []byte("module example.com/project\n"), 0o600))
dir, err := rootModuleDir(componentDir)
require.NoError(t, err)
assert.Equal(t, projectDir, dir)
})
t.Run("prefers_highest_go_mod", func(t *testing.T) {
tmp := t.TempDir()
componentDir := filepath.Join(tmp, "project", "receiver", "foo")
require.NoError(t, os.MkdirAll(componentDir, 0o700))
// go.mod at both levels
require.NoError(t, os.WriteFile(filepath.Join(tmp, "go.mod"), []byte("module example.com/root\n"), 0o600))
require.NoError(t, os.WriteFile(filepath.Join(tmp, "project", "go.mod"), []byte("module example.com/project\n"), 0o600))
dir, err := rootModuleDir(componentDir)
require.NoError(t, err)
assert.Equal(t, tmp, dir)
})
t.Run("error_when_no_go_mod_found", func(t *testing.T) {
tmp := t.TempDir()
subDir := filepath.Join(tmp, "sub")
require.NoError(t, os.MkdirAll(subDir, 0o700))
_, err := rootModuleDir(subDir)
require.Error(t, err)
assert.Contains(t, err.Error(), "no go.mod found")
})
}
func TestRootPackage(t *testing.T) {
t.Run("returns_correct_module_path", func(t *testing.T) {
wd, err := os.Getwd()
require.NoError(t, err)
pkg, err := RootPackage(wd)
require.NoError(t, err)
assert.Equal(t, "go.opentelemetry.io/collector", pkg)
})
t.Run("error_for_nonexistent_directory", func(t *testing.T) {
_, err := RootPackage("/nonexistent/path/that/does/not/exist")
require.Error(t, err)
})
t.Run("resolves_module_from_synthetic_go_mod", func(t *testing.T) {
tmp := t.TempDir()
subDir := filepath.Join(tmp, "pkg", "foo")
require.NoError(t, os.MkdirAll(subDir, 0o700))
require.NoError(t, os.WriteFile(
filepath.Join(tmp, "go.mod"),
[]byte("module example.com/my-project\n\ngo 1.21\n"),
0o600,
))
pkg, err := RootPackage(subDir)
require.NoError(t, err)
assert.Equal(t, "example.com/my-project", pkg)
})
t.Run("monorepo_go_mod_not_at_top_level", func(t *testing.T) {
tmp := t.TempDir()
projectDir := filepath.Join(tmp, "collector")
componentDir := filepath.Join(projectDir, "receiver", "foo")
require.NoError(t, os.MkdirAll(componentDir, 0o700))
// No go.mod at top level; only in collector/ subdirectory
require.NoError(t, os.WriteFile(
filepath.Join(projectDir, "go.mod"),
[]byte("module go.opentelemetry.io/collector\n\ngo 1.21\n"),
0o600,
))
pkg, err := RootPackage(componentDir)
require.NoError(t, err)
assert.Equal(t, "go.opentelemetry.io/collector", pkg)
})
}
================================================
FILE: cmd/mdatagen/internal/loader.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/cmd/mdatagen/internal"
import (
"context"
"errors"
"fmt"
"os/exec"
"path/filepath"
"strings"
"go.opentelemetry.io/collector/confmap/confmaptest"
"go.opentelemetry.io/collector/confmap/provider/fileprovider"
)
func setAttributeDefaultFields(attrs map[AttributeName]Attribute) {
for k, v := range attrs {
v.FullName = k
if v.RequirementLevel == "" {
v.RequirementLevel = AttributeRequirementLevelRecommended
}
attrs[k] = v
}
}
type TemplateContext struct {
Metadata
// Package name for generated code.
Package string
// ImportRootPath is the repo-local import prefix used to localize same-tree schema references.
ImportRootPath string
}
func LoadMetadata(filePath string) (Metadata, error) {
cp, err := fileprovider.NewFactory().Create(confmaptest.NewNopProviderSettings()).Retrieve(context.Background(), "file:"+filePath, nil)
if err != nil {
return Metadata{}, err
}
conf, err := cp.AsConf()
if err != nil {
return Metadata{}, err
}
md := Metadata{ShortFolderName: shortFolderName(filePath), Tests: Tests{Host: "newMdatagenNopHost()"}}
err = conf.Unmarshal(&md)
if err != nil {
return md, err
}
packageName, err := packageName(filepath.Dir(filePath))
if err != nil {
return md, fmt.Errorf("unable to determine package name: %w", err)
}
md.PackageName = packageName
if md.ScopeName == "" {
md.ScopeName = packageName
}
if md.GeneratedPackageName == "" {
md.GeneratedPackageName = "metadata"
}
if err := md.Validate(); err != nil {
return md, err
}
setAttributeDefaultFields(md.Attributes)
setAttributeDefaultFields(md.ResourceAttributes)
return md, nil
}
var componentTypes = []string{
"connector",
"exporter",
"extension",
"processor",
"scraper",
"receiver",
}
func shortFolderName(filePath string) string {
parentFolder := filepath.Base(filepath.Dir(filePath))
for _, cType := range componentTypes {
if before, ok := strings.CutSuffix(parentFolder, cType); ok {
return before
}
}
return parentFolder
}
func packageName(filePath string) (string, error) {
cmd := exec.Command("go", "list", "-f", "{{.ImportPath}}")
cmd.Dir = filePath
output, err := cmd.Output()
if err != nil {
var ee *exec.ExitError
if errors.As(err, &ee) {
return "", fmt.Errorf("unable to determine package name: %v failed: (stderr) %v", cmd.Args, string(ee.Stderr))
}
return "", fmt.Errorf("unable to determine package name: %v failed: %v %w", cmd.Args, string(output), err)
}
return strings.TrimSpace(string(output)), nil
}
================================================
FILE: cmd/mdatagen/internal/loader_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/cmd/mdatagen/internal/cfggen"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/pmetric"
)
func boolPtr(b bool) *bool {
return &b
}
func TestTwoPackagesInDirectory(t *testing.T) {
contents, err := os.ReadFile("testdata/twopackages.yaml")
require.NoError(t, err)
tempDir := t.TempDir()
metadataPath := filepath.Join(tempDir, "metadata.yaml")
// we create a trivial module and packages to avoid having invalid go checked into our test directory.
require.NoError(t, os.WriteFile(filepath.Join(tempDir, "go.mod"), []byte("module twopackages"), 0o600))
require.NoError(t, os.WriteFile(filepath.Join(tempDir, "package1.go"), []byte("package package1"), 0o600))
require.NoError(t, os.WriteFile(filepath.Join(tempDir, "package2.go"), []byte("package package2"), 0o600))
require.NoError(t, os.WriteFile(metadataPath, contents, 0o600))
_, err = LoadMetadata(metadataPath)
require.Error(t, err)
require.ErrorContains(t, err, "unable to determine package name: [go list -f {{.ImportPath}}] failed: (stderr) found packages package1 (package1.go) and package2 (package2.go)")
}
func TestLoadMetadata(t *testing.T) {
tests := []struct {
name string
want Metadata
wantErr string
}{
{
name: "samplereceiver/metadata.yaml",
want: Metadata{
GithubProject: "open-telemetry/opentelemetry-collector",
GeneratedPackageName: "metadata",
Type: "sample",
DisplayName: "Sample Receiver",
Description: "This receiver is used for testing purposes to check the output of mdatagen.",
SemConvVersion: "1.38.0",
PackageName: "go.opentelemetry.io/collector/cmd/mdatagen/internal/samplereceiver",
ReaggregationEnabled: true,
Status: &Status{
DisableCodeCov: true,
Class: "receiver",
Stability: map[component.StabilityLevel][]string{
component.StabilityLevelDevelopment: {"logs"},
component.StabilityLevelBeta: {"traces"},
component.StabilityLevelStable: {"metrics"},
component.StabilityLevelDeprecated: {"profiles"},
},
Distributions: []string{},
Deprecation: DeprecationMap{
"profiles": DeprecationInfo{
Date: "2025-02-05",
Migration: "no migration needed",
},
},
Codeowners: &Codeowners{
Active: []string{"dmitryax"},
},
Warnings: []string{"Any additional information that should be brought to the consumer's attention"},
UnsupportedPlatforms: []string{"freebsd", "illumos"},
},
Config: &cfggen.ConfigMetadata{
Type: "object",
AllOf: []*cfggen.ConfigMetadata{
{
Ref: "./internal/metadata.metrics_builder_config",
},
},
Properties: map[string]*cfggen.ConfigMetadata{
"endpoint": {
Description: "The endpoint to scrape metrics from.",
Type: "string",
Default: "localhost:12345",
},
"timeout": {
Description: "Timeout for scraping metrics.",
Type: "string",
Format: "duration",
Default: "10s",
},
},
Required: []string{"endpoint"},
},
ResourceAttributes: map[AttributeName]Attribute{
"string.resource.attr": {
Description: "Resource attribute with any string value.",
EnabledPtr: boolPtr(true),
Type: ValueType{
ValueType: pcommon.ValueTypeStr,
},
FullName: "string.resource.attr",
RequirementLevel: AttributeRequirementLevelRecommended,
},
"string.enum.resource.attr": {
Description: "Resource attribute with a known set of string values.",
EnabledPtr: boolPtr(true),
Enum: []string{"one", "two"},
Type: ValueType{
ValueType: pcommon.ValueTypeStr,
},
FullName: "string.enum.resource.attr",
RequirementLevel: AttributeRequirementLevelRecommended,
},
"optional.resource.attr": {
Description: "Explicitly disabled ResourceAttribute.",
EnabledPtr: boolPtr(false),
Type: ValueType{
ValueType: pcommon.ValueTypeStr,
},
FullName: "optional.resource.attr",
RequirementLevel: AttributeRequirementLevelRecommended,
},
"slice.resource.attr": {
Description: "Resource attribute with a slice value.",
EnabledPtr: boolPtr(true),
Type: ValueType{
ValueType: pcommon.ValueTypeSlice,
},
FullName: "slice.resource.attr",
RequirementLevel: AttributeRequirementLevelRecommended,
},
"map.resource.attr": {
Description: "Resource attribute with a map value.",
EnabledPtr: boolPtr(true),
Type: ValueType{
ValueType: pcommon.ValueTypeMap,
},
FullName: "map.resource.attr",
RequirementLevel: AttributeRequirementLevelRecommended,
},
"string.resource.attr_disable_warning": {
Description: "Resource attribute with any string value.",
Warnings: Warnings{
IfEnabledNotSet: "This resource_attribute will be disabled by default soon.",
},
EnabledPtr: boolPtr(true),
Type: ValueType{
ValueType: pcommon.ValueTypeStr,
},
FullName: "string.resource.attr_disable_warning",
RequirementLevel: AttributeRequirementLevelRecommended,
},
"string.resource.attr_remove_warning": {
Description: "Resource attribute with any string value.",
Warnings: Warnings{
IfConfigured: "This resource_attribute is deprecated and will be removed soon.",
},
EnabledPtr: boolPtr(false),
Type: ValueType{
ValueType: pcommon.ValueTypeStr,
},
FullName: "string.resource.attr_remove_warning",
RequirementLevel: AttributeRequirementLevelRecommended,
},
"string.resource.attr_to_be_removed": {
Description: "Resource attribute with any string value.",
Warnings: Warnings{
IfEnabled: "This resource_attribute is deprecated and will be removed soon.",
},
EnabledPtr: boolPtr(true),
Type: ValueType{
ValueType: pcommon.ValueTypeStr,
},
FullName: "string.resource.attr_to_be_removed",
RequirementLevel: AttributeRequirementLevelRecommended,
},
},
Attributes: map[AttributeName]Attribute{
"enum_attr": {
Description: "Attribute with a known set of string values.",
NameOverride: "",
Enum: []string{"red", "green", "blue"},
Type: ValueType{
ValueType: pcommon.ValueTypeStr,
},
FullName: "enum_attr",
RequirementLevel: AttributeRequirementLevelRecommended,
},
"string_attr": {
Description: "Attribute with any string value.",
NameOverride: "",
Type: ValueType{
ValueType: pcommon.ValueTypeStr,
},
FullName: "string_attr",
RequirementLevel: AttributeRequirementLevelRecommended,
},
"overridden_int_attr": {
Description: "Integer attribute with overridden name.",
NameOverride: "state",
Type: ValueType{
ValueType: pcommon.ValueTypeInt,
},
FullName: "overridden_int_attr",
RequirementLevel: AttributeRequirementLevelRecommended,
},
"boolean_attr": {
Description: "Attribute with a boolean value.",
Type: ValueType{
ValueType: pcommon.ValueTypeBool,
},
FullName: "boolean_attr",
RequirementLevel: AttributeRequirementLevelRecommended,
},
"boolean_attr2": {
Description: "Another attribute with a boolean value.",
Type: ValueType{
ValueType: pcommon.ValueTypeBool,
},
FullName: "boolean_attr2",
RequirementLevel: AttributeRequirementLevelRecommended,
},
"slice_attr": {
Description: "Attribute with a slice value.",
Type: ValueType{
ValueType: pcommon.ValueTypeSlice,
},
FullName: "slice_attr",
RequirementLevel: AttributeRequirementLevelRecommended,
},
"map_attr": {
Description: "Attribute with a map value.",
Type: ValueType{
ValueType: pcommon.ValueTypeMap,
},
FullName: "map_attr",
RequirementLevel: AttributeRequirementLevelRecommended,
},
"conditional_int_attr": {
Description: "A conditional attribute with an integer value",
Type: ValueType{
ValueType: pcommon.ValueTypeInt,
},
FullName: "conditional_int_attr",
RequirementLevel: AttributeRequirementLevelConditionallyRequired,
},
"conditional_string_attr": {
Description: "A conditional attribute with any string value",
Type: ValueType{
ValueType: pcommon.ValueTypeStr,
},
FullName: "conditional_string_attr",
RequirementLevel: AttributeRequirementLevelConditionallyRequired,
},
"opt_in_bool_attr": {
Description: "An opt-in attribute with a boolean value",
Type: ValueType{
ValueType: pcommon.ValueTypeBool,
},
FullName: "opt_in_bool_attr",
RequirementLevel: AttributeRequirementLevelOptIn,
},
"required_string_attr": {
Description: "A required attribute with a string value",
Type: ValueType{
ValueType: pcommon.ValueTypeStr,
},
FullName: "required_string_attr",
RequirementLevel: AttributeRequirementLevelRequired,
},
},
Metrics: map[MetricName]Metric{
"default.metric": {
Signal: Signal{
Enabled: true,
Description: "Monotonic cumulative sum int metric enabled by default.",
ExtendedDocumentation: "The metric will be become optional soon.",
Stability: component.StabilityLevelDeprecated,
Warnings: Warnings{
IfEnabledNotSet: "This metric will be disabled by default soon.",
},
Attributes: []AttributeName{"string_attr", "overridden_int_attr", "enum_attr", "slice_attr", "map_attr", "conditional_int_attr", "conditional_string_attr", "opt_in_bool_attr"},
},
Unit: strPtr("s"),
Sum: &Sum{
MetricValueType: MetricValueType{pmetric.NumberDataPointValueTypeInt},
AggregationTemporality: AggregationTemporality{Aggregation: pmetric.AggregationTemporalityCumulative},
Mono: Mono{Monotonic: true},
},
Deprecated: &Deprecated{
Since: "1.0.0",
Note: "This metric will be removed",
},
},
"reaggregate.metric": {
Signal: Signal{
Enabled: true,
Description: "Metric for testing spatial reaggregation",
Stability: component.StabilityLevelBeta,
Attributes: []AttributeName{"string_attr", "boolean_attr"},
},
Unit: strPtr("1"),
Gauge: &Gauge{
MetricValueType: MetricValueType{pmetric.NumberDataPointValueTypeDouble},
},
},
"reaggregate.metric.with_required": {
Signal: Signal{
Enabled: true,
Description: "Metric for testing spatial reaggregation with required attributes",
Stability: component.StabilityLevelBeta,
Attributes: []AttributeName{"required_string_attr", "string_attr", "boolean_attr"},
},
Unit: strPtr("1"),
Gauge: &Gauge{
MetricValueType: MetricValueType{pmetric.NumberDataPointValueTypeDouble},
},
},
"system.cpu.time": {
Signal: Signal{
Enabled: true,
Stability: component.StabilityLevelBeta,
SemanticConvention: &SemanticConvention{SemanticConventionRef: "https://github.com/open-telemetry/semantic-conventions/blob/v1.38.0/docs/system/system-metrics.md#metric-systemcputime"},
Description: "Monotonic cumulative sum int metric enabled by default.",
ExtendedDocumentation: "The metric will be become optional soon.",
},
Unit: strPtr("s"),
Sum: &Sum{
MetricValueType: MetricValueType{pmetric.NumberDataPointValueTypeInt},
AggregationTemporality: AggregationTemporality{Aggregation: pmetric.AggregationTemporalityCumulative},
Mono: Mono{Monotonic: true},
},
},
"optional.metric": {
Signal: Signal{
Enabled: false,
Description: "[DEPRECATED] Gauge double metric disabled by default.",
Stability: component.StabilityLevelDeprecated,
Warnings: Warnings{
IfConfigured: "This metric is deprecated and will be removed soon.",
},
Attributes: []AttributeName{"string_attr", "boolean_attr", "boolean_attr2", "conditional_string_attr"},
},
Deprecated: &Deprecated{
Since: "1.0.0",
Note: "This metric will be removed",
},
Unit: strPtr("1"),
Gauge: &Gauge{
MetricValueType: MetricValueType{pmetric.NumberDataPointValueTypeDouble},
},
},
"optional.metric.empty_unit": {
Signal: Signal{
Enabled: false,
Description: "[DEPRECATED] Gauge double metric disabled by default.",
Stability: component.StabilityLevelDeprecated,
Warnings: Warnings{
IfConfigured: "This metric is deprecated and will be removed soon.",
},
Attributes: []AttributeName{"string_attr", "boolean_attr"},
},
Deprecated: &Deprecated{
Since: "1.0.0",
Note: "This metric will be removed",
},
Unit: strPtr(""),
Gauge: &Gauge{
MetricValueType: MetricValueType{pmetric.NumberDataPointValueTypeDouble},
},
},
"default.metric.to_be_removed": {
Signal: Signal{
Enabled: true,
Description: "[DEPRECATED] Non-monotonic delta sum double metric enabled by default.",
ExtendedDocumentation: "The metric will be removed soon.",
Stability: component.StabilityLevelDeprecated,
Warnings: Warnings{
IfEnabled: "This metric is deprecated and will be removed soon.",
},
},
Deprecated: &Deprecated{
Since: "1.0.0",
Note: "This metric will be removed",
},
Unit: strPtr("s"),
Sum: &Sum{
MetricValueType: MetricValueType{pmetric.NumberDataPointValueTypeDouble},
AggregationTemporality: AggregationTemporality{Aggregation: pmetric.AggregationTemporalityDelta},
Mono: Mono{Monotonic: false},
},
},
"metric.input_type": {
Signal: Signal{
Enabled: true,
Description: "Monotonic cumulative sum int metric with string input_type enabled by default.",
Stability: component.StabilityLevelDevelopment,
Attributes: []AttributeName{"string_attr", "overridden_int_attr", "enum_attr", "slice_attr", "map_attr"},
},
Unit: strPtr("s"),
Sum: &Sum{
MetricValueType: MetricValueType{pmetric.NumberDataPointValueTypeInt},
MetricInputType: MetricInputType{InputType: "string"},
AggregationTemporality: AggregationTemporality{Aggregation: pmetric.AggregationTemporalityCumulative},
Mono: Mono{Monotonic: true},
},
},
},
Events: map[EventName]Event{
"default.event": {
Signal: Signal{
Enabled: true,
Description: "Example event enabled by default.",
Warnings: Warnings{
IfEnabledNotSet: "This event will be disabled by default soon.",
},
Attributes: []AttributeName{"string_attr", "overridden_int_attr", "enum_attr", "slice_attr", "map_attr", "conditional_int_attr", "conditional_string_attr", "opt_in_bool_attr"},
},
},
"default.event.to_be_renamed": {
Signal: Signal{
Enabled: false,
Description: "[DEPRECATED] Example event disabled by default.",
ExtendedDocumentation: "The event will be renamed soon.",
Warnings: Warnings{
IfConfigured: "This event is deprecated and will be renamed soon.",
},
Attributes: []AttributeName{"string_attr", "boolean_attr", "boolean_attr2", "conditional_string_attr"},
},
},
"default.event.to_be_removed": {
Signal: Signal{
Enabled: true,
Description: "[DEPRECATED] Example to-be-removed event enabled by default.",
ExtendedDocumentation: "The event will be removed soon.",
Warnings: Warnings{
IfEnabled: "This event is deprecated and will be removed soon.",
},
Attributes: []AttributeName{"string_attr", "overridden_int_attr", "enum_attr", "slice_attr", "map_attr"},
},
},
},
Telemetry: Telemetry{
Metrics: map[MetricName]Metric{
"batch_size_trigger_send": {
Signal: Signal{
Enabled: true,
Stability: component.StabilityLevelDeprecated,
Description: "Number of times the batch was sent due to a size trigger",
},
Deprecated: &Deprecated{
Since: "1.5.0",
Note: "This metric will be removed in favor of batch_send_trigger_size",
},
Unit: strPtr("{time}"),
Sum: &Sum{
MetricValueType: MetricValueType{pmetric.NumberDataPointValueTypeInt},
Mono: Mono{Monotonic: true},
},
},
"request_duration": {
Signal: Signal{
Enabled: true,
Stability: component.StabilityLevelAlpha,
Description: "Duration of request",
},
Unit: strPtr("s"),
Histogram: &Histogram{
MetricValueType: MetricValueType{pmetric.NumberDataPointValueTypeDouble},
Boundaries: []float64{1, 10, 100},
},
},
"process_runtime_total_alloc_bytes": {
Signal: Signal{
Enabled: true,
Stability: component.StabilityLevelStable,
Description: "Cumulative bytes allocated for heap objects (see 'go doc runtime.MemStats.TotalAlloc')",
},
Unit: strPtr("By"),
Sum: &Sum{
Mono: Mono{true},
MetricValueType: MetricValueType{
ValueType: pmetric.NumberDataPointValueTypeInt,
},
Async: true,
},
},
"queue_length": {
Signal: Signal{
Enabled: true,
Stability: component.StabilityLevelAlpha,
Description: "This metric is optional and therefore not initialized in NewTelemetryBuilder.",
ExtendedDocumentation: "For example this metric only exists if feature A is enabled.",
},
Unit: strPtr("{item}"),
Optional: true,
Gauge: &Gauge{
MetricValueType: MetricValueType{
ValueType: pmetric.NumberDataPointValueTypeInt,
},
Async: true,
},
},
"queue_capacity": {
Signal: Signal{
Enabled: true,
Description: "Queue capacity - sync gauge example.",
Stability: component.StabilityLevelDevelopment,
},
Unit: strPtr("{item}"),
Gauge: &Gauge{
MetricValueType: MetricValueType{
ValueType: pmetric.NumberDataPointValueTypeInt,
},
},
},
},
},
ScopeName: "go.opentelemetry.io/collector/internal/receiver/samplereceiver",
ShortFolderName: "sample",
Tests: Tests{Host: "newMdatagenNopHost()"},
FeatureGates: []FeatureGate{
{
ID: "receiver.sample.featuregate.example",
Description: "This is an example feature gate for testing mdatagen code generation.",
Stage: "alpha",
FromVersion: "v0.100.0",
ReferenceURL: "https://github.com/open-telemetry/opentelemetry-collector/issues/12345",
},
},
},
},
{
name: "testdata/parent.yaml",
want: Metadata{
Type: "subcomponent",
Parent: "parentComponent",
GeneratedPackageName: "metadata",
ScopeName: "go.opentelemetry.io/collector/cmd/mdatagen/internal/testdata",
PackageName: "go.opentelemetry.io/collector/cmd/mdatagen/internal/testdata",
ShortFolderName: "testdata",
Tests: Tests{Host: "newMdatagenNopHost()"},
},
},
{
name: "testdata/generated_package_name.yaml",
want: Metadata{
Type: "custom",
GeneratedPackageName: "customname",
ScopeName: "go.opentelemetry.io/collector/cmd/mdatagen/internal/testdata",
PackageName: "go.opentelemetry.io/collector/cmd/mdatagen/internal/testdata",
ShortFolderName: "testdata",
Tests: Tests{Host: "newMdatagenNopHost()"},
Status: &Status{
Class: "receiver",
Stability: map[component.StabilityLevel][]string{
component.StabilityLevelDevelopment: {"logs"},
component.StabilityLevelBeta: {"traces"},
component.StabilityLevelStable: {"metrics"},
},
},
},
},
{
name: "testdata/empty_test_config.yaml",
want: Metadata{
Type: "test",
GeneratedPackageName: "metadata",
ScopeName: "go.opentelemetry.io/collector/cmd/mdatagen/internal/testdata",
PackageName: "go.opentelemetry.io/collector/cmd/mdatagen/internal/testdata",
ShortFolderName: "testdata",
Tests: Tests{Host: "newMdatagenNopHost()"},
Status: &Status{
Class: "receiver",
Stability: map[component.StabilityLevel][]string{
component.StabilityLevelBeta: {"logs"},
},
},
},
},
{
name: "testdata/invalid_type_rattr.yaml",
want: Metadata{},
wantErr: "decoding failed due to the following error(s):\n\n'resource_attributes[string.resource.attr].type' invalid type: \"invalidtype\"",
},
{
name: "testdata/no_enabled.yaml",
want: Metadata{},
wantErr: "decoding failed due to the following error(s):\n\n'metrics[system.cpu.time]' missing required field: `enabled`",
},
{
name: "testdata/events/no_enabled.yaml",
want: Metadata{},
wantErr: "decoding failed due to the following error(s):\n\n'events[system.event]' missing required field: `enabled`",
},
{
name: "testdata/no_value_type.yaml",
want: Metadata{},
wantErr: "decoding failed due to the following error(s):\n\n'metrics[system.cpu.time]' decoding failed due to the following error(s):\n\n" +
"'sum' missing required field: `value_type`",
},
{
name: "testdata/unknown_value_type.yaml",
wantErr: "decoding failed due to the following error(s):\n\n'metrics[system.cpu.time]' decoding failed due to the following error(s):\n\n'sum' decoding failed due to the following error(s):\n\n'value_type' invalid value_type: \"unknown\"",
},
{
name: "testdata/invalid_aggregation.yaml",
want: Metadata{},
wantErr: "decoding failed due to the following error(s):\n\n'metrics[default.metric]' decoding failed due to the following error(s):\n\n'sum' decoding failed due to the following error(s):\n\n'aggregation_temporality' invalid aggregation: \"invalidaggregation\"",
},
{
name: "testdata/invalid_type_attr.yaml",
want: Metadata{},
wantErr: "decoding failed due to the following error(s):\n\n'attributes[used_attr].type' invalid type: \"invalidtype\"",
},
{
name: "testdata/invalid_metric_stability.yaml",
want: Metadata{},
wantErr: "decoding failed due to the following error(s):\n\n'metrics[default.metric]' decoding failed due to the following error(s):\n\n'stability' unsupported stability level: \"development42\"",
},
{
name: "testdata/invalid_metric_semconvref.yaml",
want: Metadata{},
wantErr: "metric \"default.metric\": invalid semantic-conventions URL: want https://github.com/open-telemetry/semantic-conventions/blob/v1.37.2/*#metric-defaultmetric, got \"https://github.com/open-telemetry/semantic-conventions/blob/v1.38.0/docs/system/system-metrics.md#metric-systemcputime\"",
},
{
name: "testdata/no_metric_stability.yaml",
want: Metadata{},
wantErr: "metric \"default.metric\": missing required field: `stability.level`",
},
{
name: "testdata/undeprecated_with_deprecation.yaml",
want: Metadata{},
wantErr: "`stability` must be `deprecated` when specifying a `deprecated` field",
},
{
name: "testdata/invalid_config.yaml",
want: Metadata{},
wantErr: "config type must be \"object\", got \"string\"",
},
{
name: "testdata/~~this file doesn't exist~~.yaml",
wantErr: "unable to read the file file:testdata/~~this file doesn't exist~~.yaml",
},
{
name: "testdata/display_name.yaml",
want: Metadata{
Type: "test",
DisplayName: "Test Receiver",
GeneratedPackageName: "metadata",
ScopeName: "go.opentelemetry.io/collector/cmd/mdatagen/internal/testdata",
PackageName: "go.opentelemetry.io/collector/cmd/mdatagen/internal/testdata",
ShortFolderName: "testdata",
Tests: Tests{Host: "newMdatagenNopHost()"},
Status: &Status{
Class: "receiver",
Stability: map[component.StabilityLevel][]string{
component.StabilityLevelBeta: {"logs"},
},
},
},
},
{
name: "testdata/no_display_name.yaml",
want: Metadata{
Type: "nodisplayname",
DisplayName: "",
GeneratedPackageName: "metadata",
ScopeName: "go.opentelemetry.io/collector/cmd/mdatagen/internal/testdata",
PackageName: "go.opentelemetry.io/collector/cmd/mdatagen/internal/testdata",
ShortFolderName: "testdata",
Tests: Tests{Host: "newMdatagenNopHost()"},
Status: &Status{
Class: "receiver",
Stability: map[component.StabilityLevel][]string{
component.StabilityLevelBeta: {"logs"},
},
},
},
},
{
name: "testdata/with_description.yaml",
want: Metadata{
Type: "testdesc",
DisplayName: "Test Component",
Description: "This is a test component with a description.",
GeneratedPackageName: "metadata",
ScopeName: "go.opentelemetry.io/collector/cmd/mdatagen/internal/testdata",
PackageName: "go.opentelemetry.io/collector/cmd/mdatagen/internal/testdata",
ShortFolderName: "testdata",
Tests: Tests{Host: "newMdatagenNopHost()"},
Status: &Status{
Class: "receiver",
Stability: map[component.StabilityLevel][]string{
component.StabilityLevelBeta: {"logs"},
},
},
},
},
{
name: "testdata/with_underscore_in_semconv_ref_anchor_tag.yaml",
want: Metadata{
Type: "metricreceiver",
GeneratedPackageName: "metadata",
SemConvVersion: "1.38.0",
ScopeName: "go.opentelemetry.io/collector/cmd/mdatagen/internal/testdata",
PackageName: "go.opentelemetry.io/collector/cmd/mdatagen/internal/testdata",
ShortFolderName: "testdata",
Tests: Tests{Host: "newMdatagenNopHost()"},
Status: &Status{
Class: "receiver",
Stability: map[component.StabilityLevel][]string{
component.StabilityLevelDevelopment: {"logs"},
component.StabilityLevelBeta: {"traces"},
component.StabilityLevelStable: {"metrics"},
},
Distributions: []string{"contrib"},
Warnings: []string{"Any additional information that should be brought to the consumer's attention"},
},
Metrics: map[MetricName]Metric{
"system.disk.io_time": {
Signal: Signal{
Enabled: true,
Description: "Time disk spent activated..",
Stability: component.StabilityLevelDevelopment,
SemanticConvention: &SemanticConvention{
SemanticConventionRef: "https://github.com/open-telemetry/semantic-conventions/blob/v1.38.0/docs/system/system-metrics.md#metric-systemdiskio_time",
},
},
Unit: strPtr("s"),
Sum: &Sum{
AggregationTemporality: AggregationTemporality{Aggregation: pmetric.AggregationTemporalityCumulative},
Mono: Mono{Monotonic: true},
MetricValueType: MetricValueType{ValueType: pmetric.NumberDataPointValueTypeDouble},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := LoadMetadata(tt.name)
if tt.wantErr != "" {
require.Error(t, err)
require.ErrorContains(t, err, tt.wantErr)
} else {
require.NoError(t, err)
require.Equal(t, tt.want, got)
}
})
}
}
func strPtr(s string) *string {
return &s
}
================================================
FILE: cmd/mdatagen/internal/metadata.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/cmd/mdatagen/internal"
import (
"errors"
"fmt"
"regexp"
"slices"
"strconv"
"strings"
"go.opentelemetry.io/collector/cmd/mdatagen/internal/cfggen"
"go.opentelemetry.io/collector/cmd/mdatagen/internal/helpers"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/filter"
"go.opentelemetry.io/collector/pdata/pcommon"
)
type Metadata struct {
// Type of the component.
Type string `mapstructure:"type"`
// DeprecatedType of the component.
DeprecatedType string `mapstructure:"deprecated_type"`
// DisplayName is a human-readable display name for the component.
DisplayName string `mapstructure:"display_name"`
// Description is a brief description of the component.
Description string `mapstructure:"description"`
// Type of the parent component (applicable to subcomponents).
Parent string `mapstructure:"parent"`
// Status information for the component.
Status *Status `mapstructure:"status"`
// Spatial Re-aggregation featuregate.
ReaggregationEnabled bool `mapstructure:"reaggregation_enabled"`
// The name of the package that will be generated.
GeneratedPackageName string `mapstructure:"generated_package_name"`
// Telemetry information for the component.
Telemetry Telemetry `mapstructure:"telemetry"`
// SemConvVersion is a version number of OpenTelemetry semantic conventions applied to the scraped metrics.
SemConvVersion string `mapstructure:"sem_conv_version"`
// ResourceAttributes that can be emitted by the component.
ResourceAttributes map[AttributeName]Attribute `mapstructure:"resource_attributes"`
// Entities organizes resource attributes into logical entities.
Entities []Entity `mapstructure:"entities"`
// Attributes emitted by one or more metrics.
Attributes map[AttributeName]Attribute `mapstructure:"attributes"`
// Metrics that can be emitted by the component.
Metrics map[MetricName]Metric `mapstructure:"metrics"`
// Events that can be emitted by the component.
Events map[EventName]Event `mapstructure:"events"`
// GithubProject is the project where the component README lives in the format of org/repo, defaults to open-telemetry/opentelemetry-collector-contrib
GithubProject string `mapstructure:"github_project"`
// ScopeName of the metrics emitted by the component.
ScopeName string `mapstructure:"scope_name"`
// ShortFolderName is the shortened folder name of the component, removing class if present
ShortFolderName string `mapstructure:"-"`
// Tests is the set of tests generated with the component
Tests Tests `mapstructure:"tests"`
// PackageName is the name of the package where the component is defined.
PackageName string `mapstructure:"package_name"`
// FeatureGates that are managed by the component.
FeatureGates []FeatureGate `mapstructure:"feature_gates"`
// Config is the configuration schema for the component.
Config *cfggen.ConfigMetadata `mapstructure:"config"`
}
type Deprecated struct {
Since string `mapstructure:"since"`
Note string `mapstructure:"note"`
}
func (d *Deprecated) validate() error {
if strings.TrimSpace(d.Since) == "" {
return errors.New("deprecated.since must be set")
}
// NOTE: note is optional, but if present, it must not be empty
if d.Note != "" && strings.TrimSpace(d.Note) == "" {
return errors.New("deprecated.note must not be empty")
}
return nil
}
func (md Metadata) GetCodeCovComponentID() string {
if md.Status.CodeCovComponentID != "" {
return md.Status.CodeCovComponentID
}
return strings.ReplaceAll(md.Status.Class+"_"+strings.ReplaceAll(md.Type, "_", ""), "/", "_")
}
func (md Metadata) HasEntities() bool {
return len(md.Entities) > 0
}
func (md *Metadata) Validate() error {
var errs error
if err := md.validateType(); err != nil {
errs = errors.Join(errs, err)
}
if md.Parent != "" {
if md.Status != nil {
// status is not required for subcomponents.
errs = errors.Join(errs, errors.New("status must be empty for subcomponents"))
}
} else {
errs = errors.Join(errs, md.Status.Validate())
}
if err := md.validateResourceAttributes(); err != nil {
errs = errors.Join(errs, err)
}
if err := md.validateEntities(); err != nil {
errs = errors.Join(errs, err)
}
if err := md.validateMetricsAndEvents(); err != nil {
errs = errors.Join(errs, err)
}
if err := md.validateFeatureGates(); err != nil {
errs = errors.Join(errs, err)
}
if err := md.validateConfig(); err != nil {
errs = errors.Join(errs, err)
}
return errs
}
// typeRegexp is used to validate the type of a component.
// A type must start with an ASCII alphabetic character and
// can only contain ASCII alphanumeric characters and '_'.
// We allow '/' for subcomponents.
// This must be kept in sync with the regex in component/config.go.
var typeRegexp = regexp.MustCompile(`^[a-zA-Z][0-9a-zA-Z_]{0,62}$`)
func (md *Metadata) validateType() error {
if md.Type == "" {
return errors.New("missing type")
}
if md.Parent != "" {
// subcomponents are allowed to have a '/' in their type.
return nil
}
if !typeRegexp.MatchString(md.Type) {
return fmt.Errorf("invalid character(s) in type %q", md.Type)
}
return nil
}
func (md *Metadata) validateResourceAttributes() error {
var errs error
for name, attr := range md.ResourceAttributes {
if attr.Description == "" {
errs = errors.Join(errs, fmt.Errorf("empty description for resource attribute: %v", name))
}
empty := ValueType{ValueType: pcommon.ValueTypeEmpty}
if attr.Type == empty {
errs = errors.Join(errs, fmt.Errorf("empty type for resource attribute: %v", name))
}
if attr.EnabledPtr == nil {
errs = errors.Join(errs, fmt.Errorf("enabled field is required for resource attribute: %v", name))
}
}
return errs
}
func (md *Metadata) validateEntities() error {
var errs error
usedAttrs := make(map[AttributeName]string)
seenTypes := make(map[string]bool)
// First pass: collect entity types and validate basic entity properties
for _, entity := range md.Entities {
if entity.Type == "" {
errs = errors.Join(errs, errors.New("entity type cannot be empty"))
continue
}
if seenTypes[entity.Type] {
errs = errors.Join(errs, fmt.Errorf(`duplicate entity type: %v`, entity.Type))
}
seenTypes[entity.Type] = true
if entity.Brief == "" {
errs = errors.Join(errs, fmt.Errorf(`entity "%v": brief is required`, entity.Type))
}
if len(entity.Identity) == 0 {
errs = errors.Join(errs, fmt.Errorf(`entity "%v": identity is required`, entity.Type))
}
for _, ref := range entity.Identity {
if _, ok := md.ResourceAttributes[ref.Ref]; !ok {
errs = errors.Join(errs, fmt.Errorf(`entity "%v": identity refers to undefined resource attribute: %v`, entity.Type, ref.Ref))
}
if otherEntity, used := usedAttrs[ref.Ref]; used {
errs = errors.Join(errs, fmt.Errorf(`entity "%v": attribute %v is already used by entity "%v"`, entity.Type, ref.Ref, otherEntity))
} else {
usedAttrs[ref.Ref] = entity.Type
}
}
for _, ref := range entity.Description {
if _, ok := md.ResourceAttributes[ref.Ref]; !ok {
errs = errors.Join(errs, fmt.Errorf(`entity "%v": description refers to undefined resource attribute: %v`, entity.Type, ref.Ref))
}
if otherEntity, used := usedAttrs[ref.Ref]; used {
errs = errors.Join(errs, fmt.Errorf(`entity "%v": attribute %v is already used by entity "%v"`, entity.Type, ref.Ref, otherEntity))
} else {
usedAttrs[ref.Ref] = entity.Type
}
}
for _, ref := range entity.ExtraAttributes {
if _, ok := md.ResourceAttributes[ref.Ref]; !ok {
errs = errors.Join(errs, fmt.Errorf(`entity "%v": extra_attributes refers to undefined resource attribute: %v`, entity.Type, ref.Ref))
}
}
}
// Second pass: validate relationships
seenRelationships := make(map[string]string)
for _, entity := range md.Entities {
for _, rel := range entity.Relationships {
if rel.Type == "" {
errs = errors.Join(errs, fmt.Errorf(`entity "%v": relationship type cannot be empty`, entity.Type))
continue
}
if rel.Target == "" {
errs = errors.Join(errs, fmt.Errorf(`entity "%v": relationship target cannot be empty`, entity.Type))
continue
}
if !seenTypes[rel.Target] {
errs = errors.Join(errs, fmt.Errorf(`entity "%v": relationship target "%v" does not exist`, entity.Type, rel.Target))
continue
}
if seenRelationships[rel.Target] == entity.Type || seenRelationships[entity.Type] == rel.Target {
errs = errors.Join(errs, fmt.Errorf(`entity "%v": duplicate relationship to target "%v" (only one relationship allowed between two entities)`, entity.Type, rel.Target))
continue
}
seenRelationships[rel.Target] = entity.Type
seenRelationships[entity.Type] = rel.Target
}
}
return errs
}
func (md *Metadata) validateMetricsAndEvents() error {
var errs error
usedAttrs := map[AttributeName]bool{}
errs = errors.Join(errs,
validateMetrics(md.Metrics, md.Attributes, usedAttrs, md.SemConvVersion),
validateMetrics(md.Telemetry.Metrics, md.Attributes, usedAttrs, md.SemConvVersion),
validateEvents(md.Events, md.Attributes, usedAttrs),
md.validateAttributes(usedAttrs),
md.validateEntityAssociations())
return errs
}
func (md *Metadata) validateAttributes(usedAttrs map[AttributeName]bool) error {
var errs error
unusedAttrs := make([]AttributeName, 0, len(md.Attributes))
for attrName, attr := range md.Attributes {
if attr.Description == "" {
errs = errors.Join(errs, fmt.Errorf(`missing attribute description for: %v`, attrName))
}
empty := ValueType{ValueType: pcommon.ValueTypeEmpty}
if attr.Type == empty {
errs = errors.Join(errs, fmt.Errorf("empty type for attribute: %v", attrName))
}
if attr.EnabledPtr != nil {
errs = errors.Join(errs, fmt.Errorf("enabled field is not allowed for regular attribute: %v", attrName))
}
if !usedAttrs[attrName] {
unusedAttrs = append(unusedAttrs, attrName)
}
}
if len(unusedAttrs) > 0 {
errs = errors.Join(errs, fmt.Errorf("unused attributes: %v", unusedAttrs))
}
return errs
}
// validateEntityAssociations checks that if entities are defined, then each metric and event must be associated with an entity.
func (md *Metadata) validateEntityAssociations() error {
var errs error
requireEntityAssociation := len(md.Entities) > 0
entityTypes := make(map[string]bool)
for _, entity := range md.Entities {
entityTypes[entity.Type] = true
}
for metricName, metric := range md.Metrics {
if requireEntityAssociation && metric.Entity == "" {
errs = errors.Join(errs, fmt.Errorf(`metric "%v": entity is required when entities are defined`, metricName))
}
if metric.Entity != "" && !entityTypes[metric.Entity] {
errs = errors.Join(errs, fmt.Errorf(`metric "%v": entity refers to undefined entity type: %v`, metricName, metric.Entity))
}
}
for eventName, event := range md.Events {
if requireEntityAssociation && event.Entity == "" {
errs = errors.Join(errs, fmt.Errorf(`event "%v": entity is required when entities are defined`, eventName))
}
if event.Entity != "" && !entityTypes[event.Entity] {
errs = errors.Join(errs, fmt.Errorf(`event "%v": entity refers to undefined entity type: %v`, eventName, event.Entity))
}
}
return errs
}
func (md *Metadata) supportsSignal(signal string) bool {
if md.Status == nil {
return false
}
for _, signals := range md.Status.Stability {
if slices.Contains(signals, signal) {
return true
}
}
return false
}
func validateMetrics(metrics map[MetricName]Metric, attributes map[AttributeName]Attribute, usedAttrs map[AttributeName]bool, semConvVersion string) error {
var errs error
for mn, m := range metrics {
if err := m.validate(mn, semConvVersion); err != nil {
errs = errors.Join(errs, fmt.Errorf(`metric "%v": %w`, mn, err))
continue
}
unknownAttrs := make([]AttributeName, 0, len(m.Attributes))
for _, attr := range m.Attributes {
if _, ok := attributes[attr]; ok {
usedAttrs[attr] = true
} else {
unknownAttrs = append(unknownAttrs, attr)
}
}
if len(unknownAttrs) > 0 {
errs = errors.Join(errs, fmt.Errorf(`metric "%v" refers to undefined attributes: %v`, mn, unknownAttrs))
}
}
return errs
}
func validateEvents(events map[EventName]Event, attributes map[AttributeName]Attribute, usedAttrs map[AttributeName]bool) error {
var errs error
for en, e := range events {
if err := e.validate(); err != nil {
errs = errors.Join(errs, fmt.Errorf(`event "%v": %w`, en, err))
continue
}
unknownAttrs := make([]AttributeName, 0, len(e.Attributes))
for _, attr := range e.Attributes {
if _, ok := attributes[attr]; ok {
usedAttrs[attr] = true
} else {
unknownAttrs = append(unknownAttrs, attr)
}
}
if len(unknownAttrs) > 0 {
errs = errors.Join(errs, fmt.Errorf(`event "%v" refers to undefined attributes: %v`, en, unknownAttrs))
}
}
return errs
}
func (md *Metadata) validateFeatureGates() error {
var errs error
seen := make(map[FeatureGateID]bool)
idRegexp := regexp.MustCompile(`^[0-9a-zA-Z.]*$`)
// Validate that feature gates are sorted by ID
if !slices.IsSortedFunc(md.FeatureGates, func(a, b FeatureGate) int {
return strings.Compare(string(a.ID), string(b.ID))
}) {
errs = errors.Join(errs, errors.New("feature gates must be sorted by ID"))
}
for i, gate := range md.FeatureGates {
// Validate gate ID is not empty
if string(gate.ID) == "" {
errs = errors.Join(errs, fmt.Errorf("feature gate at index %d: ID cannot be empty", i))
continue
}
// Validate ID follows the allowed character pattern
if !idRegexp.MatchString(string(gate.ID)) {
errs = errors.Join(errs, fmt.Errorf(`feature gate "%v": ID contains invalid characters, must match ^[0-9a-zA-Z.]*$`, gate.ID))
}
// Check for duplicate IDs
if seen[gate.ID] {
errs = errors.Join(errs, fmt.Errorf(`feature gate "%v": duplicate ID`, gate.ID))
continue
}
seen[gate.ID] = true
// Validate gate has required fields
if gate.Description == "" {
errs = errors.Join(errs, fmt.Errorf(`feature gate "%v": description is required`, gate.ID))
}
// Validate that each feature gate has a reference link
if gate.ReferenceURL == "" {
errs = errors.Join(errs, fmt.Errorf(`feature gate "%v": reference_url is required`, gate.ID))
}
// Validate stage is one of the allowed values
validStages := map[FeatureGateStage]bool{
FeatureGateStageAlpha: true,
FeatureGateStageBeta: true,
FeatureGateStageStable: true,
FeatureGateStageDeprecated: true,
}
if !validStages[gate.Stage] {
errs = errors.Join(errs, fmt.Errorf(`feature gate "%v": invalid stage "%v", must be one of: alpha, beta, stable, deprecated`, gate.ID, gate.Stage))
}
// Validate from_version is required
if gate.FromVersion == "" {
errs = errors.Join(errs, fmt.Errorf(`feature gate "%v": from_version is required`, gate.ID))
} else if !strings.HasPrefix(gate.FromVersion, "v") {
errs = errors.Join(errs, fmt.Errorf(`feature gate "%v": from_version "%v" must start with 'v'`, gate.ID, gate.FromVersion))
}
if gate.ToVersion != "" && !strings.HasPrefix(gate.ToVersion, "v") {
errs = errors.Join(errs, fmt.Errorf(`feature gate "%v": to_version "%v" must start with 'v'`, gate.ID, gate.ToVersion))
}
// Validate that stable/deprecated gates should have to_version
if (gate.Stage == FeatureGateStageStable || gate.Stage == FeatureGateStageDeprecated) && gate.ToVersion == "" {
errs = errors.Join(errs, fmt.Errorf(`feature gate "%v": to_version is required for %v stage gates`, gate.ID, gate.Stage))
}
}
return errs
}
func (md *Metadata) validateConfig() error {
if md.Config != nil {
return md.Config.Validate()
}
return nil
}
type AttributeName string
// AttributeRequirementLevel defines the requirement level of an attribute.
type AttributeRequirementLevel string
const (
// AttributeRequirementLevelRequired means the attribute is always included and cannot be excluded.
AttributeRequirementLevelRequired AttributeRequirementLevel = "required"
// AttributeRequirementLevelConditionallyRequired means the attribute is included by default when certain conditions are met.
AttributeRequirementLevelConditionallyRequired AttributeRequirementLevel = "conditionally_required"
// AttributeRequirementLevelRecommended means the attribute is included by default but can be disabled via configuration.
AttributeRequirementLevelRecommended AttributeRequirementLevel = "recommended"
// AttributeRequirementLevelOptIn means the attribute is not included unless explicitly enabled in user config.
AttributeRequirementLevelOptIn AttributeRequirementLevel = "opt_in"
)
// String returns capitalized display name of the requirement level for documentation.
func (rl AttributeRequirementLevel) String() string {
switch rl {
case AttributeRequirementLevelRequired:
return "Required"
case AttributeRequirementLevelConditionallyRequired:
return "Conditionally Required"
case AttributeRequirementLevelRecommended:
return "Recommended"
case AttributeRequirementLevelOptIn:
return "Opt-In"
}
return ""
}
func (mn AttributeName) Render() (string, error) {
return helpers.FormatIdentifier(string(mn), true)
}
func (mn AttributeName) RenderUnexported() (string, error) {
return helpers.FormatIdentifier(string(mn), false)
}
// ValueType defines an attribute value type.
type ValueType struct {
// ValueType is type of the attribute value.
ValueType pcommon.ValueType
}
// UnmarshalText implements the encoding.TextUnmarshaler interface.
func (mvt *ValueType) UnmarshalText(text []byte) error {
switch vtStr := string(text); vtStr {
case "string":
mvt.ValueType = pcommon.ValueTypeStr
case "int":
mvt.ValueType = pcommon.ValueTypeInt
case "double":
mvt.ValueType = pcommon.ValueTypeDouble
case "bool":
mvt.ValueType = pcommon.ValueTypeBool
case "bytes":
mvt.ValueType = pcommon.ValueTypeBytes
case "slice":
mvt.ValueType = pcommon.ValueTypeSlice
case "map":
mvt.ValueType = pcommon.ValueTypeMap
default:
return fmt.Errorf("invalid type: %q", vtStr)
}
return nil
}
// String returns capitalized name of the ValueType.
func (mvt ValueType) String() string {
return strings.Title(strings.ToLower(mvt.ValueType.String())) //nolint:staticcheck // SA1019
}
// Primitive returns name of primitive type for the ValueType.
func (mvt ValueType) Primitive() string {
switch mvt.ValueType {
case pcommon.ValueTypeStr:
return "string"
case pcommon.ValueTypeInt:
return "int64"
case pcommon.ValueTypeDouble:
return "float64"
case pcommon.ValueTypeBool:
return "bool"
case pcommon.ValueTypeBytes:
return "[]byte"
case pcommon.ValueTypeSlice:
return "[]any"
case pcommon.ValueTypeMap:
return "map[string]any"
case pcommon.ValueTypeEmpty:
return ""
default:
return ""
}
}
type SemanticConvention struct {
SemanticConventionRef string `mapstructure:"ref"`
}
type Warnings struct {
// A warning that will be displayed if the field is enabled in user config.
IfEnabled string `mapstructure:"if_enabled"`
// A warning that will be displayed if `enabled` field is not set explicitly in user config.
IfEnabledNotSet string `mapstructure:"if_enabled_not_set"`
// A warning that will be displayed if the field is configured by user in any way.
IfConfigured string `mapstructure:"if_configured"`
}
type Attribute struct {
// Description describes the purpose of the attribute.
Description string `mapstructure:"description"`
// NameOverride can be used to override the attribute name.
NameOverride string `mapstructure:"name_override"`
// EnabledPtr defines whether the attribute is enabled by default.
EnabledPtr *bool `mapstructure:"enabled"`
// Include can be used to filter attributes.
Include []filter.Config `mapstructure:"include"`
// Include can be used to filter attributes.
Exclude []filter.Config `mapstructure:"exclude"`
// Enum can optionally describe the set of values to which the attribute can belong.
Enum []string `mapstructure:"enum"`
// Type is an attribute type.
Type ValueType `mapstructure:"type"`
// FullName is the attribute name populated from the map key.
FullName AttributeName `mapstructure:"-"`
// Warnings that will be shown to user under specified conditions.
Warnings Warnings `mapstructure:"warnings"`
// RequirementLevel defines the requirement level of the attribute.
RequirementLevel AttributeRequirementLevel `mapstructure:"requirement_level"`
}
// IsConditional returns true if the attribute is conditionally required.
func (a Attribute) IsConditional() bool {
return a.RequirementLevel == AttributeRequirementLevelConditionallyRequired
}
// IsRequired returns true if the attribute is required.
func (a Attribute) IsRequired() bool {
return a.RequirementLevel == AttributeRequirementLevelRequired
}
// IsNotOptIn returns true if the attribute is any requirement_level above
// opt_in
func (a Attribute) IsNotOptIn() bool {
return a.RequirementLevel != AttributeRequirementLevelOptIn
}
// UnmarshalText implements the encoding.TextUnmarshaler interface.
func (rl *AttributeRequirementLevel) UnmarshalText(text []byte) error {
switch string(text) {
case "required":
*rl = AttributeRequirementLevelRequired
case "conditionally_required":
*rl = AttributeRequirementLevelConditionallyRequired
case "recommended":
*rl = AttributeRequirementLevelRecommended
case "opt_in":
*rl = AttributeRequirementLevelOptIn
case "":
*rl = AttributeRequirementLevelRecommended
default:
return fmt.Errorf("invalid requirement_level %q", string(text))
}
return nil
}
// Enabled returns the boolean value of EnabledPtr.
// This method is needed to differentiate between different types of attributes:
// - Resource attributes: EnabledPtr is always set (non-nil) due to validation
// - Regular attributes: EnabledPtr is always nil due to validation
// Panics if EnabledPtr is nil, indicating incorrect template usage.
func (a Attribute) Enabled() bool {
if a.EnabledPtr == nil {
panic("Enabled() must not be called on regular attributes, only on resource attributes")
}
return *a.EnabledPtr
}
// Name returns actual name of the attribute that is set on the metric after applying NameOverride.
func (a Attribute) Name() AttributeName {
if a.NameOverride != "" {
return AttributeName(a.NameOverride)
}
return a.FullName
}
func (a Attribute) TestValue() string {
if a.Enum != nil {
return fmt.Sprintf(`%q`, a.Enum[0])
}
switch a.Type.ValueType {
case pcommon.ValueTypeEmpty:
return ""
case pcommon.ValueTypeStr:
return fmt.Sprintf(`"%s-val"`, a.FullName)
case pcommon.ValueTypeInt:
return strconv.Itoa(len(a.FullName))
case pcommon.ValueTypeDouble:
return fmt.Sprintf("%f", 0.1+float64(len(a.FullName)))
case pcommon.ValueTypeBool:
return strconv.FormatBool(len(a.FullName)%2 == 0)
case pcommon.ValueTypeMap:
return fmt.Sprintf(`map[string]any{"key1": "%s-val1", "key2": "%s-val2"}`, a.FullName, a.FullName)
case pcommon.ValueTypeSlice:
return fmt.Sprintf(`[]any{"%s-item1", "%s-item2"}`, a.FullName, a.FullName)
case pcommon.ValueTypeBytes:
return fmt.Sprintf(`[]byte("%s-val")`, a.FullName)
}
return ""
}
func (a Attribute) TestValueTwo() string {
if a.Enum != nil {
return fmt.Sprintf(`%q`, a.Enum[1])
}
switch a.Type.ValueType {
case pcommon.ValueTypeEmpty:
return ""
case pcommon.ValueTypeStr:
return fmt.Sprintf(`"%s-val-2"`, a.FullName)
case pcommon.ValueTypeInt:
return strconv.Itoa(len(a.FullName) + 1)
case pcommon.ValueTypeDouble:
return fmt.Sprintf("%f", 1.1+float64(len(a.FullName)))
case pcommon.ValueTypeBool:
return strconv.FormatBool(len(a.FullName)%2 == 1)
case pcommon.ValueTypeMap:
return fmt.Sprintf(`map[string]any{"key3": "%s-val3", "key4": "%s-val4"}`, a.FullName, a.FullName)
case pcommon.ValueTypeSlice:
return fmt.Sprintf(`[]any{"%s-item3", "%s-item4"}`, a.FullName, a.FullName)
case pcommon.ValueTypeBytes:
return fmt.Sprintf(`[]byte("%s-val-2")`, a.FullName)
}
return ""
}
type Signal struct {
// Enabled defines whether the signal is enabled by default.
Enabled bool `mapstructure:"enabled"`
// Warnings that will be shown to user under specified conditions.
Warnings Warnings `mapstructure:"warnings"`
// Description of the signal.
Description string `mapstructure:"description"`
// The semantic convention reference of the signal.
SemanticConvention *SemanticConvention `mapstructure:"semantic_convention"`
// The stability level of the signal.
Stability component.StabilityLevel `mapstructure:"stability"`
// Extended documentation of the signal. If specified, this will be appended to the description used in generated documentation.
ExtendedDocumentation string `mapstructure:"extended_documentation"`
// Attributes is the list of attributes that the signal emits.
Attributes []AttributeName `mapstructure:"attributes"`
// Entity is the type of entity this signal is associated with.
// Required when entities are defined.
Entity string `mapstructure:"entity"`
}
func (s Signal) HasConditionalAttributes(attrs map[AttributeName]Attribute) bool {
for _, attr := range s.Attributes {
if v, exists := attrs[attr]; exists && v.IsConditional() {
return true
}
}
return false
}
type Entity struct {
// Type is the type of the entity.
Type string `mapstructure:"type"`
// Brief is a brief description of the entity.
Brief string `mapstructure:"brief"`
// Stability is the stability level of the entity.
Stability component.StabilityLevel `mapstructure:"stability"`
// Identity contains references to resource attributes that uniquely identify the entity.
Identity []EntityAttributeRef `mapstructure:"identity"`
// Description contains references to resource attributes that describe the entity.
Description []EntityAttributeRef `mapstructure:"description"`
// ExtraAttributes contains references to resource attributes that are contextually
// relevant to the entity but are not part of its identity or description
// (e.g. k8s.namespace.name on a pod entity).
ExtraAttributes []EntityAttributeRef `mapstructure:"extra_attributes"`
// Relationships defines how this entity relates to other entities (optional).
// Relationships should be defined only on one end. It is recommended to define
// relationships on entities with lower lifespan (higher churn).
Relationships []EntityRelationship `mapstructure:"relationships"`
}
type EntityAttributeRef struct {
// Ref is the reference to a resource attribute.
Ref AttributeName `mapstructure:"ref"`
}
type EntityRelationship struct {
// Type is the relationship type (e.g., "parent", "child", "peer").
Type string `mapstructure:"type"`
// Target is the entity type this entity relates to.
Target string `mapstructure:"target"`
}
// FeatureGateID represents the identifier for a feature gate.
type FeatureGateID string
// FeatureGateStage represents the lifecycle stage of a feature gate.
type FeatureGateStage string
const (
FeatureGateStageAlpha FeatureGateStage = "alpha"
FeatureGateStageBeta FeatureGateStage = "beta"
FeatureGateStageStable FeatureGateStage = "stable"
FeatureGateStageDeprecated FeatureGateStage = "deprecated"
)
// FeatureGate represents a feature gate definition in metadata.
type FeatureGate struct {
// ID is the unique identifier for the feature gate.
ID FeatureGateID `mapstructure:"id"`
// Description of the feature gate.
Description string `mapstructure:"description"`
// Stage is the lifecycle stage of the feature gate.
Stage FeatureGateStage `mapstructure:"stage"`
// FromVersion is the version when the feature gate was introduced.
FromVersion string `mapstructure:"from_version"`
// ToVersion is the version when the feature gate reached stable stage.
ToVersion string `mapstructure:"to_version"`
// ReferenceURL is the URL with contextual information about the feature gate.
ReferenceURL string `mapstructure:"reference_url"`
}
================================================
FILE: cmd/mdatagen/internal/metadata_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/cmd/mdatagen/internal/cfggen"
)
func TestValidate(t *testing.T) {
tests := []struct {
name string
wantErr string
}{
{
name: "testdata/no_type.yaml",
wantErr: "missing type",
},
{
name: "testdata/no_status.yaml",
wantErr: "missing status",
},
{
name: "testdata/no_class.yaml",
wantErr: "missing class",
},
{
name: "testdata/invalid_class.yaml",
wantErr: "invalid class: incorrectclass",
},
{
name: "testdata/no_stability.yaml",
wantErr: "missing stability",
},
{
name: "testdata/no_deprecation_info.yaml",
wantErr: "deprecated component missing deprecation date and migration guide for traces",
},
{
name: "testdata/no_deprecation_date_info.yaml",
wantErr: "deprecated component missing date in YYYY-MM-DD format: traces",
},
{
name: "testdata/no_deprecation_migration_info.yaml",
wantErr: "deprecated component missing migration guide: traces",
},
{
name: "testdata/deprecation_info_invalid_date.yaml",
wantErr: "deprecated component missing valid date in YYYY-MM-DD format: traces",
},
{
name: "testdata/invalid_stability.yaml",
wantErr: "decoding failed due to the following error(s):\n\n'status.stability' unsupported stability level: \"incorrectstability\"",
},
{
name: "testdata/no_stability_component.yaml",
wantErr: "missing component for stability: Beta",
},
{
name: "testdata/invalid_stability_component.yaml",
wantErr: "invalid component: incorrectcomponent",
},
{
name: "testdata/no_description_rattr.yaml",
wantErr: "empty description for resource attribute: string.resource.attr",
},
{
name: "testdata/no_type_rattr.yaml",
wantErr: "empty type for resource attribute: string.resource.attr",
},
{
name: "testdata/no_metric_description.yaml",
wantErr: "metric \"default.metric\": missing metric description",
},
{
name: "testdata/events/no_description.yaml",
wantErr: "event \"default.event\": missing event description",
},
{
name: "testdata/no_metric_unit.yaml",
wantErr: "metric \"default.metric\": missing metric unit",
},
{
name: "testdata/no_metric_type.yaml",
wantErr: "metric \"system.cpu.time\": missing metric type key, " +
"one of the following has to be specified: sum, gauge, histogram",
},
{
name: "testdata/two_metric_types.yaml",
wantErr: "metric \"system.cpu.time\": more than one metric type keys, " +
"only one of the following has to be specified: sum, gauge, histogram",
},
{
name: "testdata/invalid_input_type.yaml",
wantErr: "metric \"system.cpu.time\": invalid `input_type` value \"double\", must be \"\" or \"string\"",
},
{
name: "testdata/unknown_metric_attribute.yaml",
wantErr: "metric \"system.cpu.time\" refers to undefined attributes: [missing]",
},
{
name: "testdata/events/unknown_attribute.yaml",
wantErr: "event \"system.event\" refers to undefined attributes: [missing]",
},
{
name: "testdata/unused_attribute.yaml",
wantErr: "unused attributes: [unused_attr]",
},
{
name: "testdata/no_description_attr.yaml",
wantErr: "missing attribute description for: string_attr",
},
{
name: "testdata/no_type_attr.yaml",
wantErr: "empty type for attribute: used_attr",
},
{
name: "testdata/entity_undefined_id_attribute.yaml",
wantErr: `entity "host": identity refers to undefined resource attribute: host.missing`,
},
{
name: "testdata/entity_undefined_description_attribute.yaml",
wantErr: `entity "host": description refers to undefined resource attribute: host.missing`,
},
{
name: "testdata/entity_empty_id_attributes.yaml",
wantErr: `entity "host": identity is required`,
},
{
name: "testdata/entity_duplicate_attributes.yaml",
wantErr: `attribute host.name is already used by entity`,
},
{
name: "testdata/entity_duplicate_types.yaml",
wantErr: `duplicate entity type: host`,
},
{
name: "testdata/invalid_entity_stability.yaml",
wantErr: `unsupported stability level: "stable42"`,
},
{
name: "testdata/entity_relationships_bidirectional.yaml",
wantErr: `duplicate relationship to target "k8s.replicaset" (only one relationship allowed between two entities)`,
},
{
name: "testdata/entity_relationships_empty_type.yaml",
wantErr: `entity "k8s.pod": relationship type cannot be empty`,
},
{
name: "testdata/entity_relationships_empty_target.yaml",
wantErr: `entity "k8s.pod": relationship target cannot be empty`,
},
{
name: "testdata/entity_relationships_undefined_target.yaml",
wantErr: `entity "k8s.pod": relationship target "k8s.replicaset" does not exist`,
},
{
name: "testdata/entity_metric_missing_association.yaml",
wantErr: `metric "host.cpu.time": entity is required when entities are defined`,
},
{
name: "testdata/entity_event_missing_association.yaml",
wantErr: `event "host.restart": entity is required when entities are defined`,
},
{
name: "testdata/entity_undefined_reference.yaml",
wantErr: `metric "host.cpu.time": entity refers to undefined entity type: undefined_entity`,
},
{
name: "testdata/entity_single_metric_missing_association.yaml",
wantErr: `metric "host.cpu.time": entity is required when entities are defined`,
},
{
name: "testdata/entity_metrics_events_valid.yaml",
wantErr: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := LoadMetadata(tt.name)
if tt.wantErr != "" {
require.Error(t, err)
require.ErrorContains(t, err, tt.wantErr)
} else {
require.NoError(t, err)
}
})
}
}
func TestSupportsSignal(t *testing.T) {
md := Metadata{}
assert.False(t, md.supportsSignal("logs"))
}
func TestCodeCovID(t *testing.T) {
tests := []struct {
md Metadata
want string
}{
{
md: Metadata{
Type: "aes",
Status: &Status{
Class: "provider",
CodeCovComponentID: "my_custom_id",
},
},
want: "my_custom_id",
},
{
md: Metadata{
Type: "count",
Status: &Status{
Class: "connector",
},
},
want: "connector_count",
},
{
md: Metadata{
Type: "file",
Status: &Status{
Class: "exporter",
},
},
want: "exporter_file",
},
{
md: Metadata{
Type: "file_log_thing",
Status: &Status{
Class: "exporter",
},
},
want: "exporter_filelogthing",
},
}
for _, tt := range tests {
t.Run(tt.md.Type, func(t *testing.T) {
got := tt.md.GetCodeCovComponentID()
assert.Equal(t, tt.want, got)
})
}
}
func TestAttributeRequirementLevel(t *testing.T) {
tests := []struct {
name string
requirementLevel AttributeRequirementLevel
wantConditional bool
}{
{
name: "required",
requirementLevel: AttributeRequirementLevelRequired,
wantConditional: false,
},
{
name: "conditionally_required",
requirementLevel: AttributeRequirementLevelConditionallyRequired,
wantConditional: true,
},
{
name: "recommended",
requirementLevel: AttributeRequirementLevelRecommended,
wantConditional: false,
},
{
name: "opt_in",
requirementLevel: AttributeRequirementLevelOptIn,
wantConditional: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
attr := Attribute{RequirementLevel: tt.requirementLevel}
assert.Equal(t, tt.wantConditional, attr.IsConditional())
})
}
}
func TestAttributeRequirementLevelUnmarshalText(t *testing.T) {
tests := []struct {
name string
input string
want AttributeRequirementLevel
wantErr bool
}{
{
name: "required",
input: "required",
want: AttributeRequirementLevelRequired,
},
{
name: "conditionally_required",
input: "conditionally_required",
want: AttributeRequirementLevelConditionallyRequired,
},
{
name: "recommended",
input: "recommended",
want: AttributeRequirementLevelRecommended,
},
{
name: "opt_in",
input: "opt_in",
want: AttributeRequirementLevelOptIn,
},
{
name: "empty defaults to recommended",
input: "",
want: AttributeRequirementLevelRecommended,
},
{
name: "invalid value",
input: "invalid",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var rl AttributeRequirementLevel
err := rl.UnmarshalText([]byte(tt.input))
if tt.wantErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tt.want, rl)
})
}
}
func TestValidateFeatureGates(t *testing.T) {
tests := []struct {
name string
featureGate FeatureGate
wantErr string
}{
{
name: "valid alpha gate",
featureGate: FeatureGate{
ID: "component.feature",
Description: "Test feature gate",
Stage: FeatureGateStageAlpha,
FromVersion: "v0.100.0",
ReferenceURL: "https://example.com",
},
},
{
name: "valid stable gate with to_version",
featureGate: FeatureGate{
ID: "component.stable",
Description: "Stable feature gate",
Stage: FeatureGateStageStable,
FromVersion: "v0.90.0",
ToVersion: "v0.95.0",
ReferenceURL: "https://example.com",
},
},
{
name: "empty description",
featureGate: FeatureGate{
ID: "component.feature",
Stage: FeatureGateStageAlpha,
FromVersion: "v0.100.0",
},
wantErr: `description is required`,
},
{
name: "invalid stage",
featureGate: FeatureGate{
ID: "component.feature",
Description: "Test feature",
Stage: "invalid",
FromVersion: "v0.100.0",
},
wantErr: `invalid stage "invalid"`,
},
{
name: "missing from_version",
featureGate: FeatureGate{
ID: "component.feature",
Description: "Test feature",
Stage: FeatureGateStageAlpha,
},
wantErr: `from_version is required`,
},
{
name: "from_version without v prefix",
featureGate: FeatureGate{
ID: "component.feature",
Description: "Test feature",
Stage: FeatureGateStageAlpha,
FromVersion: "0.100.0",
},
wantErr: `from_version "0.100.0" must start with 'v'`,
},
{
name: "to_version without v prefix",
featureGate: FeatureGate{
ID: "component.feature",
Description: "Test feature",
Stage: FeatureGateStageStable,
FromVersion: "v0.90.0",
ToVersion: "0.95.0",
},
wantErr: `to_version "0.95.0" must start with 'v'`,
},
{
name: "stable gate missing to_version",
featureGate: FeatureGate{
ID: "component.feature",
Description: "Test feature",
Stage: FeatureGateStageStable,
FromVersion: "v0.90.0",
},
wantErr: `to_version is required for stable stage gates`,
},
{
name: "deprecated gate missing to_version",
featureGate: FeatureGate{
ID: "component.feature",
Description: "Test feature",
Stage: FeatureGateStageDeprecated,
FromVersion: "v0.90.0",
},
wantErr: `to_version is required for deprecated stage gates`,
},
{
name: "missing reference_url",
featureGate: FeatureGate{
ID: "component.feature",
Description: "Test feature",
Stage: FeatureGateStageAlpha,
FromVersion: "v0.100.0",
},
wantErr: `reference_url is required`,
},
{
name: "invalid characters in ID",
featureGate: FeatureGate{
ID: "component.feature@invalid",
Description: "Test feature",
Stage: FeatureGateStageAlpha,
FromVersion: "v0.100.0",
ReferenceURL: "https://example.com",
},
wantErr: `ID contains invalid characters`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
md := &Metadata{
FeatureGates: []FeatureGate{tt.featureGate},
}
err := md.validateFeatureGates()
if tt.wantErr != "" {
require.Error(t, err)
assert.ErrorContains(t, err, tt.wantErr)
} else {
require.NoError(t, err)
}
})
}
}
func TestValidateFeatureGatesEmptyID(t *testing.T) {
md := &Metadata{
FeatureGates: []FeatureGate{
{
Description: "Test",
Stage: FeatureGateStageAlpha,
},
},
}
err := md.validateFeatureGates()
require.Error(t, err)
assert.ErrorContains(t, err, "ID cannot be empty")
}
func TestValidateFeatureGatesDuplicateID(t *testing.T) {
md := &Metadata{
FeatureGates: []FeatureGate{
{
ID: "component.feature",
Description: "Test feature",
Stage: FeatureGateStageAlpha,
},
{
ID: "component.feature",
Description: "Duplicate feature",
Stage: FeatureGateStageAlpha,
},
},
}
err := md.validateFeatureGates()
require.Error(t, err)
assert.ErrorContains(t, err, "duplicate ID")
}
func TestValidateFeatureGatesNotSorted(t *testing.T) {
md := &Metadata{
FeatureGates: []FeatureGate{
{
ID: "component.zebra",
Description: "Test feature",
Stage: FeatureGateStageAlpha,
ReferenceURL: "https://example.com",
},
{
ID: "component.alpha",
Description: "Another feature",
Stage: FeatureGateStageAlpha,
ReferenceURL: "https://example.com",
},
},
}
err := md.validateFeatureGates()
require.Error(t, err)
assert.ErrorContains(t, err, "feature gates must be sorted by ID")
}
func TestValidateConfig(t *testing.T) {
tests := []struct {
name string
config *cfggen.ConfigMetadata
wantErr bool
}{
{
name: "valid config",
config: &cfggen.ConfigMetadata{
Type: "object",
AllOf: []*cfggen.ConfigMetadata{
{
Ref: "component.config",
},
},
},
wantErr: false,
},
{
name: "no config defined",
config: nil,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
md := &Metadata{
Type: "test",
Status: &Status{
Class: "exporter",
Stability: StabilityMap{
6: {"traces"},
},
},
Config: tt.config,
}
err := md.Validate()
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}
================================================
FILE: cmd/mdatagen/internal/metric.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/cmd/mdatagen/internal"
import (
"errors"
"fmt"
"regexp"
"strings"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"go.opentelemetry.io/collector/cmd/mdatagen/internal/helpers"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/pdata/pmetric"
)
var reNonAlnum = regexp.MustCompile(`[^a-z0-9_]+`)
type MetricName string
func (mn MetricName) Render() (string, error) {
return helpers.FormatIdentifier(string(mn), true)
}
func (mn MetricName) RenderUnexported() (string, error) {
return helpers.FormatIdentifier(string(mn), false)
}
type Metric struct {
Signal `mapstructure:",squash"`
// Optional can be used to specify metrics that may
// or may not be present in all cases, depending on configuration.
Optional bool `mapstructure:"optional"`
// Unit of the metric.
Unit *string `mapstructure:"unit"`
// Sum stores metadata for sum metric type
Sum *Sum `mapstructure:"sum,omitempty"`
// Gauge stores metadata for gauge metric type
Gauge *Gauge `mapstructure:"gauge,omitempty"`
// Histogram stores metadata for histogram metric type
Histogram *Histogram `mapstructure:"histogram,omitempty"`
// Override the default prefix for the metric name.
Prefix string `mapstructure:"prefix"`
// Deprecation metadata for deprecated metrics
Deprecated *Deprecated `mapstructure:"deprecated,omitempty"`
}
func (m *Metric) validate(metricName MetricName, semConvVersion string) error {
var errs error
if m.Deprecated != nil {
if m.Stability != component.StabilityLevelDeprecated {
errs = errors.Join(errs, errors.New("`stability` must be `deprecated` when specifying a `deprecated` field"))
}
if err := m.Deprecated.validate(); err != nil {
errs = errors.Join(errs, err)
}
}
if m.Sum == nil && m.Gauge == nil && m.Histogram == nil {
errs = errors.Join(errs, errors.New("missing metric type key, "+
"one of the following has to be specified: sum, gauge, histogram"))
}
if (m.Sum != nil && m.Gauge != nil) || (m.Sum != nil && m.Histogram != nil) || (m.Gauge != nil && m.Histogram != nil) {
errs = errors.Join(errs, errors.New("more than one metric type keys, "+
"only one of the following has to be specified: sum, gauge, histogram"))
}
if m.Stability == component.StabilityLevelUndefined {
errs = errors.Join(errs, errors.New("missing required field: `stability.level`"))
}
if m.Description == "" {
errs = errors.Join(errs, errors.New(`missing metric description`))
}
if m.Unit == nil {
errs = errors.Join(errs, errors.New(`missing metric unit`))
}
if m.Sum != nil {
errs = errors.Join(errs, m.Sum.Validate())
}
if m.Gauge != nil {
errs = errors.Join(errs, m.Gauge.Validate())
}
if m.SemanticConvention != nil {
if err := validateSemConvMetricURL(m.SemanticConvention.SemanticConventionRef, semConvVersion, string(metricName)); err != nil {
errs = errors.Join(errs, err)
}
}
return errs
}
func metricAnchor(metricName string) string {
m := strings.ToLower(strings.TrimSpace(metricName))
m = reNonAlnum.ReplaceAllString(m, "")
return "metric-" + m
}
// validateSemConvMetricURL verifies the URL matches exactly:
// https://github.com/open-telemetry/semantic-conventions/blob//*#metric-
func validateSemConvMetricURL(rawURL, semConvVersion, metricName string) error {
if strings.TrimSpace(rawURL) == "" {
return errors.New("url is empty")
}
if strings.TrimSpace(semConvVersion) == "" {
return errors.New("semConvVersion is empty")
}
if strings.TrimSpace(metricName) == "" {
return errors.New("metricName is empty")
}
semConvVersion = "v" + semConvVersion
anchor := metricAnchor(metricName)
// Build a strict regex that enforces https, repo, blob, given version, any doc path, and exact anchor.
pattern := fmt.Sprintf(`^https://github\.com/open-telemetry/semantic-conventions/blob/%s/[^#\s]+#%s$`,
semConvVersion,
anchor,
)
re := regexp.MustCompile(pattern)
if !re.MatchString(rawURL) {
return fmt.Errorf(
"invalid semantic-conventions URL: want https://github.com/open-telemetry/semantic-conventions/blob/%s/*#%s, got %q",
semConvVersion, anchor, rawURL)
}
return nil
}
func (m *Metric) Unmarshal(parser *confmap.Conf) error {
if !parser.IsSet("enabled") {
return errors.New("missing required field: `enabled`")
}
if !parser.IsSet("stability") {
return errors.New("missing required field: `stability`")
}
return parser.Unmarshal(m)
}
func (m Metric) Data() MetricData {
if m.Sum != nil {
return m.Sum
}
if m.Gauge != nil {
return m.Gauge
}
if m.Histogram != nil {
return m.Histogram
}
return nil
}
// MetricData is generic interface for all metric datatypes.
type MetricData interface {
Type() string
HasMonotonic() bool
HasAggregated() bool
HasMetricInputType() bool
Instrument() string
IsAsync() bool
}
// AggregationTemporality defines a metric aggregation type.
type AggregationTemporality struct {
// Aggregation describes if the aggregator reports delta changes
// since last report time, or cumulative changes since a fixed start time.
Aggregation pmetric.AggregationTemporality
}
// UnmarshalText implements the encoding.TextUnmarshaler interface.
func (agg *AggregationTemporality) UnmarshalText(text []byte) error {
switch vtStr := string(text); vtStr {
case "cumulative":
agg.Aggregation = pmetric.AggregationTemporalityCumulative
case "delta":
agg.Aggregation = pmetric.AggregationTemporalityDelta
default:
return fmt.Errorf("invalid aggregation: %q", vtStr)
}
return nil
}
// String returns string representation of the aggregation temporality.
func (agg *AggregationTemporality) String() string {
return agg.Aggregation.String()
}
// Mono defines the metric monotonicity.
type Mono struct {
// Monotonic is true if the sum is monotonic.
Monotonic bool `mapstructure:"monotonic"`
}
// MetricInputType defines the metric input value type
type MetricInputType struct {
// InputType is the type the metric needs to be parsed from, options are "string"
InputType string `mapstructure:"input_type"`
}
func (mit MetricInputType) HasMetricInputType() bool {
return mit.InputType != ""
}
// String returns name of the datapoint type.
func (mit MetricInputType) String() string {
return mit.InputType
}
func (mit MetricInputType) Validate() error {
if mit.InputType != "" && mit.InputType != "string" {
return fmt.Errorf("invalid `input_type` value \"%v\", must be \"\" or \"string\"", mit.InputType)
}
return nil
}
// MetricValueType defines the metric number type.
type MetricValueType struct {
// ValueType is type of the metric number, options are "double", "int".
ValueType pmetric.NumberDataPointValueType
}
func (mvt *MetricValueType) Unmarshal(parser *confmap.Conf) error {
if !parser.IsSet("value_type") {
return errors.New("missing required field: `value_type`")
}
return nil
}
// UnmarshalText implements the encoding.TextUnmarshaler interface.
func (mvt *MetricValueType) UnmarshalText(text []byte) error {
switch vtStr := string(text); vtStr {
case "int":
mvt.ValueType = pmetric.NumberDataPointValueTypeInt
case "double":
mvt.ValueType = pmetric.NumberDataPointValueTypeDouble
default:
return fmt.Errorf("invalid value_type: %q", vtStr)
}
return nil
}
// String returns name of the datapoint type.
func (mvt MetricValueType) String() string {
return mvt.ValueType.String()
}
// BasicType returns name of a golang basic type for the datapoint type.
func (mvt MetricValueType) BasicType() string {
switch mvt.ValueType {
case pmetric.NumberDataPointValueTypeInt:
return "int64"
case pmetric.NumberDataPointValueTypeDouble:
return "float64"
case pmetric.NumberDataPointValueTypeEmpty:
return ""
default:
return ""
}
}
var _ MetricData = (*Gauge)(nil)
type Gauge struct {
MetricValueType `mapstructure:"value_type"`
MetricInputType `mapstructure:",squash"`
Async bool `mapstructure:"async,omitempty"`
}
// Unmarshal is a custom unmarshaler for gauge. Needed mostly to avoid MetricValueType.Unmarshal inheritance.
func (d *Gauge) Unmarshal(parser *confmap.Conf) error {
if err := d.MetricValueType.Unmarshal(parser); err != nil {
return err
}
return parser.Unmarshal(d, confmap.WithIgnoreUnused())
}
func (d *Gauge) Type() string {
return "Gauge"
}
func (d *Gauge) HasMonotonic() bool {
return false
}
func (d *Gauge) HasAggregated() bool {
return false
}
func (d *Gauge) Instrument() string {
instrumentName := cases.Title(language.English).String(d.BasicType())
if d.Async {
instrumentName += "Observable"
}
instrumentName += "Gauge"
return instrumentName
}
func (d *Gauge) IsAsync() bool {
return d.Async
}
var _ MetricData = (*Sum)(nil)
type Sum struct {
AggregationTemporality `mapstructure:"aggregation_temporality"`
Mono `mapstructure:",squash"`
MetricValueType `mapstructure:"value_type"`
MetricInputType `mapstructure:",squash"`
Async bool `mapstructure:"async,omitempty"`
}
// Unmarshal is a custom unmarshaler for sum. Needed mostly to avoid MetricValueType.Unmarshal inheritance.
func (d *Sum) Unmarshal(parser *confmap.Conf) error {
if err := d.MetricValueType.Unmarshal(parser); err != nil {
return err
}
return parser.Unmarshal(d, confmap.WithIgnoreUnused())
}
// TODO: Currently, this func will not be called because of https://github.com/open-telemetry/opentelemetry-collector/issues/6671. Uncomment function and
// add a test case to Test_LoadMetadata for file no_monotonic.yaml once the issue is solved.
//
// Unmarshal is a custom unmarshaler for Mono.
// func (m *Mono) Unmarshal(parser *confmap.Conf) error {
// if !parser.IsSet("monotonic") {
// return errors.New("missing required field: `monotonic`")
// }
// return parser.Unmarshal(m)
// }
func (d *Sum) Type() string {
return "Sum"
}
func (d *Sum) HasMonotonic() bool {
return true
}
func (d *Sum) HasAggregated() bool {
return true
}
func (d *Sum) Instrument() string {
instrumentName := cases.Title(language.English).String(d.BasicType())
if d.Async {
instrumentName += "Observable"
}
if !d.Monotonic {
instrumentName += "UpDown"
}
instrumentName += "Counter"
return instrumentName
}
func (d *Sum) IsAsync() bool {
return d.Async
}
var _ MetricData = (*Histogram)(nil)
type Histogram struct {
AggregationTemporality `mapstructure:"aggregation_temporality"`
Mono `mapstructure:",squash"`
MetricValueType `mapstructure:"value_type"`
MetricInputType `mapstructure:",squash"`
Async bool `mapstructure:"async,omitempty"`
Boundaries []float64 `mapstructure:"bucket_boundaries"`
}
func (d *Histogram) Type() string {
return "Histogram"
}
func (d *Histogram) HasMonotonic() bool {
return false
}
func (d *Histogram) HasAggregated() bool {
return true
}
func (d *Histogram) Instrument() string {
instrumentName := cases.Title(language.English).String(d.BasicType())
return instrumentName + d.Type()
}
// Unmarshal is a custom unmarshaler for histogram. Needed mostly to avoid MetricValueType.Unmarshal inheritance.
func (d *Histogram) Unmarshal(parser *confmap.Conf) error {
if err := d.MetricValueType.Unmarshal(parser); err != nil {
return err
}
return parser.Unmarshal(d, confmap.WithIgnoreUnused())
}
func (d *Histogram) IsAsync() bool {
return d.Async
}
================================================
FILE: cmd/mdatagen/internal/metric_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/pmetric"
)
func TestMetricData(t *testing.T) {
for _, arg := range []struct {
metricData MetricData
wantType string
wantHasAggregated bool
wantHasMonotonic bool
wantInstrument string
wantAsync bool
}{
{&Gauge{}, "Gauge", false, false, "Gauge", false},
{&Gauge{Async: true}, "Gauge", false, false, "ObservableGauge", true},
{&Gauge{MetricValueType: MetricValueType{pmetric.NumberDataPointValueTypeInt}, Async: true}, "Gauge", false, false, "Int64ObservableGauge", true},
{&Gauge{MetricValueType: MetricValueType{pmetric.NumberDataPointValueTypeDouble}, Async: true}, "Gauge", false, false, "Float64ObservableGauge", true},
{&Sum{}, "Sum", true, true, "UpDownCounter", false},
{&Sum{Mono: Mono{true}}, "Sum", true, true, "Counter", false},
{&Sum{Async: true}, "Sum", true, true, "ObservableUpDownCounter", true},
{&Sum{MetricValueType: MetricValueType{pmetric.NumberDataPointValueTypeInt}, Async: true}, "Sum", true, true, "Int64ObservableUpDownCounter", true},
{&Sum{MetricValueType: MetricValueType{pmetric.NumberDataPointValueTypeDouble}, Async: true}, "Sum", true, true, "Float64ObservableUpDownCounter", true},
{&Histogram{}, "Histogram", true, false, "Histogram", false},
} {
assert.Equal(t, arg.wantType, arg.metricData.Type())
assert.Equal(t, arg.wantHasAggregated, arg.metricData.HasAggregated())
assert.Equal(t, arg.wantHasMonotonic, arg.metricData.HasMonotonic())
assert.Equal(t, arg.wantInstrument, arg.metricData.Instrument())
assert.Equal(t, arg.wantAsync, arg.metricData.IsAsync())
}
}
================================================
FILE: cmd/mdatagen/internal/sampleconnector/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Generate a test metrics builder from a sample metrics set covering all configuration options.
//go:generate mdatagen metadata.yaml
package sampleconnector // import "go.opentelemetry.io/collector/cmd/mdatagen/internal/sampleconnector"
================================================
FILE: cmd/mdatagen/internal/sampleconnector/documentation.md
================================================
[comment]: <> (Code generated by mdatagen. DO NOT EDIT.)
# sample
## Default Metrics
The following metrics are emitted by default. Each of them can be disabled by applying the following configuration:
```yaml
metrics:
:
enabled: false
```
### default.metric
Monotonic cumulative sum int metric enabled by default.
The metric will be become optional soon.
| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability |
| ---- | ----------- | ---------- | ----------------------- | --------- | --------- |
| s | Sum | Int | Cumulative | true | Development |
#### Attributes
| Name | Description | Values | Requirement Level |
| ---- | ----------- | ------ | -------- |
| string_attr | Attribute with any string value. | Any Str | Recommended |
| state | Integer attribute with overridden name. | Any Int | Recommended |
| enum_attr | Attribute with a known set of string values. | Str: ``red``, ``green``, ``blue`` | Recommended |
| slice_attr | Attribute with a slice value. | Any Slice | Recommended |
| map_attr | Attribute with a map value. | Any Map | Recommended |
### default.metric.to_be_removed
[DEPRECATED] Non-monotonic delta sum double metric enabled by default.
The metric will be removed soon.
| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability |
| ---- | ----------- | ---------- | ----------------------- | --------- | --------- |
| s | Sum | Double | Delta | false | Deprecated since 1.0.0 |
**Deprecation note**: This metric will be removed
### metric.input_type
Monotonic cumulative sum int metric with string input_type enabled by default.
| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability |
| ---- | ----------- | ---------- | ----------------------- | --------- | --------- |
| s | Sum | Int | Cumulative | true | Development |
#### Attributes
| Name | Description | Values | Requirement Level |
| ---- | ----------- | ------ | -------- |
| string_attr | Attribute with any string value. | Any Str | Recommended |
| state | Integer attribute with overridden name. | Any Int | Recommended |
| enum_attr | Attribute with a known set of string values. | Str: ``red``, ``green``, ``blue`` | Recommended |
| slice_attr | Attribute with a slice value. | Any Slice | Recommended |
| map_attr | Attribute with a map value. | Any Map | Recommended |
### reaggregate.metric
Metric for testing spatial reaggregation
| Unit | Metric Type | Value Type | Stability |
| ---- | ----------- | ---------- | --------- |
| 1 | Gauge | Double | Beta |
#### Attributes
| Name | Description | Values | Requirement Level |
| ---- | ----------- | ------ | -------- |
| string_attr | Attribute with any string value. | Any Str | Recommended |
| boolean_attr | Attribute with a boolean value. | Any Bool | Recommended |
## Optional Metrics
The following metrics are not emitted by default. Each of them can be enabled by applying the following configuration:
```yaml
metrics:
:
enabled: true
```
### optional.metric
[DEPRECATED] Gauge double metric disabled by default.
| Unit | Metric Type | Value Type | Stability |
| ---- | ----------- | ---------- | --------- |
| 1 | Gauge | Double | Deprecated since 1.0.0 |
**Deprecation note**: This metric will be removed
#### Attributes
| Name | Description | Values | Requirement Level |
| ---- | ----------- | ------ | -------- |
| string_attr | Attribute with any string value. | Any Str | Recommended |
| boolean_attr | Attribute with a boolean value. | Any Bool | Recommended |
| boolean_attr2 | Another attribute with a boolean value. | Any Bool | Recommended |
### optional.metric.empty_unit
[DEPRECATED] Gauge double metric disabled by default.
| Unit | Metric Type | Value Type | Stability |
| ---- | ----------- | ---------- | --------- |
| | Gauge | Double | Deprecated since 1.0.0 |
**Deprecation note**: This metric will be removed
#### Attributes
| Name | Description | Values | Requirement Level |
| ---- | ----------- | ------ | -------- |
| string_attr | Attribute with any string value. | Any Str | Recommended |
| boolean_attr | Attribute with a boolean value. | Any Bool | Recommended |
## Resource Attributes
| Name | Description | Values | Enabled |
| ---- | ----------- | ------ | ------- |
| map.resource.attr | Resource attribute with a map value. | Any Map | true |
| optional.resource.attr | Explicitly disabled ResourceAttribute. | Any Str | false |
| slice.resource.attr | Resource attribute with a slice value. | Any Slice | true |
| string.enum.resource.attr | Resource attribute with a known set of string values. | Str: ``one``, ``two`` | true |
| string.resource.attr | Resource attribute with any string value. | Any Str | true |
| string.resource.attr_disable_warning | Resource attribute with any string value. | Any Str | true |
| string.resource.attr_remove_warning | Resource attribute with any string value. | Any Str | false |
| string.resource.attr_to_be_removed | Resource attribute with any string value. | Any Str | true |
## Entities
The following entities are defined for this component:
### test.entity
A test entity.
**Stability:** Stable
**Identifying Attributes:**
- `string.resource.attr`
**Descriptive Attributes:**
- `map.resource.attr`
================================================
FILE: cmd/mdatagen/internal/sampleconnector/factory.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sampleconnector // import "go.opentelemetry.io/collector/cmd/mdatagen/internal/sampleconnector"
import (
"context"
"go.opentelemetry.io/collector/cmd/mdatagen/internal/sampleconnector/internal/metadata"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/connector"
"go.opentelemetry.io/collector/connector/xconnector"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/pprofile"
)
// NewFactory returns a connector.Factory for sample connector.
func NewFactory() connector.Factory {
return xconnector.NewFactory(
metadata.Type,
func() component.Config { return &struct{}{} },
xconnector.WithMetricsToMetrics(createMetricsToMetricsConnector, metadata.MetricsToMetricsStability),
xconnector.WithProfilesToProfiles(createProfilesToProfilesConnector, metadata.ProfilesToProfilesStability),
)
}
func createMetricsToMetricsConnector(context.Context, connector.Settings, component.Config, consumer.Metrics) (connector.Metrics, error) {
return nopInstance, nil
}
func createProfilesToProfilesConnector(context.Context, connector.Settings, component.Config, xconsumer.Profiles) (xconnector.Profiles, error) {
return nopInstance, nil
}
var nopInstance = &nopConnector{}
type nopConnector struct {
component.StartFunc
component.ShutdownFunc
}
func (n nopConnector) Capabilities() consumer.Capabilities {
return consumer.Capabilities{MutatesData: false}
}
func (n nopConnector) ConsumeMetrics(context.Context, pmetric.Metrics) error {
return nil
}
func (n nopConnector) ConsumeProfiles(context.Context, pprofile.Profiles) error {
return nil
}
================================================
FILE: cmd/mdatagen/internal/sampleconnector/generated_component_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
//go:build !freebsd && !illumos
package sampleconnector
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/confmap/confmaptest"
"go.opentelemetry.io/collector/connector"
"go.opentelemetry.io/collector/connector/connectortest"
"go.opentelemetry.io/collector/connector/xconnector"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/pipeline/xpipeline"
)
var typ = component.MustNewType("sample")
func TestComponentFactoryType(t *testing.T) {
require.Equal(t, typ, NewFactory().Type())
}
func TestComponentConfigStruct(t *testing.T) {
require.NoError(t, componenttest.CheckConfigStruct(NewFactory().CreateDefaultConfig()))
}
func TestComponentLifecycle(t *testing.T) {
factory := NewFactory()
tests := []struct {
createFn func(ctx context.Context, set connector.Settings, cfg component.Config) (component.Component, error)
name string
}{
{
name: "metrics_to_metrics",
createFn: func(ctx context.Context, set connector.Settings, cfg component.Config) (component.Component, error) {
router := connector.NewMetricsRouter(map[pipeline.ID]consumer.Metrics{pipeline.NewID(pipeline.SignalMetrics): consumertest.NewNop()})
return factory.CreateMetricsToMetrics(ctx, set, cfg, router)
},
},
{
name: "profiles_to_profiles",
createFn: func(ctx context.Context, set connector.Settings, cfg component.Config) (component.Component, error) {
router := xconnector.NewProfilesRouter(map[pipeline.ID]xconsumer.Profiles{pipeline.NewID(xpipeline.SignalProfiles): consumertest.NewNop()})
return factory.(xconnector.Factory).CreateProfilesToProfiles(ctx, set, cfg, router)
},
},
}
cm, err := confmaptest.LoadConf("metadata.yaml")
require.NoError(t, err)
cfg := factory.CreateDefaultConfig()
sub, err := cm.Sub("tests::config")
require.NoError(t, err)
require.NoError(t, sub.Unmarshal(&cfg))
for _, tt := range tests {
t.Run(tt.name+"-shutdown", func(t *testing.T) {
c, err := tt.createFn(context.Background(), connectortest.NewNopSettings(typ), cfg)
require.NoError(t, err)
err = c.Shutdown(context.Background())
require.NoError(t, err)
})
t.Run(tt.name+"-lifecycle", func(t *testing.T) {
firstConnector, err := tt.createFn(context.Background(), connectortest.NewNopSettings(typ), cfg)
require.NoError(t, err)
host := newMdatagenNopHost()
require.NoError(t, err)
require.NoError(t, firstConnector.Start(context.Background(), host))
require.NoError(t, firstConnector.Shutdown(context.Background()))
secondConnector, err := tt.createFn(context.Background(), connectortest.NewNopSettings(typ), cfg)
require.NoError(t, err)
require.NoError(t, secondConnector.Start(context.Background(), host))
require.NoError(t, secondConnector.Shutdown(context.Background()))
})
}
}
var _ component.Host = (*mdatagenNopHost)(nil)
type mdatagenNopHost struct{}
func newMdatagenNopHost() component.Host {
return &mdatagenNopHost{}
}
func (mnh *mdatagenNopHost) GetExtensions() map[component.ID]component.Component {
return nil
}
func (mnh *mdatagenNopHost) GetFactory(_ component.Kind, _ component.Type) component.Factory {
return nil
}
================================================
FILE: cmd/mdatagen/internal/sampleconnector/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package sampleconnector
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: cmd/mdatagen/internal/sampleconnector/internal/metadata/config.schema.yaml
================================================
# Code generated by mdatagen. DO NOT EDIT.
$defs:
metrics_config:
description: MetricsConfig provides config for sample metrics.
type: object
properties:
default.metric:
description: "DefaultMetricMetricConfig provides config for the default.metric metric."
type: object
properties:
enabled:
type: boolean
default: true
aggregation_strategy:
type: string
enum:
- "sum"
- "avg"
- "min"
- "max"
default: "sum"
attributes:
type: array
items:
type: string
enum:
- "string_attr"
- "state"
- "enum_attr"
- "slice_attr"
- "map_attr"
default:
- "string_attr"
- "state"
- "enum_attr"
- "slice_attr"
- "map_attr"
default.metric.to_be_removed:
description: "DefaultMetricToBeRemovedMetricConfig provides config for the default.metric.to_be_removed metric."
type: object
properties:
enabled:
type: boolean
default: true
metric.input_type:
description: "MetricInputTypeMetricConfig provides config for the metric.input_type metric."
type: object
properties:
enabled:
type: boolean
default: true
aggregation_strategy:
type: string
enum:
- "sum"
- "avg"
- "min"
- "max"
default: "sum"
attributes:
type: array
items:
type: string
enum:
- "string_attr"
- "state"
- "enum_attr"
- "slice_attr"
- "map_attr"
default:
- "string_attr"
- "state"
- "enum_attr"
- "slice_attr"
- "map_attr"
optional.metric:
description: "OptionalMetricMetricConfig provides config for the optional.metric metric."
type: object
properties:
enabled:
type: boolean
default: false
aggregation_strategy:
type: string
enum:
- "sum"
- "avg"
- "min"
- "max"
default: "avg"
attributes:
type: array
items:
type: string
enum:
- "string_attr"
- "boolean_attr"
- "boolean_attr2"
default:
- "string_attr"
- "boolean_attr"
- "boolean_attr2"
optional.metric.empty_unit:
description: "OptionalMetricEmptyUnitMetricConfig provides config for the optional.metric.empty_unit metric."
type: object
properties:
enabled:
type: boolean
default: false
aggregation_strategy:
type: string
enum:
- "sum"
- "avg"
- "min"
- "max"
default: "avg"
attributes:
type: array
items:
type: string
enum:
- "string_attr"
- "boolean_attr"
default:
- "string_attr"
- "boolean_attr"
reaggregate.metric:
description: "ReaggregateMetricMetricConfig provides config for the reaggregate.metric metric."
type: object
properties:
enabled:
type: boolean
default: true
aggregation_strategy:
type: string
enum:
- "sum"
- "avg"
- "min"
- "max"
default: "avg"
attributes:
type: array
items:
type: string
enum:
- "string_attr"
- "boolean_attr"
default:
- "string_attr"
- "boolean_attr"
resource_attributes_config:
description: ResourceAttributesConfig provides config for sample resource attributes.
type: object
properties:
map.resource.attr:
description: ResourceAttributeConfig provides common config for a map.resource.attr resource attribute.
type: object
properties:
enabled:
type: boolean
default: true
metrics_include:
description: "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
metrics_exclude:
description: "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude."
type: array
items:
$ref: /filter.config
optional.resource.attr:
description: ResourceAttributeConfig provides common config for a optional.resource.attr resource attribute.
type: object
properties:
enabled:
type: boolean
default: false
metrics_include:
description: "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
metrics_exclude:
description: "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude."
type: array
items:
$ref: /filter.config
slice.resource.attr:
description: ResourceAttributeConfig provides common config for a slice.resource.attr resource attribute.
type: object
properties:
enabled:
type: boolean
default: true
metrics_include:
description: "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
metrics_exclude:
description: "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude."
type: array
items:
$ref: /filter.config
string.enum.resource.attr:
description: ResourceAttributeConfig provides common config for a string.enum.resource.attr resource attribute.
type: object
properties:
enabled:
type: boolean
default: true
metrics_include:
description: "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
metrics_exclude:
description: "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude."
type: array
items:
$ref: /filter.config
string.resource.attr:
description: ResourceAttributeConfig provides common config for a string.resource.attr resource attribute.
type: object
properties:
enabled:
type: boolean
default: true
metrics_include:
description: "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
metrics_exclude:
description: "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude."
type: array
items:
$ref: /filter.config
string.resource.attr_disable_warning:
description: ResourceAttributeConfig provides common config for a string.resource.attr_disable_warning resource attribute.
type: object
properties:
enabled:
type: boolean
default: true
metrics_include:
description: "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
metrics_exclude:
description: "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude."
type: array
items:
$ref: /filter.config
string.resource.attr_remove_warning:
description: ResourceAttributeConfig provides common config for a string.resource.attr_remove_warning resource attribute.
type: object
properties:
enabled:
type: boolean
default: false
metrics_include:
description: "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
metrics_exclude:
description: "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude."
type: array
items:
$ref: /filter.config
string.resource.attr_to_be_removed:
description: ResourceAttributeConfig provides common config for a string.resource.attr_to_be_removed resource attribute.
type: object
properties:
enabled:
type: boolean
default: true
metrics_include:
description: "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
metrics_exclude:
description: "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude."
type: array
items:
$ref: /filter.config
metrics_builder_config:
description: MetricsBuilderConfig is a configuration for sample metrics builder.
type: object
properties:
metrics:
$ref: metrics_config
resource_attributes:
$ref: resource_attributes_config
================================================
FILE: cmd/mdatagen/internal/sampleconnector/internal/metadata/generated_config.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"fmt"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/filter"
)
// DefaultMetricMetricAttributeKey specifies the key of an attribute for the default.metric metric.
type DefaultMetricMetricAttributeKey string
const (
DefaultMetricMetricAttributeKeyStringAttr DefaultMetricMetricAttributeKey = "string_attr"
DefaultMetricMetricAttributeKeyOverriddenIntAttr DefaultMetricMetricAttributeKey = "state"
DefaultMetricMetricAttributeKeyEnumAttr DefaultMetricMetricAttributeKey = "enum_attr"
DefaultMetricMetricAttributeKeySliceAttr DefaultMetricMetricAttributeKey = "slice_attr"
DefaultMetricMetricAttributeKeyMapAttr DefaultMetricMetricAttributeKey = "map_attr"
)
// DefaultMetricMetricConfig provides config for the default.metric metric.
type DefaultMetricMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []DefaultMetricMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *DefaultMetricMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *DefaultMetricMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case DefaultMetricMetricAttributeKeyStringAttr, DefaultMetricMetricAttributeKeyOverriddenIntAttr, DefaultMetricMetricAttributeKeyEnumAttr, DefaultMetricMetricAttributeKeySliceAttr, DefaultMetricMetricAttributeKeyMapAttr:
default:
return fmt.Errorf("metric default.metric doesn't have an attribute %v, valid attributes: [string_attr, state, enum_attr, slice_attr, map_attr]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// DefaultMetricToBeRemovedMetricConfig provides config for the default.metric.to_be_removed metric.
type DefaultMetricToBeRemovedMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
}
func (ms *DefaultMetricToBeRemovedMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
// MetricInputTypeMetricAttributeKey specifies the key of an attribute for the metric.input_type metric.
type MetricInputTypeMetricAttributeKey string
const (
MetricInputTypeMetricAttributeKeyStringAttr MetricInputTypeMetricAttributeKey = "string_attr"
MetricInputTypeMetricAttributeKeyOverriddenIntAttr MetricInputTypeMetricAttributeKey = "state"
MetricInputTypeMetricAttributeKeyEnumAttr MetricInputTypeMetricAttributeKey = "enum_attr"
MetricInputTypeMetricAttributeKeySliceAttr MetricInputTypeMetricAttributeKey = "slice_attr"
MetricInputTypeMetricAttributeKeyMapAttr MetricInputTypeMetricAttributeKey = "map_attr"
)
// MetricInputTypeMetricConfig provides config for the metric.input_type metric.
type MetricInputTypeMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MetricInputTypeMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MetricInputTypeMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MetricInputTypeMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MetricInputTypeMetricAttributeKeyStringAttr, MetricInputTypeMetricAttributeKeyOverriddenIntAttr, MetricInputTypeMetricAttributeKeyEnumAttr, MetricInputTypeMetricAttributeKeySliceAttr, MetricInputTypeMetricAttributeKeyMapAttr:
default:
return fmt.Errorf("metric metric.input_type doesn't have an attribute %v, valid attributes: [string_attr, state, enum_attr, slice_attr, map_attr]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// OptionalMetricMetricAttributeKey specifies the key of an attribute for the optional.metric metric.
type OptionalMetricMetricAttributeKey string
const (
OptionalMetricMetricAttributeKeyStringAttr OptionalMetricMetricAttributeKey = "string_attr"
OptionalMetricMetricAttributeKeyBooleanAttr OptionalMetricMetricAttributeKey = "boolean_attr"
OptionalMetricMetricAttributeKeyBooleanAttr2 OptionalMetricMetricAttributeKey = "boolean_attr2"
)
// OptionalMetricMetricConfig provides config for the optional.metric metric.
type OptionalMetricMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []OptionalMetricMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *OptionalMetricMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *OptionalMetricMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case OptionalMetricMetricAttributeKeyStringAttr, OptionalMetricMetricAttributeKeyBooleanAttr, OptionalMetricMetricAttributeKeyBooleanAttr2:
default:
return fmt.Errorf("metric optional.metric doesn't have an attribute %v, valid attributes: [string_attr, boolean_attr, boolean_attr2]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// OptionalMetricEmptyUnitMetricAttributeKey specifies the key of an attribute for the optional.metric.empty_unit metric.
type OptionalMetricEmptyUnitMetricAttributeKey string
const (
OptionalMetricEmptyUnitMetricAttributeKeyStringAttr OptionalMetricEmptyUnitMetricAttributeKey = "string_attr"
OptionalMetricEmptyUnitMetricAttributeKeyBooleanAttr OptionalMetricEmptyUnitMetricAttributeKey = "boolean_attr"
)
// OptionalMetricEmptyUnitMetricConfig provides config for the optional.metric.empty_unit metric.
type OptionalMetricEmptyUnitMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []OptionalMetricEmptyUnitMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *OptionalMetricEmptyUnitMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *OptionalMetricEmptyUnitMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case OptionalMetricEmptyUnitMetricAttributeKeyStringAttr, OptionalMetricEmptyUnitMetricAttributeKeyBooleanAttr:
default:
return fmt.Errorf("metric optional.metric.empty_unit doesn't have an attribute %v, valid attributes: [string_attr, boolean_attr]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// ReaggregateMetricMetricAttributeKey specifies the key of an attribute for the reaggregate.metric metric.
type ReaggregateMetricMetricAttributeKey string
const (
ReaggregateMetricMetricAttributeKeyStringAttr ReaggregateMetricMetricAttributeKey = "string_attr"
ReaggregateMetricMetricAttributeKeyBooleanAttr ReaggregateMetricMetricAttributeKey = "boolean_attr"
)
// ReaggregateMetricMetricConfig provides config for the reaggregate.metric metric.
type ReaggregateMetricMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []ReaggregateMetricMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *ReaggregateMetricMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *ReaggregateMetricMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case ReaggregateMetricMetricAttributeKeyStringAttr, ReaggregateMetricMetricAttributeKeyBooleanAttr:
default:
return fmt.Errorf("metric reaggregate.metric doesn't have an attribute %v, valid attributes: [string_attr, boolean_attr]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MetricsConfig provides config for sample metrics.
type MetricsConfig struct {
DefaultMetric DefaultMetricMetricConfig `mapstructure:"default.metric"`
DefaultMetricToBeRemoved DefaultMetricToBeRemovedMetricConfig `mapstructure:"default.metric.to_be_removed"`
MetricInputType MetricInputTypeMetricConfig `mapstructure:"metric.input_type"`
OptionalMetric OptionalMetricMetricConfig `mapstructure:"optional.metric"`
OptionalMetricEmptyUnit OptionalMetricEmptyUnitMetricConfig `mapstructure:"optional.metric.empty_unit"`
ReaggregateMetric ReaggregateMetricMetricConfig `mapstructure:"reaggregate.metric"`
}
func DefaultMetricsConfig() MetricsConfig {
return MetricsConfig{
DefaultMetric: DefaultMetricMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategySum,
EnabledAttributes: []DefaultMetricMetricAttributeKey{DefaultMetricMetricAttributeKeyStringAttr, DefaultMetricMetricAttributeKeyOverriddenIntAttr, DefaultMetricMetricAttributeKeyEnumAttr, DefaultMetricMetricAttributeKeySliceAttr, DefaultMetricMetricAttributeKeyMapAttr},
},
DefaultMetricToBeRemoved: DefaultMetricToBeRemovedMetricConfig{
Enabled: true,
},
MetricInputType: MetricInputTypeMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategySum,
EnabledAttributes: []MetricInputTypeMetricAttributeKey{MetricInputTypeMetricAttributeKeyStringAttr, MetricInputTypeMetricAttributeKeyOverriddenIntAttr, MetricInputTypeMetricAttributeKeyEnumAttr, MetricInputTypeMetricAttributeKeySliceAttr, MetricInputTypeMetricAttributeKeyMapAttr},
},
OptionalMetric: OptionalMetricMetricConfig{
Enabled: false,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []OptionalMetricMetricAttributeKey{OptionalMetricMetricAttributeKeyStringAttr, OptionalMetricMetricAttributeKeyBooleanAttr, OptionalMetricMetricAttributeKeyBooleanAttr2},
},
OptionalMetricEmptyUnit: OptionalMetricEmptyUnitMetricConfig{
Enabled: false,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []OptionalMetricEmptyUnitMetricAttributeKey{OptionalMetricEmptyUnitMetricAttributeKeyStringAttr, OptionalMetricEmptyUnitMetricAttributeKeyBooleanAttr},
},
ReaggregateMetric: ReaggregateMetricMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []ReaggregateMetricMetricAttributeKey{ReaggregateMetricMetricAttributeKeyStringAttr, ReaggregateMetricMetricAttributeKeyBooleanAttr},
},
}
}
// ResourceAttributeConfig provides common config for a particular resource attribute.
type ResourceAttributeConfig struct {
Enabled bool `mapstructure:"enabled"`
// Experimental: MetricsInclude defines a list of filters for attribute values.
// If the list is not empty, only metrics with matching resource attribute values will be emitted.
MetricsInclude []filter.Config `mapstructure:"metrics_include"`
// Experimental: MetricsExclude defines a list of filters for attribute values.
// If the list is not empty, metrics with matching resource attribute values will not be emitted.
// MetricsInclude has higher priority than MetricsExclude.
MetricsExclude []filter.Config `mapstructure:"metrics_exclude"`
enabledSetByUser bool
}
func (rac *ResourceAttributeConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(rac)
if err != nil {
return err
}
rac.enabledSetByUser = parser.IsSet("enabled")
return nil
}
// ResourceAttributesConfig provides config for sample resource attributes.
type ResourceAttributesConfig struct {
MapResourceAttr ResourceAttributeConfig `mapstructure:"map.resource.attr"`
OptionalResourceAttr ResourceAttributeConfig `mapstructure:"optional.resource.attr"`
SliceResourceAttr ResourceAttributeConfig `mapstructure:"slice.resource.attr"`
StringEnumResourceAttr ResourceAttributeConfig `mapstructure:"string.enum.resource.attr"`
StringResourceAttr ResourceAttributeConfig `mapstructure:"string.resource.attr"`
StringResourceAttrDisableWarning ResourceAttributeConfig `mapstructure:"string.resource.attr_disable_warning"`
StringResourceAttrRemoveWarning ResourceAttributeConfig `mapstructure:"string.resource.attr_remove_warning"`
StringResourceAttrToBeRemoved ResourceAttributeConfig `mapstructure:"string.resource.attr_to_be_removed"`
}
func DefaultResourceAttributesConfig() ResourceAttributesConfig {
return ResourceAttributesConfig{
MapResourceAttr: ResourceAttributeConfig{
Enabled: true,
},
OptionalResourceAttr: ResourceAttributeConfig{
Enabled: false,
},
SliceResourceAttr: ResourceAttributeConfig{
Enabled: true,
},
StringEnumResourceAttr: ResourceAttributeConfig{
Enabled: true,
},
StringResourceAttr: ResourceAttributeConfig{
Enabled: true,
},
StringResourceAttrDisableWarning: ResourceAttributeConfig{
Enabled: true,
},
StringResourceAttrRemoveWarning: ResourceAttributeConfig{
Enabled: false,
},
StringResourceAttrToBeRemoved: ResourceAttributeConfig{
Enabled: true,
},
}
}
// MetricsBuilderConfig is a configuration for sample metrics builder.
type MetricsBuilderConfig struct {
Metrics MetricsConfig `mapstructure:"metrics"`
ResourceAttributes ResourceAttributesConfig `mapstructure:"resource_attributes"`
}
func DefaultMetricsBuilderConfig() MetricsBuilderConfig {
return MetricsBuilderConfig{
Metrics: DefaultMetricsConfig(),
ResourceAttributes: DefaultResourceAttributesConfig(),
}
}
================================================
FILE: cmd/mdatagen/internal/sampleconnector/internal/metadata/generated_config_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"path/filepath"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/confmaptest"
)
func TestMetricsBuilderConfig(t *testing.T) {
tests := []struct {
name string
want MetricsBuilderConfig
}{
{
name: "default",
want: DefaultMetricsBuilderConfig(),
},
{
name: "all_set",
want: MetricsBuilderConfig{
Metrics: MetricsConfig{
DefaultMetric: DefaultMetricMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategySum,
EnabledAttributes: []DefaultMetricMetricAttributeKey{DefaultMetricMetricAttributeKeyStringAttr, DefaultMetricMetricAttributeKeyOverriddenIntAttr, DefaultMetricMetricAttributeKeyEnumAttr, DefaultMetricMetricAttributeKeySliceAttr, DefaultMetricMetricAttributeKeyMapAttr},
},
DefaultMetricToBeRemoved: DefaultMetricToBeRemovedMetricConfig{
Enabled: true,
},
MetricInputType: MetricInputTypeMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategySum,
EnabledAttributes: []MetricInputTypeMetricAttributeKey{MetricInputTypeMetricAttributeKeyStringAttr, MetricInputTypeMetricAttributeKeyOverriddenIntAttr, MetricInputTypeMetricAttributeKeyEnumAttr, MetricInputTypeMetricAttributeKeySliceAttr, MetricInputTypeMetricAttributeKeyMapAttr},
},
OptionalMetric: OptionalMetricMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []OptionalMetricMetricAttributeKey{OptionalMetricMetricAttributeKeyStringAttr, OptionalMetricMetricAttributeKeyBooleanAttr, OptionalMetricMetricAttributeKeyBooleanAttr2},
},
OptionalMetricEmptyUnit: OptionalMetricEmptyUnitMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []OptionalMetricEmptyUnitMetricAttributeKey{OptionalMetricEmptyUnitMetricAttributeKeyStringAttr, OptionalMetricEmptyUnitMetricAttributeKeyBooleanAttr},
},
ReaggregateMetric: ReaggregateMetricMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []ReaggregateMetricMetricAttributeKey{ReaggregateMetricMetricAttributeKeyStringAttr, ReaggregateMetricMetricAttributeKeyBooleanAttr},
},
},
ResourceAttributes: ResourceAttributesConfig{
MapResourceAttr: ResourceAttributeConfig{Enabled: true},
OptionalResourceAttr: ResourceAttributeConfig{Enabled: true},
SliceResourceAttr: ResourceAttributeConfig{Enabled: true},
StringEnumResourceAttr: ResourceAttributeConfig{Enabled: true},
StringResourceAttr: ResourceAttributeConfig{Enabled: true},
StringResourceAttrDisableWarning: ResourceAttributeConfig{Enabled: true},
StringResourceAttrRemoveWarning: ResourceAttributeConfig{Enabled: true},
StringResourceAttrToBeRemoved: ResourceAttributeConfig{Enabled: true},
},
},
},
{
name: "none_set",
want: MetricsBuilderConfig{
Metrics: MetricsConfig{
DefaultMetric: DefaultMetricMetricConfig{
Enabled: false,
AggregationStrategy: AggregationStrategySum,
EnabledAttributes: []DefaultMetricMetricAttributeKey{DefaultMetricMetricAttributeKeyStringAttr, DefaultMetricMetricAttributeKeyOverriddenIntAttr, DefaultMetricMetricAttributeKeyEnumAttr, DefaultMetricMetricAttributeKeySliceAttr, DefaultMetricMetricAttributeKeyMapAttr},
},
DefaultMetricToBeRemoved: DefaultMetricToBeRemovedMetricConfig{
Enabled: false,
},
MetricInputType: MetricInputTypeMetricConfig{
Enabled: false,
AggregationStrategy: AggregationStrategySum,
EnabledAttributes: []MetricInputTypeMetricAttributeKey{MetricInputTypeMetricAttributeKeyStringAttr, MetricInputTypeMetricAttributeKeyOverriddenIntAttr, MetricInputTypeMetricAttributeKeyEnumAttr, MetricInputTypeMetricAttributeKeySliceAttr, MetricInputTypeMetricAttributeKeyMapAttr},
},
OptionalMetric: OptionalMetricMetricConfig{
Enabled: false,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []OptionalMetricMetricAttributeKey{OptionalMetricMetricAttributeKeyStringAttr, OptionalMetricMetricAttributeKeyBooleanAttr, OptionalMetricMetricAttributeKeyBooleanAttr2},
},
OptionalMetricEmptyUnit: OptionalMetricEmptyUnitMetricConfig{
Enabled: false,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []OptionalMetricEmptyUnitMetricAttributeKey{OptionalMetricEmptyUnitMetricAttributeKeyStringAttr, OptionalMetricEmptyUnitMetricAttributeKeyBooleanAttr},
},
ReaggregateMetric: ReaggregateMetricMetricConfig{
Enabled: false,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []ReaggregateMetricMetricAttributeKey{ReaggregateMetricMetricAttributeKeyStringAttr, ReaggregateMetricMetricAttributeKeyBooleanAttr},
},
},
ResourceAttributes: ResourceAttributesConfig{
MapResourceAttr: ResourceAttributeConfig{Enabled: false},
OptionalResourceAttr: ResourceAttributeConfig{Enabled: false},
SliceResourceAttr: ResourceAttributeConfig{Enabled: false},
StringEnumResourceAttr: ResourceAttributeConfig{Enabled: false},
StringResourceAttr: ResourceAttributeConfig{Enabled: false},
StringResourceAttrDisableWarning: ResourceAttributeConfig{Enabled: false},
StringResourceAttrRemoveWarning: ResourceAttributeConfig{Enabled: false},
StringResourceAttrToBeRemoved: ResourceAttributeConfig{Enabled: false},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := loadMetricsBuilderConfig(t, tt.name)
diff := cmp.Diff(tt.want, cfg, cmpopts.IgnoreUnexported(DefaultMetricMetricConfig{}, DefaultMetricToBeRemovedMetricConfig{}, MetricInputTypeMetricConfig{}, OptionalMetricMetricConfig{}, OptionalMetricEmptyUnitMetricConfig{}, ReaggregateMetricMetricConfig{}, ResourceAttributeConfig{}))
require.Emptyf(t, diff, "Config mismatch (-expected +actual):\n%s", diff)
})
}
}
func loadMetricsBuilderConfig(t *testing.T, name string) MetricsBuilderConfig {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
require.NoError(t, err)
sub, err := cm.Sub(name)
require.NoError(t, err)
cfg := DefaultMetricsBuilderConfig()
require.NoError(t, sub.Unmarshal(&cfg, confmap.WithIgnoreUnused()))
return cfg
}
func TestResourceAttributesConfig(t *testing.T) {
tests := []struct {
name string
want ResourceAttributesConfig
}{
{
name: "default",
want: DefaultResourceAttributesConfig(),
},
{
name: "all_set",
want: ResourceAttributesConfig{
MapResourceAttr: ResourceAttributeConfig{Enabled: true},
OptionalResourceAttr: ResourceAttributeConfig{Enabled: true},
SliceResourceAttr: ResourceAttributeConfig{Enabled: true},
StringEnumResourceAttr: ResourceAttributeConfig{Enabled: true},
StringResourceAttr: ResourceAttributeConfig{Enabled: true},
StringResourceAttrDisableWarning: ResourceAttributeConfig{Enabled: true},
StringResourceAttrRemoveWarning: ResourceAttributeConfig{Enabled: true},
StringResourceAttrToBeRemoved: ResourceAttributeConfig{Enabled: true},
},
},
{
name: "none_set",
want: ResourceAttributesConfig{
MapResourceAttr: ResourceAttributeConfig{Enabled: false},
OptionalResourceAttr: ResourceAttributeConfig{Enabled: false},
SliceResourceAttr: ResourceAttributeConfig{Enabled: false},
StringEnumResourceAttr: ResourceAttributeConfig{Enabled: false},
StringResourceAttr: ResourceAttributeConfig{Enabled: false},
StringResourceAttrDisableWarning: ResourceAttributeConfig{Enabled: false},
StringResourceAttrRemoveWarning: ResourceAttributeConfig{Enabled: false},
StringResourceAttrToBeRemoved: ResourceAttributeConfig{Enabled: false},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := loadResourceAttributesConfig(t, tt.name)
diff := cmp.Diff(tt.want, cfg, cmpopts.IgnoreUnexported(ResourceAttributeConfig{}))
require.Emptyf(t, diff, "Config mismatch (-expected +actual):\n%s", diff)
})
}
}
func loadResourceAttributesConfig(t *testing.T, name string) ResourceAttributesConfig {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
require.NoError(t, err)
sub, err := cm.Sub(name)
require.NoError(t, err)
sub, err = sub.Sub("resource_attributes")
require.NoError(t, err)
cfg := DefaultResourceAttributesConfig()
require.NoError(t, sub.Unmarshal(&cfg))
return cfg
}
================================================
FILE: cmd/mdatagen/internal/sampleconnector/internal/metadata/generated_entity_metrics.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"fmt"
"strconv"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/xpdata/entity"
)
// TestEntityEntity represents a test.entity entity.
// Create one with NewTestEntityEntity and pass it to EmitForEntity.
type TestEntityEntity struct {
stringResourceAttr string
mapResourceAttr map[string]any
}
// NewTestEntityEntity creates a new TestEntityEntity.
// Identity attributes are required and must be provided at construction time.
func NewTestEntityEntity(stringResourceAttr string) *TestEntityEntity {
return &TestEntityEntity{
stringResourceAttr: stringResourceAttr,
}
}
// Description attribute setters for test.entity.
// SetMapResourceAttr sets the map.resource.attr description attribute.
func (e *TestEntityEntity) SetMapResourceAttr(val map[string]any) {
e.mapResourceAttr = val
}
// copyToResource populates res with the entity's attributes according to cfg.
// If all identity attributes are enabled, an entity ref is produced; otherwise
// the enabled attributes are written directly as plain resource attributes.
func (e *TestEntityEntity) copyToResource(cfg ResourceAttributesConfig, res pcommon.Resource) {
if cfg.StringResourceAttr.Enabled {
ent := entity.ResourceEntities(res).PutEmpty("test.entity")
ent.IdentifyingAttributes().PutStr("string.resource.attr", e.stringResourceAttr)
if cfg.MapResourceAttr.Enabled {
ent.DescriptiveAttributes().PutEmpty("map.resource.attr").SetEmptyMap().FromRaw(e.mapResourceAttr)
}
} else {
if cfg.StringResourceAttr.Enabled {
res.Attributes().PutStr("string.resource.attr", e.stringResourceAttr)
}
if cfg.MapResourceAttr.Enabled {
res.Attributes().PutEmptyMap("map.resource.attr").FromRaw(e.mapResourceAttr)
}
}
}
// TestEntityMetricsBuilder records metrics for the test.entity entity.
// Obtain one via MetricsBuilder.ForTestEntity().
type TestEntityMetricsBuilder struct {
mb *MetricsBuilder
entity *TestEntityEntity
}
// RecordDefaultMetricDataPoint records a data point for the default.metric metric.
func (eb *TestEntityMetricsBuilder) RecordDefaultMetricDataPoint(ts pcommon.Timestamp, val int64, stringAttrAttributeValue string, overriddenIntAttrAttributeValue int64, enumAttrAttributeValue AttributeEnumAttr, sliceAttrAttributeValue []any, mapAttrAttributeValue map[string]any) {
eb.mb.metricDefaultMetric.recordDataPoint(eb.mb.startTime, ts, val, stringAttrAttributeValue, overriddenIntAttrAttributeValue, enumAttrAttributeValue.String(), sliceAttrAttributeValue, mapAttrAttributeValue)
}
// RecordDefaultMetricToBeRemovedDataPoint records a data point for the default.metric.to_be_removed metric.
func (eb *TestEntityMetricsBuilder) RecordDefaultMetricToBeRemovedDataPoint(ts pcommon.Timestamp, val float64) {
eb.mb.metricDefaultMetricToBeRemoved.recordDataPoint(eb.mb.startTime, ts, val)
}
// RecordMetricInputTypeDataPoint records a data point for the metric.input_type metric.
func (eb *TestEntityMetricsBuilder) RecordMetricInputTypeDataPoint(ts pcommon.Timestamp, inputVal string, stringAttrAttributeValue string, overriddenIntAttrAttributeValue int64, enumAttrAttributeValue AttributeEnumAttr, sliceAttrAttributeValue []any, mapAttrAttributeValue map[string]any) error {
val, err := strconv.ParseInt(inputVal, 10, 64)
if err != nil {
return fmt.Errorf("failed to parse int64 for MetricInputType, value was %s: %w", inputVal, err)
}
eb.mb.metricMetricInputType.recordDataPoint(eb.mb.startTime, ts, val, stringAttrAttributeValue, overriddenIntAttrAttributeValue, enumAttrAttributeValue.String(), sliceAttrAttributeValue, mapAttrAttributeValue)
return nil
}
// RecordOptionalMetricDataPoint records a data point for the optional.metric metric.
func (eb *TestEntityMetricsBuilder) RecordOptionalMetricDataPoint(ts pcommon.Timestamp, val float64, stringAttrAttributeValue string, booleanAttrAttributeValue bool, booleanAttr2AttributeValue bool) {
eb.mb.metricOptionalMetric.recordDataPoint(eb.mb.startTime, ts, val, stringAttrAttributeValue, booleanAttrAttributeValue, booleanAttr2AttributeValue)
}
// RecordOptionalMetricEmptyUnitDataPoint records a data point for the optional.metric.empty_unit metric.
func (eb *TestEntityMetricsBuilder) RecordOptionalMetricEmptyUnitDataPoint(ts pcommon.Timestamp, val float64, stringAttrAttributeValue string, booleanAttrAttributeValue bool) {
eb.mb.metricOptionalMetricEmptyUnit.recordDataPoint(eb.mb.startTime, ts, val, stringAttrAttributeValue, booleanAttrAttributeValue)
}
// RecordReaggregateMetricDataPoint records a data point for the reaggregate.metric metric.
func (eb *TestEntityMetricsBuilder) RecordReaggregateMetricDataPoint(ts pcommon.Timestamp, val float64, stringAttrAttributeValue string, booleanAttrAttributeValue bool) {
eb.mb.metricReaggregateMetric.recordDataPoint(eb.mb.startTime, ts, val, stringAttrAttributeValue, booleanAttrAttributeValue)
}
// Emit emits all pending metrics for the entity. Resource attributes are filtered by config:
// disabled identity attributes suppress the entity (other enabled attributes are added directly
// to the resource); disabled descriptive/extra attributes are omitted entirely.
func (eb *TestEntityMetricsBuilder) Emit() {
res := pcommon.NewResource()
cfg := eb.mb.config.ResourceAttributes
eb.entity.copyToResource(cfg, res)
eb.mb.EmitForResource(withResourceMoved(res))
}
================================================
FILE: cmd/mdatagen/internal/sampleconnector/internal/metadata/generated_entity_metrics_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/connector/connectortest"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/xpdata/entity"
)
func TestEntityBuilders(t *testing.T) {
start := pcommon.Timestamp(1_000_000_000)
ts := pcommon.Timestamp(1_000_001_000)
settings := connectortest.NewNopSettings(connectortest.NopType)
mb := NewMetricsBuilder(DefaultMetricsBuilderConfig(), settings, WithStartTime(start))
t.Run("test.entity", func(t *testing.T) {
e := NewTestEntityEntity("string.resource.attr-val")
require.NotNil(t, e)
e.SetMapResourceAttr(map[string]any{"key1": "map.resource.attr-val1", "key2": "map.resource.attr-val2"})
eb := mb.ForTestEntity(e)
eb.RecordDefaultMetricDataPoint(ts, 1, "string_attr-val", 19, AttributeEnumAttrRed, []any{"slice_attr-item1", "slice_attr-item2"}, map[string]any{"key1": "map_attr-val1", "key2": "map_attr-val2"})
eb.RecordDefaultMetricToBeRemovedDataPoint(ts, 1)
eb.RecordMetricInputTypeDataPoint(ts, "1", "string_attr-val", 19, AttributeEnumAttrRed, []any{"slice_attr-item1", "slice_attr-item2"}, map[string]any{"key1": "map_attr-val1", "key2": "map_attr-val2"})
eb.RecordOptionalMetricDataPoint(ts, 1, "string_attr-val", true, false)
eb.RecordOptionalMetricEmptyUnitDataPoint(ts, 1, "string_attr-val", true)
eb.RecordReaggregateMetricDataPoint(ts, 1, "string_attr-val", true)
eb.Emit()
metrics := mb.Emit()
require.Equal(t, 1, metrics.ResourceMetrics().Len())
rm := metrics.ResourceMetrics().At(0)
entityVal, ok := entity.ResourceEntities(rm.Resource()).Get("test.entity")
require.True(t, ok)
stringResourceAttrAttrVal, ok := entityVal.IdentifyingAttributes().Get("string.resource.attr")
require.True(t, ok)
assert.Equal(t, "string.resource.attr-val", stringResourceAttrAttrVal.Str())
mapResourceAttrAttrVal, ok := entityVal.DescriptiveAttributes().Get("map.resource.attr")
require.True(t, ok)
assert.Equal(t, map[string]any{"key1": "map.resource.attr-val1", "key2": "map.resource.attr-val2"}, mapResourceAttrAttrVal.Map().AsRaw())
require.Equal(t, 1, rm.ScopeMetrics().Len())
ms := rm.ScopeMetrics().At(0).Metrics()
assert.Equal(t, 4, ms.Len())
})
t.Run("test.entity/disabled_identity_attr", func(t *testing.T) {
// When an identity attribute is disabled, the entity is not produced but
// other enabled attributes are still added to the resource directly.
cfg := DefaultMetricsBuilderConfig()
cfg.ResourceAttributes.StringResourceAttr.Enabled = false
mb := NewMetricsBuilder(cfg, settings, WithStartTime(start))
e := NewTestEntityEntity("string.resource.attr-val")
e.SetMapResourceAttr(map[string]any{"key1": "map.resource.attr-val1", "key2": "map.resource.attr-val2"})
eb := mb.ForTestEntity(e)
eb.RecordDefaultMetricDataPoint(ts, 1, "string_attr-val", 19, AttributeEnumAttrRed, []any{"slice_attr-item1", "slice_attr-item2"}, map[string]any{"key1": "map_attr-val1", "key2": "map_attr-val2"})
eb.RecordDefaultMetricToBeRemovedDataPoint(ts, 1)
eb.RecordMetricInputTypeDataPoint(ts, "1", "string_attr-val", 19, AttributeEnumAttrRed, []any{"slice_attr-item1", "slice_attr-item2"}, map[string]any{"key1": "map_attr-val1", "key2": "map_attr-val2"})
eb.RecordOptionalMetricDataPoint(ts, 1, "string_attr-val", true, false)
eb.RecordOptionalMetricEmptyUnitDataPoint(ts, 1, "string_attr-val", true)
eb.RecordReaggregateMetricDataPoint(ts, 1, "string_attr-val", true)
eb.Emit()
metrics := mb.Emit()
require.Equal(t, 1, metrics.ResourceMetrics().Len())
rm := metrics.ResourceMetrics().At(0)
// Entity must not be present since its identity attribute is disabled.
_, ok := entity.ResourceEntities(rm.Resource()).Get("test.entity")
assert.False(t, ok)
// Enabled descriptive attributes should still be on the resource directly.
_, ok = rm.Resource().Attributes().Get("map.resource.attr")
assert.True(t, ok)
})
t.Run("test.entity/disabled_descriptive_attr", func(t *testing.T) {
// When a descriptive attribute is disabled, the entity is still produced
// with its identity but the disabled attribute is not added.
cfg := DefaultMetricsBuilderConfig()
cfg.ResourceAttributes.MapResourceAttr.Enabled = false
mb := NewMetricsBuilder(cfg, settings, WithStartTime(start))
e := NewTestEntityEntity("string.resource.attr-val")
e.SetMapResourceAttr(map[string]any{"key1": "map.resource.attr-val1", "key2": "map.resource.attr-val2"})
eb := mb.ForTestEntity(e)
eb.RecordDefaultMetricDataPoint(ts, 1, "string_attr-val", 19, AttributeEnumAttrRed, []any{"slice_attr-item1", "slice_attr-item2"}, map[string]any{"key1": "map_attr-val1", "key2": "map_attr-val2"})
eb.RecordDefaultMetricToBeRemovedDataPoint(ts, 1)
eb.RecordMetricInputTypeDataPoint(ts, "1", "string_attr-val", 19, AttributeEnumAttrRed, []any{"slice_attr-item1", "slice_attr-item2"}, map[string]any{"key1": "map_attr-val1", "key2": "map_attr-val2"})
eb.RecordOptionalMetricDataPoint(ts, 1, "string_attr-val", true, false)
eb.RecordOptionalMetricEmptyUnitDataPoint(ts, 1, "string_attr-val", true)
eb.RecordReaggregateMetricDataPoint(ts, 1, "string_attr-val", true)
eb.Emit()
metrics := mb.Emit()
require.Equal(t, 1, metrics.ResourceMetrics().Len())
rm := metrics.ResourceMetrics().At(0)
// Entity must still be produced since identity attributes are enabled.
entityVal, ok := entity.ResourceEntities(rm.Resource()).Get("test.entity")
require.True(t, ok)
stringResourceAttrAttrVal, ok := entityVal.IdentifyingAttributes().Get("string.resource.attr")
require.True(t, ok)
assert.Equal(t, "string.resource.attr-val", stringResourceAttrAttrVal.Str())
// Disabled descriptive/extra attributes must not be present.
_, ok = entityVal.DescriptiveAttributes().Get("map.resource.attr")
assert.False(t, ok)
})
}
================================================
FILE: cmd/mdatagen/internal/sampleconnector/internal/metadata/generated_metrics.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"fmt"
"slices"
"strconv"
"time"
conventions "go.opentelemetry.io/otel/semconv/v1.9.0"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/connector"
"go.opentelemetry.io/collector/filter"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/pmetric"
)
const (
AggregationStrategySum = "sum"
AggregationStrategyAvg = "avg"
AggregationStrategyMin = "min"
AggregationStrategyMax = "max"
)
// AttributeEnumAttr specifies the value enum_attr attribute.
type AttributeEnumAttr int
const (
_ AttributeEnumAttr = iota
AttributeEnumAttrRed
AttributeEnumAttrGreen
AttributeEnumAttrBlue
)
// String returns the string representation of the AttributeEnumAttr.
func (av AttributeEnumAttr) String() string {
switch av {
case AttributeEnumAttrRed:
return "red"
case AttributeEnumAttrGreen:
return "green"
case AttributeEnumAttrBlue:
return "blue"
}
return ""
}
// MapAttributeEnumAttr is a helper map of string to AttributeEnumAttr attribute value.
var MapAttributeEnumAttr = map[string]AttributeEnumAttr{
"red": AttributeEnumAttrRed,
"green": AttributeEnumAttrGreen,
"blue": AttributeEnumAttrBlue,
}
var MetricsInfo = metricsInfo{
DefaultMetric: metricInfo{
Name: "default.metric",
},
DefaultMetricToBeRemoved: metricInfo{
Name: "default.metric.to_be_removed",
},
MetricInputType: metricInfo{
Name: "metric.input_type",
},
OptionalMetric: metricInfo{
Name: "optional.metric",
},
OptionalMetricEmptyUnit: metricInfo{
Name: "optional.metric.empty_unit",
},
ReaggregateMetric: metricInfo{
Name: "reaggregate.metric",
},
}
type metricsInfo struct {
DefaultMetric metricInfo
DefaultMetricToBeRemoved metricInfo
MetricInputType metricInfo
OptionalMetric metricInfo
OptionalMetricEmptyUnit metricInfo
ReaggregateMetric metricInfo
}
type metricInfo struct {
Name string
}
type metricDefaultMetric struct {
data pmetric.Metric // data buffer for generated metric.
config DefaultMetricMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []int64 // slice containing number of aggregated datapoints at each index
}
// init fills default.metric metric with initial data.
func (m *metricDefaultMetric) init() {
m.data.SetName("default.metric")
m.data.SetDescription("Monotonic cumulative sum int metric enabled by default.")
m.data.SetUnit("s")
m.data.SetEmptySum()
m.data.Sum().SetIsMonotonic(true)
m.data.Sum().SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
m.data.Sum().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
}
func (m *metricDefaultMetric) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val int64, stringAttrAttributeValue string, overriddenIntAttrAttributeValue int64, enumAttrAttributeValue string, sliceAttrAttributeValue []any, mapAttrAttributeValue map[string]any) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, DefaultMetricMetricAttributeKeyStringAttr) {
dp.Attributes().PutStr("string_attr", stringAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, DefaultMetricMetricAttributeKeyOverriddenIntAttr) {
dp.Attributes().PutInt("state", overriddenIntAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, DefaultMetricMetricAttributeKeyEnumAttr) {
dp.Attributes().PutStr("enum_attr", enumAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, DefaultMetricMetricAttributeKeySliceAttr) {
dp.Attributes().PutEmptySlice("slice_attr").FromRaw(sliceAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, DefaultMetricMetricAttributeKeyMapAttr) {
dp.Attributes().PutEmptyMap("map_attr").FromRaw(mapAttrAttributeValue)
}
var s string
dps := m.data.Sum().DataPoints()
for i := 0; i < dps.Len(); i++ {
dpi := dps.At(i)
if dp.Attributes().Equal(dpi.Attributes()) && dp.StartTimestamp() == dpi.StartTimestamp() && dp.Timestamp() == dpi.Timestamp() {
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetIntValue(dpi.IntValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.IntValue() > val {
dpi.SetIntValue(val)
}
return
case AggregationStrategyMax:
if dpi.IntValue() < val {
dpi.SetIntValue(val)
}
return
}
}
}
dp.SetIntValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricDefaultMetric) updateCapacity() {
if m.data.Sum().DataPoints().Len() > m.capacity {
m.capacity = m.data.Sum().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricDefaultMetric) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Sum().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Sum().DataPoints().At(i).SetIntValue(m.data.Sum().DataPoints().At(i).IntValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricDefaultMetric(cfg DefaultMetricMetricConfig) metricDefaultMetric {
m := metricDefaultMetric{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricDefaultMetricToBeRemoved struct {
data pmetric.Metric // data buffer for generated metric.
config DefaultMetricToBeRemovedMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
}
// init fills default.metric.to_be_removed metric with initial data.
func (m *metricDefaultMetricToBeRemoved) init() {
m.data.SetName("default.metric.to_be_removed")
m.data.SetDescription("[DEPRECATED] Non-monotonic delta sum double metric enabled by default.")
m.data.SetUnit("s")
m.data.SetEmptySum()
m.data.Sum().SetIsMonotonic(false)
m.data.Sum().SetAggregationTemporality(pmetric.AggregationTemporalityDelta)
}
func (m *metricDefaultMetricToBeRemoved) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64) {
if !m.config.Enabled {
return
}
dp := m.data.Sum().DataPoints().AppendEmpty()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
dp.SetDoubleValue(val)
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricDefaultMetricToBeRemoved) updateCapacity() {
if m.data.Sum().DataPoints().Len() > m.capacity {
m.capacity = m.data.Sum().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricDefaultMetricToBeRemoved) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Sum().DataPoints().Len() > 0 {
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricDefaultMetricToBeRemoved(cfg DefaultMetricToBeRemovedMetricConfig) metricDefaultMetricToBeRemoved {
m := metricDefaultMetricToBeRemoved{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMetricInputType struct {
data pmetric.Metric // data buffer for generated metric.
config MetricInputTypeMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []int64 // slice containing number of aggregated datapoints at each index
}
// init fills metric.input_type metric with initial data.
func (m *metricMetricInputType) init() {
m.data.SetName("metric.input_type")
m.data.SetDescription("Monotonic cumulative sum int metric with string input_type enabled by default.")
m.data.SetUnit("s")
m.data.SetEmptySum()
m.data.Sum().SetIsMonotonic(true)
m.data.Sum().SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
m.data.Sum().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
}
func (m *metricMetricInputType) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val int64, stringAttrAttributeValue string, overriddenIntAttrAttributeValue int64, enumAttrAttributeValue string, sliceAttrAttributeValue []any, mapAttrAttributeValue map[string]any) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MetricInputTypeMetricAttributeKeyStringAttr) {
dp.Attributes().PutStr("string_attr", stringAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, MetricInputTypeMetricAttributeKeyOverriddenIntAttr) {
dp.Attributes().PutInt("state", overriddenIntAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, MetricInputTypeMetricAttributeKeyEnumAttr) {
dp.Attributes().PutStr("enum_attr", enumAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, MetricInputTypeMetricAttributeKeySliceAttr) {
dp.Attributes().PutEmptySlice("slice_attr").FromRaw(sliceAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, MetricInputTypeMetricAttributeKeyMapAttr) {
dp.Attributes().PutEmptyMap("map_attr").FromRaw(mapAttrAttributeValue)
}
var s string
dps := m.data.Sum().DataPoints()
for i := 0; i < dps.Len(); i++ {
dpi := dps.At(i)
if dp.Attributes().Equal(dpi.Attributes()) && dp.StartTimestamp() == dpi.StartTimestamp() && dp.Timestamp() == dpi.Timestamp() {
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetIntValue(dpi.IntValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.IntValue() > val {
dpi.SetIntValue(val)
}
return
case AggregationStrategyMax:
if dpi.IntValue() < val {
dpi.SetIntValue(val)
}
return
}
}
}
dp.SetIntValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMetricInputType) updateCapacity() {
if m.data.Sum().DataPoints().Len() > m.capacity {
m.capacity = m.data.Sum().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMetricInputType) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Sum().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Sum().DataPoints().At(i).SetIntValue(m.data.Sum().DataPoints().At(i).IntValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMetricInputType(cfg MetricInputTypeMetricConfig) metricMetricInputType {
m := metricMetricInputType{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricOptionalMetric struct {
data pmetric.Metric // data buffer for generated metric.
config OptionalMetricMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
}
// init fills optional.metric metric with initial data.
func (m *metricOptionalMetric) init() {
m.data.SetName("optional.metric")
m.data.SetDescription("[DEPRECATED] Gauge double metric disabled by default.")
m.data.SetUnit("1")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
}
func (m *metricOptionalMetric) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, stringAttrAttributeValue string, booleanAttrAttributeValue bool, booleanAttr2AttributeValue bool) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, OptionalMetricMetricAttributeKeyStringAttr) {
dp.Attributes().PutStr("string_attr", stringAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, OptionalMetricMetricAttributeKeyBooleanAttr) {
dp.Attributes().PutBool("boolean_attr", booleanAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, OptionalMetricMetricAttributeKeyBooleanAttr2) {
dp.Attributes().PutBool("boolean_attr2", booleanAttr2AttributeValue)
}
var s string
dps := m.data.Gauge().DataPoints()
for i := 0; i < dps.Len(); i++ {
dpi := dps.At(i)
if dp.Attributes().Equal(dpi.Attributes()) && dp.StartTimestamp() == dpi.StartTimestamp() && dp.Timestamp() == dpi.Timestamp() {
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricOptionalMetric) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricOptionalMetric) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricOptionalMetric(cfg OptionalMetricMetricConfig) metricOptionalMetric {
m := metricOptionalMetric{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricOptionalMetricEmptyUnit struct {
data pmetric.Metric // data buffer for generated metric.
config OptionalMetricEmptyUnitMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
}
// init fills optional.metric.empty_unit metric with initial data.
func (m *metricOptionalMetricEmptyUnit) init() {
m.data.SetName("optional.metric.empty_unit")
m.data.SetDescription("[DEPRECATED] Gauge double metric disabled by default.")
m.data.SetUnit("")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
}
func (m *metricOptionalMetricEmptyUnit) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, stringAttrAttributeValue string, booleanAttrAttributeValue bool) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, OptionalMetricEmptyUnitMetricAttributeKeyStringAttr) {
dp.Attributes().PutStr("string_attr", stringAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, OptionalMetricEmptyUnitMetricAttributeKeyBooleanAttr) {
dp.Attributes().PutBool("boolean_attr", booleanAttrAttributeValue)
}
var s string
dps := m.data.Gauge().DataPoints()
for i := 0; i < dps.Len(); i++ {
dpi := dps.At(i)
if dp.Attributes().Equal(dpi.Attributes()) && dp.StartTimestamp() == dpi.StartTimestamp() && dp.Timestamp() == dpi.Timestamp() {
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricOptionalMetricEmptyUnit) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricOptionalMetricEmptyUnit) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricOptionalMetricEmptyUnit(cfg OptionalMetricEmptyUnitMetricConfig) metricOptionalMetricEmptyUnit {
m := metricOptionalMetricEmptyUnit{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricReaggregateMetric struct {
data pmetric.Metric // data buffer for generated metric.
config ReaggregateMetricMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
}
// init fills reaggregate.metric metric with initial data.
func (m *metricReaggregateMetric) init() {
m.data.SetName("reaggregate.metric")
m.data.SetDescription("Metric for testing spatial reaggregation")
m.data.SetUnit("1")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
}
func (m *metricReaggregateMetric) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, stringAttrAttributeValue string, booleanAttrAttributeValue bool) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, ReaggregateMetricMetricAttributeKeyStringAttr) {
dp.Attributes().PutStr("string_attr", stringAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, ReaggregateMetricMetricAttributeKeyBooleanAttr) {
dp.Attributes().PutBool("boolean_attr", booleanAttrAttributeValue)
}
var s string
dps := m.data.Gauge().DataPoints()
for i := 0; i < dps.Len(); i++ {
dpi := dps.At(i)
if dp.Attributes().Equal(dpi.Attributes()) && dp.StartTimestamp() == dpi.StartTimestamp() && dp.Timestamp() == dpi.Timestamp() {
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricReaggregateMetric) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricReaggregateMetric) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricReaggregateMetric(cfg ReaggregateMetricMetricConfig) metricReaggregateMetric {
m := metricReaggregateMetric{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
// MetricsBuilder provides an interface for scrapers to report metrics while taking care of all the transformations
// required to produce metric representation defined in metadata and user config.
type MetricsBuilder struct {
config MetricsBuilderConfig // config of the metrics builder.
startTime pcommon.Timestamp // start time that will be applied to all recorded data points.
metricsCapacity int // maximum observed number of metrics per resource.
metricsBuffer pmetric.Metrics // accumulates metrics data before emitting.
buildInfo component.BuildInfo // contains version information.
resourceAttributeIncludeFilter map[string]filter.Filter
resourceAttributeExcludeFilter map[string]filter.Filter
metricDefaultMetric metricDefaultMetric
metricDefaultMetricToBeRemoved metricDefaultMetricToBeRemoved
metricMetricInputType metricMetricInputType
metricOptionalMetric metricOptionalMetric
metricOptionalMetricEmptyUnit metricOptionalMetricEmptyUnit
metricReaggregateMetric metricReaggregateMetric
}
// MetricBuilderOption applies changes to default metrics builder.
type MetricBuilderOption interface {
apply(*MetricsBuilder)
}
type metricBuilderOptionFunc func(mb *MetricsBuilder)
func (mbof metricBuilderOptionFunc) apply(mb *MetricsBuilder) {
mbof(mb)
}
// WithStartTime sets startTime on the metrics builder.
func WithStartTime(startTime pcommon.Timestamp) MetricBuilderOption {
return metricBuilderOptionFunc(func(mb *MetricsBuilder) {
mb.startTime = startTime
})
}
func NewMetricsBuilder(mbc MetricsBuilderConfig, settings connector.Settings, options ...MetricBuilderOption) *MetricsBuilder {
if !mbc.Metrics.DefaultMetric.enabledSetByUser {
settings.Logger.Warn("[WARNING] Please set `enabled` field explicitly for `default.metric`: This metric will be disabled by default soon.")
}
if mbc.Metrics.DefaultMetricToBeRemoved.Enabled {
settings.Logger.Warn("[WARNING] `default.metric.to_be_removed` should not be enabled: This metric is deprecated and will be removed soon.")
}
if mbc.Metrics.OptionalMetric.enabledSetByUser {
settings.Logger.Warn("[WARNING] `optional.metric` should not be configured: This metric is deprecated and will be removed soon.")
}
if mbc.Metrics.OptionalMetricEmptyUnit.enabledSetByUser {
settings.Logger.Warn("[WARNING] `optional.metric.empty_unit` should not be configured: This metric is deprecated and will be removed soon.")
}
if !mbc.ResourceAttributes.StringResourceAttrDisableWarning.enabledSetByUser {
settings.Logger.Warn("[WARNING] Please set `enabled` field explicitly for `string.resource.attr_disable_warning`: This resource_attribute will be disabled by default soon.")
}
if mbc.ResourceAttributes.StringResourceAttrRemoveWarning.enabledSetByUser || mbc.ResourceAttributes.StringResourceAttrRemoveWarning.MetricsInclude != nil || mbc.ResourceAttributes.StringResourceAttrRemoveWarning.MetricsExclude != nil {
settings.Logger.Warn("[WARNING] `string.resource.attr_remove_warning` should not be configured: This resource_attribute is deprecated and will be removed soon.")
}
if mbc.ResourceAttributes.StringResourceAttrToBeRemoved.Enabled {
settings.Logger.Warn("[WARNING] `string.resource.attr_to_be_removed` should not be enabled: This resource_attribute is deprecated and will be removed soon.")
}
mb := &MetricsBuilder{
config: mbc,
startTime: pcommon.NewTimestampFromTime(time.Now()),
metricsBuffer: pmetric.NewMetrics(),
buildInfo: settings.BuildInfo,
metricDefaultMetric: newMetricDefaultMetric(mbc.Metrics.DefaultMetric),
metricDefaultMetricToBeRemoved: newMetricDefaultMetricToBeRemoved(mbc.Metrics.DefaultMetricToBeRemoved),
metricMetricInputType: newMetricMetricInputType(mbc.Metrics.MetricInputType),
metricOptionalMetric: newMetricOptionalMetric(mbc.Metrics.OptionalMetric),
metricOptionalMetricEmptyUnit: newMetricOptionalMetricEmptyUnit(mbc.Metrics.OptionalMetricEmptyUnit),
metricReaggregateMetric: newMetricReaggregateMetric(mbc.Metrics.ReaggregateMetric),
resourceAttributeIncludeFilter: make(map[string]filter.Filter),
resourceAttributeExcludeFilter: make(map[string]filter.Filter),
}
if mbc.ResourceAttributes.MapResourceAttr.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["map.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.MapResourceAttr.MetricsInclude)
}
if mbc.ResourceAttributes.MapResourceAttr.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["map.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.MapResourceAttr.MetricsExclude)
}
if mbc.ResourceAttributes.OptionalResourceAttr.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["optional.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.OptionalResourceAttr.MetricsInclude)
}
if mbc.ResourceAttributes.OptionalResourceAttr.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["optional.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.OptionalResourceAttr.MetricsExclude)
}
if mbc.ResourceAttributes.SliceResourceAttr.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["slice.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.SliceResourceAttr.MetricsInclude)
}
if mbc.ResourceAttributes.SliceResourceAttr.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["slice.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.SliceResourceAttr.MetricsExclude)
}
if mbc.ResourceAttributes.StringEnumResourceAttr.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["string.enum.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.StringEnumResourceAttr.MetricsInclude)
}
if mbc.ResourceAttributes.StringEnumResourceAttr.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["string.enum.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.StringEnumResourceAttr.MetricsExclude)
}
if mbc.ResourceAttributes.StringResourceAttr.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["string.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.StringResourceAttr.MetricsInclude)
}
if mbc.ResourceAttributes.StringResourceAttr.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["string.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.StringResourceAttr.MetricsExclude)
}
if mbc.ResourceAttributes.StringResourceAttrDisableWarning.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["string.resource.attr_disable_warning"] = filter.CreateFilter(mbc.ResourceAttributes.StringResourceAttrDisableWarning.MetricsInclude)
}
if mbc.ResourceAttributes.StringResourceAttrDisableWarning.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["string.resource.attr_disable_warning"] = filter.CreateFilter(mbc.ResourceAttributes.StringResourceAttrDisableWarning.MetricsExclude)
}
if mbc.ResourceAttributes.StringResourceAttrRemoveWarning.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["string.resource.attr_remove_warning"] = filter.CreateFilter(mbc.ResourceAttributes.StringResourceAttrRemoveWarning.MetricsInclude)
}
if mbc.ResourceAttributes.StringResourceAttrRemoveWarning.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["string.resource.attr_remove_warning"] = filter.CreateFilter(mbc.ResourceAttributes.StringResourceAttrRemoveWarning.MetricsExclude)
}
if mbc.ResourceAttributes.StringResourceAttrToBeRemoved.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["string.resource.attr_to_be_removed"] = filter.CreateFilter(mbc.ResourceAttributes.StringResourceAttrToBeRemoved.MetricsInclude)
}
if mbc.ResourceAttributes.StringResourceAttrToBeRemoved.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["string.resource.attr_to_be_removed"] = filter.CreateFilter(mbc.ResourceAttributes.StringResourceAttrToBeRemoved.MetricsExclude)
}
for _, op := range options {
op.apply(mb)
}
return mb
}
// NewResourceBuilder returns a new resource builder that should be used to build a resource associated with for the emitted metrics.
func (mb *MetricsBuilder) NewResourceBuilder() *ResourceBuilder {
return NewResourceBuilder(mb.config.ResourceAttributes)
}
// updateCapacity updates max length of metrics and resource attributes that will be used for the slice capacity.
func (mb *MetricsBuilder) updateCapacity(rm pmetric.ResourceMetrics) {
if mb.metricsCapacity < rm.ScopeMetrics().At(0).Metrics().Len() {
mb.metricsCapacity = rm.ScopeMetrics().At(0).Metrics().Len()
}
}
// ResourceMetricsOption applies changes to provided resource metrics.
type ResourceMetricsOption interface {
apply(pmetric.ResourceMetrics)
}
type resourceMetricsOptionFunc func(pmetric.ResourceMetrics)
func (rmof resourceMetricsOptionFunc) apply(rm pmetric.ResourceMetrics) {
rmof(rm)
}
// WithResource sets the provided resource on the emitted ResourceMetrics.
// It's recommended to use ResourceBuilder to create the resource.
func WithResource(res pcommon.Resource) ResourceMetricsOption {
return resourceMetricsOptionFunc(func(rm pmetric.ResourceMetrics) {
res.CopyTo(rm.Resource())
})
}
func withResourceMoved(res pcommon.Resource) ResourceMetricsOption {
return resourceMetricsOptionFunc(func(rm pmetric.ResourceMetrics) {
res.MoveTo(rm.Resource())
})
}
// WithStartTimeOverride overrides start time for all the resource metrics data points.
// This option should be only used if different start time has to be set on metrics coming from different resources.
func WithStartTimeOverride(start pcommon.Timestamp) ResourceMetricsOption {
return resourceMetricsOptionFunc(func(rm pmetric.ResourceMetrics) {
var dps pmetric.NumberDataPointSlice
metrics := rm.ScopeMetrics().At(0).Metrics()
for i := 0; i < metrics.Len(); i++ {
switch metrics.At(i).Type() {
case pmetric.MetricTypeGauge:
dps = metrics.At(i).Gauge().DataPoints()
case pmetric.MetricTypeSum:
dps = metrics.At(i).Sum().DataPoints()
}
for j := 0; j < dps.Len(); j++ {
dps.At(j).SetStartTimestamp(start)
}
}
})
}
// ForTestEntity returns a TestEntityMetricsBuilder that restricts metric recording
// to metrics belonging to the test.entity entity.
func (mb *MetricsBuilder) ForTestEntity(e *TestEntityEntity) *TestEntityMetricsBuilder {
return &TestEntityMetricsBuilder{mb: mb, entity: e}
}
// EmitForResource saves all the generated metrics under a new resource and updates the internal state to be ready for
// recording another set of data points as part of another resource. This function can be helpful when one scraper
// needs to emit metrics from several resources. Otherwise calling this function is not required,
// just `Emit` function can be called instead.
// Resource attributes should be provided as ResourceMetricsOption arguments.
//
// Deprecated: Use the For methods to get entity-scoped builders and call Emit() on them instead.
func (mb *MetricsBuilder) EmitForResource(options ...ResourceMetricsOption) {
rm := pmetric.NewResourceMetrics()
rm.SetSchemaUrl(conventions.SchemaURL)
ils := rm.ScopeMetrics().AppendEmpty()
ils.Scope().SetName(ScopeName)
ils.Scope().SetVersion(mb.buildInfo.Version)
ils.Metrics().EnsureCapacity(mb.metricsCapacity)
mb.metricDefaultMetric.emit(ils.Metrics())
mb.metricDefaultMetricToBeRemoved.emit(ils.Metrics())
mb.metricMetricInputType.emit(ils.Metrics())
mb.metricOptionalMetric.emit(ils.Metrics())
mb.metricOptionalMetricEmptyUnit.emit(ils.Metrics())
mb.metricReaggregateMetric.emit(ils.Metrics())
for _, op := range options {
op.apply(rm)
}
for attr, filter := range mb.resourceAttributeIncludeFilter {
if val, ok := rm.Resource().Attributes().Get(attr); ok && !filter.Matches(val.AsString()) {
return
}
}
for attr, filter := range mb.resourceAttributeExcludeFilter {
if val, ok := rm.Resource().Attributes().Get(attr); ok && filter.Matches(val.AsString()) {
return
}
}
if ils.Metrics().Len() > 0 {
mb.updateCapacity(rm)
rm.MoveTo(mb.metricsBuffer.ResourceMetrics().AppendEmpty())
}
}
// Emit returns all the metrics accumulated by the metrics builder and updates the internal state to be ready for
// recording another set of metrics. This function will be responsible for applying all the transformations required to
// produce metric representation defined in metadata and user config, e.g. delta or cumulative.
func (mb *MetricsBuilder) Emit(options ...ResourceMetricsOption) pmetric.Metrics {
mb.EmitForResource(options...)
metrics := mb.metricsBuffer
mb.metricsBuffer = pmetric.NewMetrics()
return metrics
}
// RecordDefaultMetricDataPoint adds a data point to default.metric metric.
//
// Deprecated: Use mb.ForTestEntity(entity).RecordDefaultMetricDataPoint(...) instead.
func (mb *MetricsBuilder) RecordDefaultMetricDataPoint(ts pcommon.Timestamp, val int64, stringAttrAttributeValue string, overriddenIntAttrAttributeValue int64, enumAttrAttributeValue AttributeEnumAttr, sliceAttrAttributeValue []any, mapAttrAttributeValue map[string]any) {
mb.metricDefaultMetric.recordDataPoint(mb.startTime, ts, val, stringAttrAttributeValue, overriddenIntAttrAttributeValue, enumAttrAttributeValue.String(), sliceAttrAttributeValue, mapAttrAttributeValue)
}
// RecordDefaultMetricToBeRemovedDataPoint adds a data point to default.metric.to_be_removed metric.
//
// Deprecated: Use mb.ForTestEntity(entity).RecordDefaultMetricToBeRemovedDataPoint(...) instead.
func (mb *MetricsBuilder) RecordDefaultMetricToBeRemovedDataPoint(ts pcommon.Timestamp, val float64) {
mb.metricDefaultMetricToBeRemoved.recordDataPoint(mb.startTime, ts, val)
}
// RecordMetricInputTypeDataPoint adds a data point to metric.input_type metric.
//
// Deprecated: Use mb.ForTestEntity(entity).RecordMetricInputTypeDataPoint(...) instead.
func (mb *MetricsBuilder) RecordMetricInputTypeDataPoint(ts pcommon.Timestamp, inputVal string, stringAttrAttributeValue string, overriddenIntAttrAttributeValue int64, enumAttrAttributeValue AttributeEnumAttr, sliceAttrAttributeValue []any, mapAttrAttributeValue map[string]any) error {
val, err := strconv.ParseInt(inputVal, 10, 64)
if err != nil {
return fmt.Errorf("failed to parse int64 for MetricInputType, value was %s: %w", inputVal, err)
}
mb.metricMetricInputType.recordDataPoint(mb.startTime, ts, val, stringAttrAttributeValue, overriddenIntAttrAttributeValue, enumAttrAttributeValue.String(), sliceAttrAttributeValue, mapAttrAttributeValue)
return nil
}
// RecordOptionalMetricDataPoint adds a data point to optional.metric metric.
//
// Deprecated: Use mb.ForTestEntity(entity).RecordOptionalMetricDataPoint(...) instead.
func (mb *MetricsBuilder) RecordOptionalMetricDataPoint(ts pcommon.Timestamp, val float64, stringAttrAttributeValue string, booleanAttrAttributeValue bool, booleanAttr2AttributeValue bool) {
mb.metricOptionalMetric.recordDataPoint(mb.startTime, ts, val, stringAttrAttributeValue, booleanAttrAttributeValue, booleanAttr2AttributeValue)
}
// RecordOptionalMetricEmptyUnitDataPoint adds a data point to optional.metric.empty_unit metric.
//
// Deprecated: Use mb.ForTestEntity(entity).RecordOptionalMetricEmptyUnitDataPoint(...) instead.
func (mb *MetricsBuilder) RecordOptionalMetricEmptyUnitDataPoint(ts pcommon.Timestamp, val float64, stringAttrAttributeValue string, booleanAttrAttributeValue bool) {
mb.metricOptionalMetricEmptyUnit.recordDataPoint(mb.startTime, ts, val, stringAttrAttributeValue, booleanAttrAttributeValue)
}
// RecordReaggregateMetricDataPoint adds a data point to reaggregate.metric metric.
//
// Deprecated: Use mb.ForTestEntity(entity).RecordReaggregateMetricDataPoint(...) instead.
func (mb *MetricsBuilder) RecordReaggregateMetricDataPoint(ts pcommon.Timestamp, val float64, stringAttrAttributeValue string, booleanAttrAttributeValue bool) {
mb.metricReaggregateMetric.recordDataPoint(mb.startTime, ts, val, stringAttrAttributeValue, booleanAttrAttributeValue)
}
// Reset resets metrics builder to its initial state. It should be used when external metrics source is restarted,
// and metrics builder should update its startTime and reset it's internal state accordingly.
func (mb *MetricsBuilder) Reset(options ...MetricBuilderOption) {
mb.startTime = pcommon.NewTimestampFromTime(time.Now())
for _, op := range options {
op.apply(mb)
}
}
================================================
FILE: cmd/mdatagen/internal/sampleconnector/internal/metadata/generated_metrics_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"testing"
"github.com/stretchr/testify/assert"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"go.opentelemetry.io/collector/connector/connectortest"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/pmetric"
)
type testDataSet int
const (
testDataSetDefault testDataSet = iota
testDataSetAll
testDataSetNone
testDataSetReag
)
func TestMetricsBuilder(t *testing.T) {
tests := []struct {
name string
metricsSet testDataSet
resAttrsSet testDataSet
expectEmpty bool
}{
{
name: "default",
},
{
name: "all_set",
metricsSet: testDataSetAll,
resAttrsSet: testDataSetAll,
},
{
name: "reaggregate_set",
metricsSet: testDataSetReag,
resAttrsSet: testDataSetReag,
},
{
name: "none_set",
metricsSet: testDataSetNone,
resAttrsSet: testDataSetNone,
expectEmpty: true,
},
{
name: "filter_set_include",
resAttrsSet: testDataSetAll,
},
{
name: "filter_set_exclude",
resAttrsSet: testDataSetAll,
expectEmpty: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
start := pcommon.Timestamp(1_000_000_000)
ts := pcommon.Timestamp(1_000_001_000)
observedZapCore, observedLogs := observer.New(zap.WarnLevel)
settings := connectortest.NewNopSettings(connectortest.NopType)
settings.Logger = zap.New(observedZapCore)
mb := NewMetricsBuilder(loadMetricsBuilderConfig(t, tt.name), settings, WithStartTime(start))
aggMap := make(map[string]string) // contains the aggregation strategies for each metric name
aggMap["DefaultMetric"] = mb.metricDefaultMetric.config.AggregationStrategy
aggMap["MetricInputType"] = mb.metricMetricInputType.config.AggregationStrategy
aggMap["OptionalMetric"] = mb.metricOptionalMetric.config.AggregationStrategy
aggMap["OptionalMetricEmptyUnit"] = mb.metricOptionalMetricEmptyUnit.config.AggregationStrategy
aggMap["ReaggregateMetric"] = mb.metricReaggregateMetric.config.AggregationStrategy
expectedWarnings := 0
if tt.metricsSet == testDataSetDefault {
assert.Equal(t, "[WARNING] Please set `enabled` field explicitly for `default.metric`: This metric will be disabled by default soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
if tt.metricsSet == testDataSetDefault || tt.metricsSet == testDataSetAll {
assert.Equal(t, "[WARNING] `default.metric.to_be_removed` should not be enabled: This metric is deprecated and will be removed soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
if tt.metricsSet == testDataSetAll || tt.metricsSet == testDataSetNone {
assert.Equal(t, "[WARNING] `optional.metric` should not be configured: This metric is deprecated and will be removed soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
if tt.metricsSet == testDataSetAll || tt.metricsSet == testDataSetNone {
assert.Equal(t, "[WARNING] `optional.metric.empty_unit` should not be configured: This metric is deprecated and will be removed soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
if tt.resAttrsSet == testDataSetDefault {
assert.Equal(t, "[WARNING] Please set `enabled` field explicitly for `string.resource.attr_disable_warning`: This resource_attribute will be disabled by default soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
if tt.resAttrsSet == testDataSetAll || tt.resAttrsSet == testDataSetNone {
assert.Equal(t, "[WARNING] `string.resource.attr_remove_warning` should not be configured: This resource_attribute is deprecated and will be removed soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
if tt.resAttrsSet == testDataSetDefault || tt.resAttrsSet == testDataSetAll {
assert.Equal(t, "[WARNING] `string.resource.attr_to_be_removed` should not be enabled: This resource_attribute is deprecated and will be removed soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
if tt.metricsSet != testDataSetReag {
assert.Equal(t, expectedWarnings, observedLogs.Len())
}
defaultMetricsCount := 0
allMetricsCount := 0
ebTestEntity := mb.ForTestEntity(NewTestEntityEntity("string.resource.attr-val"))
defaultMetricsCount++
allMetricsCount++
ebTestEntity.RecordDefaultMetricDataPoint(ts, 1, "string_attr-val", 19, AttributeEnumAttrRed, []any{"slice_attr-item1", "slice_attr-item2"}, map[string]any{"key1": "map_attr-val1", "key2": "map_attr-val2"})
if tt.name == "reaggregate_set" {
ebTestEntity.RecordDefaultMetricDataPoint(ts, 3, "string_attr-val-2", 20, AttributeEnumAttrGreen, []any{"slice_attr-item3", "slice_attr-item4"}, map[string]any{"key3": "map_attr-val3", "key4": "map_attr-val4"})
}
defaultMetricsCount++
allMetricsCount++
ebTestEntity.RecordDefaultMetricToBeRemovedDataPoint(ts, 1)
defaultMetricsCount++
allMetricsCount++
ebTestEntity.RecordMetricInputTypeDataPoint(ts, "1", "string_attr-val", 19, AttributeEnumAttrRed, []any{"slice_attr-item1", "slice_attr-item2"}, map[string]any{"key1": "map_attr-val1", "key2": "map_attr-val2"})
if tt.name == "reaggregate_set" {
ebTestEntity.RecordMetricInputTypeDataPoint(ts, "3", "string_attr-val-2", 20, AttributeEnumAttrGreen, []any{"slice_attr-item3", "slice_attr-item4"}, map[string]any{"key3": "map_attr-val3", "key4": "map_attr-val4"})
}
allMetricsCount++
ebTestEntity.RecordOptionalMetricDataPoint(ts, 1, "string_attr-val", true, false)
if tt.name == "reaggregate_set" {
ebTestEntity.RecordOptionalMetricDataPoint(ts, 3, "string_attr-val-2", false, true)
}
allMetricsCount++
ebTestEntity.RecordOptionalMetricEmptyUnitDataPoint(ts, 1, "string_attr-val", true)
if tt.name == "reaggregate_set" {
ebTestEntity.RecordOptionalMetricEmptyUnitDataPoint(ts, 3, "string_attr-val-2", false)
}
defaultMetricsCount++
allMetricsCount++
ebTestEntity.RecordReaggregateMetricDataPoint(ts, 1, "string_attr-val", true)
if tt.name == "reaggregate_set" {
ebTestEntity.RecordReaggregateMetricDataPoint(ts, 3, "string_attr-val-2", false)
}
ebTestEntity.Emit()
rb := mb.NewResourceBuilder()
rb.SetMapResourceAttr(map[string]any{"key1": "map.resource.attr-val1", "key2": "map.resource.attr-val2"})
rb.SetOptionalResourceAttr("optional.resource.attr-val")
rb.SetSliceResourceAttr([]any{"slice.resource.attr-item1", "slice.resource.attr-item2"})
rb.SetStringEnumResourceAttrOne()
rb.SetStringResourceAttr("string.resource.attr-val")
rb.SetStringResourceAttrDisableWarning("string.resource.attr_disable_warning-val")
rb.SetStringResourceAttrRemoveWarning("string.resource.attr_remove_warning-val")
rb.SetStringResourceAttrToBeRemoved("string.resource.attr_to_be_removed-val")
res := rb.Emit()
metrics := mb.Emit(WithResource(res))
if tt.name == "reaggregate_set" {
assert.Empty(t, mb.metricDefaultMetric.aggDataPoints)
assert.Empty(t, mb.metricMetricInputType.aggDataPoints)
assert.Empty(t, mb.metricOptionalMetric.aggDataPoints)
assert.Empty(t, mb.metricOptionalMetricEmptyUnit.aggDataPoints)
assert.Empty(t, mb.metricReaggregateMetric.aggDataPoints)
}
if tt.expectEmpty {
assert.Equal(t, 0, metrics.ResourceMetrics().Len())
return
}
var allMetricsList []pmetric.Metric
totalMetricsCount := 0
for ri := 0; ri < metrics.ResourceMetrics().Len(); ri++ {
rm := metrics.ResourceMetrics().At(ri)
assert.Equal(t, 1, rm.ScopeMetrics().Len())
ms := rm.ScopeMetrics().At(0).Metrics()
totalMetricsCount += ms.Len()
for mi := 0; mi < ms.Len(); mi++ {
allMetricsList = append(allMetricsList, ms.At(mi))
}
}
if tt.metricsSet == testDataSetDefault {
assert.Equal(t, defaultMetricsCount, totalMetricsCount)
}
if tt.metricsSet == testDataSetAll {
assert.Equal(t, allMetricsCount, totalMetricsCount)
}
validatedMetrics := make(map[string]bool)
for _, mi := range allMetricsList {
switch mi.Name() {
case "default.metric":
if tt.name != "reaggregate_set" {
assert.False(t, validatedMetrics["default.metric"], "Found a duplicate in the metrics slice: default.metric")
validatedMetrics["default.metric"] = true
assert.Equal(t, pmetric.MetricTypeSum, mi.Type())
assert.Equal(t, 1, mi.Sum().DataPoints().Len())
assert.Equal(t, "Monotonic cumulative sum int metric enabled by default.", mi.Description())
assert.Equal(t, "s", mi.Unit())
assert.True(t, mi.Sum().IsMonotonic())
assert.Equal(t, pmetric.AggregationTemporalityCumulative, mi.Sum().AggregationTemporality())
dp := mi.Sum().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeInt, dp.ValueType())
assert.Equal(t, int64(1), dp.IntValue())
stringAttrAttrVal, ok := dp.Attributes().Get("string_attr")
assert.True(t, ok)
assert.Equal(t, "string_attr-val", stringAttrAttrVal.Str())
overriddenIntAttrAttrVal, ok := dp.Attributes().Get("state")
assert.True(t, ok)
assert.EqualValues(t, 19, overriddenIntAttrAttrVal.Int())
enumAttrAttrVal, ok := dp.Attributes().Get("enum_attr")
assert.True(t, ok)
assert.Equal(t, "red", enumAttrAttrVal.Str())
sliceAttrAttrVal, ok := dp.Attributes().Get("slice_attr")
assert.True(t, ok)
assert.Equal(t, []any{"slice_attr-item1", "slice_attr-item2"}, sliceAttrAttrVal.Slice().AsRaw())
mapAttrAttrVal, ok := dp.Attributes().Get("map_attr")
assert.True(t, ok)
assert.Equal(t, map[string]any{"key1": "map_attr-val1", "key2": "map_attr-val2"}, mapAttrAttrVal.Map().AsRaw())
} else {
assert.False(t, validatedMetrics["default.metric"], "Found a duplicate in the metrics slice: default.metric")
validatedMetrics["default.metric"] = true
assert.Equal(t, pmetric.MetricTypeSum, mi.Type())
assert.Equal(t, 1, mi.Sum().DataPoints().Len())
assert.Equal(t, "Monotonic cumulative sum int metric enabled by default.", mi.Description())
assert.Equal(t, "s", mi.Unit())
assert.True(t, mi.Sum().IsMonotonic())
assert.Equal(t, pmetric.AggregationTemporalityCumulative, mi.Sum().AggregationTemporality())
dp := mi.Sum().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeInt, dp.ValueType())
switch aggMap["default.metric"] {
case "sum":
assert.Equal(t, int64(4), dp.IntValue())
case "avg":
assert.Equal(t, int64(2), dp.IntValue())
case "min":
assert.Equal(t, int64(1), dp.IntValue())
case "max":
assert.Equal(t, int64(3), dp.IntValue())
}
_, ok := dp.Attributes().Get("string_attr")
assert.False(t, ok)
_, ok = dp.Attributes().Get("state")
assert.False(t, ok)
_, ok = dp.Attributes().Get("enum_attr")
assert.False(t, ok)
_, ok = dp.Attributes().Get("slice_attr")
assert.False(t, ok)
_, ok = dp.Attributes().Get("map_attr")
assert.False(t, ok)
}
case "default.metric.to_be_removed":
assert.False(t, validatedMetrics["default.metric.to_be_removed"], "Found a duplicate in the metrics slice: default.metric.to_be_removed")
validatedMetrics["default.metric.to_be_removed"] = true
assert.Equal(t, pmetric.MetricTypeSum, mi.Type())
assert.Equal(t, 1, mi.Sum().DataPoints().Len())
assert.Equal(t, "[DEPRECATED] Non-monotonic delta sum double metric enabled by default.", mi.Description())
assert.Equal(t, "s", mi.Unit())
assert.False(t, mi.Sum().IsMonotonic())
assert.Equal(t, pmetric.AggregationTemporalityDelta, mi.Sum().AggregationTemporality())
dp := mi.Sum().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeDouble, dp.ValueType())
assert.InDelta(t, float64(1), dp.DoubleValue(), 0.01)
case "metric.input_type":
if tt.name != "reaggregate_set" {
assert.False(t, validatedMetrics["metric.input_type"], "Found a duplicate in the metrics slice: metric.input_type")
validatedMetrics["metric.input_type"] = true
assert.Equal(t, pmetric.MetricTypeSum, mi.Type())
assert.Equal(t, 1, mi.Sum().DataPoints().Len())
assert.Equal(t, "Monotonic cumulative sum int metric with string input_type enabled by default.", mi.Description())
assert.Equal(t, "s", mi.Unit())
assert.True(t, mi.Sum().IsMonotonic())
assert.Equal(t, pmetric.AggregationTemporalityCumulative, mi.Sum().AggregationTemporality())
dp := mi.Sum().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeInt, dp.ValueType())
assert.Equal(t, int64(1), dp.IntValue())
stringAttrAttrVal, ok := dp.Attributes().Get("string_attr")
assert.True(t, ok)
assert.Equal(t, "string_attr-val", stringAttrAttrVal.Str())
overriddenIntAttrAttrVal, ok := dp.Attributes().Get("state")
assert.True(t, ok)
assert.EqualValues(t, 19, overriddenIntAttrAttrVal.Int())
enumAttrAttrVal, ok := dp.Attributes().Get("enum_attr")
assert.True(t, ok)
assert.Equal(t, "red", enumAttrAttrVal.Str())
sliceAttrAttrVal, ok := dp.Attributes().Get("slice_attr")
assert.True(t, ok)
assert.Equal(t, []any{"slice_attr-item1", "slice_attr-item2"}, sliceAttrAttrVal.Slice().AsRaw())
mapAttrAttrVal, ok := dp.Attributes().Get("map_attr")
assert.True(t, ok)
assert.Equal(t, map[string]any{"key1": "map_attr-val1", "key2": "map_attr-val2"}, mapAttrAttrVal.Map().AsRaw())
} else {
assert.False(t, validatedMetrics["metric.input_type"], "Found a duplicate in the metrics slice: metric.input_type")
validatedMetrics["metric.input_type"] = true
assert.Equal(t, pmetric.MetricTypeSum, mi.Type())
assert.Equal(t, 1, mi.Sum().DataPoints().Len())
assert.Equal(t, "Monotonic cumulative sum int metric with string input_type enabled by default.", mi.Description())
assert.Equal(t, "s", mi.Unit())
assert.True(t, mi.Sum().IsMonotonic())
assert.Equal(t, pmetric.AggregationTemporalityCumulative, mi.Sum().AggregationTemporality())
dp := mi.Sum().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeInt, dp.ValueType())
switch aggMap["metric.input_type"] {
case "sum":
assert.Equal(t, int64(4), dp.IntValue())
case "avg":
assert.Equal(t, int64(2), dp.IntValue())
case "min":
assert.Equal(t, int64(1), dp.IntValue())
case "max":
assert.Equal(t, int64(3), dp.IntValue())
}
_, ok := dp.Attributes().Get("string_attr")
assert.False(t, ok)
_, ok = dp.Attributes().Get("state")
assert.False(t, ok)
_, ok = dp.Attributes().Get("enum_attr")
assert.False(t, ok)
_, ok = dp.Attributes().Get("slice_attr")
assert.False(t, ok)
_, ok = dp.Attributes().Get("map_attr")
assert.False(t, ok)
}
case "optional.metric":
if tt.name != "reaggregate_set" {
assert.False(t, validatedMetrics["optional.metric"], "Found a duplicate in the metrics slice: optional.metric")
validatedMetrics["optional.metric"] = true
assert.Equal(t, pmetric.MetricTypeGauge, mi.Type())
assert.Equal(t, 1, mi.Gauge().DataPoints().Len())
assert.Equal(t, "[DEPRECATED] Gauge double metric disabled by default.", mi.Description())
assert.Equal(t, "1", mi.Unit())
dp := mi.Gauge().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeDouble, dp.ValueType())
assert.InDelta(t, float64(1), dp.DoubleValue(), 0.01)
stringAttrAttrVal, ok := dp.Attributes().Get("string_attr")
assert.True(t, ok)
assert.Equal(t, "string_attr-val", stringAttrAttrVal.Str())
booleanAttrAttrVal, ok := dp.Attributes().Get("boolean_attr")
assert.True(t, ok)
assert.True(t, booleanAttrAttrVal.Bool())
booleanAttr2AttrVal, ok := dp.Attributes().Get("boolean_attr2")
assert.True(t, ok)
assert.False(t, booleanAttr2AttrVal.Bool())
} else {
assert.False(t, validatedMetrics["optional.metric"], "Found a duplicate in the metrics slice: optional.metric")
validatedMetrics["optional.metric"] = true
assert.Equal(t, pmetric.MetricTypeGauge, mi.Type())
assert.Equal(t, 1, mi.Gauge().DataPoints().Len())
assert.Equal(t, "[DEPRECATED] Gauge double metric disabled by default.", mi.Description())
assert.Equal(t, "1", mi.Unit())
dp := mi.Gauge().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeDouble, dp.ValueType())
switch aggMap["optional.metric"] {
case "sum":
assert.InDelta(t, float64(4), dp.DoubleValue(), 0.01)
case "avg":
assert.InDelta(t, float64(2), dp.DoubleValue(), 0.01)
case "min":
assert.InDelta(t, float64(1), dp.DoubleValue(), 0.01)
case "max":
assert.InDelta(t, float64(3), dp.DoubleValue(), 0.01)
}
_, ok := dp.Attributes().Get("string_attr")
assert.False(t, ok)
_, ok = dp.Attributes().Get("boolean_attr")
assert.False(t, ok)
_, ok = dp.Attributes().Get("boolean_attr2")
assert.False(t, ok)
}
case "optional.metric.empty_unit":
if tt.name != "reaggregate_set" {
assert.False(t, validatedMetrics["optional.metric.empty_unit"], "Found a duplicate in the metrics slice: optional.metric.empty_unit")
validatedMetrics["optional.metric.empty_unit"] = true
assert.Equal(t, pmetric.MetricTypeGauge, mi.Type())
assert.Equal(t, 1, mi.Gauge().DataPoints().Len())
assert.Equal(t, "[DEPRECATED] Gauge double metric disabled by default.", mi.Description())
assert.Empty(t, mi.Unit())
dp := mi.Gauge().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeDouble, dp.ValueType())
assert.InDelta(t, float64(1), dp.DoubleValue(), 0.01)
stringAttrAttrVal, ok := dp.Attributes().Get("string_attr")
assert.True(t, ok)
assert.Equal(t, "string_attr-val", stringAttrAttrVal.Str())
booleanAttrAttrVal, ok := dp.Attributes().Get("boolean_attr")
assert.True(t, ok)
assert.True(t, booleanAttrAttrVal.Bool())
} else {
assert.False(t, validatedMetrics["optional.metric.empty_unit"], "Found a duplicate in the metrics slice: optional.metric.empty_unit")
validatedMetrics["optional.metric.empty_unit"] = true
assert.Equal(t, pmetric.MetricTypeGauge, mi.Type())
assert.Equal(t, 1, mi.Gauge().DataPoints().Len())
assert.Equal(t, "[DEPRECATED] Gauge double metric disabled by default.", mi.Description())
assert.Empty(t, mi.Unit())
dp := mi.Gauge().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeDouble, dp.ValueType())
switch aggMap["optional.metric.empty_unit"] {
case "sum":
assert.InDelta(t, float64(4), dp.DoubleValue(), 0.01)
case "avg":
assert.InDelta(t, float64(2), dp.DoubleValue(), 0.01)
case "min":
assert.InDelta(t, float64(1), dp.DoubleValue(), 0.01)
case "max":
assert.InDelta(t, float64(3), dp.DoubleValue(), 0.01)
}
_, ok := dp.Attributes().Get("string_attr")
assert.False(t, ok)
_, ok = dp.Attributes().Get("boolean_attr")
assert.False(t, ok)
}
case "reaggregate.metric":
if tt.name != "reaggregate_set" {
assert.False(t, validatedMetrics["reaggregate.metric"], "Found a duplicate in the metrics slice: reaggregate.metric")
validatedMetrics["reaggregate.metric"] = true
assert.Equal(t, pmetric.MetricTypeGauge, mi.Type())
assert.Equal(t, 1, mi.Gauge().DataPoints().Len())
assert.Equal(t, "Metric for testing spatial reaggregation", mi.Description())
assert.Equal(t, "1", mi.Unit())
dp := mi.Gauge().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeDouble, dp.ValueType())
assert.InDelta(t, float64(1), dp.DoubleValue(), 0.01)
stringAttrAttrVal, ok := dp.Attributes().Get("string_attr")
assert.True(t, ok)
assert.Equal(t, "string_attr-val", stringAttrAttrVal.Str())
booleanAttrAttrVal, ok := dp.Attributes().Get("boolean_attr")
assert.True(t, ok)
assert.True(t, booleanAttrAttrVal.Bool())
} else {
assert.False(t, validatedMetrics["reaggregate.metric"], "Found a duplicate in the metrics slice: reaggregate.metric")
validatedMetrics["reaggregate.metric"] = true
assert.Equal(t, pmetric.MetricTypeGauge, mi.Type())
assert.Equal(t, 1, mi.Gauge().DataPoints().Len())
assert.Equal(t, "Metric for testing spatial reaggregation", mi.Description())
assert.Equal(t, "1", mi.Unit())
dp := mi.Gauge().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeDouble, dp.ValueType())
switch aggMap["reaggregate.metric"] {
case "sum":
assert.InDelta(t, float64(4), dp.DoubleValue(), 0.01)
case "avg":
assert.InDelta(t, float64(2), dp.DoubleValue(), 0.01)
case "min":
assert.InDelta(t, float64(1), dp.DoubleValue(), 0.01)
case "max":
assert.InDelta(t, float64(3), dp.DoubleValue(), 0.01)
}
_, ok := dp.Attributes().Get("string_attr")
assert.False(t, ok)
_, ok = dp.Attributes().Get("boolean_attr")
assert.False(t, ok)
}
}
}
})
}
}
================================================
FILE: cmd/mdatagen/internal/sampleconnector/internal/metadata/generated_resource.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/pdata/pcommon"
)
// ResourceBuilder is a helper struct to build resources predefined in metadata.yaml.
// The ResourceBuilder is not thread-safe and must not to be used in multiple goroutines.
type ResourceBuilder struct {
config ResourceAttributesConfig
res pcommon.Resource
}
// NewResourceBuilder creates a new ResourceBuilder. This method should be called on the start of the application.
func NewResourceBuilder(rac ResourceAttributesConfig) *ResourceBuilder {
return &ResourceBuilder{
config: rac,
res: pcommon.NewResource(),
}
}
// SetMapResourceAttr sets provided value as "map.resource.attr" attribute.
func (rb *ResourceBuilder) SetMapResourceAttr(val map[string]any) {
if rb.config.MapResourceAttr.Enabled {
rb.res.Attributes().PutEmptyMap("map.resource.attr").FromRaw(val)
}
}
// SetOptionalResourceAttr sets provided value as "optional.resource.attr" attribute.
func (rb *ResourceBuilder) SetOptionalResourceAttr(val string) {
if rb.config.OptionalResourceAttr.Enabled {
rb.res.Attributes().PutStr("optional.resource.attr", val)
}
}
// SetSliceResourceAttr sets provided value as "slice.resource.attr" attribute.
func (rb *ResourceBuilder) SetSliceResourceAttr(val []any) {
if rb.config.SliceResourceAttr.Enabled {
rb.res.Attributes().PutEmptySlice("slice.resource.attr").FromRaw(val)
}
}
// SetStringEnumResourceAttrOne sets "string.enum.resource.attr=one" attribute.
func (rb *ResourceBuilder) SetStringEnumResourceAttrOne() {
if rb.config.StringEnumResourceAttr.Enabled {
rb.res.Attributes().PutStr("string.enum.resource.attr", "one")
}
}
// SetStringEnumResourceAttrTwo sets "string.enum.resource.attr=two" attribute.
func (rb *ResourceBuilder) SetStringEnumResourceAttrTwo() {
if rb.config.StringEnumResourceAttr.Enabled {
rb.res.Attributes().PutStr("string.enum.resource.attr", "two")
}
}
// SetStringResourceAttr sets provided value as "string.resource.attr" attribute.
func (rb *ResourceBuilder) SetStringResourceAttr(val string) {
if rb.config.StringResourceAttr.Enabled {
rb.res.Attributes().PutStr("string.resource.attr", val)
}
}
// SetStringResourceAttrDisableWarning sets provided value as "string.resource.attr_disable_warning" attribute.
func (rb *ResourceBuilder) SetStringResourceAttrDisableWarning(val string) {
if rb.config.StringResourceAttrDisableWarning.Enabled {
rb.res.Attributes().PutStr("string.resource.attr_disable_warning", val)
}
}
// SetStringResourceAttrRemoveWarning sets provided value as "string.resource.attr_remove_warning" attribute.
func (rb *ResourceBuilder) SetStringResourceAttrRemoveWarning(val string) {
if rb.config.StringResourceAttrRemoveWarning.Enabled {
rb.res.Attributes().PutStr("string.resource.attr_remove_warning", val)
}
}
// SetStringResourceAttrToBeRemoved sets provided value as "string.resource.attr_to_be_removed" attribute.
func (rb *ResourceBuilder) SetStringResourceAttrToBeRemoved(val string) {
if rb.config.StringResourceAttrToBeRemoved.Enabled {
rb.res.Attributes().PutStr("string.resource.attr_to_be_removed", val)
}
}
// Emit returns the built resource and resets the internal builder state.
func (rb *ResourceBuilder) Emit() pcommon.Resource {
r := rb.res
rb.res = pcommon.NewResource()
return r
}
================================================
FILE: cmd/mdatagen/internal/sampleconnector/internal/metadata/generated_resource_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestResourceBuilder(t *testing.T) {
for _, tt := range []string{"default", "all_set", "none_set"} {
t.Run(tt, func(t *testing.T) {
cfg := loadResourceAttributesConfig(t, tt)
rb := NewResourceBuilder(cfg)
rb.SetMapResourceAttr(map[string]any{"key1": "map.resource.attr-val1", "key2": "map.resource.attr-val2"})
rb.SetOptionalResourceAttr("optional.resource.attr-val")
rb.SetSliceResourceAttr([]any{"slice.resource.attr-item1", "slice.resource.attr-item2"})
rb.SetStringEnumResourceAttrOne()
rb.SetStringResourceAttr("string.resource.attr-val")
rb.SetStringResourceAttrDisableWarning("string.resource.attr_disable_warning-val")
rb.SetStringResourceAttrRemoveWarning("string.resource.attr_remove_warning-val")
rb.SetStringResourceAttrToBeRemoved("string.resource.attr_to_be_removed-val")
res := rb.Emit()
assert.Equal(t, 0, rb.Emit().Attributes().Len()) // Second call should return empty Resource
switch tt {
case "default":
assert.Equal(t, 6, res.Attributes().Len())
case "all_set":
assert.Equal(t, 8, res.Attributes().Len())
case "none_set":
assert.Equal(t, 0, res.Attributes().Len())
return
default:
assert.Failf(t, "unexpected test case: %s", tt)
}
mapResourceAttrAttrVal, ok := res.Attributes().Get("map.resource.attr")
assert.True(t, ok)
if ok {
assert.Equal(t, map[string]any{"key1": "map.resource.attr-val1", "key2": "map.resource.attr-val2"}, mapResourceAttrAttrVal.Map().AsRaw())
}
optionalResourceAttrAttrVal, ok := res.Attributes().Get("optional.resource.attr")
assert.Equal(t, tt == "all_set", ok)
if ok {
assert.Equal(t, "optional.resource.attr-val", optionalResourceAttrAttrVal.Str())
}
sliceResourceAttrAttrVal, ok := res.Attributes().Get("slice.resource.attr")
assert.True(t, ok)
if ok {
assert.Equal(t, []any{"slice.resource.attr-item1", "slice.resource.attr-item2"}, sliceResourceAttrAttrVal.Slice().AsRaw())
}
stringEnumResourceAttrAttrVal, ok := res.Attributes().Get("string.enum.resource.attr")
assert.True(t, ok)
if ok {
assert.Equal(t, "one", stringEnumResourceAttrAttrVal.Str())
}
stringResourceAttrAttrVal, ok := res.Attributes().Get("string.resource.attr")
assert.True(t, ok)
if ok {
assert.Equal(t, "string.resource.attr-val", stringResourceAttrAttrVal.Str())
}
stringResourceAttrDisableWarningAttrVal, ok := res.Attributes().Get("string.resource.attr_disable_warning")
assert.True(t, ok)
if ok {
assert.Equal(t, "string.resource.attr_disable_warning-val", stringResourceAttrDisableWarningAttrVal.Str())
}
stringResourceAttrRemoveWarningAttrVal, ok := res.Attributes().Get("string.resource.attr_remove_warning")
assert.Equal(t, tt == "all_set", ok)
if ok {
assert.Equal(t, "string.resource.attr_remove_warning-val", stringResourceAttrRemoveWarningAttrVal.Str())
}
stringResourceAttrToBeRemovedAttrVal, ok := res.Attributes().Get("string.resource.attr_to_be_removed")
assert.True(t, ok)
if ok {
assert.Equal(t, "string.resource.attr_to_be_removed-val", stringResourceAttrToBeRemovedAttrVal.Str())
}
})
}
}
================================================
FILE: cmd/mdatagen/internal/sampleconnector/internal/metadata/generated_status.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/component"
)
var (
Type = component.MustNewType("sample")
ScopeName = "go.opentelemetry.io/collector/cmd/mdatagen/internal/sampleconnector"
)
const (
MetricsToMetricsStability = component.StabilityLevelDevelopment
ProfilesToProfilesStability = component.StabilityLevelDevelopment
)
================================================
FILE: cmd/mdatagen/internal/sampleconnector/internal/metadata/testdata/config.yaml
================================================
default:
all_set:
metrics:
default.metric:
enabled: true
attributes: ["string_attr","state","enum_attr","slice_attr","map_attr"]
default.metric.to_be_removed:
enabled: true
metric.input_type:
enabled: true
attributes: ["string_attr","state","enum_attr","slice_attr","map_attr"]
optional.metric:
enabled: true
attributes: ["string_attr","boolean_attr","boolean_attr2"]
optional.metric.empty_unit:
enabled: true
attributes: ["string_attr","boolean_attr"]
reaggregate.metric:
enabled: true
attributes: ["string_attr","boolean_attr"]
resource_attributes:
map.resource.attr:
enabled: true
optional.resource.attr:
enabled: true
slice.resource.attr:
enabled: true
string.enum.resource.attr:
enabled: true
string.resource.attr:
enabled: true
string.resource.attr_disable_warning:
enabled: true
string.resource.attr_remove_warning:
enabled: true
string.resource.attr_to_be_removed:
enabled: true
reaggregate_set:
metrics:
default.metric:
enabled: true
attributes: []
default.metric.to_be_removed:
enabled: true
metric.input_type:
enabled: true
attributes: []
optional.metric:
enabled: true
attributes: []
optional.metric.empty_unit:
enabled: true
attributes: []
reaggregate.metric:
enabled: true
attributes: []
resource_attributes:
map.resource.attr:
enabled: true
optional.resource.attr:
enabled: true
slice.resource.attr:
enabled: true
string.enum.resource.attr:
enabled: true
string.resource.attr:
enabled: true
string.resource.attr_disable_warning:
enabled: true
string.resource.attr_remove_warning:
enabled: true
string.resource.attr_to_be_removed:
enabled: true
none_set:
metrics:
default.metric:
enabled: false
attributes: ["string_attr","state","enum_attr","slice_attr","map_attr"]
default.metric.to_be_removed:
enabled: false
metric.input_type:
enabled: false
attributes: ["string_attr","state","enum_attr","slice_attr","map_attr"]
optional.metric:
enabled: false
attributes: ["string_attr","boolean_attr","boolean_attr2"]
optional.metric.empty_unit:
enabled: false
attributes: ["string_attr","boolean_attr"]
reaggregate.metric:
enabled: false
attributes: ["string_attr","boolean_attr"]
resource_attributes:
map.resource.attr:
enabled: false
optional.resource.attr:
enabled: false
slice.resource.attr:
enabled: false
string.enum.resource.attr:
enabled: false
string.resource.attr:
enabled: false
string.resource.attr_disable_warning:
enabled: false
string.resource.attr_remove_warning:
enabled: false
string.resource.attr_to_be_removed:
enabled: false
filter_set_include:
resource_attributes:
map.resource.attr:
enabled: true
metrics_include:
- regexp: ".*"
optional.resource.attr:
enabled: true
metrics_include:
- regexp: ".*"
slice.resource.attr:
enabled: true
metrics_include:
- regexp: ".*"
string.enum.resource.attr:
enabled: true
metrics_include:
- regexp: ".*"
string.resource.attr:
enabled: true
metrics_include:
- regexp: ".*"
string.resource.attr_disable_warning:
enabled: true
metrics_include:
- regexp: ".*"
string.resource.attr_remove_warning:
enabled: true
metrics_include:
- regexp: ".*"
string.resource.attr_to_be_removed:
enabled: true
metrics_include:
- regexp: ".*"
filter_set_exclude:
resource_attributes:
map.resource.attr:
enabled: true
metrics_exclude:
- regexp: ".*"
optional.resource.attr:
enabled: true
metrics_exclude:
- strict: "optional.resource.attr-val"
slice.resource.attr:
enabled: true
metrics_exclude:
- regexp: ".*"
string.enum.resource.attr:
enabled: true
metrics_exclude:
- strict: "one"
string.resource.attr:
enabled: true
metrics_exclude:
- strict: "string.resource.attr-val"
string.resource.attr_disable_warning:
enabled: true
metrics_exclude:
- strict: "string.resource.attr_disable_warning-val"
string.resource.attr_remove_warning:
enabled: true
metrics_exclude:
- strict: "string.resource.attr_remove_warning-val"
string.resource.attr_to_be_removed:
enabled: true
metrics_exclude:
- strict: "string.resource.attr_to_be_removed-val"
================================================
FILE: cmd/mdatagen/internal/sampleconnector/metadata.yaml
================================================
# Sample metadata file with all available configurations for a connector.
type: sample
display_name: Sample Connector
description: This connector is used for testing purposes to check the output of mdatagen.
github_project: open-telemetry/opentelemetry-collector
reaggregation_enabled: true
sem_conv_version: 1.9.0
status:
disable_codecov_badge: true
class: connector
stability:
development: [metrics_to_metrics, profiles_to_profiles]
distributions: []
unsupported_platforms: [freebsd, illumos]
codeowners:
active: []
warnings:
- Any additional information that should be brought to the consumer's attention
resource_attributes:
map.resource.attr:
description: Resource attribute with a map value.
type: map
enabled: true
optional.resource.attr:
description: Explicitly disabled ResourceAttribute.
type: string
enabled: false
slice.resource.attr:
description: Resource attribute with a slice value.
type: slice
enabled: true
string.enum.resource.attr:
description: Resource attribute with a known set of string values.
type: string
enum: [one, two]
enabled: true
string.resource.attr:
description: Resource attribute with any string value.
type: string
enabled: true
string.resource.attr_disable_warning:
description: Resource attribute with any string value.
type: string
enabled: true
warnings:
if_enabled_not_set: This resource_attribute will be disabled by default soon.
string.resource.attr_remove_warning:
description: Resource attribute with any string value.
type: string
enabled: false
warnings:
if_configured: This resource_attribute is deprecated and will be removed soon.
string.resource.attr_to_be_removed:
description: Resource attribute with any string value.
type: string
enabled: true
warnings:
if_enabled: This resource_attribute is deprecated and will be removed soon.
entities:
- type: test.entity
brief: A test entity.
stability: stable
identity:
- ref: string.resource.attr
description:
- ref: map.resource.attr
attributes:
boolean_attr:
description: Attribute with a boolean value.
type: bool
# This 2nd boolean attribute allows us to test both values for boolean attributes,
# as test values are based on the parity of the attribute name length.
boolean_attr2:
description: Another attribute with a boolean value.
type: bool
enum_attr:
description: Attribute with a known set of string values.
type: string
enum: [red, green, blue]
map_attr:
description: Attribute with a map value.
type: map
overridden_int_attr:
name_override: state
description: Integer attribute with overridden name.
type: int
slice_attr:
description: Attribute with a slice value.
type: slice
string_attr:
description: Attribute with any string value.
type: string
metrics:
default.metric:
enabled: true
description: Monotonic cumulative sum int metric enabled by default.
extended_documentation: The metric will be become optional soon.
stability: development
unit: s
sum:
value_type: int
monotonic: true
aggregation_temporality: cumulative
attributes:
[string_attr, overridden_int_attr, enum_attr, slice_attr, map_attr]
entity: test.entity
warnings:
if_enabled_not_set: This metric will be disabled by default soon.
default.metric.to_be_removed:
enabled: true
description: "[DEPRECATED] Non-monotonic delta sum double metric enabled by default."
extended_documentation: The metric will be removed soon.
stability: deprecated
deprecated:
since: "1.0.0"
note: "This metric will be removed"
unit: s
sum:
value_type: double
monotonic: false
aggregation_temporality: delta
entity: test.entity
warnings:
if_enabled: This metric is deprecated and will be removed soon.
metric.input_type:
enabled: true
description: Monotonic cumulative sum int metric with string input_type enabled by default.
stability: development
unit: s
sum:
value_type: int
input_type: string
monotonic: true
aggregation_temporality: cumulative
attributes:
[string_attr, overridden_int_attr, enum_attr, slice_attr, map_attr]
entity: test.entity
optional.metric:
enabled: false
description: "[DEPRECATED] Gauge double metric disabled by default."
stability: deprecated
deprecated:
since: "1.0.0"
note: "This metric will be removed"
unit: "1"
gauge:
value_type: double
attributes: [string_attr, boolean_attr, boolean_attr2]
entity: test.entity
warnings:
if_configured: This metric is deprecated and will be removed soon.
optional.metric.empty_unit:
enabled: false
description: "[DEPRECATED] Gauge double metric disabled by default."
stability: deprecated
deprecated:
since: "1.0.0"
note: "This metric will be removed"
unit: ""
gauge:
value_type: double
attributes: [string_attr, boolean_attr]
entity: test.entity
warnings:
if_configured: This metric is deprecated and will be removed soon.
reaggregate.metric:
enabled: true
description: Metric for testing spatial reaggregation
unit: "1"
stability: beta
gauge:
value_type: double
attributes: [string_attr, boolean_attr]
entity: test.entity
================================================
FILE: cmd/mdatagen/internal/sampleconnector/metrics_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sampleconnector
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/cmd/mdatagen/internal/sampleconnector/internal/metadata"
"go.opentelemetry.io/collector/connector/connectortest"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/pdata/pmetric"
)
// TestGeneratedMetrics verifies that the internal/metadata API is generated correctly.
func TestGeneratedMetrics(t *testing.T) {
mb := metadata.NewMetricsBuilder(metadata.DefaultMetricsBuilderConfig(), connectortest.NewNopSettings(metadata.Type))
m := mb.Emit()
require.Equal(t, 0, m.ResourceMetrics().Len())
}
func TestNopConnector(t *testing.T) {
connector, err := createMetricsToMetricsConnector(context.Background(), connectortest.NewNopSettings(metadata.Type), newMdatagenNopHost(), new(consumertest.MetricsSink))
require.NoError(t, err)
require.False(t, connector.Capabilities().MutatesData)
require.NoError(t, connector.ConsumeMetrics(context.Background(), pmetric.NewMetrics()))
}
================================================
FILE: cmd/mdatagen/internal/sampleentityreceiver/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Generate a sample entity-based metrics builder
//go:generate mdatagen metadata.yaml
package sampleentityreceiver // import "go.opentelemetry.io/collector/cmd/mdatagen/internal/sampleentityreceiver"
================================================
FILE: cmd/mdatagen/internal/sampleentityreceiver/documentation.md
================================================
[comment]: <> (Code generated by mdatagen. DO NOT EDIT.)
# sampleentity
## Default Metrics
The following metrics are emitted by default. Each of them can be disabled by applying the following configuration:
```yaml
metrics:
:
enabled: false
```
### k8s.pod.cpu_time
CPU time consumed by the pod
| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability |
| ---- | ----------- | ---------- | ----------------------- | --------- | --------- |
| s | Sum | Double | Cumulative | true | Development |
### k8s.pod.phase
Current phase of the pod
| Unit | Metric Type | Value Type | Stability |
| ---- | ----------- | ---------- | --------- |
| 1 | Gauge | Int | Development |
#### Attributes
| Name | Description | Values | Requirement Level |
| ---- | ----------- | ------ | -------- |
| phase | The phase of the pod (Pending, Running, Succeeded, Failed, Unknown) | Str: ``Pending``, ``Running``, ``Succeeded``, ``Failed``, ``Unknown`` | Recommended |
### k8s.replicaset.desired
Number of desired replicas
| Unit | Metric Type | Value Type | Stability |
| ---- | ----------- | ---------- | --------- |
| {replicas} | Gauge | Int | Development |
## Resource Attributes
| Name | Description | Values | Enabled |
| ---- | ----------- | ------ | ------- |
| k8s.namespace.name | The name of the Kubernetes Namespace | Any Str | true |
| k8s.pod.name | The name of the Kubernetes Pod | Any Str | true |
| k8s.pod.uid | The UID of the Kubernetes Pod | Any Str | true |
| k8s.replicaset.name | The name of the Kubernetes ReplicaSet | Any Str | true |
| k8s.replicaset.uid | The UID of the Kubernetes ReplicaSet | Any Str | true |
## Entities
The following entities are defined for this component:
### k8s.replicaset
A Kubernetes ReplicaSet
**Stability:** Development
**Identifying Attributes:**
- `k8s.replicaset.uid`
**Descriptive Attributes:**
- `k8s.replicaset.name`
### k8s.pod
A Kubernetes Pod
**Stability:** Development
**Identifying Attributes:**
- `k8s.pod.uid`
**Descriptive Attributes:**
- `k8s.pod.name`
================================================
FILE: cmd/mdatagen/internal/sampleentityreceiver/factory.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sampleentityreceiver // import "go.opentelemetry.io/collector/cmd/mdatagen/internal/sampleentityreceiver"
import (
"context"
"go.opentelemetry.io/collector/cmd/mdatagen/internal/sampleentityreceiver/internal/metadata"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/xreceiver"
)
// NewFactory returns a receiver.Factory for sample entity receiver.
func NewFactory() xreceiver.Factory {
return xreceiver.NewFactory(
metadata.Type,
func() component.Config { return &struct{}{} },
xreceiver.WithMetrics(createMetrics, metadata.MetricsStability),
)
}
func createMetrics(context.Context, receiver.Settings, component.Config, consumer.Metrics) (receiver.Metrics, error) {
return nopInstance, nil
}
var nopInstance = &nopReceiver{}
type nopReceiver struct {
component.StartFunc
}
func (nopReceiver) Shutdown(context.Context) error {
return nil
}
================================================
FILE: cmd/mdatagen/internal/sampleentityreceiver/generated_component_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package sampleentityreceiver
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/confmap/confmaptest"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/receivertest"
)
var typ = component.MustNewType("sampleentity")
func TestComponentFactoryType(t *testing.T) {
require.Equal(t, typ, NewFactory().Type())
}
func TestComponentConfigStruct(t *testing.T) {
require.NoError(t, componenttest.CheckConfigStruct(NewFactory().CreateDefaultConfig()))
}
func TestComponentLifecycle(t *testing.T) {
factory := NewFactory()
tests := []struct {
createFn func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error)
name string
}{
{
name: "metrics",
createFn: func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateMetrics(ctx, set, cfg, consumertest.NewNop())
},
},
}
cm, err := confmaptest.LoadConf("metadata.yaml")
require.NoError(t, err)
cfg := factory.CreateDefaultConfig()
sub, err := cm.Sub("tests::config")
require.NoError(t, err)
require.NoError(t, sub.Unmarshal(&cfg))
for _, tt := range tests {
t.Run(tt.name+"-shutdown", func(t *testing.T) {
c, err := tt.createFn(context.Background(), receivertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
err = c.Shutdown(context.Background())
require.NoError(t, err)
})
t.Run(tt.name+"-lifecycle", func(t *testing.T) {
firstRcvr, err := tt.createFn(context.Background(), receivertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
host := newMdatagenNopHost()
require.NoError(t, err)
require.NoError(t, firstRcvr.Start(context.Background(), host))
require.NoError(t, firstRcvr.Shutdown(context.Background()))
secondRcvr, err := tt.createFn(context.Background(), receivertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
require.NoError(t, secondRcvr.Start(context.Background(), host))
require.NoError(t, secondRcvr.Shutdown(context.Background()))
})
}
}
var _ component.Host = (*mdatagenNopHost)(nil)
type mdatagenNopHost struct{}
func newMdatagenNopHost() component.Host {
return &mdatagenNopHost{}
}
func (mnh *mdatagenNopHost) GetExtensions() map[component.ID]component.Component {
return nil
}
func (mnh *mdatagenNopHost) GetFactory(_ component.Kind, _ component.Type) component.Factory {
return nil
}
================================================
FILE: cmd/mdatagen/internal/sampleentityreceiver/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package sampleentityreceiver
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: cmd/mdatagen/internal/sampleentityreceiver/internal/metadata/config.schema.yaml
================================================
# Code generated by mdatagen. DO NOT EDIT.
$defs:
metrics_config:
description: MetricsConfig provides config for sampleentity metrics.
type: object
properties:
k8s.pod.cpu_time:
description: "K8sPodCPUTimeMetricConfig provides config for the k8s.pod.cpu_time metric."
type: object
properties:
enabled:
type: boolean
default: true
k8s.pod.phase:
description: "K8sPodPhaseMetricConfig provides config for the k8s.pod.phase metric."
type: object
properties:
enabled:
type: boolean
default: true
k8s.replicaset.desired:
description: "K8sReplicasetDesiredMetricConfig provides config for the k8s.replicaset.desired metric."
type: object
properties:
enabled:
type: boolean
default: true
resource_attributes_config:
description: ResourceAttributesConfig provides config for sampleentity resource attributes.
type: object
properties:
k8s.namespace.name:
description: ResourceAttributeConfig provides common config for a k8s.namespace.name resource attribute.
type: object
properties:
enabled:
type: boolean
default: true
metrics_include:
description: "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
metrics_exclude:
description: "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude."
type: array
items:
$ref: /filter.config
k8s.pod.name:
description: ResourceAttributeConfig provides common config for a k8s.pod.name resource attribute.
type: object
properties:
enabled:
type: boolean
default: true
metrics_include:
description: "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
metrics_exclude:
description: "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude."
type: array
items:
$ref: /filter.config
k8s.pod.uid:
description: ResourceAttributeConfig provides common config for a k8s.pod.uid resource attribute.
type: object
properties:
enabled:
type: boolean
default: true
metrics_include:
description: "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
metrics_exclude:
description: "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude."
type: array
items:
$ref: /filter.config
k8s.replicaset.name:
description: ResourceAttributeConfig provides common config for a k8s.replicaset.name resource attribute.
type: object
properties:
enabled:
type: boolean
default: true
metrics_include:
description: "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
metrics_exclude:
description: "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude."
type: array
items:
$ref: /filter.config
k8s.replicaset.uid:
description: ResourceAttributeConfig provides common config for a k8s.replicaset.uid resource attribute.
type: object
properties:
enabled:
type: boolean
default: true
metrics_include:
description: "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
metrics_exclude:
description: "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude."
type: array
items:
$ref: /filter.config
metrics_builder_config:
description: MetricsBuilderConfig is a configuration for sampleentity metrics builder.
type: object
properties:
metrics:
$ref: metrics_config
resource_attributes:
$ref: resource_attributes_config
================================================
FILE: cmd/mdatagen/internal/sampleentityreceiver/internal/metadata/generated_config.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/filter"
)
// MetricConfig provides common config for a particular metric.
type MetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
}
func (ms *MetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
// MetricsConfig provides config for sampleentity metrics.
type MetricsConfig struct {
K8sPodCPUTime MetricConfig `mapstructure:"k8s.pod.cpu_time"`
K8sPodPhase MetricConfig `mapstructure:"k8s.pod.phase"`
K8sReplicasetDesired MetricConfig `mapstructure:"k8s.replicaset.desired"`
}
func DefaultMetricsConfig() MetricsConfig {
return MetricsConfig{
K8sPodCPUTime: MetricConfig{
Enabled: true,
},
K8sPodPhase: MetricConfig{
Enabled: true,
},
K8sReplicasetDesired: MetricConfig{
Enabled: true,
},
}
}
// ResourceAttributeConfig provides common config for a particular resource attribute.
type ResourceAttributeConfig struct {
Enabled bool `mapstructure:"enabled"`
// Experimental: MetricsInclude defines a list of filters for attribute values.
// If the list is not empty, only metrics with matching resource attribute values will be emitted.
MetricsInclude []filter.Config `mapstructure:"metrics_include"`
// Experimental: MetricsExclude defines a list of filters for attribute values.
// If the list is not empty, metrics with matching resource attribute values will not be emitted.
// MetricsInclude has higher priority than MetricsExclude.
MetricsExclude []filter.Config `mapstructure:"metrics_exclude"`
enabledSetByUser bool
}
func (rac *ResourceAttributeConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(rac)
if err != nil {
return err
}
rac.enabledSetByUser = parser.IsSet("enabled")
return nil
}
// ResourceAttributesConfig provides config for sampleentity resource attributes.
type ResourceAttributesConfig struct {
K8sNamespaceName ResourceAttributeConfig `mapstructure:"k8s.namespace.name"`
K8sPodName ResourceAttributeConfig `mapstructure:"k8s.pod.name"`
K8sPodUID ResourceAttributeConfig `mapstructure:"k8s.pod.uid"`
K8sReplicasetName ResourceAttributeConfig `mapstructure:"k8s.replicaset.name"`
K8sReplicasetUID ResourceAttributeConfig `mapstructure:"k8s.replicaset.uid"`
}
func DefaultResourceAttributesConfig() ResourceAttributesConfig {
return ResourceAttributesConfig{
K8sNamespaceName: ResourceAttributeConfig{
Enabled: true,
},
K8sPodName: ResourceAttributeConfig{
Enabled: true,
},
K8sPodUID: ResourceAttributeConfig{
Enabled: true,
},
K8sReplicasetName: ResourceAttributeConfig{
Enabled: true,
},
K8sReplicasetUID: ResourceAttributeConfig{
Enabled: true,
},
}
}
// MetricsBuilderConfig is a configuration for sampleentity metrics builder.
type MetricsBuilderConfig struct {
Metrics MetricsConfig `mapstructure:"metrics"`
ResourceAttributes ResourceAttributesConfig `mapstructure:"resource_attributes"`
}
func DefaultMetricsBuilderConfig() MetricsBuilderConfig {
return MetricsBuilderConfig{
Metrics: DefaultMetricsConfig(),
ResourceAttributes: DefaultResourceAttributesConfig(),
}
}
================================================
FILE: cmd/mdatagen/internal/sampleentityreceiver/internal/metadata/generated_config_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"path/filepath"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/confmaptest"
)
func TestMetricsBuilderConfig(t *testing.T) {
tests := []struct {
name string
want MetricsBuilderConfig
}{
{
name: "default",
want: DefaultMetricsBuilderConfig(),
},
{
name: "all_set",
want: MetricsBuilderConfig{
Metrics: MetricsConfig{
K8sPodCPUTime: MetricConfig{
Enabled: true,
},
K8sPodPhase: MetricConfig{
Enabled: true,
},
K8sReplicasetDesired: MetricConfig{
Enabled: true,
},
},
ResourceAttributes: ResourceAttributesConfig{
K8sNamespaceName: ResourceAttributeConfig{Enabled: true},
K8sPodName: ResourceAttributeConfig{Enabled: true},
K8sPodUID: ResourceAttributeConfig{Enabled: true},
K8sReplicasetName: ResourceAttributeConfig{Enabled: true},
K8sReplicasetUID: ResourceAttributeConfig{Enabled: true},
},
},
},
{
name: "none_set",
want: MetricsBuilderConfig{
Metrics: MetricsConfig{
K8sPodCPUTime: MetricConfig{
Enabled: false,
},
K8sPodPhase: MetricConfig{
Enabled: false,
},
K8sReplicasetDesired: MetricConfig{
Enabled: false,
},
},
ResourceAttributes: ResourceAttributesConfig{
K8sNamespaceName: ResourceAttributeConfig{Enabled: false},
K8sPodName: ResourceAttributeConfig{Enabled: false},
K8sPodUID: ResourceAttributeConfig{Enabled: false},
K8sReplicasetName: ResourceAttributeConfig{Enabled: false},
K8sReplicasetUID: ResourceAttributeConfig{Enabled: false},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := loadMetricsBuilderConfig(t, tt.name)
diff := cmp.Diff(tt.want, cfg, cmpopts.IgnoreUnexported(MetricConfig{}, ResourceAttributeConfig{}))
require.Emptyf(t, diff, "Config mismatch (-expected +actual):\n%s", diff)
})
}
}
func loadMetricsBuilderConfig(t *testing.T, name string) MetricsBuilderConfig {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
require.NoError(t, err)
sub, err := cm.Sub(name)
require.NoError(t, err)
cfg := DefaultMetricsBuilderConfig()
require.NoError(t, sub.Unmarshal(&cfg, confmap.WithIgnoreUnused()))
return cfg
}
func TestResourceAttributesConfig(t *testing.T) {
tests := []struct {
name string
want ResourceAttributesConfig
}{
{
name: "default",
want: DefaultResourceAttributesConfig(),
},
{
name: "all_set",
want: ResourceAttributesConfig{
K8sNamespaceName: ResourceAttributeConfig{Enabled: true},
K8sPodName: ResourceAttributeConfig{Enabled: true},
K8sPodUID: ResourceAttributeConfig{Enabled: true},
K8sReplicasetName: ResourceAttributeConfig{Enabled: true},
K8sReplicasetUID: ResourceAttributeConfig{Enabled: true},
},
},
{
name: "none_set",
want: ResourceAttributesConfig{
K8sNamespaceName: ResourceAttributeConfig{Enabled: false},
K8sPodName: ResourceAttributeConfig{Enabled: false},
K8sPodUID: ResourceAttributeConfig{Enabled: false},
K8sReplicasetName: ResourceAttributeConfig{Enabled: false},
K8sReplicasetUID: ResourceAttributeConfig{Enabled: false},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := loadResourceAttributesConfig(t, tt.name)
diff := cmp.Diff(tt.want, cfg, cmpopts.IgnoreUnexported(ResourceAttributeConfig{}))
require.Emptyf(t, diff, "Config mismatch (-expected +actual):\n%s", diff)
})
}
}
func loadResourceAttributesConfig(t *testing.T, name string) ResourceAttributesConfig {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
require.NoError(t, err)
sub, err := cm.Sub(name)
require.NoError(t, err)
sub, err = sub.Sub("resource_attributes")
require.NoError(t, err)
cfg := DefaultResourceAttributesConfig()
require.NoError(t, sub.Unmarshal(&cfg))
return cfg
}
================================================
FILE: cmd/mdatagen/internal/sampleentityreceiver/internal/metadata/generated_entity_metrics.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/xpdata/entity"
)
// K8sReplicasetEntity represents a k8s.replicaset entity.
// Create one with NewK8sReplicasetEntity and pass it to EmitForEntity.
type K8sReplicasetEntity struct {
k8sReplicasetUID string
k8sReplicasetName string
}
// NewK8sReplicasetEntity creates a new K8sReplicasetEntity.
// Identity attributes are required and must be provided at construction time.
func NewK8sReplicasetEntity(k8sReplicasetUID string) *K8sReplicasetEntity {
return &K8sReplicasetEntity{
k8sReplicasetUID: k8sReplicasetUID,
}
}
// Description attribute setters for k8s.replicaset.
// SetK8sReplicasetName sets the k8s.replicaset.name description attribute.
func (e *K8sReplicasetEntity) SetK8sReplicasetName(val string) {
e.k8sReplicasetName = val
}
// copyToResource populates res with the entity's attributes according to cfg.
// If all identity attributes are enabled, an entity ref is produced; otherwise
// the enabled attributes are written directly as plain resource attributes.
func (e *K8sReplicasetEntity) copyToResource(cfg ResourceAttributesConfig, res pcommon.Resource) {
if cfg.K8sReplicasetUID.Enabled {
ent := entity.ResourceEntities(res).PutEmpty("k8s.replicaset")
ent.IdentifyingAttributes().PutStr("k8s.replicaset.uid", e.k8sReplicasetUID)
if cfg.K8sReplicasetName.Enabled {
ent.DescriptiveAttributes().PutStr("k8s.replicaset.name", e.k8sReplicasetName)
}
} else {
if cfg.K8sReplicasetUID.Enabled {
res.Attributes().PutStr("k8s.replicaset.uid", e.k8sReplicasetUID)
}
if cfg.K8sReplicasetName.Enabled {
res.Attributes().PutStr("k8s.replicaset.name", e.k8sReplicasetName)
}
}
}
// K8sPodEntity represents a k8s.pod entity.
// Create one with NewK8sPodEntity and pass it to EmitForEntity.
type K8sPodEntity struct {
k8sPodUID string
k8sPodName string
k8sNamespaceName string
controlledByK8sReplicaset *K8sReplicasetEntity
}
// NewK8sPodEntity creates a new K8sPodEntity.
// Identity attributes are required and must be provided at construction time.
func NewK8sPodEntity(k8sPodUID string) *K8sPodEntity {
return &K8sPodEntity{
k8sPodUID: k8sPodUID,
}
}
// Description attribute setters for k8s.pod.
// SetK8sPodName sets the k8s.pod.name description attribute.
func (e *K8sPodEntity) SetK8sPodName(val string) {
e.k8sPodName = val
}
// Extra attribute setters for k8s.pod.
// These attributes are contextually relevant but are not part of the entity's identity or description.
// SetK8sNamespaceName sets the k8s.namespace.name extra attribute on the resource.
func (e *K8sPodEntity) SetK8sNamespaceName(val string) {
e.k8sNamespaceName = val
}
// Relationship setters for k8s.pod.
// SetControlledByK8sReplicaset sets the controlled_by relationship to a k8s.replicaset entity.
// The related entity will be emitted alongside this entity's metrics.
func (e *K8sPodEntity) SetControlledByK8sReplicaset(target *K8sReplicasetEntity) {
e.controlledByK8sReplicaset = target
}
// copyToResource populates res with the entity's attributes according to cfg.
// If all identity attributes are enabled, an entity ref is produced; otherwise
// the enabled attributes are written directly as plain resource attributes.
func (e *K8sPodEntity) copyToResource(cfg ResourceAttributesConfig, res pcommon.Resource) {
if cfg.K8sPodUID.Enabled {
ent := entity.ResourceEntities(res).PutEmpty("k8s.pod")
ent.IdentifyingAttributes().PutStr("k8s.pod.uid", e.k8sPodUID)
if cfg.K8sPodName.Enabled {
ent.DescriptiveAttributes().PutStr("k8s.pod.name", e.k8sPodName)
}
if cfg.K8sNamespaceName.Enabled {
res.Attributes().PutStr("k8s.namespace.name", e.k8sNamespaceName)
}
} else {
if cfg.K8sPodUID.Enabled {
res.Attributes().PutStr("k8s.pod.uid", e.k8sPodUID)
}
if cfg.K8sPodName.Enabled {
res.Attributes().PutStr("k8s.pod.name", e.k8sPodName)
}
if cfg.K8sNamespaceName.Enabled {
res.Attributes().PutStr("k8s.namespace.name", e.k8sNamespaceName)
}
}
}
// K8sReplicasetMetricsBuilder records metrics for the k8s.replicaset entity.
// Obtain one via MetricsBuilder.ForK8sReplicaset().
type K8sReplicasetMetricsBuilder struct {
mb *MetricsBuilder
entity *K8sReplicasetEntity
}
// RecordK8sReplicasetDesiredDataPoint records a data point for the k8s.replicaset.desired metric.
func (eb *K8sReplicasetMetricsBuilder) RecordK8sReplicasetDesiredDataPoint(ts pcommon.Timestamp, val int64) {
eb.mb.metricK8sReplicasetDesired.recordDataPoint(eb.mb.startTime, ts, val)
}
// Emit emits all pending metrics for the entity. Resource attributes are filtered by config:
// disabled identity attributes suppress the entity (other enabled attributes are added directly
// to the resource); disabled descriptive/extra attributes are omitted entirely.
func (eb *K8sReplicasetMetricsBuilder) Emit() {
res := pcommon.NewResource()
cfg := eb.mb.config.ResourceAttributes
eb.entity.copyToResource(cfg, res)
eb.mb.EmitForResource(withResourceMoved(res))
}
// K8sPodMetricsBuilder records metrics for the k8s.pod entity.
// Obtain one via MetricsBuilder.ForK8sPod().
type K8sPodMetricsBuilder struct {
mb *MetricsBuilder
entity *K8sPodEntity
}
// RecordK8sPodCPUTimeDataPoint records a data point for the k8s.pod.cpu_time metric.
func (eb *K8sPodMetricsBuilder) RecordK8sPodCPUTimeDataPoint(ts pcommon.Timestamp, val float64) {
eb.mb.metricK8sPodCPUTime.recordDataPoint(eb.mb.startTime, ts, val)
}
// RecordK8sPodPhaseDataPoint records a data point for the k8s.pod.phase metric.
func (eb *K8sPodMetricsBuilder) RecordK8sPodPhaseDataPoint(ts pcommon.Timestamp, val int64, phaseAttributeValue AttributePhase) {
eb.mb.metricK8sPodPhase.recordDataPoint(eb.mb.startTime, ts, val, phaseAttributeValue.String())
}
// Emit emits all pending metrics for the entity. Resource attributes are filtered by config:
// disabled identity attributes suppress the entity (other enabled attributes are added directly
// to the resource); disabled descriptive/extra attributes are omitted entirely.
func (eb *K8sPodMetricsBuilder) Emit() {
res := pcommon.NewResource()
cfg := eb.mb.config.ResourceAttributes
eb.entity.copyToResource(cfg, res)
if eb.entity.controlledByK8sReplicaset != nil {
eb.entity.controlledByK8sReplicaset.copyToResource(cfg, res)
}
eb.mb.EmitForResource(withResourceMoved(res))
}
================================================
FILE: cmd/mdatagen/internal/sampleentityreceiver/internal/metadata/generated_entity_metrics_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/xpdata/entity"
"go.opentelemetry.io/collector/receiver/receivertest"
)
func TestEntityBuilders(t *testing.T) {
start := pcommon.Timestamp(1_000_000_000)
ts := pcommon.Timestamp(1_000_001_000)
settings := receivertest.NewNopSettings(receivertest.NopType)
mb := NewMetricsBuilder(DefaultMetricsBuilderConfig(), settings, WithStartTime(start))
t.Run("k8s.replicaset", func(t *testing.T) {
e := NewK8sReplicasetEntity("k8s.replicaset.uid-val")
require.NotNil(t, e)
e.SetK8sReplicasetName("k8s.replicaset.name-val")
eb := mb.ForK8sReplicaset(e)
eb.RecordK8sReplicasetDesiredDataPoint(ts, 1)
eb.Emit()
metrics := mb.Emit()
require.Equal(t, 1, metrics.ResourceMetrics().Len())
rm := metrics.ResourceMetrics().At(0)
entityVal, ok := entity.ResourceEntities(rm.Resource()).Get("k8s.replicaset")
require.True(t, ok)
k8sReplicasetUIDAttrVal, ok := entityVal.IdentifyingAttributes().Get("k8s.replicaset.uid")
require.True(t, ok)
assert.Equal(t, "k8s.replicaset.uid-val", k8sReplicasetUIDAttrVal.Str())
k8sReplicasetNameAttrVal, ok := entityVal.DescriptiveAttributes().Get("k8s.replicaset.name")
require.True(t, ok)
assert.Equal(t, "k8s.replicaset.name-val", k8sReplicasetNameAttrVal.Str())
require.Equal(t, 1, rm.ScopeMetrics().Len())
ms := rm.ScopeMetrics().At(0).Metrics()
assert.Equal(t, 1, ms.Len())
})
t.Run("k8s.replicaset/disabled_identity_attr", func(t *testing.T) {
// When an identity attribute is disabled, the entity is not produced but
// other enabled attributes are still added to the resource directly.
cfg := DefaultMetricsBuilderConfig()
cfg.ResourceAttributes.K8sReplicasetUID.Enabled = false
mb := NewMetricsBuilder(cfg, settings, WithStartTime(start))
e := NewK8sReplicasetEntity("k8s.replicaset.uid-val")
e.SetK8sReplicasetName("k8s.replicaset.name-val")
eb := mb.ForK8sReplicaset(e)
eb.RecordK8sReplicasetDesiredDataPoint(ts, 1)
eb.Emit()
metrics := mb.Emit()
require.Equal(t, 1, metrics.ResourceMetrics().Len())
rm := metrics.ResourceMetrics().At(0)
// Entity must not be present since its identity attribute is disabled.
_, ok := entity.ResourceEntities(rm.Resource()).Get("k8s.replicaset")
assert.False(t, ok)
// Enabled descriptive attributes should still be on the resource directly.
_, ok = rm.Resource().Attributes().Get("k8s.replicaset.name")
assert.True(t, ok)
})
t.Run("k8s.replicaset/disabled_descriptive_attr", func(t *testing.T) {
// When a descriptive attribute is disabled, the entity is still produced
// with its identity but the disabled attribute is not added.
cfg := DefaultMetricsBuilderConfig()
cfg.ResourceAttributes.K8sReplicasetName.Enabled = false
mb := NewMetricsBuilder(cfg, settings, WithStartTime(start))
e := NewK8sReplicasetEntity("k8s.replicaset.uid-val")
e.SetK8sReplicasetName("k8s.replicaset.name-val")
eb := mb.ForK8sReplicaset(e)
eb.RecordK8sReplicasetDesiredDataPoint(ts, 1)
eb.Emit()
metrics := mb.Emit()
require.Equal(t, 1, metrics.ResourceMetrics().Len())
rm := metrics.ResourceMetrics().At(0)
// Entity must still be produced since identity attributes are enabled.
entityVal, ok := entity.ResourceEntities(rm.Resource()).Get("k8s.replicaset")
require.True(t, ok)
k8sReplicasetUIDAttrVal, ok := entityVal.IdentifyingAttributes().Get("k8s.replicaset.uid")
require.True(t, ok)
assert.Equal(t, "k8s.replicaset.uid-val", k8sReplicasetUIDAttrVal.Str())
// Disabled descriptive/extra attributes must not be present.
_, ok = entityVal.DescriptiveAttributes().Get("k8s.replicaset.name")
assert.False(t, ok)
})
t.Run("k8s.pod", func(t *testing.T) {
e := NewK8sPodEntity("k8s.pod.uid-val")
require.NotNil(t, e)
e.SetK8sPodName("k8s.pod.name-val")
e.SetK8sNamespaceName("k8s.namespace.name-val")
relatedK8sReplicaset := NewK8sReplicasetEntity("k8s.replicaset.uid-val")
e.SetControlledByK8sReplicaset(relatedK8sReplicaset)
eb := mb.ForK8sPod(e)
eb.RecordK8sPodCPUTimeDataPoint(ts, 1)
eb.RecordK8sPodPhaseDataPoint(ts, 1, AttributePhasePending)
eb.Emit()
metrics := mb.Emit()
require.Equal(t, 1, metrics.ResourceMetrics().Len())
rm := metrics.ResourceMetrics().At(0)
entityVal, ok := entity.ResourceEntities(rm.Resource()).Get("k8s.pod")
require.True(t, ok)
k8sPodUIDAttrVal, ok := entityVal.IdentifyingAttributes().Get("k8s.pod.uid")
require.True(t, ok)
assert.Equal(t, "k8s.pod.uid-val", k8sPodUIDAttrVal.Str())
k8sPodNameAttrVal, ok := entityVal.DescriptiveAttributes().Get("k8s.pod.name")
require.True(t, ok)
assert.Equal(t, "k8s.pod.name-val", k8sPodNameAttrVal.Str())
_, ok = entityVal.DescriptiveAttributes().Get("k8s.namespace.name")
assert.False(t, ok)
k8sNamespaceNameAttrVal, ok := rm.Resource().Attributes().Get("k8s.namespace.name")
require.True(t, ok)
assert.Equal(t, "k8s.namespace.name-val", k8sNamespaceNameAttrVal.Str())
require.Equal(t, 1, rm.ScopeMetrics().Len())
ms := rm.ScopeMetrics().At(0).Metrics()
assert.Equal(t, 2, ms.Len())
})
t.Run("k8s.pod/disabled_identity_attr", func(t *testing.T) {
// When an identity attribute is disabled, the entity is not produced but
// other enabled attributes are still added to the resource directly.
cfg := DefaultMetricsBuilderConfig()
cfg.ResourceAttributes.K8sPodUID.Enabled = false
mb := NewMetricsBuilder(cfg, settings, WithStartTime(start))
e := NewK8sPodEntity("k8s.pod.uid-val")
e.SetK8sPodName("k8s.pod.name-val")
eb := mb.ForK8sPod(e)
eb.RecordK8sPodCPUTimeDataPoint(ts, 1)
eb.RecordK8sPodPhaseDataPoint(ts, 1, AttributePhasePending)
eb.Emit()
metrics := mb.Emit()
require.Equal(t, 1, metrics.ResourceMetrics().Len())
rm := metrics.ResourceMetrics().At(0)
// Entity must not be present since its identity attribute is disabled.
_, ok := entity.ResourceEntities(rm.Resource()).Get("k8s.pod")
assert.False(t, ok)
// Enabled descriptive attributes should still be on the resource directly.
_, ok = rm.Resource().Attributes().Get("k8s.pod.name")
assert.True(t, ok)
})
t.Run("k8s.pod/disabled_descriptive_attr", func(t *testing.T) {
// When a descriptive attribute is disabled, the entity is still produced
// with its identity but the disabled attribute is not added.
cfg := DefaultMetricsBuilderConfig()
cfg.ResourceAttributes.K8sPodName.Enabled = false
cfg.ResourceAttributes.K8sNamespaceName.Enabled = false
mb := NewMetricsBuilder(cfg, settings, WithStartTime(start))
e := NewK8sPodEntity("k8s.pod.uid-val")
e.SetK8sPodName("k8s.pod.name-val")
e.SetK8sNamespaceName("k8s.namespace.name-val")
eb := mb.ForK8sPod(e)
eb.RecordK8sPodCPUTimeDataPoint(ts, 1)
eb.RecordK8sPodPhaseDataPoint(ts, 1, AttributePhasePending)
eb.Emit()
metrics := mb.Emit()
require.Equal(t, 1, metrics.ResourceMetrics().Len())
rm := metrics.ResourceMetrics().At(0)
// Entity must still be produced since identity attributes are enabled.
entityVal, ok := entity.ResourceEntities(rm.Resource()).Get("k8s.pod")
require.True(t, ok)
k8sPodUIDAttrVal, ok := entityVal.IdentifyingAttributes().Get("k8s.pod.uid")
require.True(t, ok)
assert.Equal(t, "k8s.pod.uid-val", k8sPodUIDAttrVal.Str())
// Disabled descriptive/extra attributes must not be present.
_, ok = entityVal.DescriptiveAttributes().Get("k8s.pod.name")
assert.False(t, ok)
_, ok = entityVal.DescriptiveAttributes().Get("k8s.namespace.name")
assert.False(t, ok)
})
}
================================================
FILE: cmd/mdatagen/internal/sampleentityreceiver/internal/metadata/generated_metrics.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"time"
conventions "go.opentelemetry.io/otel/semconv/v1.38.0"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/filter"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/receiver"
)
// AttributePhase specifies the value phase attribute.
type AttributePhase int
const (
_ AttributePhase = iota
AttributePhasePending
AttributePhaseRunning
AttributePhaseSucceeded
AttributePhaseFailed
AttributePhaseUnknown
)
// String returns the string representation of the AttributePhase.
func (av AttributePhase) String() string {
switch av {
case AttributePhasePending:
return "Pending"
case AttributePhaseRunning:
return "Running"
case AttributePhaseSucceeded:
return "Succeeded"
case AttributePhaseFailed:
return "Failed"
case AttributePhaseUnknown:
return "Unknown"
}
return ""
}
// MapAttributePhase is a helper map of string to AttributePhase attribute value.
var MapAttributePhase = map[string]AttributePhase{
"Pending": AttributePhasePending,
"Running": AttributePhaseRunning,
"Succeeded": AttributePhaseSucceeded,
"Failed": AttributePhaseFailed,
"Unknown": AttributePhaseUnknown,
}
var MetricsInfo = metricsInfo{
K8sPodCPUTime: metricInfo{
Name: "k8s.pod.cpu_time",
},
K8sPodPhase: metricInfo{
Name: "k8s.pod.phase",
},
K8sReplicasetDesired: metricInfo{
Name: "k8s.replicaset.desired",
},
}
type metricsInfo struct {
K8sPodCPUTime metricInfo
K8sPodPhase metricInfo
K8sReplicasetDesired metricInfo
}
type metricInfo struct {
Name string
}
type metricK8sPodCPUTime struct {
data pmetric.Metric // data buffer for generated metric.
config MetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
}
// init fills k8s.pod.cpu_time metric with initial data.
func (m *metricK8sPodCPUTime) init() {
m.data.SetName("k8s.pod.cpu_time")
m.data.SetDescription("CPU time consumed by the pod")
m.data.SetUnit("s")
m.data.SetEmptySum()
m.data.Sum().SetIsMonotonic(true)
m.data.Sum().SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
}
func (m *metricK8sPodCPUTime) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64) {
if !m.config.Enabled {
return
}
dp := m.data.Sum().DataPoints().AppendEmpty()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
dp.SetDoubleValue(val)
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricK8sPodCPUTime) updateCapacity() {
if m.data.Sum().DataPoints().Len() > m.capacity {
m.capacity = m.data.Sum().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricK8sPodCPUTime) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Sum().DataPoints().Len() > 0 {
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricK8sPodCPUTime(cfg MetricConfig) metricK8sPodCPUTime {
m := metricK8sPodCPUTime{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricK8sPodPhase struct {
data pmetric.Metric // data buffer for generated metric.
config MetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
}
// init fills k8s.pod.phase metric with initial data.
func (m *metricK8sPodPhase) init() {
m.data.SetName("k8s.pod.phase")
m.data.SetDescription("Current phase of the pod")
m.data.SetUnit("1")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
}
func (m *metricK8sPodPhase) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val int64, phaseAttributeValue string) {
if !m.config.Enabled {
return
}
dp := m.data.Gauge().DataPoints().AppendEmpty()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
dp.SetIntValue(val)
dp.Attributes().PutStr("phase", phaseAttributeValue)
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricK8sPodPhase) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricK8sPodPhase) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricK8sPodPhase(cfg MetricConfig) metricK8sPodPhase {
m := metricK8sPodPhase{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricK8sReplicasetDesired struct {
data pmetric.Metric // data buffer for generated metric.
config MetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
}
// init fills k8s.replicaset.desired metric with initial data.
func (m *metricK8sReplicasetDesired) init() {
m.data.SetName("k8s.replicaset.desired")
m.data.SetDescription("Number of desired replicas")
m.data.SetUnit("{replicas}")
m.data.SetEmptyGauge()
}
func (m *metricK8sReplicasetDesired) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val int64) {
if !m.config.Enabled {
return
}
dp := m.data.Gauge().DataPoints().AppendEmpty()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
dp.SetIntValue(val)
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricK8sReplicasetDesired) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricK8sReplicasetDesired) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricK8sReplicasetDesired(cfg MetricConfig) metricK8sReplicasetDesired {
m := metricK8sReplicasetDesired{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
// MetricsBuilder provides an interface for scrapers to report metrics while taking care of all the transformations
// required to produce metric representation defined in metadata and user config.
type MetricsBuilder struct {
config MetricsBuilderConfig // config of the metrics builder.
startTime pcommon.Timestamp // start time that will be applied to all recorded data points.
metricsCapacity int // maximum observed number of metrics per resource.
metricsBuffer pmetric.Metrics // accumulates metrics data before emitting.
buildInfo component.BuildInfo // contains version information.
resourceAttributeIncludeFilter map[string]filter.Filter
resourceAttributeExcludeFilter map[string]filter.Filter
metricK8sPodCPUTime metricK8sPodCPUTime
metricK8sPodPhase metricK8sPodPhase
metricK8sReplicasetDesired metricK8sReplicasetDesired
}
// MetricBuilderOption applies changes to default metrics builder.
type MetricBuilderOption interface {
apply(*MetricsBuilder)
}
type metricBuilderOptionFunc func(mb *MetricsBuilder)
func (mbof metricBuilderOptionFunc) apply(mb *MetricsBuilder) {
mbof(mb)
}
// WithStartTime sets startTime on the metrics builder.
func WithStartTime(startTime pcommon.Timestamp) MetricBuilderOption {
return metricBuilderOptionFunc(func(mb *MetricsBuilder) {
mb.startTime = startTime
})
}
func NewMetricsBuilder(mbc MetricsBuilderConfig, settings receiver.Settings, options ...MetricBuilderOption) *MetricsBuilder {
mb := &MetricsBuilder{
config: mbc,
startTime: pcommon.NewTimestampFromTime(time.Now()),
metricsBuffer: pmetric.NewMetrics(),
buildInfo: settings.BuildInfo,
metricK8sPodCPUTime: newMetricK8sPodCPUTime(mbc.Metrics.K8sPodCPUTime),
metricK8sPodPhase: newMetricK8sPodPhase(mbc.Metrics.K8sPodPhase),
metricK8sReplicasetDesired: newMetricK8sReplicasetDesired(mbc.Metrics.K8sReplicasetDesired),
resourceAttributeIncludeFilter: make(map[string]filter.Filter),
resourceAttributeExcludeFilter: make(map[string]filter.Filter),
}
if mbc.ResourceAttributes.K8sNamespaceName.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["k8s.namespace.name"] = filter.CreateFilter(mbc.ResourceAttributes.K8sNamespaceName.MetricsInclude)
}
if mbc.ResourceAttributes.K8sNamespaceName.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["k8s.namespace.name"] = filter.CreateFilter(mbc.ResourceAttributes.K8sNamespaceName.MetricsExclude)
}
if mbc.ResourceAttributes.K8sPodName.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["k8s.pod.name"] = filter.CreateFilter(mbc.ResourceAttributes.K8sPodName.MetricsInclude)
}
if mbc.ResourceAttributes.K8sPodName.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["k8s.pod.name"] = filter.CreateFilter(mbc.ResourceAttributes.K8sPodName.MetricsExclude)
}
if mbc.ResourceAttributes.K8sPodUID.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["k8s.pod.uid"] = filter.CreateFilter(mbc.ResourceAttributes.K8sPodUID.MetricsInclude)
}
if mbc.ResourceAttributes.K8sPodUID.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["k8s.pod.uid"] = filter.CreateFilter(mbc.ResourceAttributes.K8sPodUID.MetricsExclude)
}
if mbc.ResourceAttributes.K8sReplicasetName.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["k8s.replicaset.name"] = filter.CreateFilter(mbc.ResourceAttributes.K8sReplicasetName.MetricsInclude)
}
if mbc.ResourceAttributes.K8sReplicasetName.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["k8s.replicaset.name"] = filter.CreateFilter(mbc.ResourceAttributes.K8sReplicasetName.MetricsExclude)
}
if mbc.ResourceAttributes.K8sReplicasetUID.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["k8s.replicaset.uid"] = filter.CreateFilter(mbc.ResourceAttributes.K8sReplicasetUID.MetricsInclude)
}
if mbc.ResourceAttributes.K8sReplicasetUID.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["k8s.replicaset.uid"] = filter.CreateFilter(mbc.ResourceAttributes.K8sReplicasetUID.MetricsExclude)
}
for _, op := range options {
op.apply(mb)
}
return mb
}
// NewResourceBuilder returns a new resource builder that should be used to build a resource associated with for the emitted metrics.
func (mb *MetricsBuilder) NewResourceBuilder() *ResourceBuilder {
return NewResourceBuilder(mb.config.ResourceAttributes)
}
// updateCapacity updates max length of metrics and resource attributes that will be used for the slice capacity.
func (mb *MetricsBuilder) updateCapacity(rm pmetric.ResourceMetrics) {
if mb.metricsCapacity < rm.ScopeMetrics().At(0).Metrics().Len() {
mb.metricsCapacity = rm.ScopeMetrics().At(0).Metrics().Len()
}
}
// ResourceMetricsOption applies changes to provided resource metrics.
type ResourceMetricsOption interface {
apply(pmetric.ResourceMetrics)
}
type resourceMetricsOptionFunc func(pmetric.ResourceMetrics)
func (rmof resourceMetricsOptionFunc) apply(rm pmetric.ResourceMetrics) {
rmof(rm)
}
// WithResource sets the provided resource on the emitted ResourceMetrics.
// It's recommended to use ResourceBuilder to create the resource.
func WithResource(res pcommon.Resource) ResourceMetricsOption {
return resourceMetricsOptionFunc(func(rm pmetric.ResourceMetrics) {
res.CopyTo(rm.Resource())
})
}
func withResourceMoved(res pcommon.Resource) ResourceMetricsOption {
return resourceMetricsOptionFunc(func(rm pmetric.ResourceMetrics) {
res.MoveTo(rm.Resource())
})
}
// WithStartTimeOverride overrides start time for all the resource metrics data points.
// This option should be only used if different start time has to be set on metrics coming from different resources.
func WithStartTimeOverride(start pcommon.Timestamp) ResourceMetricsOption {
return resourceMetricsOptionFunc(func(rm pmetric.ResourceMetrics) {
var dps pmetric.NumberDataPointSlice
metrics := rm.ScopeMetrics().At(0).Metrics()
for i := 0; i < metrics.Len(); i++ {
switch metrics.At(i).Type() {
case pmetric.MetricTypeGauge:
dps = metrics.At(i).Gauge().DataPoints()
case pmetric.MetricTypeSum:
dps = metrics.At(i).Sum().DataPoints()
}
for j := 0; j < dps.Len(); j++ {
dps.At(j).SetStartTimestamp(start)
}
}
})
}
// ForK8sReplicaset returns a K8sReplicasetMetricsBuilder that restricts metric recording
// to metrics belonging to the k8s.replicaset entity.
func (mb *MetricsBuilder) ForK8sReplicaset(e *K8sReplicasetEntity) *K8sReplicasetMetricsBuilder {
return &K8sReplicasetMetricsBuilder{mb: mb, entity: e}
}
// ForK8sPod returns a K8sPodMetricsBuilder that restricts metric recording
// to metrics belonging to the k8s.pod entity.
func (mb *MetricsBuilder) ForK8sPod(e *K8sPodEntity) *K8sPodMetricsBuilder {
return &K8sPodMetricsBuilder{mb: mb, entity: e}
}
// EmitForResource saves all the generated metrics under a new resource and updates the internal state to be ready for
// recording another set of data points as part of another resource. This function can be helpful when one scraper
// needs to emit metrics from several resources. Otherwise calling this function is not required,
// just `Emit` function can be called instead.
// Resource attributes should be provided as ResourceMetricsOption arguments.
//
// Deprecated: Use the For methods to get entity-scoped builders and call Emit() on them instead.
func (mb *MetricsBuilder) EmitForResource(options ...ResourceMetricsOption) {
rm := pmetric.NewResourceMetrics()
rm.SetSchemaUrl(conventions.SchemaURL)
ils := rm.ScopeMetrics().AppendEmpty()
ils.Scope().SetName(ScopeName)
ils.Scope().SetVersion(mb.buildInfo.Version)
ils.Metrics().EnsureCapacity(mb.metricsCapacity)
mb.metricK8sPodCPUTime.emit(ils.Metrics())
mb.metricK8sPodPhase.emit(ils.Metrics())
mb.metricK8sReplicasetDesired.emit(ils.Metrics())
for _, op := range options {
op.apply(rm)
}
for attr, filter := range mb.resourceAttributeIncludeFilter {
if val, ok := rm.Resource().Attributes().Get(attr); ok && !filter.Matches(val.AsString()) {
return
}
}
for attr, filter := range mb.resourceAttributeExcludeFilter {
if val, ok := rm.Resource().Attributes().Get(attr); ok && filter.Matches(val.AsString()) {
return
}
}
if ils.Metrics().Len() > 0 {
mb.updateCapacity(rm)
rm.MoveTo(mb.metricsBuffer.ResourceMetrics().AppendEmpty())
}
}
// Emit returns all the metrics accumulated by the metrics builder and updates the internal state to be ready for
// recording another set of metrics. This function will be responsible for applying all the transformations required to
// produce metric representation defined in metadata and user config, e.g. delta or cumulative.
func (mb *MetricsBuilder) Emit(options ...ResourceMetricsOption) pmetric.Metrics {
mb.EmitForResource(options...)
metrics := mb.metricsBuffer
mb.metricsBuffer = pmetric.NewMetrics()
return metrics
}
// RecordK8sPodCPUTimeDataPoint adds a data point to k8s.pod.cpu_time metric.
//
// Deprecated: Use mb.ForK8sPod(entity).RecordK8sPodCPUTimeDataPoint(...) instead.
func (mb *MetricsBuilder) RecordK8sPodCPUTimeDataPoint(ts pcommon.Timestamp, val float64) {
mb.metricK8sPodCPUTime.recordDataPoint(mb.startTime, ts, val)
}
// RecordK8sPodPhaseDataPoint adds a data point to k8s.pod.phase metric.
//
// Deprecated: Use mb.ForK8sPod(entity).RecordK8sPodPhaseDataPoint(...) instead.
func (mb *MetricsBuilder) RecordK8sPodPhaseDataPoint(ts pcommon.Timestamp, val int64, phaseAttributeValue AttributePhase) {
mb.metricK8sPodPhase.recordDataPoint(mb.startTime, ts, val, phaseAttributeValue.String())
}
// RecordK8sReplicasetDesiredDataPoint adds a data point to k8s.replicaset.desired metric.
//
// Deprecated: Use mb.ForK8sReplicaset(entity).RecordK8sReplicasetDesiredDataPoint(...) instead.
func (mb *MetricsBuilder) RecordK8sReplicasetDesiredDataPoint(ts pcommon.Timestamp, val int64) {
mb.metricK8sReplicasetDesired.recordDataPoint(mb.startTime, ts, val)
}
// Reset resets metrics builder to its initial state. It should be used when external metrics source is restarted,
// and metrics builder should update its startTime and reset it's internal state accordingly.
func (mb *MetricsBuilder) Reset(options ...MetricBuilderOption) {
mb.startTime = pcommon.NewTimestampFromTime(time.Now())
for _, op := range options {
op.apply(mb)
}
}
================================================
FILE: cmd/mdatagen/internal/sampleentityreceiver/internal/metadata/generated_metrics_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"testing"
"github.com/stretchr/testify/assert"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/receiver/receivertest"
)
type testDataSet int
const (
testDataSetDefault testDataSet = iota
testDataSetAll
testDataSetNone
)
func TestMetricsBuilder(t *testing.T) {
tests := []struct {
name string
metricsSet testDataSet
resAttrsSet testDataSet
expectEmpty bool
}{
{
name: "default",
},
{
name: "all_set",
metricsSet: testDataSetAll,
resAttrsSet: testDataSetAll,
},
{
name: "none_set",
metricsSet: testDataSetNone,
resAttrsSet: testDataSetNone,
expectEmpty: true,
},
{
name: "filter_set_include",
resAttrsSet: testDataSetAll,
},
{
name: "filter_set_exclude",
resAttrsSet: testDataSetAll,
expectEmpty: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
start := pcommon.Timestamp(1_000_000_000)
ts := pcommon.Timestamp(1_000_001_000)
observedZapCore, observedLogs := observer.New(zap.WarnLevel)
settings := receivertest.NewNopSettings(receivertest.NopType)
settings.Logger = zap.New(observedZapCore)
mb := NewMetricsBuilder(loadMetricsBuilderConfig(t, tt.name), settings, WithStartTime(start))
expectedWarnings := 0
assert.Equal(t, expectedWarnings, observedLogs.Len())
defaultMetricsCount := 0
allMetricsCount := 0
ebK8sReplicaset := mb.ForK8sReplicaset(NewK8sReplicasetEntity("k8s.replicaset.uid-val"))
ebK8sPod := mb.ForK8sPod(NewK8sPodEntity("k8s.pod.uid-val"))
defaultMetricsCount++
allMetricsCount++
ebK8sPod.RecordK8sPodCPUTimeDataPoint(ts, 1)
defaultMetricsCount++
allMetricsCount++
ebK8sPod.RecordK8sPodPhaseDataPoint(ts, 1, AttributePhasePending)
defaultMetricsCount++
allMetricsCount++
ebK8sReplicaset.RecordK8sReplicasetDesiredDataPoint(ts, 1)
ebK8sReplicaset.Emit()
ebK8sPod.Emit()
rb := mb.NewResourceBuilder()
rb.SetK8sNamespaceName("k8s.namespace.name-val")
rb.SetK8sPodName("k8s.pod.name-val")
rb.SetK8sPodUID("k8s.pod.uid-val")
rb.SetK8sReplicasetName("k8s.replicaset.name-val")
rb.SetK8sReplicasetUID("k8s.replicaset.uid-val")
res := rb.Emit()
metrics := mb.Emit(WithResource(res))
if tt.expectEmpty {
assert.Equal(t, 0, metrics.ResourceMetrics().Len())
return
}
var allMetricsList []pmetric.Metric
totalMetricsCount := 0
for ri := 0; ri < metrics.ResourceMetrics().Len(); ri++ {
rm := metrics.ResourceMetrics().At(ri)
assert.Equal(t, 1, rm.ScopeMetrics().Len())
ms := rm.ScopeMetrics().At(0).Metrics()
totalMetricsCount += ms.Len()
for mi := 0; mi < ms.Len(); mi++ {
allMetricsList = append(allMetricsList, ms.At(mi))
}
}
if tt.metricsSet == testDataSetDefault {
assert.Equal(t, defaultMetricsCount, totalMetricsCount)
}
if tt.metricsSet == testDataSetAll {
assert.Equal(t, allMetricsCount, totalMetricsCount)
}
validatedMetrics := make(map[string]bool)
for _, mi := range allMetricsList {
switch mi.Name() {
case "k8s.pod.cpu_time":
assert.False(t, validatedMetrics["k8s.pod.cpu_time"], "Found a duplicate in the metrics slice: k8s.pod.cpu_time")
validatedMetrics["k8s.pod.cpu_time"] = true
assert.Equal(t, pmetric.MetricTypeSum, mi.Type())
assert.Equal(t, 1, mi.Sum().DataPoints().Len())
assert.Equal(t, "CPU time consumed by the pod", mi.Description())
assert.Equal(t, "s", mi.Unit())
assert.True(t, mi.Sum().IsMonotonic())
assert.Equal(t, pmetric.AggregationTemporalityCumulative, mi.Sum().AggregationTemporality())
dp := mi.Sum().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeDouble, dp.ValueType())
assert.InDelta(t, float64(1), dp.DoubleValue(), 0.01)
case "k8s.pod.phase":
assert.False(t, validatedMetrics["k8s.pod.phase"], "Found a duplicate in the metrics slice: k8s.pod.phase")
validatedMetrics["k8s.pod.phase"] = true
assert.Equal(t, pmetric.MetricTypeGauge, mi.Type())
assert.Equal(t, 1, mi.Gauge().DataPoints().Len())
assert.Equal(t, "Current phase of the pod", mi.Description())
assert.Equal(t, "1", mi.Unit())
dp := mi.Gauge().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeInt, dp.ValueType())
assert.Equal(t, int64(1), dp.IntValue())
phaseAttrVal, ok := dp.Attributes().Get("phase")
assert.True(t, ok)
assert.Equal(t, "Pending", phaseAttrVal.Str())
case "k8s.replicaset.desired":
assert.False(t, validatedMetrics["k8s.replicaset.desired"], "Found a duplicate in the metrics slice: k8s.replicaset.desired")
validatedMetrics["k8s.replicaset.desired"] = true
assert.Equal(t, pmetric.MetricTypeGauge, mi.Type())
assert.Equal(t, 1, mi.Gauge().DataPoints().Len())
assert.Equal(t, "Number of desired replicas", mi.Description())
assert.Equal(t, "{replicas}", mi.Unit())
dp := mi.Gauge().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeInt, dp.ValueType())
assert.Equal(t, int64(1), dp.IntValue())
}
}
})
}
}
================================================
FILE: cmd/mdatagen/internal/sampleentityreceiver/internal/metadata/generated_resource.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/pdata/pcommon"
)
// ResourceBuilder is a helper struct to build resources predefined in metadata.yaml.
// The ResourceBuilder is not thread-safe and must not to be used in multiple goroutines.
type ResourceBuilder struct {
config ResourceAttributesConfig
res pcommon.Resource
}
// NewResourceBuilder creates a new ResourceBuilder. This method should be called on the start of the application.
func NewResourceBuilder(rac ResourceAttributesConfig) *ResourceBuilder {
return &ResourceBuilder{
config: rac,
res: pcommon.NewResource(),
}
}
// SetK8sNamespaceName sets provided value as "k8s.namespace.name" attribute.
func (rb *ResourceBuilder) SetK8sNamespaceName(val string) {
if rb.config.K8sNamespaceName.Enabled {
rb.res.Attributes().PutStr("k8s.namespace.name", val)
}
}
// SetK8sPodName sets provided value as "k8s.pod.name" attribute.
func (rb *ResourceBuilder) SetK8sPodName(val string) {
if rb.config.K8sPodName.Enabled {
rb.res.Attributes().PutStr("k8s.pod.name", val)
}
}
// SetK8sPodUID sets provided value as "k8s.pod.uid" attribute.
func (rb *ResourceBuilder) SetK8sPodUID(val string) {
if rb.config.K8sPodUID.Enabled {
rb.res.Attributes().PutStr("k8s.pod.uid", val)
}
}
// SetK8sReplicasetName sets provided value as "k8s.replicaset.name" attribute.
func (rb *ResourceBuilder) SetK8sReplicasetName(val string) {
if rb.config.K8sReplicasetName.Enabled {
rb.res.Attributes().PutStr("k8s.replicaset.name", val)
}
}
// SetK8sReplicasetUID sets provided value as "k8s.replicaset.uid" attribute.
func (rb *ResourceBuilder) SetK8sReplicasetUID(val string) {
if rb.config.K8sReplicasetUID.Enabled {
rb.res.Attributes().PutStr("k8s.replicaset.uid", val)
}
}
// Emit returns the built resource and resets the internal builder state.
func (rb *ResourceBuilder) Emit() pcommon.Resource {
r := rb.res
rb.res = pcommon.NewResource()
return r
}
================================================
FILE: cmd/mdatagen/internal/sampleentityreceiver/internal/metadata/generated_resource_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestResourceBuilder(t *testing.T) {
for _, tt := range []string{"default", "all_set", "none_set"} {
t.Run(tt, func(t *testing.T) {
cfg := loadResourceAttributesConfig(t, tt)
rb := NewResourceBuilder(cfg)
rb.SetK8sNamespaceName("k8s.namespace.name-val")
rb.SetK8sPodName("k8s.pod.name-val")
rb.SetK8sPodUID("k8s.pod.uid-val")
rb.SetK8sReplicasetName("k8s.replicaset.name-val")
rb.SetK8sReplicasetUID("k8s.replicaset.uid-val")
res := rb.Emit()
assert.Equal(t, 0, rb.Emit().Attributes().Len()) // Second call should return empty Resource
switch tt {
case "default":
assert.Equal(t, 5, res.Attributes().Len())
case "all_set":
assert.Equal(t, 5, res.Attributes().Len())
case "none_set":
assert.Equal(t, 0, res.Attributes().Len())
return
default:
assert.Failf(t, "unexpected test case: %s", tt)
}
k8sNamespaceNameAttrVal, ok := res.Attributes().Get("k8s.namespace.name")
assert.True(t, ok)
if ok {
assert.Equal(t, "k8s.namespace.name-val", k8sNamespaceNameAttrVal.Str())
}
k8sPodNameAttrVal, ok := res.Attributes().Get("k8s.pod.name")
assert.True(t, ok)
if ok {
assert.Equal(t, "k8s.pod.name-val", k8sPodNameAttrVal.Str())
}
k8sPodUIDAttrVal, ok := res.Attributes().Get("k8s.pod.uid")
assert.True(t, ok)
if ok {
assert.Equal(t, "k8s.pod.uid-val", k8sPodUIDAttrVal.Str())
}
k8sReplicasetNameAttrVal, ok := res.Attributes().Get("k8s.replicaset.name")
assert.True(t, ok)
if ok {
assert.Equal(t, "k8s.replicaset.name-val", k8sReplicasetNameAttrVal.Str())
}
k8sReplicasetUIDAttrVal, ok := res.Attributes().Get("k8s.replicaset.uid")
assert.True(t, ok)
if ok {
assert.Equal(t, "k8s.replicaset.uid-val", k8sReplicasetUIDAttrVal.Str())
}
})
}
}
================================================
FILE: cmd/mdatagen/internal/sampleentityreceiver/internal/metadata/generated_status.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/component"
)
var (
Type = component.MustNewType("sampleentity")
ScopeName = "go.opentelemetry.io/collector/internal/receiver/sampleentityreceiver"
)
const (
MetricsStability = component.StabilityLevelDevelopment
)
================================================
FILE: cmd/mdatagen/internal/sampleentityreceiver/internal/metadata/testdata/config.yaml
================================================
default:
all_set:
metrics:
k8s.pod.cpu_time:
enabled: true
k8s.pod.phase:
enabled: true
k8s.replicaset.desired:
enabled: true
resource_attributes:
k8s.namespace.name:
enabled: true
k8s.pod.name:
enabled: true
k8s.pod.uid:
enabled: true
k8s.replicaset.name:
enabled: true
k8s.replicaset.uid:
enabled: true
none_set:
metrics:
k8s.pod.cpu_time:
enabled: false
k8s.pod.phase:
enabled: false
k8s.replicaset.desired:
enabled: false
resource_attributes:
k8s.namespace.name:
enabled: false
k8s.pod.name:
enabled: false
k8s.pod.uid:
enabled: false
k8s.replicaset.name:
enabled: false
k8s.replicaset.uid:
enabled: false
filter_set_include:
resource_attributes:
k8s.namespace.name:
enabled: true
metrics_include:
- regexp: ".*"
k8s.pod.name:
enabled: true
metrics_include:
- regexp: ".*"
k8s.pod.uid:
enabled: true
metrics_include:
- regexp: ".*"
k8s.replicaset.name:
enabled: true
metrics_include:
- regexp: ".*"
k8s.replicaset.uid:
enabled: true
metrics_include:
- regexp: ".*"
filter_set_exclude:
resource_attributes:
k8s.namespace.name:
enabled: true
metrics_exclude:
- strict: "k8s.namespace.name-val"
k8s.pod.name:
enabled: true
metrics_exclude:
- strict: "k8s.pod.name-val"
k8s.pod.uid:
enabled: true
metrics_exclude:
- strict: "k8s.pod.uid-val"
k8s.replicaset.name:
enabled: true
metrics_exclude:
- strict: "k8s.replicaset.name-val"
k8s.replicaset.uid:
enabled: true
metrics_exclude:
- strict: "k8s.replicaset.uid-val"
================================================
FILE: cmd/mdatagen/internal/sampleentityreceiver/metadata.yaml
================================================
# Sample metadata file with entities for testing entity-based builder API
type: sampleentity
display_name: Sample Entity Receiver
description: This receiver demonstrates entity-based metrics builder API.
scope_name: go.opentelemetry.io/collector/internal/receiver/sampleentityreceiver
sem_conv_version: 1.38.0
status:
class: receiver
stability:
development: [metrics]
codeowners:
active: [dmitryax]
resource_attributes:
k8s.namespace.name:
description: The name of the Kubernetes Namespace
type: string
enabled: true
k8s.pod.name:
description: The name of the Kubernetes Pod
type: string
enabled: true
k8s.pod.uid:
description: The UID of the Kubernetes Pod
type: string
enabled: true
k8s.replicaset.name:
description: The name of the Kubernetes ReplicaSet
type: string
enabled: true
k8s.replicaset.uid:
description: The UID of the Kubernetes ReplicaSet
type: string
enabled: true
entities:
- type: k8s.replicaset
brief: A Kubernetes ReplicaSet
stability: development
identity:
- ref: k8s.replicaset.uid
description:
- ref: k8s.replicaset.name
- type: k8s.pod
brief: A Kubernetes Pod
stability: development
identity:
- ref: k8s.pod.uid
description:
- ref: k8s.pod.name
extra_attributes:
- ref: k8s.namespace.name
relationships:
- type: controlled_by
target: k8s.replicaset
attributes:
phase:
description: The phase of the pod (Pending, Running, Succeeded, Failed, Unknown)
type: string
enum: [Pending, Running, Succeeded, Failed, Unknown]
metrics:
k8s.pod.cpu_time:
enabled: true
description: CPU time consumed by the pod
unit: s
sum:
value_type: double
monotonic: true
aggregation_temporality: cumulative
attributes: []
entity: k8s.pod
stability: development
k8s.pod.phase:
enabled: true
description: Current phase of the pod
unit: "1"
gauge:
value_type: int
attributes: [phase]
entity: k8s.pod
stability: development
k8s.replicaset.desired:
enabled: true
description: Number of desired replicas
unit: "{replicas}"
gauge:
value_type: int
attributes: []
entity: k8s.replicaset
stability: development
================================================
FILE: cmd/mdatagen/internal/samplefactoryreceiver/README.md
================================================
# Sample Factory Receiver
This receiver is used for testing purposes to check the output of mdatagen.
| Status | |
| ------------- |-----------|
| Stability | [deprecated]: profiles |
| | [development]: logs |
| | [beta]: traces |
| | [stable]: metrics |
| Deprecation of profiles | [Date]: 2025-02-05 |
| | [Migration Note]: no migration needed |
| Unsupported Platforms | freebsd, illumos |
| Distributions | [] |
| Warnings | [Any additional information that should be brought to the consumer's attention](#warnings) |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Areceiver%2Fsamplefactory) [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aclosed+is%3Aissue+label%3Areceiver%2Fsamplefactory) |
| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@dmitryax](https://www.github.com/dmitryax) |
[deprecated]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#deprecated
[development]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#development
[beta]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#beta
[stable]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#stable
[Date]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#deprecation-information
[Migration Note]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#deprecation-information
## Warnings
This is where warnings are described.
================================================
FILE: cmd/mdatagen/internal/samplefactoryreceiver/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Generate a test metrics builder from a sample metrics set covering all configuration options.
//go:generate mdatagen metadata.yaml
package samplefactoryreceiver // import "go.opentelemetry.io/collector/cmd/mdatagen/internal/samplefactoryreceiver"
================================================
FILE: cmd/mdatagen/internal/samplefactoryreceiver/documentation.md
================================================
[comment]: <> (Code generated by mdatagen. DO NOT EDIT.)
# sample
## Default Metrics
The following metrics are emitted by default. Each of them can be disabled by applying the following configuration:
```yaml
metrics:
:
enabled: false
```
### default.metric
Monotonic cumulative sum int metric enabled by default.
The metric will be become optional soon.
| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic |
| ---- | ----------- | ---------- | ----------------------- | --------- |
| s | Sum | Int | Cumulative | true |
#### Attributes
| Name | Description | Values | Optional |
| ---- | ----------- | ------ | -------- |
| string_attr | Attribute with any string value. | Any Str | false |
| state | Integer attribute with overridden name. | Any Int | false |
| enum_attr | Attribute with a known set of string values. | Str: ``red``, ``green``, ``blue`` | false |
| slice_attr | Attribute with a slice value. | Any Slice | false |
| map_attr | Attribute with a map value. | Any Map | false |
| optional_int_attr | An optional attribute with an integer value | Any Int | true |
| optional_string_attr | An optional attribute with any string value | Any Str | true |
### default.metric.to_be_removed
[DEPRECATED] Non-monotonic delta sum double metric enabled by default.
The metric will be removed soon.
| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic |
| ---- | ----------- | ---------- | ----------------------- | --------- |
| s | Sum | Double | Delta | false |
### metric.input_type
Monotonic cumulative sum int metric with string input_type enabled by default.
| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic |
| ---- | ----------- | ---------- | ----------------------- | --------- |
| s | Sum | Int | Cumulative | true |
#### Attributes
| Name | Description | Values | Optional |
| ---- | ----------- | ------ | -------- |
| string_attr | Attribute with any string value. | Any Str | false |
| state | Integer attribute with overridden name. | Any Int | false |
| enum_attr | Attribute with a known set of string values. | Str: ``red``, ``green``, ``blue`` | false |
| slice_attr | Attribute with a slice value. | Any Slice | false |
| map_attr | Attribute with a map value. | Any Map | false |
## Optional Metrics
The following metrics are not emitted by default. Each of them can be enabled by applying the following configuration:
```yaml
metrics:
:
enabled: true
```
### optional.metric
[DEPRECATED] Gauge double metric disabled by default.
| Unit | Metric Type | Value Type |
| ---- | ----------- | ---------- |
| 1 | Gauge | Double |
#### Attributes
| Name | Description | Values | Optional |
| ---- | ----------- | ------ | -------- |
| string_attr | Attribute with any string value. | Any Str | false |
| boolean_attr | Attribute with a boolean value. | Any Bool | false |
| boolean_attr2 | Another attribute with a boolean value. | Any Bool | false |
| optional_string_attr | An optional attribute with any string value | Any Str | true |
### optional.metric.empty_unit
[DEPRECATED] Gauge double metric disabled by default.
| Unit | Metric Type | Value Type |
| ---- | ----------- | ---------- |
| | Gauge | Double |
#### Attributes
| Name | Description | Values | Optional |
| ---- | ----------- | ------ | -------- |
| string_attr | Attribute with any string value. | Any Str | false |
| boolean_attr | Attribute with a boolean value. | Any Bool | false |
## Default Events
The following events are emitted by default. Each of them can be disabled by applying the following configuration:
```yaml
events:
:
enabled: false
```
### default.event
Example event enabled by default.
#### Attributes
| Name | Description | Values |
| ---- | ----------- | ------ |
| string_attr | Attribute with any string value. | Any Str |
| state | Integer attribute with overridden name. | Any Int |
| enum_attr | Attribute with a known set of string values. | Str: ``red``, ``green``, ``blue`` |
| slice_attr | Attribute with a slice value. | Any Slice |
| map_attr | Attribute with a map value. | Any Map |
| optional_int_attr | An optional attribute with an integer value | Any Int |
| optional_string_attr | An optional attribute with any string value | Any Str |
### default.event.to_be_removed
[DEPRECATED] Example to-be-removed event enabled by default.
The event will be removed soon.
#### Attributes
| Name | Description | Values |
| ---- | ----------- | ------ |
| string_attr | Attribute with any string value. | Any Str |
| state | Integer attribute with overridden name. | Any Int |
| enum_attr | Attribute with a known set of string values. | Str: ``red``, ``green``, ``blue`` |
| slice_attr | Attribute with a slice value. | Any Slice |
| map_attr | Attribute with a map value. | Any Map |
## Optional Events
The following events are not emitted by default. Each of them can be enabled by applying the following configuration:
```yaml
events:
:
enabled: true
```
### default.event.to_be_renamed
[DEPRECATED] Example event disabled by default.
The event will be renamed soon.
#### Attributes
| Name | Description | Values |
| ---- | ----------- | ------ |
| string_attr | Attribute with any string value. | Any Str |
| boolean_attr | Attribute with a boolean value. | Any Bool |
| boolean_attr2 | Another attribute with a boolean value. | Any Bool |
| optional_string_attr | An optional attribute with any string value | Any Str |
## Resource Attributes
| Name | Description | Values | Enabled |
| ---- | ----------- | ------ | ------- |
| map.resource.attr | Resource attribute with a map value. | Any Map | true |
| optional.resource.attr | Explicitly disabled ResourceAttribute. | Any Str | false |
| slice.resource.attr | Resource attribute with a slice value. | Any Slice | true |
| string.enum.resource.attr | Resource attribute with a known set of string values. | Str: ``one``, ``two`` | true |
| string.resource.attr | Resource attribute with any string value. | Any Str | true |
| string.resource.attr_disable_warning | Resource attribute with any string value. | Any Str | true |
| string.resource.attr_remove_warning | Resource attribute with any string value. | Any Str | false |
| string.resource.attr_to_be_removed | Resource attribute with any string value. | Any Str | true |
## Internal Telemetry
The following telemetry is emitted by this component.
### otelcol_batch_size_trigger_send
Number of times the batch was sent due to a size trigger [deprecated since v0.110.0]
| Unit | Metric Type | Value Type | Monotonic |
| ---- | ----------- | ---------- | --------- |
| {times} | Sum | Int | true |
### otelcol_process_runtime_total_alloc_bytes
Cumulative bytes allocated for heap objects (see 'go doc runtime.MemStats.TotalAlloc')
| Unit | Metric Type | Value Type | Monotonic |
| ---- | ----------- | ---------- | --------- |
| By | Sum | Int | true |
### otelcol_queue_capacity
Queue capacity - sync gauge example.
| Unit | Metric Type | Value Type |
| ---- | ----------- | ---------- |
| {items} | Gauge | Int |
### otelcol_queue_length
This metric is optional and therefore not initialized in NewTelemetryBuilder. [alpha]
For example this metric only exists if feature A is enabled.
| Unit | Metric Type | Value Type |
| ---- | ----------- | ---------- |
| {items} | Gauge | Int |
### otelcol_request_duration
Duration of request [alpha]
| Unit | Metric Type | Value Type |
| ---- | ----------- | ---------- |
| s | Histogram | Double |
================================================
FILE: cmd/mdatagen/internal/samplefactoryreceiver/factory.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package samplefactoryreceiver // import "go.opentelemetry.io/collector/cmd/mdatagen/internal/samplefactoryreceiver"
import (
"context"
"errors"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/collector/cmd/mdatagen/internal/samplefactoryreceiver/internal/metadata"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/xreceiver"
"go.opentelemetry.io/collector/service/hostcapabilities"
)
// NewFactory returns a receiver.Factory for sample receiver.
func NewFactory() xreceiver.Factory {
return xreceiver.NewFactory(
metadata.Type,
func() component.Config { return &struct{}{} },
xreceiver.WithTraces(createTraces, metadata.TracesStability),
xreceiver.WithMetrics(createMetrics, metadata.MetricsStability),
xreceiver.WithLogs(createLogs, metadata.LogsStability),
xreceiver.WithProfiles(createProfiles, metadata.ProfilesStability),
)
}
func createTraces(context.Context, receiver.Settings, component.Config, consumer.Traces) (receiver.Traces, error) {
return nopInstance, nil
}
func createMetrics(ctx context.Context, set receiver.Settings, _ component.Config, _ consumer.Metrics) (receiver.Metrics, error) {
telemetryBuilder, err := metadata.NewTelemetryBuilder(set.TelemetrySettings)
if err != nil {
return nil, err
}
err = telemetryBuilder.RegisterProcessRuntimeTotalAllocBytesCallback(func(_ context.Context, observer metric.Int64Observer) error {
observer.Observe(2)
return nil
})
if err != nil {
return nil, err
}
telemetryBuilder.BatchSizeTriggerSend.Add(ctx, 1)
return nopReceiver{telemetryBuilder: telemetryBuilder}, nil
}
func createLogs(context.Context, receiver.Settings, component.Config, consumer.Logs) (receiver.Logs, error) {
return nopInstance, nil
}
func createProfiles(context.Context, receiver.Settings, component.Config, xconsumer.Profiles) (xreceiver.Profiles, error) {
return nopInstance, nil
}
var nopInstance = &nopReceiver{}
type nopReceiver struct {
component.StartFunc
telemetryBuilder *metadata.TelemetryBuilder
}
func (r nopReceiver) Start(_ context.Context, host component.Host) error {
if _, ok := host.(hostcapabilities.ComponentFactory); !ok {
return errors.New("host does not implement hostcapabilities.ComponentFactory")
}
return nil
}
// Shutdown shuts down the component.
func (r nopReceiver) Shutdown(context.Context) error {
if r.telemetryBuilder != nil {
r.telemetryBuilder.Shutdown()
}
return nil
}
================================================
FILE: cmd/mdatagen/internal/samplefactoryreceiver/generated_component_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
//go:build !freebsd && !illumos
package samplefactoryreceiver
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/confmap/confmaptest"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/receivertest"
"go.opentelemetry.io/collector/receiver/xreceiver"
)
var typ = component.MustNewType("sample")
func TestComponentFactoryType(t *testing.T) {
require.Equal(t, typ, NewFactory().Type())
}
func TestComponentConfigStruct(t *testing.T) {
require.NoError(t, componenttest.CheckConfigStruct(NewFactory().CreateDefaultConfig()))
}
func TestComponentLifecycle(t *testing.T) {
factory := NewFactory()
tests := []struct {
createFn func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error)
name string
}{
{
name: "logs",
createFn: func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateLogs(ctx, set, cfg, consumertest.NewNop())
},
},
{
name: "metrics",
createFn: func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateMetrics(ctx, set, cfg, consumertest.NewNop())
},
},
{
name: "traces",
createFn: func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateTraces(ctx, set, cfg, consumertest.NewNop())
},
},
{
name: "profiles",
createFn: func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error) {
return factory.(xreceiver.Factory).CreateProfiles(ctx, set, cfg, consumertest.NewNop())
},
},
}
cm, err := confmaptest.LoadConf("metadata.yaml")
require.NoError(t, err)
cfg := factory.CreateDefaultConfig()
sub, err := cm.Sub("tests::config")
require.NoError(t, err)
require.NoError(t, sub.Unmarshal(&cfg))
for _, tt := range tests {
t.Run(tt.name+"-shutdown", func(t *testing.T) {
c, err := tt.createFn(context.Background(), receivertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
err = c.Shutdown(context.Background())
require.NoError(t, err)
})
t.Run(tt.name+"-lifecycle", func(t *testing.T) {
firstRcvr, err := tt.createFn(context.Background(), receivertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
host := newMdatagenNopHost()
require.NoError(t, err)
require.NoError(t, firstRcvr.Start(context.Background(), host))
require.NoError(t, firstRcvr.Shutdown(context.Background()))
secondRcvr, err := tt.createFn(context.Background(), receivertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
require.NoError(t, secondRcvr.Start(context.Background(), host))
require.NoError(t, secondRcvr.Shutdown(context.Background()))
})
}
}
var _ component.Host = (*mdatagenNopHost)(nil)
type mdatagenNopHost struct{}
func newMdatagenNopHost() component.Host {
return &mdatagenNopHost{}
}
func (mnh *mdatagenNopHost) GetExtensions() map[component.ID]component.Component {
return nil
}
func (mnh *mdatagenNopHost) GetFactory(_ component.Kind, _ component.Type) component.Factory {
return nil
}
================================================
FILE: cmd/mdatagen/internal/samplefactoryreceiver/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package samplefactoryreceiver
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: cmd/mdatagen/internal/samplefactoryreceiver/internal/metadata/generated_config.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/filter"
)
// MetricConfig provides common config for a particular metric.
type MetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
}
func (ms *MetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
// MetricsConfig provides config for sample metrics.
type MetricsConfig struct {
DefaultMetric MetricConfig `mapstructure:"default.metric"`
DefaultMetricToBeRemoved MetricConfig `mapstructure:"default.metric.to_be_removed"`
MetricInputType MetricConfig `mapstructure:"metric.input_type"`
OptionalMetric MetricConfig `mapstructure:"optional.metric"`
OptionalMetricEmptyUnit MetricConfig `mapstructure:"optional.metric.empty_unit"`
}
func DefaultMetricsConfig() MetricsConfig {
return MetricsConfig{
DefaultMetric: MetricConfig{
Enabled: true,
},
DefaultMetricToBeRemoved: MetricConfig{
Enabled: true,
},
MetricInputType: MetricConfig{
Enabled: true,
},
OptionalMetric: MetricConfig{
Enabled: false,
},
OptionalMetricEmptyUnit: MetricConfig{
Enabled: false,
},
}
}
// EventConfig provides common config for a particular event.
type EventConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
}
func (ec *EventConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ec)
if err != nil {
return err
}
ec.enabledSetByUser = parser.IsSet("enabled")
return nil
}
// EventsConfig provides config for sample events.
type EventsConfig struct {
DefaultEvent EventConfig `mapstructure:"default.event"`
DefaultEventToBeRemoved EventConfig `mapstructure:"default.event.to_be_removed"`
DefaultEventToBeRenamed EventConfig `mapstructure:"default.event.to_be_renamed"`
}
func DefaultEventsConfig() EventsConfig {
return EventsConfig{
DefaultEvent: EventConfig{
Enabled: true,
},
DefaultEventToBeRemoved: EventConfig{
Enabled: true,
},
DefaultEventToBeRenamed: EventConfig{
Enabled: false,
},
}
}
// ResourceAttributeConfig provides common config for a particular resource attribute.
type ResourceAttributeConfig struct {
Enabled bool `mapstructure:"enabled"`
// Experimental: MetricsInclude defines a list of filters for attribute values.
// If the list is not empty, only metrics with matching resource attribute values will be emitted.
MetricsInclude []filter.Config `mapstructure:"metrics_include"`
// Experimental: MetricsExclude defines a list of filters for attribute values.
// If the list is not empty, metrics with matching resource attribute values will not be emitted.
// MetricsInclude has higher priority than MetricsExclude.
MetricsExclude []filter.Config `mapstructure:"metrics_exclude"`
// Experimental: EventsInclude defines a list of filters for attribute values.
// If the list is not empty, only events with matching resource attribute values will be emitted.
EventsInclude []filter.Config `mapstructure:"events_include"`
// Experimental: EventsExclude defines a list of filters for attribute values.
// If the list is not empty, events with matching resource attribute values will not be emitted.
// EventsInclude has higher priority than EventsExclude.
EventsExclude []filter.Config `mapstructure:"events_exclude"`
enabledSetByUser bool
}
func (rac *ResourceAttributeConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(rac)
if err != nil {
return err
}
rac.enabledSetByUser = parser.IsSet("enabled")
return nil
}
// ResourceAttributesConfig provides config for sample resource attributes.
type ResourceAttributesConfig struct {
MapResourceAttr ResourceAttributeConfig `mapstructure:"map.resource.attr"`
OptionalResourceAttr ResourceAttributeConfig `mapstructure:"optional.resource.attr"`
SliceResourceAttr ResourceAttributeConfig `mapstructure:"slice.resource.attr"`
StringEnumResourceAttr ResourceAttributeConfig `mapstructure:"string.enum.resource.attr"`
StringResourceAttr ResourceAttributeConfig `mapstructure:"string.resource.attr"`
StringResourceAttrDisableWarning ResourceAttributeConfig `mapstructure:"string.resource.attr_disable_warning"`
StringResourceAttrRemoveWarning ResourceAttributeConfig `mapstructure:"string.resource.attr_remove_warning"`
StringResourceAttrToBeRemoved ResourceAttributeConfig `mapstructure:"string.resource.attr_to_be_removed"`
}
func DefaultResourceAttributesConfig() ResourceAttributesConfig {
return ResourceAttributesConfig{
MapResourceAttr: ResourceAttributeConfig{
Enabled: true,
},
OptionalResourceAttr: ResourceAttributeConfig{
Enabled: false,
},
SliceResourceAttr: ResourceAttributeConfig{
Enabled: true,
},
StringEnumResourceAttr: ResourceAttributeConfig{
Enabled: true,
},
StringResourceAttr: ResourceAttributeConfig{
Enabled: true,
},
StringResourceAttrDisableWarning: ResourceAttributeConfig{
Enabled: true,
},
StringResourceAttrRemoveWarning: ResourceAttributeConfig{
Enabled: false,
},
StringResourceAttrToBeRemoved: ResourceAttributeConfig{
Enabled: true,
},
}
}
// MetricsBuilderConfig is a configuration for sample metrics builder.
type MetricsBuilderConfig struct {
Metrics MetricsConfig `mapstructure:"metrics"`
ResourceAttributes ResourceAttributesConfig `mapstructure:"resource_attributes"`
}
func DefaultMetricsBuilderConfig() MetricsBuilderConfig {
return MetricsBuilderConfig{
Metrics: DefaultMetricsConfig(),
ResourceAttributes: DefaultResourceAttributesConfig(),
}
}
// LogsBuilderConfig is a configuration for sample logs builder.
type LogsBuilderConfig struct {
Events EventsConfig `mapstructure:"events"`
ResourceAttributes ResourceAttributesConfig `mapstructure:"resource_attributes"`
}
func DefaultLogsBuilderConfig() LogsBuilderConfig {
return LogsBuilderConfig{
Events: DefaultEventsConfig(),
ResourceAttributes: DefaultResourceAttributesConfig(),
}
}
================================================
FILE: cmd/mdatagen/internal/samplefactoryreceiver/internal/metadata/generated_config_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"path/filepath"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/confmaptest"
)
func TestMetricsBuilderConfig(t *testing.T) {
tests := []struct {
name string
want MetricsBuilderConfig
}{
{
name: "default",
want: DefaultMetricsBuilderConfig(),
},
{
name: "all_set",
want: MetricsBuilderConfig{
Metrics: MetricsConfig{
DefaultMetric: MetricConfig{Enabled: true},
DefaultMetricToBeRemoved: MetricConfig{Enabled: true},
MetricInputType: MetricConfig{Enabled: true},
OptionalMetric: MetricConfig{Enabled: true},
OptionalMetricEmptyUnit: MetricConfig{Enabled: true},
},
ResourceAttributes: ResourceAttributesConfig{
MapResourceAttr: ResourceAttributeConfig{Enabled: true},
OptionalResourceAttr: ResourceAttributeConfig{Enabled: true},
SliceResourceAttr: ResourceAttributeConfig{Enabled: true},
StringEnumResourceAttr: ResourceAttributeConfig{Enabled: true},
StringResourceAttr: ResourceAttributeConfig{Enabled: true},
StringResourceAttrDisableWarning: ResourceAttributeConfig{Enabled: true},
StringResourceAttrRemoveWarning: ResourceAttributeConfig{Enabled: true},
StringResourceAttrToBeRemoved: ResourceAttributeConfig{Enabled: true},
},
},
},
{
name: "none_set",
want: MetricsBuilderConfig{
Metrics: MetricsConfig{
DefaultMetric: MetricConfig{Enabled: false},
DefaultMetricToBeRemoved: MetricConfig{Enabled: false},
MetricInputType: MetricConfig{Enabled: false},
OptionalMetric: MetricConfig{Enabled: false},
OptionalMetricEmptyUnit: MetricConfig{Enabled: false},
},
ResourceAttributes: ResourceAttributesConfig{
MapResourceAttr: ResourceAttributeConfig{Enabled: false},
OptionalResourceAttr: ResourceAttributeConfig{Enabled: false},
SliceResourceAttr: ResourceAttributeConfig{Enabled: false},
StringEnumResourceAttr: ResourceAttributeConfig{Enabled: false},
StringResourceAttr: ResourceAttributeConfig{Enabled: false},
StringResourceAttrDisableWarning: ResourceAttributeConfig{Enabled: false},
StringResourceAttrRemoveWarning: ResourceAttributeConfig{Enabled: false},
StringResourceAttrToBeRemoved: ResourceAttributeConfig{Enabled: false},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := loadMetricsBuilderConfig(t, tt.name)
diff := cmp.Diff(tt.want, cfg, cmpopts.IgnoreUnexported(MetricConfig{}, ResourceAttributeConfig{}))
require.Emptyf(t, diff, "Config mismatch (-expected +actual):\n%s", diff)
})
}
}
func loadMetricsBuilderConfig(t *testing.T, name string) MetricsBuilderConfig {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
require.NoError(t, err)
sub, err := cm.Sub(name)
require.NoError(t, err)
cfg := DefaultMetricsBuilderConfig()
require.NoError(t, sub.Unmarshal(&cfg, confmap.WithIgnoreUnused()))
return cfg
}
func loadLogsBuilderConfig(t *testing.T, name string) LogsBuilderConfig {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
require.NoError(t, err)
sub, err := cm.Sub(name)
require.NoError(t, err)
cfg := DefaultLogsBuilderConfig()
require.NoError(t, sub.Unmarshal(&cfg, confmap.WithIgnoreUnused()))
return cfg
}
func TestResourceAttributesConfig(t *testing.T) {
tests := []struct {
name string
want ResourceAttributesConfig
}{
{
name: "default",
want: DefaultResourceAttributesConfig(),
},
{
name: "all_set",
want: ResourceAttributesConfig{
MapResourceAttr: ResourceAttributeConfig{Enabled: true},
OptionalResourceAttr: ResourceAttributeConfig{Enabled: true},
SliceResourceAttr: ResourceAttributeConfig{Enabled: true},
StringEnumResourceAttr: ResourceAttributeConfig{Enabled: true},
StringResourceAttr: ResourceAttributeConfig{Enabled: true},
StringResourceAttrDisableWarning: ResourceAttributeConfig{Enabled: true},
StringResourceAttrRemoveWarning: ResourceAttributeConfig{Enabled: true},
StringResourceAttrToBeRemoved: ResourceAttributeConfig{Enabled: true},
},
},
{
name: "none_set",
want: ResourceAttributesConfig{
MapResourceAttr: ResourceAttributeConfig{Enabled: false},
OptionalResourceAttr: ResourceAttributeConfig{Enabled: false},
SliceResourceAttr: ResourceAttributeConfig{Enabled: false},
StringEnumResourceAttr: ResourceAttributeConfig{Enabled: false},
StringResourceAttr: ResourceAttributeConfig{Enabled: false},
StringResourceAttrDisableWarning: ResourceAttributeConfig{Enabled: false},
StringResourceAttrRemoveWarning: ResourceAttributeConfig{Enabled: false},
StringResourceAttrToBeRemoved: ResourceAttributeConfig{Enabled: false},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := loadResourceAttributesConfig(t, tt.name)
diff := cmp.Diff(tt.want, cfg, cmpopts.IgnoreUnexported(ResourceAttributeConfig{}))
require.Emptyf(t, diff, "Config mismatch (-expected +actual):\n%s", diff)
})
}
}
func loadResourceAttributesConfig(t *testing.T, name string) ResourceAttributesConfig {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
require.NoError(t, err)
sub, err := cm.Sub(name)
require.NoError(t, err)
sub, err = sub.Sub("resource_attributes")
require.NoError(t, err)
cfg := DefaultResourceAttributesConfig()
require.NoError(t, sub.Unmarshal(&cfg))
return cfg
}
================================================
FILE: cmd/mdatagen/internal/samplefactoryreceiver/internal/metadata/generated_logs.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
conventions "go.opentelemetry.io/otel/semconv/v1.9.0"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/receiver"
)
// LogsBuilder provides an interface for scrapers to report logs while taking care of all the transformations
// required to produce log representation defined in metadata and user config.
type LogsBuilder struct {
logsBuffer plog.Logs
logRecordsBuffer plog.LogRecordSlice
buildInfo component.BuildInfo // contains version information.
}
// LogBuilderOption applies changes to default logs builder.
type LogBuilderOption interface {
apply(*LogsBuilder)
}
func NewLogsBuilder(settings receiver.Settings) *LogsBuilder {
lb := &LogsBuilder{
logsBuffer: plog.NewLogs(),
logRecordsBuffer: plog.NewLogRecordSlice(),
buildInfo: settings.BuildInfo,
}
return lb
}
// ResourceLogsOption applies changes to provided resource logs.
type ResourceLogsOption interface {
apply(plog.ResourceLogs)
}
type resourceLogsOptionFunc func(plog.ResourceLogs)
func (rlof resourceLogsOptionFunc) apply(rl plog.ResourceLogs) {
rlof(rl)
}
// WithLogsResource sets the provided resource on the emitted ResourceLogs.
// It's recommended to use ResourceBuilder to create the resource.
func WithLogsResource(res pcommon.Resource) ResourceLogsOption {
return resourceLogsOptionFunc(func(rl plog.ResourceLogs) {
res.CopyTo(rl.Resource())
})
}
// AppendLogRecord adds a log record to the logs builder.
func (lb *LogsBuilder) AppendLogRecord(lr plog.LogRecord) {
lr.MoveTo(lb.logRecordsBuffer.AppendEmpty())
}
// EmitForResource saves all the generated logs under a new resource and updates the internal state to be ready for
// recording another set of log records as part of another resource. This function can be helpful when one scraper
// needs to emit logs from several resources. Otherwise calling this function is not required,
// just `Emit` function can be called instead.
// Resource attributes should be provided as ResourceLogsOption arguments.
func (lb *LogsBuilder) EmitForResource(options ...ResourceLogsOption) {
rl := plog.NewResourceLogs()
rl.SetSchemaUrl(conventions.SchemaURL)
ils := rl.ScopeLogs().AppendEmpty()
ils.Scope().SetName(ScopeName)
ils.Scope().SetVersion(lb.buildInfo.Version)
for _, op := range options {
op.apply(rl)
}
if lb.logRecordsBuffer.Len() > 0 {
lb.logRecordsBuffer.MoveAndAppendTo(ils.LogRecords())
lb.logRecordsBuffer = plog.NewLogRecordSlice()
}
if ils.LogRecords().Len() > 0 {
rl.MoveTo(lb.logsBuffer.ResourceLogs().AppendEmpty())
}
}
// Emit returns all the logs accumulated by the logs builder and updates the internal state to be ready for
// recording another set of logs. This function will be responsible for applying all the transformations required to
// produce logs representation defined in metadata and user config.
func (lb *LogsBuilder) Emit(options ...ResourceLogsOption) plog.Logs {
lb.EmitForResource(options...)
logs := lb.logsBuffer
lb.logsBuffer = plog.NewLogs()
return logs
}
================================================
FILE: cmd/mdatagen/internal/samplefactoryreceiver/internal/metadata/generated_logs_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/receiver/receivertest"
)
func TestLogsBuilderAppendLogRecord(t *testing.T) {
observedZapCore, _ := observer.New(zap.WarnLevel)
settings := receivertest.NewNopSettings(receivertest.NopType)
settings.Logger = zap.New(observedZapCore)
lb := NewLogsBuilder(settings)
res := pcommon.NewResource()
// append the first log record
lr := plog.NewLogRecord()
lr.SetTimestamp(pcommon.NewTimestampFromTime(time.Now()))
lr.Attributes().PutStr("type", "log")
lr.Body().SetStr("the first log record")
// append the second log record
lr2 := plog.NewLogRecord()
lr2.SetTimestamp(pcommon.NewTimestampFromTime(time.Now()))
lr2.Attributes().PutStr("type", "event")
lr2.Body().SetStr("the second log record")
lb.AppendLogRecord(lr)
lb.AppendLogRecord(lr2)
logs := lb.Emit(WithLogsResource(res))
assert.Equal(t, 1, logs.ResourceLogs().Len())
rl := logs.ResourceLogs().At(0)
assert.Equal(t, 1, rl.ScopeLogs().Len())
sl := rl.ScopeLogs().At(0)
assert.Equal(t, ScopeName, sl.Scope().Name())
assert.Equal(t, lb.buildInfo.Version, sl.Scope().Version())
assert.Equal(t, 2, sl.LogRecords().Len())
attrVal, ok := sl.LogRecords().At(0).Attributes().Get("type")
assert.True(t, ok)
assert.Equal(t, "log", attrVal.Str())
assert.Equal(t, pcommon.ValueTypeStr, sl.LogRecords().At(0).Body().Type())
assert.Equal(t, "the first log record", sl.LogRecords().At(0).Body().Str())
attrVal, ok = sl.LogRecords().At(1).Attributes().Get("type")
assert.True(t, ok)
assert.Equal(t, "event", attrVal.Str())
assert.Equal(t, pcommon.ValueTypeStr, sl.LogRecords().At(1).Body().Type())
assert.Equal(t, "the second log record", sl.LogRecords().At(1).Body().Str())
}
================================================
FILE: cmd/mdatagen/internal/samplefactoryreceiver/internal/metadata/generated_metrics.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"fmt"
"strconv"
"time"
conventions "go.opentelemetry.io/otel/semconv/v1.9.0"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/filter"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/receiver"
)
// AttributeEnumAttr specifies the value enum_attr attribute.
type AttributeEnumAttr int
const (
_ AttributeEnumAttr = iota
AttributeEnumAttrRed
AttributeEnumAttrGreen
AttributeEnumAttrBlue
)
// String returns the string representation of the AttributeEnumAttr.
func (av AttributeEnumAttr) String() string {
switch av {
case AttributeEnumAttrRed:
return "red"
case AttributeEnumAttrGreen:
return "green"
case AttributeEnumAttrBlue:
return "blue"
}
return ""
}
// MapAttributeEnumAttr is a helper map of string to AttributeEnumAttr attribute value.
var MapAttributeEnumAttr = map[string]AttributeEnumAttr{
"red": AttributeEnumAttrRed,
"green": AttributeEnumAttrGreen,
"blue": AttributeEnumAttrBlue,
}
var MetricsInfo = metricsInfo{
DefaultMetric: metricInfo{
Name: "default.metric",
},
DefaultMetricToBeRemoved: metricInfo{
Name: "default.metric.to_be_removed",
},
MetricInputType: metricInfo{
Name: "metric.input_type",
},
OptionalMetric: metricInfo{
Name: "optional.metric",
},
OptionalMetricEmptyUnit: metricInfo{
Name: "optional.metric.empty_unit",
},
}
type metricsInfo struct {
DefaultMetric metricInfo
DefaultMetricToBeRemoved metricInfo
MetricInputType metricInfo
OptionalMetric metricInfo
OptionalMetricEmptyUnit metricInfo
}
type metricInfo struct {
Name string
}
type MetricAttributeOption interface {
apply(pmetric.NumberDataPoint)
}
type metricAttributeOptionFunc func(pmetric.NumberDataPoint)
func (maof metricAttributeOptionFunc) apply(dp pmetric.NumberDataPoint) {
maof(dp)
}
func WithOptionalIntAttrMetricAttribute(optionalIntAttrAttributeValue int64) MetricAttributeOption {
return metricAttributeOptionFunc(func(dp pmetric.NumberDataPoint) {
dp.Attributes().PutInt("optional_int_attr", optionalIntAttrAttributeValue)
})
}
func WithOptionalStringAttrMetricAttribute(optionalStringAttrAttributeValue string) MetricAttributeOption {
return metricAttributeOptionFunc(func(dp pmetric.NumberDataPoint) {
dp.Attributes().PutStr("optional_string_attr", optionalStringAttrAttributeValue)
})
}
type metricDefaultMetric struct {
data pmetric.Metric // data buffer for generated metric.
config MetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
}
// init fills default.metric metric with initial data.
func (m *metricDefaultMetric) init() {
m.data.SetName("default.metric")
m.data.SetDescription("Monotonic cumulative sum int metric enabled by default.")
m.data.SetUnit("s")
m.data.SetEmptySum()
m.data.Sum().SetIsMonotonic(true)
m.data.Sum().SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
m.data.Sum().DataPoints().EnsureCapacity(m.capacity)
}
func (m *metricDefaultMetric) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val int64, stringAttrAttributeValue string, overriddenIntAttrAttributeValue int64, enumAttrAttributeValue string, sliceAttrAttributeValue []any, mapAttrAttributeValue map[string]any, options ...MetricAttributeOption) {
if !m.config.Enabled {
return
}
dp := m.data.Sum().DataPoints().AppendEmpty()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
dp.SetIntValue(val)
dp.Attributes().PutStr("string_attr", stringAttrAttributeValue)
dp.Attributes().PutInt("state", overriddenIntAttrAttributeValue)
dp.Attributes().PutStr("enum_attr", enumAttrAttributeValue)
dp.Attributes().PutEmptySlice("slice_attr").FromRaw(sliceAttrAttributeValue)
dp.Attributes().PutEmptyMap("map_attr").FromRaw(mapAttrAttributeValue)
for _, op := range options {
op.apply(dp)
}
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricDefaultMetric) updateCapacity() {
if m.data.Sum().DataPoints().Len() > m.capacity {
m.capacity = m.data.Sum().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricDefaultMetric) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Sum().DataPoints().Len() > 0 {
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricDefaultMetric(cfg MetricConfig) metricDefaultMetric {
m := metricDefaultMetric{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricDefaultMetricToBeRemoved struct {
data pmetric.Metric // data buffer for generated metric.
config MetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
}
// init fills default.metric.to_be_removed metric with initial data.
func (m *metricDefaultMetricToBeRemoved) init() {
m.data.SetName("default.metric.to_be_removed")
m.data.SetDescription("[DEPRECATED] Non-monotonic delta sum double metric enabled by default.")
m.data.SetUnit("s")
m.data.SetEmptySum()
m.data.Sum().SetIsMonotonic(false)
m.data.Sum().SetAggregationTemporality(pmetric.AggregationTemporalityDelta)
}
func (m *metricDefaultMetricToBeRemoved) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64) {
if !m.config.Enabled {
return
}
dp := m.data.Sum().DataPoints().AppendEmpty()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
dp.SetDoubleValue(val)
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricDefaultMetricToBeRemoved) updateCapacity() {
if m.data.Sum().DataPoints().Len() > m.capacity {
m.capacity = m.data.Sum().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricDefaultMetricToBeRemoved) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Sum().DataPoints().Len() > 0 {
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricDefaultMetricToBeRemoved(cfg MetricConfig) metricDefaultMetricToBeRemoved {
m := metricDefaultMetricToBeRemoved{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMetricInputType struct {
data pmetric.Metric // data buffer for generated metric.
config MetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
}
// init fills metric.input_type metric with initial data.
func (m *metricMetricInputType) init() {
m.data.SetName("metric.input_type")
m.data.SetDescription("Monotonic cumulative sum int metric with string input_type enabled by default.")
m.data.SetUnit("s")
m.data.SetEmptySum()
m.data.Sum().SetIsMonotonic(true)
m.data.Sum().SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
m.data.Sum().DataPoints().EnsureCapacity(m.capacity)
}
func (m *metricMetricInputType) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val int64, stringAttrAttributeValue string, overriddenIntAttrAttributeValue int64, enumAttrAttributeValue string, sliceAttrAttributeValue []any, mapAttrAttributeValue map[string]any) {
if !m.config.Enabled {
return
}
dp := m.data.Sum().DataPoints().AppendEmpty()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
dp.SetIntValue(val)
dp.Attributes().PutStr("string_attr", stringAttrAttributeValue)
dp.Attributes().PutInt("state", overriddenIntAttrAttributeValue)
dp.Attributes().PutStr("enum_attr", enumAttrAttributeValue)
dp.Attributes().PutEmptySlice("slice_attr").FromRaw(sliceAttrAttributeValue)
dp.Attributes().PutEmptyMap("map_attr").FromRaw(mapAttrAttributeValue)
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMetricInputType) updateCapacity() {
if m.data.Sum().DataPoints().Len() > m.capacity {
m.capacity = m.data.Sum().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMetricInputType) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Sum().DataPoints().Len() > 0 {
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMetricInputType(cfg MetricConfig) metricMetricInputType {
m := metricMetricInputType{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricOptionalMetric struct {
data pmetric.Metric // data buffer for generated metric.
config MetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
}
// init fills optional.metric metric with initial data.
func (m *metricOptionalMetric) init() {
m.data.SetName("optional.metric")
m.data.SetDescription("[DEPRECATED] Gauge double metric disabled by default.")
m.data.SetUnit("1")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
}
func (m *metricOptionalMetric) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, stringAttrAttributeValue string, booleanAttrAttributeValue bool, booleanAttr2AttributeValue bool, options ...MetricAttributeOption) {
if !m.config.Enabled {
return
}
dp := m.data.Gauge().DataPoints().AppendEmpty()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
dp.SetDoubleValue(val)
dp.Attributes().PutStr("string_attr", stringAttrAttributeValue)
dp.Attributes().PutBool("boolean_attr", booleanAttrAttributeValue)
dp.Attributes().PutBool("boolean_attr2", booleanAttr2AttributeValue)
for _, op := range options {
op.apply(dp)
}
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricOptionalMetric) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricOptionalMetric) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricOptionalMetric(cfg MetricConfig) metricOptionalMetric {
m := metricOptionalMetric{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricOptionalMetricEmptyUnit struct {
data pmetric.Metric // data buffer for generated metric.
config MetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
}
// init fills optional.metric.empty_unit metric with initial data.
func (m *metricOptionalMetricEmptyUnit) init() {
m.data.SetName("optional.metric.empty_unit")
m.data.SetDescription("[DEPRECATED] Gauge double metric disabled by default.")
m.data.SetUnit("")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
}
func (m *metricOptionalMetricEmptyUnit) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, stringAttrAttributeValue string, booleanAttrAttributeValue bool) {
if !m.config.Enabled {
return
}
dp := m.data.Gauge().DataPoints().AppendEmpty()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
dp.SetDoubleValue(val)
dp.Attributes().PutStr("string_attr", stringAttrAttributeValue)
dp.Attributes().PutBool("boolean_attr", booleanAttrAttributeValue)
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricOptionalMetricEmptyUnit) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricOptionalMetricEmptyUnit) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricOptionalMetricEmptyUnit(cfg MetricConfig) metricOptionalMetricEmptyUnit {
m := metricOptionalMetricEmptyUnit{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
// MetricsBuilder provides an interface for scrapers to report metrics while taking care of all the transformations
// required to produce metric representation defined in metadata and user config.
type MetricsBuilder struct {
config MetricsBuilderConfig // config of the metrics builder.
startTime pcommon.Timestamp // start time that will be applied to all recorded data points.
metricsCapacity int // maximum observed number of metrics per resource.
metricsBuffer pmetric.Metrics // accumulates metrics data before emitting.
buildInfo component.BuildInfo // contains version information.
resourceAttributeIncludeFilter map[string]filter.Filter
resourceAttributeExcludeFilter map[string]filter.Filter
metricDefaultMetric metricDefaultMetric
metricDefaultMetricToBeRemoved metricDefaultMetricToBeRemoved
metricMetricInputType metricMetricInputType
metricOptionalMetric metricOptionalMetric
metricOptionalMetricEmptyUnit metricOptionalMetricEmptyUnit
}
// MetricBuilderOption applies changes to default metrics builder.
type MetricBuilderOption interface {
apply(*MetricsBuilder)
}
type metricBuilderOptionFunc func(mb *MetricsBuilder)
func (mbof metricBuilderOptionFunc) apply(mb *MetricsBuilder) {
mbof(mb)
}
// WithStartTime sets startTime on the metrics builder.
func WithStartTime(startTime pcommon.Timestamp) MetricBuilderOption {
return metricBuilderOptionFunc(func(mb *MetricsBuilder) {
mb.startTime = startTime
})
}
func NewMetricsBuilder(mbc MetricsBuilderConfig, settings receiver.Settings, options ...MetricBuilderOption) *MetricsBuilder {
if !mbc.Metrics.DefaultMetric.enabledSetByUser {
settings.Logger.Warn("[WARNING] Please set `enabled` field explicitly for `default.metric`: This metric will be disabled by default soon.")
}
if mbc.Metrics.DefaultMetricToBeRemoved.Enabled {
settings.Logger.Warn("[WARNING] `default.metric.to_be_removed` should not be enabled: This metric is deprecated and will be removed soon.")
}
if mbc.Metrics.OptionalMetric.enabledSetByUser {
settings.Logger.Warn("[WARNING] `optional.metric` should not be configured: This metric is deprecated and will be removed soon.")
}
if mbc.Metrics.OptionalMetricEmptyUnit.enabledSetByUser {
settings.Logger.Warn("[WARNING] `optional.metric.empty_unit` should not be configured: This metric is deprecated and will be removed soon.")
}
if !mbc.ResourceAttributes.StringResourceAttrDisableWarning.enabledSetByUser {
settings.Logger.Warn("[WARNING] Please set `enabled` field explicitly for `string.resource.attr_disable_warning`: This resource_attribute will be disabled by default soon.")
}
if mbc.ResourceAttributes.StringResourceAttrRemoveWarning.enabledSetByUser || mbc.ResourceAttributes.StringResourceAttrRemoveWarning.MetricsInclude != nil || mbc.ResourceAttributes.StringResourceAttrRemoveWarning.MetricsExclude != nil {
settings.Logger.Warn("[WARNING] `string.resource.attr_remove_warning` should not be configured: This resource_attribute is deprecated and will be removed soon.")
}
if mbc.ResourceAttributes.StringResourceAttrToBeRemoved.Enabled {
settings.Logger.Warn("[WARNING] `string.resource.attr_to_be_removed` should not be enabled: This resource_attribute is deprecated and will be removed soon.")
}
mb := &MetricsBuilder{
config: mbc,
startTime: pcommon.NewTimestampFromTime(time.Now()),
metricsBuffer: pmetric.NewMetrics(),
buildInfo: settings.BuildInfo,
metricDefaultMetric: newMetricDefaultMetric(mbc.Metrics.DefaultMetric),
metricDefaultMetricToBeRemoved: newMetricDefaultMetricToBeRemoved(mbc.Metrics.DefaultMetricToBeRemoved),
metricMetricInputType: newMetricMetricInputType(mbc.Metrics.MetricInputType),
metricOptionalMetric: newMetricOptionalMetric(mbc.Metrics.OptionalMetric),
metricOptionalMetricEmptyUnit: newMetricOptionalMetricEmptyUnit(mbc.Metrics.OptionalMetricEmptyUnit),
resourceAttributeIncludeFilter: make(map[string]filter.Filter),
resourceAttributeExcludeFilter: make(map[string]filter.Filter),
}
if mbc.ResourceAttributes.MapResourceAttr.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["map.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.MapResourceAttr.MetricsInclude)
}
if mbc.ResourceAttributes.MapResourceAttr.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["map.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.MapResourceAttr.MetricsExclude)
}
if mbc.ResourceAttributes.OptionalResourceAttr.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["optional.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.OptionalResourceAttr.MetricsInclude)
}
if mbc.ResourceAttributes.OptionalResourceAttr.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["optional.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.OptionalResourceAttr.MetricsExclude)
}
if mbc.ResourceAttributes.SliceResourceAttr.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["slice.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.SliceResourceAttr.MetricsInclude)
}
if mbc.ResourceAttributes.SliceResourceAttr.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["slice.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.SliceResourceAttr.MetricsExclude)
}
if mbc.ResourceAttributes.StringEnumResourceAttr.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["string.enum.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.StringEnumResourceAttr.MetricsInclude)
}
if mbc.ResourceAttributes.StringEnumResourceAttr.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["string.enum.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.StringEnumResourceAttr.MetricsExclude)
}
if mbc.ResourceAttributes.StringResourceAttr.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["string.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.StringResourceAttr.MetricsInclude)
}
if mbc.ResourceAttributes.StringResourceAttr.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["string.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.StringResourceAttr.MetricsExclude)
}
if mbc.ResourceAttributes.StringResourceAttrDisableWarning.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["string.resource.attr_disable_warning"] = filter.CreateFilter(mbc.ResourceAttributes.StringResourceAttrDisableWarning.MetricsInclude)
}
if mbc.ResourceAttributes.StringResourceAttrDisableWarning.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["string.resource.attr_disable_warning"] = filter.CreateFilter(mbc.ResourceAttributes.StringResourceAttrDisableWarning.MetricsExclude)
}
if mbc.ResourceAttributes.StringResourceAttrRemoveWarning.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["string.resource.attr_remove_warning"] = filter.CreateFilter(mbc.ResourceAttributes.StringResourceAttrRemoveWarning.MetricsInclude)
}
if mbc.ResourceAttributes.StringResourceAttrRemoveWarning.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["string.resource.attr_remove_warning"] = filter.CreateFilter(mbc.ResourceAttributes.StringResourceAttrRemoveWarning.MetricsExclude)
}
if mbc.ResourceAttributes.StringResourceAttrToBeRemoved.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["string.resource.attr_to_be_removed"] = filter.CreateFilter(mbc.ResourceAttributes.StringResourceAttrToBeRemoved.MetricsInclude)
}
if mbc.ResourceAttributes.StringResourceAttrToBeRemoved.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["string.resource.attr_to_be_removed"] = filter.CreateFilter(mbc.ResourceAttributes.StringResourceAttrToBeRemoved.MetricsExclude)
}
for _, op := range options {
op.apply(mb)
}
return mb
}
// NewResourceBuilder returns a new resource builder that should be used to build a resource associated with for the emitted metrics.
func (mb *MetricsBuilder) NewResourceBuilder() *ResourceBuilder {
return NewResourceBuilder(mb.config.ResourceAttributes)
}
// updateCapacity updates max length of metrics and resource attributes that will be used for the slice capacity.
func (mb *MetricsBuilder) updateCapacity(rm pmetric.ResourceMetrics) {
if mb.metricsCapacity < rm.ScopeMetrics().At(0).Metrics().Len() {
mb.metricsCapacity = rm.ScopeMetrics().At(0).Metrics().Len()
}
}
// ResourceMetricsOption applies changes to provided resource metrics.
type ResourceMetricsOption interface {
apply(pmetric.ResourceMetrics)
}
type resourceMetricsOptionFunc func(pmetric.ResourceMetrics)
func (rmof resourceMetricsOptionFunc) apply(rm pmetric.ResourceMetrics) {
rmof(rm)
}
// WithResource sets the provided resource on the emitted ResourceMetrics.
// It's recommended to use ResourceBuilder to create the resource.
func WithResource(res pcommon.Resource) ResourceMetricsOption {
return resourceMetricsOptionFunc(func(rm pmetric.ResourceMetrics) {
res.CopyTo(rm.Resource())
})
}
// WithStartTimeOverride overrides start time for all the resource metrics data points.
// This option should be only used if different start time has to be set on metrics coming from different resources.
func WithStartTimeOverride(start pcommon.Timestamp) ResourceMetricsOption {
return resourceMetricsOptionFunc(func(rm pmetric.ResourceMetrics) {
var dps pmetric.NumberDataPointSlice
metrics := rm.ScopeMetrics().At(0).Metrics()
for i := 0; i < metrics.Len(); i++ {
switch metrics.At(i).Type() {
case pmetric.MetricTypeGauge:
dps = metrics.At(i).Gauge().DataPoints()
case pmetric.MetricTypeSum:
dps = metrics.At(i).Sum().DataPoints()
}
for j := 0; j < dps.Len(); j++ {
dps.At(j).SetStartTimestamp(start)
}
}
})
}
// EmitForResource saves all the generated metrics under a new resource and updates the internal state to be ready for
// recording another set of data points as part of another resource. This function can be helpful when one scraper
// needs to emit metrics from several resources. Otherwise calling this function is not required,
// just `Emit` function can be called instead.
// Resource attributes should be provided as ResourceMetricsOption arguments.
func (mb *MetricsBuilder) EmitForResource(options ...ResourceMetricsOption) {
rm := pmetric.NewResourceMetrics()
rm.SetSchemaUrl(conventions.SchemaURL)
ils := rm.ScopeMetrics().AppendEmpty()
ils.Scope().SetName(ScopeName)
ils.Scope().SetVersion(mb.buildInfo.Version)
ils.Metrics().EnsureCapacity(mb.metricsCapacity)
mb.metricDefaultMetric.emit(ils.Metrics())
mb.metricDefaultMetricToBeRemoved.emit(ils.Metrics())
mb.metricMetricInputType.emit(ils.Metrics())
mb.metricOptionalMetric.emit(ils.Metrics())
mb.metricOptionalMetricEmptyUnit.emit(ils.Metrics())
for _, op := range options {
op.apply(rm)
}
for attr, filter := range mb.resourceAttributeIncludeFilter {
if val, ok := rm.Resource().Attributes().Get(attr); ok && !filter.Matches(val.AsString()) {
return
}
}
for attr, filter := range mb.resourceAttributeExcludeFilter {
if val, ok := rm.Resource().Attributes().Get(attr); ok && filter.Matches(val.AsString()) {
return
}
}
if ils.Metrics().Len() > 0 {
mb.updateCapacity(rm)
rm.MoveTo(mb.metricsBuffer.ResourceMetrics().AppendEmpty())
}
}
// Emit returns all the metrics accumulated by the metrics builder and updates the internal state to be ready for
// recording another set of metrics. This function will be responsible for applying all the transformations required to
// produce metric representation defined in metadata and user config, e.g. delta or cumulative.
func (mb *MetricsBuilder) Emit(options ...ResourceMetricsOption) pmetric.Metrics {
mb.EmitForResource(options...)
metrics := mb.metricsBuffer
mb.metricsBuffer = pmetric.NewMetrics()
return metrics
}
// RecordDefaultMetricDataPoint adds a data point to default.metric metric.
func (mb *MetricsBuilder) RecordDefaultMetricDataPoint(ts pcommon.Timestamp, val int64, stringAttrAttributeValue string, overriddenIntAttrAttributeValue int64, enumAttrAttributeValue AttributeEnumAttr, sliceAttrAttributeValue []any, mapAttrAttributeValue map[string]any, options ...MetricAttributeOption) {
mb.metricDefaultMetric.recordDataPoint(mb.startTime, ts, val, stringAttrAttributeValue, overriddenIntAttrAttributeValue, enumAttrAttributeValue.String(), sliceAttrAttributeValue, mapAttrAttributeValue, options...)
}
// RecordDefaultMetricToBeRemovedDataPoint adds a data point to default.metric.to_be_removed metric.
func (mb *MetricsBuilder) RecordDefaultMetricToBeRemovedDataPoint(ts pcommon.Timestamp, val float64) {
mb.metricDefaultMetricToBeRemoved.recordDataPoint(mb.startTime, ts, val)
}
// RecordMetricInputTypeDataPoint adds a data point to metric.input_type metric.
func (mb *MetricsBuilder) RecordMetricInputTypeDataPoint(ts pcommon.Timestamp, inputVal string, stringAttrAttributeValue string, overriddenIntAttrAttributeValue int64, enumAttrAttributeValue AttributeEnumAttr, sliceAttrAttributeValue []any, mapAttrAttributeValue map[string]any) error {
val, err := strconv.ParseInt(inputVal, 10, 64)
if err != nil {
return fmt.Errorf("failed to parse int64 for MetricInputType, value was %s: %w", inputVal, err)
}
mb.metricMetricInputType.recordDataPoint(mb.startTime, ts, val, stringAttrAttributeValue, overriddenIntAttrAttributeValue, enumAttrAttributeValue.String(), sliceAttrAttributeValue, mapAttrAttributeValue)
return nil
}
// RecordOptionalMetricDataPoint adds a data point to optional.metric metric.
func (mb *MetricsBuilder) RecordOptionalMetricDataPoint(ts pcommon.Timestamp, val float64, stringAttrAttributeValue string, booleanAttrAttributeValue bool, booleanAttr2AttributeValue bool, options ...MetricAttributeOption) {
mb.metricOptionalMetric.recordDataPoint(mb.startTime, ts, val, stringAttrAttributeValue, booleanAttrAttributeValue, booleanAttr2AttributeValue, options...)
}
// RecordOptionalMetricEmptyUnitDataPoint adds a data point to optional.metric.empty_unit metric.
func (mb *MetricsBuilder) RecordOptionalMetricEmptyUnitDataPoint(ts pcommon.Timestamp, val float64, stringAttrAttributeValue string, booleanAttrAttributeValue bool) {
mb.metricOptionalMetricEmptyUnit.recordDataPoint(mb.startTime, ts, val, stringAttrAttributeValue, booleanAttrAttributeValue)
}
// Reset resets metrics builder to its initial state. It should be used when external metrics source is restarted,
// and metrics builder should update its startTime and reset it's internal state accordingly.
func (mb *MetricsBuilder) Reset(options ...MetricBuilderOption) {
mb.startTime = pcommon.NewTimestampFromTime(time.Now())
for _, op := range options {
op.apply(mb)
}
}
================================================
FILE: cmd/mdatagen/internal/samplefactoryreceiver/internal/metadata/generated_metrics_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"testing"
"github.com/stretchr/testify/assert"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/receiver/receivertest"
)
type testDataSet int
const (
testDataSetDefault testDataSet = iota
testDataSetAll
testDataSetNone
)
func TestMetricsBuilder(t *testing.T) {
tests := []struct {
name string
metricsSet testDataSet
resAttrsSet testDataSet
expectEmpty bool
}{
{
name: "default",
},
{
name: "all_set",
metricsSet: testDataSetAll,
resAttrsSet: testDataSetAll,
},
{
name: "none_set",
metricsSet: testDataSetNone,
resAttrsSet: testDataSetNone,
expectEmpty: true,
},
{
name: "filter_set_include",
resAttrsSet: testDataSetAll,
},
{
name: "filter_set_exclude",
resAttrsSet: testDataSetAll,
expectEmpty: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
start := pcommon.Timestamp(1_000_000_000)
ts := pcommon.Timestamp(1_000_001_000)
observedZapCore, observedLogs := observer.New(zap.WarnLevel)
settings := receivertest.NewNopSettings(receivertest.NopType)
settings.Logger = zap.New(observedZapCore)
mb := NewMetricsBuilder(loadMetricsBuilderConfig(t, tt.name), settings, WithStartTime(start))
expectedWarnings := 0
if tt.metricsSet == testDataSetDefault {
assert.Equal(t, "[WARNING] Please set `enabled` field explicitly for `default.metric`: This metric will be disabled by default soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
if tt.metricsSet == testDataSetDefault || tt.metricsSet == testDataSetAll {
assert.Equal(t, "[WARNING] `default.metric.to_be_removed` should not be enabled: This metric is deprecated and will be removed soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
if tt.metricsSet == testDataSetAll || tt.metricsSet == testDataSetNone {
assert.Equal(t, "[WARNING] `optional.metric` should not be configured: This metric is deprecated and will be removed soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
if tt.metricsSet == testDataSetAll || tt.metricsSet == testDataSetNone {
assert.Equal(t, "[WARNING] `optional.metric.empty_unit` should not be configured: This metric is deprecated and will be removed soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
if tt.resAttrsSet == testDataSetDefault {
assert.Equal(t, "[WARNING] Please set `enabled` field explicitly for `string.resource.attr_disable_warning`: This resource_attribute will be disabled by default soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
if tt.resAttrsSet == testDataSetAll || tt.resAttrsSet == testDataSetNone {
assert.Equal(t, "[WARNING] `string.resource.attr_remove_warning` should not be configured: This resource_attribute is deprecated and will be removed soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
if tt.resAttrsSet == testDataSetDefault || tt.resAttrsSet == testDataSetAll {
assert.Equal(t, "[WARNING] `string.resource.attr_to_be_removed` should not be enabled: This resource_attribute is deprecated and will be removed soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
assert.Equal(t, expectedWarnings, observedLogs.Len())
defaultMetricsCount := 0
allMetricsCount := 0
defaultMetricsCount++
allMetricsCount++
mb.RecordDefaultMetricDataPoint(ts, 1, "string_attr-val", 19, AttributeEnumAttrRed, []any{"slice_attr-item1", "slice_attr-item2"}, map[string]any{"key1": "map_attr-val1", "key2": "map_attr-val2"}, WithOptionalIntAttrMetricAttribute(17), WithOptionalStringAttrMetricAttribute("optional_string_attr-val"))
defaultMetricsCount++
allMetricsCount++
mb.RecordDefaultMetricToBeRemovedDataPoint(ts, 1)
defaultMetricsCount++
allMetricsCount++
mb.RecordMetricInputTypeDataPoint(ts, "1", "string_attr-val", 19, AttributeEnumAttrRed, []any{"slice_attr-item1", "slice_attr-item2"}, map[string]any{"key1": "map_attr-val1", "key2": "map_attr-val2"})
allMetricsCount++
mb.RecordOptionalMetricDataPoint(ts, 1, "string_attr-val", true, false, WithOptionalStringAttrMetricAttribute("optional_string_attr-val"))
allMetricsCount++
mb.RecordOptionalMetricEmptyUnitDataPoint(ts, 1, "string_attr-val", true)
rb := mb.NewResourceBuilder()
rb.SetMapResourceAttr(map[string]any{"key1": "map.resource.attr-val1", "key2": "map.resource.attr-val2"})
rb.SetOptionalResourceAttr("optional.resource.attr-val")
rb.SetSliceResourceAttr([]any{"slice.resource.attr-item1", "slice.resource.attr-item2"})
rb.SetStringEnumResourceAttrOne()
rb.SetStringResourceAttr("string.resource.attr-val")
rb.SetStringResourceAttrDisableWarning("string.resource.attr_disable_warning-val")
rb.SetStringResourceAttrRemoveWarning("string.resource.attr_remove_warning-val")
rb.SetStringResourceAttrToBeRemoved("string.resource.attr_to_be_removed-val")
res := rb.Emit()
metrics := mb.Emit(WithResource(res))
if tt.expectEmpty {
assert.Equal(t, 0, metrics.ResourceMetrics().Len())
return
}
assert.Equal(t, 1, metrics.ResourceMetrics().Len())
rm := metrics.ResourceMetrics().At(0)
assert.Equal(t, res, rm.Resource())
assert.Equal(t, 1, rm.ScopeMetrics().Len())
ms := rm.ScopeMetrics().At(0).Metrics()
if tt.metricsSet == testDataSetDefault {
assert.Equal(t, defaultMetricsCount, ms.Len())
}
if tt.metricsSet == testDataSetAll {
assert.Equal(t, allMetricsCount, ms.Len())
}
validatedMetrics := make(map[string]bool)
for i := 0; i < ms.Len(); i++ {
switch ms.At(i).Name() {
case "default.metric":
assert.False(t, validatedMetrics["default.metric"], "Found a duplicate in the metrics slice: default.metric")
validatedMetrics["default.metric"] = true
assert.Equal(t, pmetric.MetricTypeSum, ms.At(i).Type())
assert.Equal(t, 1, ms.At(i).Sum().DataPoints().Len())
assert.Equal(t, "Monotonic cumulative sum int metric enabled by default.", ms.At(i).Description())
assert.Equal(t, "s", ms.At(i).Unit())
assert.True(t, ms.At(i).Sum().IsMonotonic())
assert.Equal(t, pmetric.AggregationTemporalityCumulative, ms.At(i).Sum().AggregationTemporality())
dp := ms.At(i).Sum().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeInt, dp.ValueType())
assert.Equal(t, int64(1), dp.IntValue())
attrVal, ok := dp.Attributes().Get("string_attr")
assert.True(t, ok)
assert.Equal(t, "string_attr-val", attrVal.Str())
attrVal, ok = dp.Attributes().Get("state")
assert.True(t, ok)
assert.EqualValues(t, 19, attrVal.Int())
attrVal, ok = dp.Attributes().Get("enum_attr")
assert.True(t, ok)
assert.Equal(t, "red", attrVal.Str())
attrVal, ok = dp.Attributes().Get("slice_attr")
assert.True(t, ok)
assert.Equal(t, []any{"slice_attr-item1", "slice_attr-item2"}, attrVal.Slice().AsRaw())
attrVal, ok = dp.Attributes().Get("map_attr")
assert.True(t, ok)
assert.Equal(t, map[string]any{"key1": "map_attr-val1", "key2": "map_attr-val2"}, attrVal.Map().AsRaw())
attrVal, ok = dp.Attributes().Get("optional_int_attr")
assert.True(t, ok)
assert.EqualValues(t, 17, attrVal.Int())
attrVal, ok = dp.Attributes().Get("optional_string_attr")
assert.True(t, ok)
assert.Equal(t, "optional_string_attr-val", attrVal.Str())
case "default.metric.to_be_removed":
assert.False(t, validatedMetrics["default.metric.to_be_removed"], "Found a duplicate in the metrics slice: default.metric.to_be_removed")
validatedMetrics["default.metric.to_be_removed"] = true
assert.Equal(t, pmetric.MetricTypeSum, ms.At(i).Type())
assert.Equal(t, 1, ms.At(i).Sum().DataPoints().Len())
assert.Equal(t, "[DEPRECATED] Non-monotonic delta sum double metric enabled by default.", ms.At(i).Description())
assert.Equal(t, "s", ms.At(i).Unit())
assert.False(t, ms.At(i).Sum().IsMonotonic())
assert.Equal(t, pmetric.AggregationTemporalityDelta, ms.At(i).Sum().AggregationTemporality())
dp := ms.At(i).Sum().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeDouble, dp.ValueType())
assert.InDelta(t, float64(1), dp.DoubleValue(), 0.01)
case "metric.input_type":
assert.False(t, validatedMetrics["metric.input_type"], "Found a duplicate in the metrics slice: metric.input_type")
validatedMetrics["metric.input_type"] = true
assert.Equal(t, pmetric.MetricTypeSum, ms.At(i).Type())
assert.Equal(t, 1, ms.At(i).Sum().DataPoints().Len())
assert.Equal(t, "Monotonic cumulative sum int metric with string input_type enabled by default.", ms.At(i).Description())
assert.Equal(t, "s", ms.At(i).Unit())
assert.True(t, ms.At(i).Sum().IsMonotonic())
assert.Equal(t, pmetric.AggregationTemporalityCumulative, ms.At(i).Sum().AggregationTemporality())
dp := ms.At(i).Sum().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeInt, dp.ValueType())
assert.Equal(t, int64(1), dp.IntValue())
attrVal, ok := dp.Attributes().Get("string_attr")
assert.True(t, ok)
assert.Equal(t, "string_attr-val", attrVal.Str())
attrVal, ok = dp.Attributes().Get("state")
assert.True(t, ok)
assert.EqualValues(t, 19, attrVal.Int())
attrVal, ok = dp.Attributes().Get("enum_attr")
assert.True(t, ok)
assert.Equal(t, "red", attrVal.Str())
attrVal, ok = dp.Attributes().Get("slice_attr")
assert.True(t, ok)
assert.Equal(t, []any{"slice_attr-item1", "slice_attr-item2"}, attrVal.Slice().AsRaw())
attrVal, ok = dp.Attributes().Get("map_attr")
assert.True(t, ok)
assert.Equal(t, map[string]any{"key1": "map_attr-val1", "key2": "map_attr-val2"}, attrVal.Map().AsRaw())
case "optional.metric":
assert.False(t, validatedMetrics["optional.metric"], "Found a duplicate in the metrics slice: optional.metric")
validatedMetrics["optional.metric"] = true
assert.Equal(t, pmetric.MetricTypeGauge, ms.At(i).Type())
assert.Equal(t, 1, ms.At(i).Gauge().DataPoints().Len())
assert.Equal(t, "[DEPRECATED] Gauge double metric disabled by default.", ms.At(i).Description())
assert.Equal(t, "1", ms.At(i).Unit())
dp := ms.At(i).Gauge().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeDouble, dp.ValueType())
assert.InDelta(t, float64(1), dp.DoubleValue(), 0.01)
attrVal, ok := dp.Attributes().Get("string_attr")
assert.True(t, ok)
assert.Equal(t, "string_attr-val", attrVal.Str())
attrVal, ok = dp.Attributes().Get("boolean_attr")
assert.True(t, ok)
assert.True(t, attrVal.Bool())
attrVal, ok = dp.Attributes().Get("boolean_attr2")
assert.True(t, ok)
assert.False(t, attrVal.Bool())
attrVal, ok = dp.Attributes().Get("optional_string_attr")
assert.True(t, ok)
assert.Equal(t, "optional_string_attr-val", attrVal.Str())
case "optional.metric.empty_unit":
assert.False(t, validatedMetrics["optional.metric.empty_unit"], "Found a duplicate in the metrics slice: optional.metric.empty_unit")
validatedMetrics["optional.metric.empty_unit"] = true
assert.Equal(t, pmetric.MetricTypeGauge, ms.At(i).Type())
assert.Equal(t, 1, ms.At(i).Gauge().DataPoints().Len())
assert.Equal(t, "[DEPRECATED] Gauge double metric disabled by default.", ms.At(i).Description())
assert.Empty(t, ms.At(i).Unit())
dp := ms.At(i).Gauge().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeDouble, dp.ValueType())
assert.InDelta(t, float64(1), dp.DoubleValue(), 0.01)
attrVal, ok := dp.Attributes().Get("string_attr")
assert.True(t, ok)
assert.Equal(t, "string_attr-val", attrVal.Str())
attrVal, ok = dp.Attributes().Get("boolean_attr")
assert.True(t, ok)
assert.True(t, attrVal.Bool())
}
}
})
}
}
================================================
FILE: cmd/mdatagen/internal/samplefactoryreceiver/internal/metadata/generated_resource.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/pdata/pcommon"
)
// ResourceBuilder is a helper struct to build resources predefined in metadata.yaml.
// The ResourceBuilder is not thread-safe and must not to be used in multiple goroutines.
type ResourceBuilder struct {
config ResourceAttributesConfig
res pcommon.Resource
}
// NewResourceBuilder creates a new ResourceBuilder. This method should be called on the start of the application.
func NewResourceBuilder(rac ResourceAttributesConfig) *ResourceBuilder {
return &ResourceBuilder{
config: rac,
res: pcommon.NewResource(),
}
}
// SetMapResourceAttr sets provided value as "map.resource.attr" attribute.
func (rb *ResourceBuilder) SetMapResourceAttr(val map[string]any) {
if rb.config.MapResourceAttr.Enabled {
rb.res.Attributes().PutEmptyMap("map.resource.attr").FromRaw(val)
}
}
// SetOptionalResourceAttr sets provided value as "optional.resource.attr" attribute.
func (rb *ResourceBuilder) SetOptionalResourceAttr(val string) {
if rb.config.OptionalResourceAttr.Enabled {
rb.res.Attributes().PutStr("optional.resource.attr", val)
}
}
// SetSliceResourceAttr sets provided value as "slice.resource.attr" attribute.
func (rb *ResourceBuilder) SetSliceResourceAttr(val []any) {
if rb.config.SliceResourceAttr.Enabled {
rb.res.Attributes().PutEmptySlice("slice.resource.attr").FromRaw(val)
}
}
// SetStringEnumResourceAttrOne sets "string.enum.resource.attr=one" attribute.
func (rb *ResourceBuilder) SetStringEnumResourceAttrOne() {
if rb.config.StringEnumResourceAttr.Enabled {
rb.res.Attributes().PutStr("string.enum.resource.attr", "one")
}
}
// SetStringEnumResourceAttrTwo sets "string.enum.resource.attr=two" attribute.
func (rb *ResourceBuilder) SetStringEnumResourceAttrTwo() {
if rb.config.StringEnumResourceAttr.Enabled {
rb.res.Attributes().PutStr("string.enum.resource.attr", "two")
}
}
// SetStringResourceAttr sets provided value as "string.resource.attr" attribute.
func (rb *ResourceBuilder) SetStringResourceAttr(val string) {
if rb.config.StringResourceAttr.Enabled {
rb.res.Attributes().PutStr("string.resource.attr", val)
}
}
// SetStringResourceAttrDisableWarning sets provided value as "string.resource.attr_disable_warning" attribute.
func (rb *ResourceBuilder) SetStringResourceAttrDisableWarning(val string) {
if rb.config.StringResourceAttrDisableWarning.Enabled {
rb.res.Attributes().PutStr("string.resource.attr_disable_warning", val)
}
}
// SetStringResourceAttrRemoveWarning sets provided value as "string.resource.attr_remove_warning" attribute.
func (rb *ResourceBuilder) SetStringResourceAttrRemoveWarning(val string) {
if rb.config.StringResourceAttrRemoveWarning.Enabled {
rb.res.Attributes().PutStr("string.resource.attr_remove_warning", val)
}
}
// SetStringResourceAttrToBeRemoved sets provided value as "string.resource.attr_to_be_removed" attribute.
func (rb *ResourceBuilder) SetStringResourceAttrToBeRemoved(val string) {
if rb.config.StringResourceAttrToBeRemoved.Enabled {
rb.res.Attributes().PutStr("string.resource.attr_to_be_removed", val)
}
}
// Emit returns the built resource and resets the internal builder state.
func (rb *ResourceBuilder) Emit() pcommon.Resource {
r := rb.res
rb.res = pcommon.NewResource()
return r
}
================================================
FILE: cmd/mdatagen/internal/samplefactoryreceiver/internal/metadata/generated_resource_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestResourceBuilder(t *testing.T) {
for _, tt := range []string{"default", "all_set", "none_set"} {
t.Run(tt, func(t *testing.T) {
cfg := loadResourceAttributesConfig(t, tt)
rb := NewResourceBuilder(cfg)
rb.SetMapResourceAttr(map[string]any{"key1": "map.resource.attr-val1", "key2": "map.resource.attr-val2"})
rb.SetOptionalResourceAttr("optional.resource.attr-val")
rb.SetSliceResourceAttr([]any{"slice.resource.attr-item1", "slice.resource.attr-item2"})
rb.SetStringEnumResourceAttrOne()
rb.SetStringResourceAttr("string.resource.attr-val")
rb.SetStringResourceAttrDisableWarning("string.resource.attr_disable_warning-val")
rb.SetStringResourceAttrRemoveWarning("string.resource.attr_remove_warning-val")
rb.SetStringResourceAttrToBeRemoved("string.resource.attr_to_be_removed-val")
res := rb.Emit()
assert.Equal(t, 0, rb.Emit().Attributes().Len()) // Second call should return empty Resource
switch tt {
case "default":
assert.Equal(t, 6, res.Attributes().Len())
case "all_set":
assert.Equal(t, 8, res.Attributes().Len())
case "none_set":
assert.Equal(t, 0, res.Attributes().Len())
return
default:
assert.Failf(t, "unexpected test case: %s", tt)
}
val, ok := res.Attributes().Get("map.resource.attr")
assert.True(t, ok)
if ok {
assert.Equal(t, map[string]any{"key1": "map.resource.attr-val1", "key2": "map.resource.attr-val2"}, val.Map().AsRaw())
}
val, ok = res.Attributes().Get("optional.resource.attr")
assert.Equal(t, tt == "all_set", ok)
if ok {
assert.Equal(t, "optional.resource.attr-val", val.Str())
}
val, ok = res.Attributes().Get("slice.resource.attr")
assert.True(t, ok)
if ok {
assert.Equal(t, []any{"slice.resource.attr-item1", "slice.resource.attr-item2"}, val.Slice().AsRaw())
}
val, ok = res.Attributes().Get("string.enum.resource.attr")
assert.True(t, ok)
if ok {
assert.Equal(t, "one", val.Str())
}
val, ok = res.Attributes().Get("string.resource.attr")
assert.True(t, ok)
if ok {
assert.Equal(t, "string.resource.attr-val", val.Str())
}
val, ok = res.Attributes().Get("string.resource.attr_disable_warning")
assert.True(t, ok)
if ok {
assert.Equal(t, "string.resource.attr_disable_warning-val", val.Str())
}
val, ok = res.Attributes().Get("string.resource.attr_remove_warning")
assert.Equal(t, tt == "all_set", ok)
if ok {
assert.Equal(t, "string.resource.attr_remove_warning-val", val.Str())
}
val, ok = res.Attributes().Get("string.resource.attr_to_be_removed")
assert.True(t, ok)
if ok {
assert.Equal(t, "string.resource.attr_to_be_removed-val", val.Str())
}
})
}
}
================================================
FILE: cmd/mdatagen/internal/samplefactoryreceiver/internal/metadata/generated_status.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/component"
)
var (
Type = component.MustNewType("sample")
ScopeName = "go.opentelemetry.io/collector/internal/receiver/samplefactoryreceiver"
)
const (
ProfilesStability = component.StabilityLevelDeprecated
LogsStability = component.StabilityLevelDevelopment
TracesStability = component.StabilityLevelBeta
MetricsStability = component.StabilityLevelStable
)
================================================
FILE: cmd/mdatagen/internal/samplefactoryreceiver/internal/metadata/generated_telemetry.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"context"
"errors"
"sync"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/metric/embedded"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/collector/component"
)
func Meter(settings component.TelemetrySettings) metric.Meter {
return settings.MeterProvider.Meter("go.opentelemetry.io/collector/internal/receiver/samplefactoryreceiver")
}
func Tracer(settings component.TelemetrySettings) trace.Tracer {
return settings.TracerProvider.Tracer("go.opentelemetry.io/collector/internal/receiver/samplefactoryreceiver")
}
// TelemetryBuilder provides an interface for components to report telemetry
// as defined in metadata and user config.
type TelemetryBuilder struct {
meter metric.Meter
mu sync.Mutex
registrations []metric.Registration
BatchSizeTriggerSend metric.Int64Counter
ProcessRuntimeTotalAllocBytes metric.Int64ObservableCounter
QueueCapacity metric.Int64Gauge
QueueLength metric.Int64ObservableGauge
RequestDuration metric.Float64Histogram
}
// TelemetryBuilderOption applies changes to default builder.
type TelemetryBuilderOption interface {
apply(*TelemetryBuilder)
}
type telemetryBuilderOptionFunc func(mb *TelemetryBuilder)
func (tbof telemetryBuilderOptionFunc) apply(mb *TelemetryBuilder) {
tbof(mb)
}
// RegisterProcessRuntimeTotalAllocBytesCallback sets callback for observable ProcessRuntimeTotalAllocBytes metric.
func (builder *TelemetryBuilder) RegisterProcessRuntimeTotalAllocBytesCallback(cb metric.Int64Callback) error {
reg, err := builder.meter.RegisterCallback(func(ctx context.Context, o metric.Observer) error {
cb(ctx, &observerInt64{inst: builder.ProcessRuntimeTotalAllocBytes, obs: o})
return nil
}, builder.ProcessRuntimeTotalAllocBytes)
if err != nil {
return err
}
builder.mu.Lock()
defer builder.mu.Unlock()
builder.registrations = append(builder.registrations, reg)
return nil
}
// RegisterQueueLengthCallback sets callback for observable QueueLength metric.
func (builder *TelemetryBuilder) RegisterQueueLengthCallback(cb metric.Int64Callback) error {
reg, err := builder.meter.RegisterCallback(func(ctx context.Context, o metric.Observer) error {
cb(ctx, &observerInt64{inst: builder.QueueLength, obs: o})
return nil
}, builder.QueueLength)
if err != nil {
return err
}
builder.mu.Lock()
defer builder.mu.Unlock()
builder.registrations = append(builder.registrations, reg)
return nil
}
type observerInt64 struct {
embedded.Int64Observer
inst metric.Int64Observable
obs metric.Observer
}
func (oi *observerInt64) Observe(value int64, opts ...metric.ObserveOption) {
oi.obs.ObserveInt64(oi.inst, value, opts...)
}
// Shutdown unregister all registered callbacks for async instruments.
func (builder *TelemetryBuilder) Shutdown() {
builder.mu.Lock()
defer builder.mu.Unlock()
for _, reg := range builder.registrations {
reg.Unregister()
}
}
// NewTelemetryBuilder provides a struct with methods to update all internal telemetry
// for a component
func NewTelemetryBuilder(settings component.TelemetrySettings, options ...TelemetryBuilderOption) (*TelemetryBuilder, error) {
builder := TelemetryBuilder{}
for _, op := range options {
op.apply(&builder)
}
builder.meter = Meter(settings)
var err, errs error
builder.BatchSizeTriggerSend, err = builder.meter.Int64Counter(
"otelcol_batch_size_trigger_send",
metric.WithDescription("Number of times the batch was sent due to a size trigger [deprecated since v0.110.0]"),
metric.WithUnit("{times}"),
)
errs = errors.Join(errs, err)
builder.ProcessRuntimeTotalAllocBytes, err = builder.meter.Int64ObservableCounter(
"otelcol_process_runtime_total_alloc_bytes",
metric.WithDescription("Cumulative bytes allocated for heap objects (see 'go doc runtime.MemStats.TotalAlloc')"),
metric.WithUnit("By"),
)
errs = errors.Join(errs, err)
builder.QueueCapacity, err = builder.meter.Int64Gauge(
"otelcol_queue_capacity",
metric.WithDescription("Queue capacity - sync gauge example."),
metric.WithUnit("{items}"),
)
errs = errors.Join(errs, err)
builder.QueueLength, err = builder.meter.Int64ObservableGauge(
"otelcol_queue_length",
metric.WithDescription("This metric is optional and therefore not initialized in NewTelemetryBuilder. [Alpha]"),
metric.WithUnit("{items}"),
)
errs = errors.Join(errs, err)
builder.RequestDuration, err = builder.meter.Float64Histogram(
"otelcol_request_duration",
metric.WithDescription("Duration of request [Alpha]"),
metric.WithUnit("s"),
metric.WithExplicitBucketBoundaries([]float64{1, 10, 100}...),
)
errs = errors.Join(errs, err)
return &builder, errs
}
================================================
FILE: cmd/mdatagen/internal/samplefactoryreceiver/internal/metadata/generated_telemetry_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/metric"
embeddedmetric "go.opentelemetry.io/otel/metric/embedded"
noopmetric "go.opentelemetry.io/otel/metric/noop"
"go.opentelemetry.io/otel/trace"
embeddedtrace "go.opentelemetry.io/otel/trace/embedded"
nooptrace "go.opentelemetry.io/otel/trace/noop"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
)
type mockMeter struct {
noopmetric.Meter
name string
}
type mockMeterProvider struct {
embeddedmetric.MeterProvider
}
func (m mockMeterProvider) Meter(name string, opts ...metric.MeterOption) metric.Meter {
return mockMeter{name: name}
}
type mockTracer struct {
nooptrace.Tracer
name string
}
type mockTracerProvider struct {
embeddedtrace.TracerProvider
}
func (m mockTracerProvider) Tracer(name string, opts ...trace.TracerOption) trace.Tracer {
return mockTracer{name: name}
}
func TestProviders(t *testing.T) {
set := component.TelemetrySettings{
MeterProvider: mockMeterProvider{},
TracerProvider: mockTracerProvider{},
}
meter := Meter(set)
if m, ok := meter.(mockMeter); ok {
require.Equal(t, "go.opentelemetry.io/collector/internal/receiver/samplefactoryreceiver", m.name)
} else {
require.Fail(t, "returned Meter not mockMeter")
}
tracer := Tracer(set)
if m, ok := tracer.(mockTracer); ok {
require.Equal(t, "go.opentelemetry.io/collector/internal/receiver/samplefactoryreceiver", m.name)
} else {
require.Fail(t, "returned Meter not mockTracer")
}
}
func TestNewTelemetryBuilder(t *testing.T) {
set := componenttest.NewNopTelemetrySettings()
applied := false
_, err := NewTelemetryBuilder(set, telemetryBuilderOptionFunc(func(b *TelemetryBuilder) {
applied = true
}))
require.NoError(t, err)
require.True(t, applied)
}
================================================
FILE: cmd/mdatagen/internal/samplefactoryreceiver/internal/metadata/testdata/config.yaml
================================================
default:
all_set:
metrics:
default.metric:
enabled: true
default.metric.to_be_removed:
enabled: true
metric.input_type:
enabled: true
optional.metric:
enabled: true
optional.metric.empty_unit:
enabled: true
events:
default.event:
enabled: true
default.event.to_be_removed:
enabled: true
default.event.to_be_renamed:
enabled: true
resource_attributes:
map.resource.attr:
enabled: true
optional.resource.attr:
enabled: true
slice.resource.attr:
enabled: true
string.enum.resource.attr:
enabled: true
string.resource.attr:
enabled: true
string.resource.attr_disable_warning:
enabled: true
string.resource.attr_remove_warning:
enabled: true
string.resource.attr_to_be_removed:
enabled: true
none_set:
metrics:
default.metric:
enabled: false
default.metric.to_be_removed:
enabled: false
metric.input_type:
enabled: false
optional.metric:
enabled: false
optional.metric.empty_unit:
enabled: false
events:
default.event:
enabled: false
default.event.to_be_removed:
enabled: false
default.event.to_be_renamed:
enabled: false
resource_attributes:
map.resource.attr:
enabled: false
optional.resource.attr:
enabled: false
slice.resource.attr:
enabled: false
string.enum.resource.attr:
enabled: false
string.resource.attr:
enabled: false
string.resource.attr_disable_warning:
enabled: false
string.resource.attr_remove_warning:
enabled: false
string.resource.attr_to_be_removed:
enabled: false
filter_set_include:
resource_attributes:
map.resource.attr:
enabled: true
metrics_include:
- regexp: ".*"
events_include:
- regexp: ".*"
optional.resource.attr:
enabled: true
metrics_include:
- regexp: ".*"
events_include:
- regexp: ".*"
slice.resource.attr:
enabled: true
metrics_include:
- regexp: ".*"
events_include:
- regexp: ".*"
string.enum.resource.attr:
enabled: true
metrics_include:
- regexp: ".*"
events_include:
- regexp: ".*"
string.resource.attr:
enabled: true
metrics_include:
- regexp: ".*"
events_include:
- regexp: ".*"
string.resource.attr_disable_warning:
enabled: true
metrics_include:
- regexp: ".*"
events_include:
- regexp: ".*"
string.resource.attr_remove_warning:
enabled: true
metrics_include:
- regexp: ".*"
events_include:
- regexp: ".*"
string.resource.attr_to_be_removed:
enabled: true
metrics_include:
- regexp: ".*"
events_include:
- regexp: ".*"
filter_set_exclude:
resource_attributes:
map.resource.attr:
enabled: true
metrics_exclude:
- regexp: ".*"
events_exclude:
- regexp: ".*"
optional.resource.attr:
enabled: true
metrics_exclude:
- strict: "optional.resource.attr-val"
events_exclude:
- strict: "optional.resource.attr-val"
slice.resource.attr:
enabled: true
metrics_exclude:
- regexp: ".*"
events_exclude:
- regexp: ".*"
string.enum.resource.attr:
enabled: true
metrics_exclude:
- strict: "one"
events_exclude:
- strict: "one"
string.resource.attr:
enabled: true
metrics_exclude:
- strict: "string.resource.attr-val"
events_exclude:
- strict: "string.resource.attr-val"
string.resource.attr_disable_warning:
enabled: true
metrics_exclude:
- strict: "string.resource.attr_disable_warning-val"
events_exclude:
- strict: "string.resource.attr_disable_warning-val"
string.resource.attr_remove_warning:
enabled: true
metrics_exclude:
- strict: "string.resource.attr_remove_warning-val"
events_exclude:
- strict: "string.resource.attr_remove_warning-val"
string.resource.attr_to_be_removed:
enabled: true
metrics_exclude:
- strict: "string.resource.attr_to_be_removed-val"
events_exclude:
- strict: "string.resource.attr_to_be_removed-val"
================================================
FILE: cmd/mdatagen/internal/samplefactoryreceiver/metadata.yaml
================================================
# Sample metadata file with all available configurations for a receiver.
type: sample
display_name: Sample Factory Receiver
description: This receiver is used for testing purposes to check the output of mdatagen.
scope_name: go.opentelemetry.io/collector/internal/receiver/samplefactoryreceiver
github_project: open-telemetry/opentelemetry-collector
sem_conv_version: 1.9.0
status:
disable_codecov_badge: true
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
deprecated: [profiles]
deprecation:
profiles:
migration: "no migration needed"
date: "2025-02-05"
distributions: []
unsupported_platforms: [freebsd, illumos]
codeowners:
active: [dmitryax]
warnings:
- Any additional information that should be brought to the consumer's attention
================================================
FILE: cmd/mdatagen/internal/sampleprocessor/README.md
================================================
# Sample Processor
This processor is used for testing purposes to check the output of mdatagen.
| Status | |
| ------------- |-----------|
| Stability | [development]: logs, profiles |
| | [beta]: traces |
| | [stable]: metrics |
| Unsupported Platforms | freebsd, illumos |
| Distributions | [] |
| Warnings | [Any additional information that should be brought to the consumer's attention](#warnings) |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Aprocessor%2Fsample) [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aclosed+is%3Aissue+label%3Aprocessor%2Fsample) |
| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | |
[development]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#development
[beta]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#beta
[stable]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#stable
## Warnings
This is where warnings are described.
================================================
FILE: cmd/mdatagen/internal/sampleprocessor/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Generate a test metrics builder from a sample metrics set covering all configuration options.
//go:generate mdatagen metadata.yaml
package sampleprocessor // import "go.opentelemetry.io/collector/cmd/mdatagen/internal/sampleprocessor"
================================================
FILE: cmd/mdatagen/internal/sampleprocessor/documentation.md
================================================
[comment]: <> (Code generated by mdatagen. DO NOT EDIT.)
# sample
## Resource Attributes
| Name | Description | Values | Enabled |
| ---- | ----------- | ------ | ------- |
| map.resource.attr | Resource attribute with a map value. | Any Map | true |
| optional.resource.attr | Explicitly disabled ResourceAttribute. | Any Str | false |
| slice.resource.attr | Resource attribute with a slice value. | Any Slice | true |
| string.enum.resource.attr | Resource attribute with a known set of string values. | Str: ``one``, ``two`` | true |
| string.resource.attr | Resource attribute with any string value. | Any Str | true |
| string.resource.attr_disable_warning | Resource attribute with any string value. | Any Str | true |
| string.resource.attr_remove_warning | Resource attribute with any string value. | Any Str | false |
| string.resource.attr_to_be_removed | Resource attribute with any string value. | Any Str | true |
================================================
FILE: cmd/mdatagen/internal/sampleprocessor/factory.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sampleprocessor // import "go.opentelemetry.io/collector/cmd/mdatagen/internal/sampleprocessor"
import (
"context"
"go.opentelemetry.io/collector/cmd/mdatagen/internal/sampleprocessor/internal/metadata"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/xprocessor"
)
// NewFactory returns a receiver.Factory for sample receiver.
func NewFactory() processor.Factory {
return xprocessor.NewFactory(
metadata.Type,
func() component.Config { return &struct{}{} },
xprocessor.WithTraces(createTracesProcessor, metadata.TracesStability),
xprocessor.WithMetrics(createMetricsProcessor, metadata.MetricsStability),
xprocessor.WithLogs(createLogsProcessor, metadata.LogsStability),
xprocessor.WithProfiles(createProfilesProcessor, metadata.ProfilesStability),
)
}
func createTracesProcessor(context.Context, processor.Settings, component.Config, consumer.Traces) (processor.Traces, error) {
return nopInstance, nil
}
func createMetricsProcessor(context.Context, processor.Settings, component.Config, consumer.Metrics) (processor.Metrics, error) {
return nopInstance, nil
}
func createLogsProcessor(context.Context, processor.Settings, component.Config, consumer.Logs) (processor.Logs, error) {
return nopInstance, nil
}
func createProfilesProcessor(context.Context, processor.Settings, component.Config, xconsumer.Profiles) (xprocessor.Profiles, error) {
return nopInstance, nil
}
var nopInstance = &nopProcessor{}
type nopProcessor struct {
component.StartFunc
component.ShutdownFunc
}
func (n nopProcessor) ConsumeTraces(context.Context, ptrace.Traces) error {
return nil
}
func (n nopProcessor) ConsumeLogs(context.Context, plog.Logs) error {
return nil
}
func (n nopProcessor) Capabilities() consumer.Capabilities {
return consumer.Capabilities{MutatesData: true}
}
func (n nopProcessor) ConsumeMetrics(context.Context, pmetric.Metrics) error {
return nil
}
func (n nopProcessor) ConsumeProfiles(context.Context, pprofile.Profiles) error {
return nil
}
================================================
FILE: cmd/mdatagen/internal/sampleprocessor/generated_component_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
//go:build !freebsd && !illumos
package sampleprocessor
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/confmap/confmaptest"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/processortest"
"go.opentelemetry.io/collector/processor/xprocessor"
)
var typ = component.MustNewType("sample")
func TestComponentFactoryType(t *testing.T) {
require.Equal(t, typ, NewFactory().Type())
}
func TestComponentConfigStruct(t *testing.T) {
require.NoError(t, componenttest.CheckConfigStruct(NewFactory().CreateDefaultConfig()))
}
func TestComponentLifecycle(t *testing.T) {
factory := NewFactory()
tests := []struct {
createFn func(ctx context.Context, set processor.Settings, cfg component.Config) (component.Component, error)
name string
}{
{
name: "logs",
createFn: func(ctx context.Context, set processor.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateLogs(ctx, set, cfg, consumertest.NewNop())
},
},
{
name: "metrics",
createFn: func(ctx context.Context, set processor.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateMetrics(ctx, set, cfg, consumertest.NewNop())
},
},
{
name: "traces",
createFn: func(ctx context.Context, set processor.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateTraces(ctx, set, cfg, consumertest.NewNop())
},
},
{
name: "profiles",
createFn: func(ctx context.Context, set processor.Settings, cfg component.Config) (component.Component, error) {
return factory.(xprocessor.Factory).CreateProfiles(ctx, set, cfg, consumertest.NewNop())
},
},
}
cm, err := confmaptest.LoadConf("metadata.yaml")
require.NoError(t, err)
cfg := factory.CreateDefaultConfig()
sub, err := cm.Sub("tests::config")
require.NoError(t, err)
require.NoError(t, sub.Unmarshal(&cfg))
for _, tt := range tests {
t.Run(tt.name+"-shutdown", func(t *testing.T) {
c, err := tt.createFn(context.Background(), processortest.NewNopSettings(typ), cfg)
require.NoError(t, err)
err = c.Shutdown(context.Background())
require.NoError(t, err)
})
t.Run(tt.name+"-lifecycle", func(t *testing.T) {
c, err := tt.createFn(context.Background(), processortest.NewNopSettings(typ), cfg)
require.NoError(t, err)
host := newMdatagenNopHost()
err = c.Start(context.Background(), host)
require.NoError(t, err)
require.NotPanics(t, func() {
switch tt.name {
case "logs":
e, ok := c.(processor.Logs)
require.True(t, ok)
logs := generateLifecycleTestLogs()
if !e.Capabilities().MutatesData {
logs.MarkReadOnly()
}
err = e.ConsumeLogs(context.Background(), logs)
case "metrics":
e, ok := c.(processor.Metrics)
require.True(t, ok)
metrics := generateLifecycleTestMetrics()
if !e.Capabilities().MutatesData {
metrics.MarkReadOnly()
}
err = e.ConsumeMetrics(context.Background(), metrics)
case "traces":
e, ok := c.(processor.Traces)
require.True(t, ok)
traces := generateLifecycleTestTraces()
if !e.Capabilities().MutatesData {
traces.MarkReadOnly()
}
err = e.ConsumeTraces(context.Background(), traces)
}
})
require.NoError(t, err)
err = c.Shutdown(context.Background())
require.NoError(t, err)
})
}
}
func generateLifecycleTestLogs() plog.Logs {
logs := plog.NewLogs()
rl := logs.ResourceLogs().AppendEmpty()
rl.Resource().Attributes().PutStr("resource", "R1")
l := rl.ScopeLogs().AppendEmpty().LogRecords().AppendEmpty()
l.Body().SetStr("test log message")
l.SetTimestamp(pcommon.NewTimestampFromTime(time.Now()))
return logs
}
func generateLifecycleTestMetrics() pmetric.Metrics {
metrics := pmetric.NewMetrics()
rm := metrics.ResourceMetrics().AppendEmpty()
rm.Resource().Attributes().PutStr("resource", "R1")
m := rm.ScopeMetrics().AppendEmpty().Metrics().AppendEmpty()
m.SetName("test_metric")
dp := m.SetEmptyGauge().DataPoints().AppendEmpty()
dp.Attributes().PutStr("test_attr", "value_1")
dp.SetIntValue(123)
dp.SetTimestamp(pcommon.NewTimestampFromTime(time.Now()))
return metrics
}
func generateLifecycleTestTraces() ptrace.Traces {
traces := ptrace.NewTraces()
rs := traces.ResourceSpans().AppendEmpty()
rs.Resource().Attributes().PutStr("resource", "R1")
span := rs.ScopeSpans().AppendEmpty().Spans().AppendEmpty()
span.Attributes().PutStr("test_attr", "value_1")
span.SetName("test_span")
span.SetStartTimestamp(pcommon.NewTimestampFromTime(time.Now().Add(-1 * time.Second)))
span.SetEndTimestamp(pcommon.NewTimestampFromTime(time.Now()))
return traces
}
var _ component.Host = (*mdatagenNopHost)(nil)
type mdatagenNopHost struct{}
func newMdatagenNopHost() component.Host {
return &mdatagenNopHost{}
}
func (mnh *mdatagenNopHost) GetExtensions() map[component.ID]component.Component {
return nil
}
func (mnh *mdatagenNopHost) GetFactory(_ component.Kind, _ component.Type) component.Factory {
return nil
}
================================================
FILE: cmd/mdatagen/internal/sampleprocessor/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package sampleprocessor
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: cmd/mdatagen/internal/sampleprocessor/internal/metadata/config.schema.yaml
================================================
# Code generated by mdatagen. DO NOT EDIT.
$defs:
resource_attributes_config:
description: ResourceAttributesConfig provides config for sample resource attributes.
type: object
properties:
map.resource.attr:
description: ResourceAttributeConfig provides common config for a map.resource.attr resource attribute.
type: object
properties:
enabled:
type: boolean
default: true
optional.resource.attr:
description: ResourceAttributeConfig provides common config for a optional.resource.attr resource attribute.
type: object
properties:
enabled:
type: boolean
default: false
slice.resource.attr:
description: ResourceAttributeConfig provides common config for a slice.resource.attr resource attribute.
type: object
properties:
enabled:
type: boolean
default: true
string.enum.resource.attr:
description: ResourceAttributeConfig provides common config for a string.enum.resource.attr resource attribute.
type: object
properties:
enabled:
type: boolean
default: true
string.resource.attr:
description: ResourceAttributeConfig provides common config for a string.resource.attr resource attribute.
type: object
properties:
enabled:
type: boolean
default: true
string.resource.attr_disable_warning:
description: ResourceAttributeConfig provides common config for a string.resource.attr_disable_warning resource attribute.
type: object
properties:
enabled:
type: boolean
default: true
string.resource.attr_remove_warning:
description: ResourceAttributeConfig provides common config for a string.resource.attr_remove_warning resource attribute.
type: object
properties:
enabled:
type: boolean
default: false
string.resource.attr_to_be_removed:
description: ResourceAttributeConfig provides common config for a string.resource.attr_to_be_removed resource attribute.
type: object
properties:
enabled:
type: boolean
default: true
================================================
FILE: cmd/mdatagen/internal/sampleprocessor/internal/metadata/generated_config.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/confmap"
)
// ResourceAttributeConfig provides common config for a particular resource attribute.
type ResourceAttributeConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
}
func (rac *ResourceAttributeConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(rac)
if err != nil {
return err
}
rac.enabledSetByUser = parser.IsSet("enabled")
return nil
}
// ResourceAttributesConfig provides config for sample resource attributes.
type ResourceAttributesConfig struct {
MapResourceAttr ResourceAttributeConfig `mapstructure:"map.resource.attr"`
OptionalResourceAttr ResourceAttributeConfig `mapstructure:"optional.resource.attr"`
SliceResourceAttr ResourceAttributeConfig `mapstructure:"slice.resource.attr"`
StringEnumResourceAttr ResourceAttributeConfig `mapstructure:"string.enum.resource.attr"`
StringResourceAttr ResourceAttributeConfig `mapstructure:"string.resource.attr"`
StringResourceAttrDisableWarning ResourceAttributeConfig `mapstructure:"string.resource.attr_disable_warning"`
StringResourceAttrRemoveWarning ResourceAttributeConfig `mapstructure:"string.resource.attr_remove_warning"`
StringResourceAttrToBeRemoved ResourceAttributeConfig `mapstructure:"string.resource.attr_to_be_removed"`
}
func DefaultResourceAttributesConfig() ResourceAttributesConfig {
return ResourceAttributesConfig{
MapResourceAttr: ResourceAttributeConfig{
Enabled: true,
},
OptionalResourceAttr: ResourceAttributeConfig{
Enabled: false,
},
SliceResourceAttr: ResourceAttributeConfig{
Enabled: true,
},
StringEnumResourceAttr: ResourceAttributeConfig{
Enabled: true,
},
StringResourceAttr: ResourceAttributeConfig{
Enabled: true,
},
StringResourceAttrDisableWarning: ResourceAttributeConfig{
Enabled: true,
},
StringResourceAttrRemoveWarning: ResourceAttributeConfig{
Enabled: false,
},
StringResourceAttrToBeRemoved: ResourceAttributeConfig{
Enabled: true,
},
}
}
================================================
FILE: cmd/mdatagen/internal/sampleprocessor/internal/metadata/generated_config_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"path/filepath"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/confmap/confmaptest"
)
func TestResourceAttributesConfig(t *testing.T) {
tests := []struct {
name string
want ResourceAttributesConfig
}{
{
name: "default",
want: DefaultResourceAttributesConfig(),
},
{
name: "all_set",
want: ResourceAttributesConfig{
MapResourceAttr: ResourceAttributeConfig{Enabled: true},
OptionalResourceAttr: ResourceAttributeConfig{Enabled: true},
SliceResourceAttr: ResourceAttributeConfig{Enabled: true},
StringEnumResourceAttr: ResourceAttributeConfig{Enabled: true},
StringResourceAttr: ResourceAttributeConfig{Enabled: true},
StringResourceAttrDisableWarning: ResourceAttributeConfig{Enabled: true},
StringResourceAttrRemoveWarning: ResourceAttributeConfig{Enabled: true},
StringResourceAttrToBeRemoved: ResourceAttributeConfig{Enabled: true},
},
},
{
name: "none_set",
want: ResourceAttributesConfig{
MapResourceAttr: ResourceAttributeConfig{Enabled: false},
OptionalResourceAttr: ResourceAttributeConfig{Enabled: false},
SliceResourceAttr: ResourceAttributeConfig{Enabled: false},
StringEnumResourceAttr: ResourceAttributeConfig{Enabled: false},
StringResourceAttr: ResourceAttributeConfig{Enabled: false},
StringResourceAttrDisableWarning: ResourceAttributeConfig{Enabled: false},
StringResourceAttrRemoveWarning: ResourceAttributeConfig{Enabled: false},
StringResourceAttrToBeRemoved: ResourceAttributeConfig{Enabled: false},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := loadResourceAttributesConfig(t, tt.name)
diff := cmp.Diff(tt.want, cfg, cmpopts.IgnoreUnexported(ResourceAttributeConfig{}))
require.Emptyf(t, diff, "Config mismatch (-expected +actual):\n%s", diff)
})
}
}
func loadResourceAttributesConfig(t *testing.T, name string) ResourceAttributesConfig {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
require.NoError(t, err)
sub, err := cm.Sub(name)
require.NoError(t, err)
sub, err = sub.Sub("resource_attributes")
require.NoError(t, err)
cfg := DefaultResourceAttributesConfig()
require.NoError(t, sub.Unmarshal(&cfg))
return cfg
}
================================================
FILE: cmd/mdatagen/internal/sampleprocessor/internal/metadata/generated_resource.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/pdata/pcommon"
)
// ResourceBuilder is a helper struct to build resources predefined in metadata.yaml.
// The ResourceBuilder is not thread-safe and must not to be used in multiple goroutines.
type ResourceBuilder struct {
config ResourceAttributesConfig
res pcommon.Resource
}
// NewResourceBuilder creates a new ResourceBuilder. This method should be called on the start of the application.
func NewResourceBuilder(rac ResourceAttributesConfig) *ResourceBuilder {
return &ResourceBuilder{
config: rac,
res: pcommon.NewResource(),
}
}
// SetMapResourceAttr sets provided value as "map.resource.attr" attribute.
func (rb *ResourceBuilder) SetMapResourceAttr(val map[string]any) {
if rb.config.MapResourceAttr.Enabled {
rb.res.Attributes().PutEmptyMap("map.resource.attr").FromRaw(val)
}
}
// SetOptionalResourceAttr sets provided value as "optional.resource.attr" attribute.
func (rb *ResourceBuilder) SetOptionalResourceAttr(val string) {
if rb.config.OptionalResourceAttr.Enabled {
rb.res.Attributes().PutStr("optional.resource.attr", val)
}
}
// SetSliceResourceAttr sets provided value as "slice.resource.attr" attribute.
func (rb *ResourceBuilder) SetSliceResourceAttr(val []any) {
if rb.config.SliceResourceAttr.Enabled {
rb.res.Attributes().PutEmptySlice("slice.resource.attr").FromRaw(val)
}
}
// SetStringEnumResourceAttrOne sets "string.enum.resource.attr=one" attribute.
func (rb *ResourceBuilder) SetStringEnumResourceAttrOne() {
if rb.config.StringEnumResourceAttr.Enabled {
rb.res.Attributes().PutStr("string.enum.resource.attr", "one")
}
}
// SetStringEnumResourceAttrTwo sets "string.enum.resource.attr=two" attribute.
func (rb *ResourceBuilder) SetStringEnumResourceAttrTwo() {
if rb.config.StringEnumResourceAttr.Enabled {
rb.res.Attributes().PutStr("string.enum.resource.attr", "two")
}
}
// SetStringResourceAttr sets provided value as "string.resource.attr" attribute.
func (rb *ResourceBuilder) SetStringResourceAttr(val string) {
if rb.config.StringResourceAttr.Enabled {
rb.res.Attributes().PutStr("string.resource.attr", val)
}
}
// SetStringResourceAttrDisableWarning sets provided value as "string.resource.attr_disable_warning" attribute.
func (rb *ResourceBuilder) SetStringResourceAttrDisableWarning(val string) {
if rb.config.StringResourceAttrDisableWarning.Enabled {
rb.res.Attributes().PutStr("string.resource.attr_disable_warning", val)
}
}
// SetStringResourceAttrRemoveWarning sets provided value as "string.resource.attr_remove_warning" attribute.
func (rb *ResourceBuilder) SetStringResourceAttrRemoveWarning(val string) {
if rb.config.StringResourceAttrRemoveWarning.Enabled {
rb.res.Attributes().PutStr("string.resource.attr_remove_warning", val)
}
}
// SetStringResourceAttrToBeRemoved sets provided value as "string.resource.attr_to_be_removed" attribute.
func (rb *ResourceBuilder) SetStringResourceAttrToBeRemoved(val string) {
if rb.config.StringResourceAttrToBeRemoved.Enabled {
rb.res.Attributes().PutStr("string.resource.attr_to_be_removed", val)
}
}
// Emit returns the built resource and resets the internal builder state.
func (rb *ResourceBuilder) Emit() pcommon.Resource {
r := rb.res
rb.res = pcommon.NewResource()
return r
}
================================================
FILE: cmd/mdatagen/internal/sampleprocessor/internal/metadata/generated_resource_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestResourceBuilder(t *testing.T) {
for _, tt := range []string{"default", "all_set", "none_set"} {
t.Run(tt, func(t *testing.T) {
cfg := loadResourceAttributesConfig(t, tt)
rb := NewResourceBuilder(cfg)
rb.SetMapResourceAttr(map[string]any{"key1": "map.resource.attr-val1", "key2": "map.resource.attr-val2"})
rb.SetOptionalResourceAttr("optional.resource.attr-val")
rb.SetSliceResourceAttr([]any{"slice.resource.attr-item1", "slice.resource.attr-item2"})
rb.SetStringEnumResourceAttrOne()
rb.SetStringResourceAttr("string.resource.attr-val")
rb.SetStringResourceAttrDisableWarning("string.resource.attr_disable_warning-val")
rb.SetStringResourceAttrRemoveWarning("string.resource.attr_remove_warning-val")
rb.SetStringResourceAttrToBeRemoved("string.resource.attr_to_be_removed-val")
res := rb.Emit()
assert.Equal(t, 0, rb.Emit().Attributes().Len()) // Second call should return empty Resource
switch tt {
case "default":
assert.Equal(t, 6, res.Attributes().Len())
case "all_set":
assert.Equal(t, 8, res.Attributes().Len())
case "none_set":
assert.Equal(t, 0, res.Attributes().Len())
return
default:
assert.Failf(t, "unexpected test case: %s", tt)
}
mapResourceAttrAttrVal, ok := res.Attributes().Get("map.resource.attr")
assert.True(t, ok)
if ok {
assert.Equal(t, map[string]any{"key1": "map.resource.attr-val1", "key2": "map.resource.attr-val2"}, mapResourceAttrAttrVal.Map().AsRaw())
}
optionalResourceAttrAttrVal, ok := res.Attributes().Get("optional.resource.attr")
assert.Equal(t, tt == "all_set", ok)
if ok {
assert.Equal(t, "optional.resource.attr-val", optionalResourceAttrAttrVal.Str())
}
sliceResourceAttrAttrVal, ok := res.Attributes().Get("slice.resource.attr")
assert.True(t, ok)
if ok {
assert.Equal(t, []any{"slice.resource.attr-item1", "slice.resource.attr-item2"}, sliceResourceAttrAttrVal.Slice().AsRaw())
}
stringEnumResourceAttrAttrVal, ok := res.Attributes().Get("string.enum.resource.attr")
assert.True(t, ok)
if ok {
assert.Equal(t, "one", stringEnumResourceAttrAttrVal.Str())
}
stringResourceAttrAttrVal, ok := res.Attributes().Get("string.resource.attr")
assert.True(t, ok)
if ok {
assert.Equal(t, "string.resource.attr-val", stringResourceAttrAttrVal.Str())
}
stringResourceAttrDisableWarningAttrVal, ok := res.Attributes().Get("string.resource.attr_disable_warning")
assert.True(t, ok)
if ok {
assert.Equal(t, "string.resource.attr_disable_warning-val", stringResourceAttrDisableWarningAttrVal.Str())
}
stringResourceAttrRemoveWarningAttrVal, ok := res.Attributes().Get("string.resource.attr_remove_warning")
assert.Equal(t, tt == "all_set", ok)
if ok {
assert.Equal(t, "string.resource.attr_remove_warning-val", stringResourceAttrRemoveWarningAttrVal.Str())
}
stringResourceAttrToBeRemovedAttrVal, ok := res.Attributes().Get("string.resource.attr_to_be_removed")
assert.True(t, ok)
if ok {
assert.Equal(t, "string.resource.attr_to_be_removed-val", stringResourceAttrToBeRemovedAttrVal.Str())
}
})
}
}
================================================
FILE: cmd/mdatagen/internal/sampleprocessor/internal/metadata/generated_status.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/component"
)
var (
Type = component.MustNewType("sample")
ScopeName = "go.opentelemetry.io/collector/internal/receiver/samplereceiver"
)
const (
LogsStability = component.StabilityLevelDevelopment
ProfilesStability = component.StabilityLevelDevelopment
TracesStability = component.StabilityLevelBeta
MetricsStability = component.StabilityLevelStable
)
================================================
FILE: cmd/mdatagen/internal/sampleprocessor/internal/metadata/testdata/config.yaml
================================================
default:
all_set:
resource_attributes:
map.resource.attr:
enabled: true
optional.resource.attr:
enabled: true
slice.resource.attr:
enabled: true
string.enum.resource.attr:
enabled: true
string.resource.attr:
enabled: true
string.resource.attr_disable_warning:
enabled: true
string.resource.attr_remove_warning:
enabled: true
string.resource.attr_to_be_removed:
enabled: true
reaggregate_set:
resource_attributes:
map.resource.attr:
enabled: true
optional.resource.attr:
enabled: true
slice.resource.attr:
enabled: true
string.enum.resource.attr:
enabled: true
string.resource.attr:
enabled: true
string.resource.attr_disable_warning:
enabled: true
string.resource.attr_remove_warning:
enabled: true
string.resource.attr_to_be_removed:
enabled: true
none_set:
resource_attributes:
map.resource.attr:
enabled: false
optional.resource.attr:
enabled: false
slice.resource.attr:
enabled: false
string.enum.resource.attr:
enabled: false
string.resource.attr:
enabled: false
string.resource.attr_disable_warning:
enabled: false
string.resource.attr_remove_warning:
enabled: false
string.resource.attr_to_be_removed:
enabled: false
================================================
FILE: cmd/mdatagen/internal/sampleprocessor/metadata.yaml
================================================
# Sample metadata file with all available configurations for a processor.
type: sample
display_name: Sample Processor
description: This processor is used for testing purposes to check the output of mdatagen.
reaggregation_enabled: true
scope_name: go.opentelemetry.io/collector/internal/receiver/samplereceiver
github_project: open-telemetry/opentelemetry-collector
sem_conv_version: 1.9.0
status:
disable_codecov_badge: true
class: processor
stability:
development: [logs, profiles]
beta: [traces]
stable: [metrics]
distributions: []
unsupported_platforms: [freebsd, illumos]
codeowners:
active: []
warnings:
- Any additional information that should be brought to the consumer's attention
resource_attributes:
map.resource.attr:
description: Resource attribute with a map value.
type: map
enabled: true
optional.resource.attr:
description: Explicitly disabled ResourceAttribute.
type: string
enabled: false
slice.resource.attr:
description: Resource attribute with a slice value.
type: slice
enabled: true
string.enum.resource.attr:
description: Resource attribute with a known set of string values.
type: string
enum: [one, two]
enabled: true
string.resource.attr:
description: Resource attribute with any string value.
type: string
enabled: true
string.resource.attr_disable_warning:
description: Resource attribute with any string value.
type: string
enabled: true
warnings:
if_enabled_not_set: This resource_attribute will be disabled by default soon.
string.resource.attr_remove_warning:
description: Resource attribute with any string value.
type: string
enabled: false
warnings:
if_configured: This resource_attribute is deprecated and will be removed soon.
string.resource.attr_to_be_removed:
description: Resource attribute with any string value.
type: string
enabled: true
warnings:
if_enabled: This resource_attribute is deprecated and will be removed soon.
================================================
FILE: cmd/mdatagen/internal/samplereceiver/README.md
================================================
# Sample Receiver
This receiver is used for testing purposes to check the output of mdatagen.
| Status | |
| ------------- |-----------|
| Stability | [deprecated]: profiles |
| | [development]: logs |
| | [beta]: traces |
| | [stable]: metrics |
| Deprecation of profiles | [Date]: 2025-02-05 |
| | [Migration Note]: no migration needed |
| Unsupported Platforms | freebsd, illumos |
| Distributions | [] |
| Warnings | [Any additional information that should be brought to the consumer's attention](#warnings) |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Areceiver%2Fsample) [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aclosed+is%3Aissue+label%3Areceiver%2Fsample) |
| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@dmitryax](https://www.github.com/dmitryax) |
[deprecated]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#deprecated
[development]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#development
[beta]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#beta
[stable]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#stable
[Date]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#deprecation-information
[Migration Note]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#deprecation-information
## Warnings
This is where warnings are described.
================================================
FILE: cmd/mdatagen/internal/samplereceiver/config.schema.json
================================================
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "go.opentelemetry.io/collector/cmd/mdatagen/internal/samplereceiver",
"title": "receiver/sample",
"type": "object",
"allOf": [
{
"description": "MetricsBuilderConfig is a configuration for sample metrics builder.",
"type": "object",
"properties": {
"metrics": {
"description": "MetricsConfig provides config for sample metrics.",
"type": "object",
"properties": {
"default.metric": {
"description": "DefaultMetricMetricConfig provides config for the default.metric metric.",
"type": "object",
"properties": {
"aggregation_strategy": {
"type": "string",
"default": "sum",
"enum": [
"sum",
"avg",
"min",
"max"
]
},
"attributes": {
"type": "array",
"default": [
"string_attr",
"state",
"enum_attr",
"slice_attr",
"map_attr",
"conditional_int_attr",
"conditional_string_attr"
],
"items": {
"type": "string",
"enum": [
"string_attr",
"state",
"enum_attr",
"slice_attr",
"map_attr",
"conditional_int_attr",
"conditional_string_attr",
"opt_in_bool_attr"
]
}
},
"enabled": {
"type": "boolean",
"default": true
}
}
},
"default.metric.to_be_removed": {
"description": "DefaultMetricToBeRemovedMetricConfig provides config for the default.metric.to_be_removed metric.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"default": true
}
}
},
"metric.input_type": {
"description": "MetricInputTypeMetricConfig provides config for the metric.input_type metric.",
"type": "object",
"properties": {
"aggregation_strategy": {
"type": "string",
"default": "sum",
"enum": [
"sum",
"avg",
"min",
"max"
]
},
"attributes": {
"type": "array",
"default": [
"string_attr",
"state",
"enum_attr",
"slice_attr",
"map_attr"
],
"items": {
"type": "string",
"enum": [
"string_attr",
"state",
"enum_attr",
"slice_attr",
"map_attr"
]
}
},
"enabled": {
"type": "boolean",
"default": true
}
}
},
"optional.metric": {
"description": "OptionalMetricMetricConfig provides config for the optional.metric metric.",
"type": "object",
"properties": {
"aggregation_strategy": {
"type": "string",
"default": "avg",
"enum": [
"sum",
"avg",
"min",
"max"
]
},
"attributes": {
"type": "array",
"default": [
"string_attr",
"boolean_attr",
"boolean_attr2",
"conditional_string_attr"
],
"items": {
"type": "string",
"enum": [
"string_attr",
"boolean_attr",
"boolean_attr2",
"conditional_string_attr"
]
}
},
"enabled": {
"type": "boolean",
"default": false
}
}
},
"optional.metric.empty_unit": {
"description": "OptionalMetricEmptyUnitMetricConfig provides config for the optional.metric.empty_unit metric.",
"type": "object",
"properties": {
"aggregation_strategy": {
"type": "string",
"default": "avg",
"enum": [
"sum",
"avg",
"min",
"max"
]
},
"attributes": {
"type": "array",
"default": [
"string_attr",
"boolean_attr"
],
"items": {
"type": "string",
"enum": [
"string_attr",
"boolean_attr"
]
}
},
"enabled": {
"type": "boolean",
"default": false
}
}
},
"reaggregate.metric": {
"description": "ReaggregateMetricMetricConfig provides config for the reaggregate.metric metric.",
"type": "object",
"properties": {
"aggregation_strategy": {
"type": "string",
"default": "avg",
"enum": [
"sum",
"avg",
"min",
"max"
]
},
"attributes": {
"type": "array",
"default": [
"string_attr",
"boolean_attr"
],
"items": {
"type": "string",
"enum": [
"string_attr",
"boolean_attr"
]
}
},
"enabled": {
"type": "boolean",
"default": true
}
}
},
"reaggregate.metric.with_required": {
"description": "ReaggregateMetricWithRequiredMetricConfig provides config for the reaggregate.metric.with_required metric.",
"type": "object",
"properties": {
"aggregation_strategy": {
"type": "string",
"default": "avg",
"enum": [
"sum",
"avg",
"min",
"max"
]
},
"attributes": {
"type": "array",
"default": [
"required_string_attr",
"string_attr",
"boolean_attr"
],
"items": {
"type": "string",
"enum": [
"required_string_attr",
"string_attr",
"boolean_attr"
]
}
},
"enabled": {
"type": "boolean",
"default": true
}
}
},
"system.cpu.time": {
"description": "SystemCPUTimeMetricConfig provides config for the system.cpu.time metric.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"default": true
}
}
}
}
},
"resource_attributes": {
"description": "ResourceAttributesConfig provides config for sample resource attributes.",
"type": "object",
"properties": {
"map.resource.attr": {
"description": "ResourceAttributeConfig provides common config for a map.resource.attr resource attribute.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"default": true
},
"events_exclude": {
"description": "Experimental: EventsExclude defines a list of filters for attribute values. If the list is not empty, events with matching resource attribute values will not be emitted. EventsInclude has higher priority than EventsExclude.",
"type": "array",
"items": {
"description": "Config configures the matching behavior of a Filter.",
"type": "object",
"properties": {
"regexp": {
"type": "string"
},
"strict": {
"type": "string"
}
}
}
},
"events_include": {
"description": "Experimental: EventsInclude defines a list of filters for attribute values. If the list is not empty, only events with matching resource attribute values will be emitted.",
"type": "array",
"items": {
"description": "Config configures the matching behavior of a Filter.",
"type": "object",
"properties": {
"regexp": {
"type": "string"
},
"strict": {
"type": "string"
}
}
}
},
"metrics_exclude": {
"description": "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude.",
"type": "array",
"items": {
"description": "Config configures the matching behavior of a Filter.",
"type": "object",
"properties": {
"regexp": {
"type": "string"
},
"strict": {
"type": "string"
}
}
}
},
"metrics_include": {
"description": "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted.",
"type": "array",
"items": {
"description": "Config configures the matching behavior of a Filter.",
"type": "object",
"properties": {
"regexp": {
"type": "string"
},
"strict": {
"type": "string"
}
}
}
}
}
},
"optional.resource.attr": {
"description": "ResourceAttributeConfig provides common config for a optional.resource.attr resource attribute.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"default": false
},
"events_exclude": {
"description": "Experimental: EventsExclude defines a list of filters for attribute values. If the list is not empty, events with matching resource attribute values will not be emitted. EventsInclude has higher priority than EventsExclude.",
"type": "array",
"items": {
"description": "Config configures the matching behavior of a Filter.",
"type": "object",
"properties": {
"regexp": {
"type": "string"
},
"strict": {
"type": "string"
}
}
}
},
"events_include": {
"description": "Experimental: EventsInclude defines a list of filters for attribute values. If the list is not empty, only events with matching resource attribute values will be emitted.",
"type": "array",
"items": {
"description": "Config configures the matching behavior of a Filter.",
"type": "object",
"properties": {
"regexp": {
"type": "string"
},
"strict": {
"type": "string"
}
}
}
},
"metrics_exclude": {
"description": "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude.",
"type": "array",
"items": {
"description": "Config configures the matching behavior of a Filter.",
"type": "object",
"properties": {
"regexp": {
"type": "string"
},
"strict": {
"type": "string"
}
}
}
},
"metrics_include": {
"description": "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted.",
"type": "array",
"items": {
"description": "Config configures the matching behavior of a Filter.",
"type": "object",
"properties": {
"regexp": {
"type": "string"
},
"strict": {
"type": "string"
}
}
}
}
}
},
"slice.resource.attr": {
"description": "ResourceAttributeConfig provides common config for a slice.resource.attr resource attribute.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"default": true
},
"events_exclude": {
"description": "Experimental: EventsExclude defines a list of filters for attribute values. If the list is not empty, events with matching resource attribute values will not be emitted. EventsInclude has higher priority than EventsExclude.",
"type": "array",
"items": {
"description": "Config configures the matching behavior of a Filter.",
"type": "object",
"properties": {
"regexp": {
"type": "string"
},
"strict": {
"type": "string"
}
}
}
},
"events_include": {
"description": "Experimental: EventsInclude defines a list of filters for attribute values. If the list is not empty, only events with matching resource attribute values will be emitted.",
"type": "array",
"items": {
"description": "Config configures the matching behavior of a Filter.",
"type": "object",
"properties": {
"regexp": {
"type": "string"
},
"strict": {
"type": "string"
}
}
}
},
"metrics_exclude": {
"description": "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude.",
"type": "array",
"items": {
"description": "Config configures the matching behavior of a Filter.",
"type": "object",
"properties": {
"regexp": {
"type": "string"
},
"strict": {
"type": "string"
}
}
}
},
"metrics_include": {
"description": "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted.",
"type": "array",
"items": {
"description": "Config configures the matching behavior of a Filter.",
"type": "object",
"properties": {
"regexp": {
"type": "string"
},
"strict": {
"type": "string"
}
}
}
}
}
},
"string.enum.resource.attr": {
"description": "ResourceAttributeConfig provides common config for a string.enum.resource.attr resource attribute.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"default": true
},
"events_exclude": {
"description": "Experimental: EventsExclude defines a list of filters for attribute values. If the list is not empty, events with matching resource attribute values will not be emitted. EventsInclude has higher priority than EventsExclude.",
"type": "array",
"items": {
"description": "Config configures the matching behavior of a Filter.",
"type": "object",
"properties": {
"regexp": {
"type": "string"
},
"strict": {
"type": "string"
}
}
}
},
"events_include": {
"description": "Experimental: EventsInclude defines a list of filters for attribute values. If the list is not empty, only events with matching resource attribute values will be emitted.",
"type": "array",
"items": {
"description": "Config configures the matching behavior of a Filter.",
"type": "object",
"properties": {
"regexp": {
"type": "string"
},
"strict": {
"type": "string"
}
}
}
},
"metrics_exclude": {
"description": "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude.",
"type": "array",
"items": {
"description": "Config configures the matching behavior of a Filter.",
"type": "object",
"properties": {
"regexp": {
"type": "string"
},
"strict": {
"type": "string"
}
}
}
},
"metrics_include": {
"description": "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted.",
"type": "array",
"items": {
"description": "Config configures the matching behavior of a Filter.",
"type": "object",
"properties": {
"regexp": {
"type": "string"
},
"strict": {
"type": "string"
}
}
}
}
}
},
"string.resource.attr": {
"description": "ResourceAttributeConfig provides common config for a string.resource.attr resource attribute.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"default": true
},
"events_exclude": {
"description": "Experimental: EventsExclude defines a list of filters for attribute values. If the list is not empty, events with matching resource attribute values will not be emitted. EventsInclude has higher priority than EventsExclude.",
"type": "array",
"items": {
"description": "Config configures the matching behavior of a Filter.",
"type": "object",
"properties": {
"regexp": {
"type": "string"
},
"strict": {
"type": "string"
}
}
}
},
"events_include": {
"description": "Experimental: EventsInclude defines a list of filters for attribute values. If the list is not empty, only events with matching resource attribute values will be emitted.",
"type": "array",
"items": {
"description": "Config configures the matching behavior of a Filter.",
"type": "object",
"properties": {
"regexp": {
"type": "string"
},
"strict": {
"type": "string"
}
}
}
},
"metrics_exclude": {
"description": "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude.",
"type": "array",
"items": {
"description": "Config configures the matching behavior of a Filter.",
"type": "object",
"properties": {
"regexp": {
"type": "string"
},
"strict": {
"type": "string"
}
}
}
},
"metrics_include": {
"description": "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted.",
"type": "array",
"items": {
"description": "Config configures the matching behavior of a Filter.",
"type": "object",
"properties": {
"regexp": {
"type": "string"
},
"strict": {
"type": "string"
}
}
}
}
}
},
"string.resource.attr_disable_warning": {
"description": "ResourceAttributeConfig provides common config for a string.resource.attr_disable_warning resource attribute.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"default": true
},
"events_exclude": {
"description": "Experimental: EventsExclude defines a list of filters for attribute values. If the list is not empty, events with matching resource attribute values will not be emitted. EventsInclude has higher priority than EventsExclude.",
"type": "array",
"items": {
"description": "Config configures the matching behavior of a Filter.",
"type": "object",
"properties": {
"regexp": {
"type": "string"
},
"strict": {
"type": "string"
}
}
}
},
"events_include": {
"description": "Experimental: EventsInclude defines a list of filters for attribute values. If the list is not empty, only events with matching resource attribute values will be emitted.",
"type": "array",
"items": {
"description": "Config configures the matching behavior of a Filter.",
"type": "object",
"properties": {
"regexp": {
"type": "string"
},
"strict": {
"type": "string"
}
}
}
},
"metrics_exclude": {
"description": "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude.",
"type": "array",
"items": {
"description": "Config configures the matching behavior of a Filter.",
"type": "object",
"properties": {
"regexp": {
"type": "string"
},
"strict": {
"type": "string"
}
}
}
},
"metrics_include": {
"description": "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted.",
"type": "array",
"items": {
"description": "Config configures the matching behavior of a Filter.",
"type": "object",
"properties": {
"regexp": {
"type": "string"
},
"strict": {
"type": "string"
}
}
}
}
}
},
"string.resource.attr_remove_warning": {
"description": "ResourceAttributeConfig provides common config for a string.resource.attr_remove_warning resource attribute.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"default": false
},
"events_exclude": {
"description": "Experimental: EventsExclude defines a list of filters for attribute values. If the list is not empty, events with matching resource attribute values will not be emitted. EventsInclude has higher priority than EventsExclude.",
"type": "array",
"items": {
"description": "Config configures the matching behavior of a Filter.",
"type": "object",
"properties": {
"regexp": {
"type": "string"
},
"strict": {
"type": "string"
}
}
}
},
"events_include": {
"description": "Experimental: EventsInclude defines a list of filters for attribute values. If the list is not empty, only events with matching resource attribute values will be emitted.",
"type": "array",
"items": {
"description": "Config configures the matching behavior of a Filter.",
"type": "object",
"properties": {
"regexp": {
"type": "string"
},
"strict": {
"type": "string"
}
}
}
},
"metrics_exclude": {
"description": "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude.",
"type": "array",
"items": {
"description": "Config configures the matching behavior of a Filter.",
"type": "object",
"properties": {
"regexp": {
"type": "string"
},
"strict": {
"type": "string"
}
}
}
},
"metrics_include": {
"description": "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted.",
"type": "array",
"items": {
"description": "Config configures the matching behavior of a Filter.",
"type": "object",
"properties": {
"regexp": {
"type": "string"
},
"strict": {
"type": "string"
}
}
}
}
}
},
"string.resource.attr_to_be_removed": {
"description": "ResourceAttributeConfig provides common config for a string.resource.attr_to_be_removed resource attribute.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"default": true
},
"events_exclude": {
"description": "Experimental: EventsExclude defines a list of filters for attribute values. If the list is not empty, events with matching resource attribute values will not be emitted. EventsInclude has higher priority than EventsExclude.",
"type": "array",
"items": {
"description": "Config configures the matching behavior of a Filter.",
"type": "object",
"properties": {
"regexp": {
"type": "string"
},
"strict": {
"type": "string"
}
}
}
},
"events_include": {
"description": "Experimental: EventsInclude defines a list of filters for attribute values. If the list is not empty, only events with matching resource attribute values will be emitted.",
"type": "array",
"items": {
"description": "Config configures the matching behavior of a Filter.",
"type": "object",
"properties": {
"regexp": {
"type": "string"
},
"strict": {
"type": "string"
}
}
}
},
"metrics_exclude": {
"description": "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude.",
"type": "array",
"items": {
"description": "Config configures the matching behavior of a Filter.",
"type": "object",
"properties": {
"regexp": {
"type": "string"
},
"strict": {
"type": "string"
}
}
}
},
"metrics_include": {
"description": "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted.",
"type": "array",
"items": {
"description": "Config configures the matching behavior of a Filter.",
"type": "object",
"properties": {
"regexp": {
"type": "string"
},
"strict": {
"type": "string"
}
}
}
}
}
}
}
}
}
}
],
"properties": {
"endpoint": {
"description": "The endpoint to scrape metrics from.",
"type": "string",
"default": "localhost:12345"
},
"timeout": {
"description": "Timeout for scraping metrics. (duration format, e.g., \"30s\", \"1h30m\")",
"type": "string",
"default": "10s",
"pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$"
}
},
"required": [
"endpoint"
]
}
================================================
FILE: cmd/mdatagen/internal/samplereceiver/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Generate a test metrics builder from a sample metrics set covering all configuration options.
//go:generate mdatagen metadata.yaml
package samplereceiver // import "go.opentelemetry.io/collector/cmd/mdatagen/internal/samplereceiver"
================================================
FILE: cmd/mdatagen/internal/samplereceiver/documentation.md
================================================
[comment]: <> (Code generated by mdatagen. DO NOT EDIT.)
# sample
## Default Metrics
The following metrics are emitted by default. Each of them can be disabled by applying the following configuration:
```yaml
metrics:
:
enabled: false
```
### default.metric
Monotonic cumulative sum int metric enabled by default.
The metric will be become optional soon.
| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability |
| ---- | ----------- | ---------- | ----------------------- | --------- | --------- |
| s | Sum | Int | Cumulative | true | Deprecated since 1.0.0 |
**Deprecation note**: This metric will be removed
#### Attributes
| Name | Description | Values | Requirement Level |
| ---- | ----------- | ------ | -------- |
| string_attr | Attribute with any string value. | Any Str | Recommended |
| state | Integer attribute with overridden name. | Any Int | Recommended |
| enum_attr | Attribute with a known set of string values. | Str: ``red``, ``green``, ``blue`` | Recommended |
| slice_attr | Attribute with a slice value. | Any Slice | Recommended |
| map_attr | Attribute with a map value. | Any Map | Recommended |
| conditional_int_attr | A conditional attribute with an integer value | Any Int | Conditionally Required |
| conditional_string_attr | A conditional attribute with any string value | Any Str | Conditionally Required |
| opt_in_bool_attr | An opt-in attribute with a boolean value | Any Bool | Opt-In |
### default.metric.to_be_removed
[DEPRECATED] Non-monotonic delta sum double metric enabled by default.
The metric will be removed soon.
| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability |
| ---- | ----------- | ---------- | ----------------------- | --------- | --------- |
| s | Sum | Double | Delta | false | Deprecated since 1.0.0 |
**Deprecation note**: This metric will be removed
### metric.input_type
Monotonic cumulative sum int metric with string input_type enabled by default.
| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability |
| ---- | ----------- | ---------- | ----------------------- | --------- | --------- |
| s | Sum | Int | Cumulative | true | Development |
#### Attributes
| Name | Description | Values | Requirement Level |
| ---- | ----------- | ------ | -------- |
| string_attr | Attribute with any string value. | Any Str | Recommended |
| state | Integer attribute with overridden name. | Any Int | Recommended |
| enum_attr | Attribute with a known set of string values. | Str: ``red``, ``green``, ``blue`` | Recommended |
| slice_attr | Attribute with a slice value. | Any Slice | Recommended |
| map_attr | Attribute with a map value. | Any Map | Recommended |
### reaggregate.metric
Metric for testing spatial reaggregation
| Unit | Metric Type | Value Type | Stability |
| ---- | ----------- | ---------- | --------- |
| 1 | Gauge | Double | Beta |
#### Attributes
| Name | Description | Values | Requirement Level |
| ---- | ----------- | ------ | -------- |
| string_attr | Attribute with any string value. | Any Str | Recommended |
| boolean_attr | Attribute with a boolean value. | Any Bool | Recommended |
### reaggregate.metric.with_required
Metric for testing spatial reaggregation with required attributes
| Unit | Metric Type | Value Type | Stability |
| ---- | ----------- | ---------- | --------- |
| 1 | Gauge | Double | Beta |
#### Attributes
| Name | Description | Values | Requirement Level |
| ---- | ----------- | ------ | -------- |
| required_string_attr | A required attribute with a string value | Any Str | Required |
| string_attr | Attribute with any string value. | Any Str | Recommended |
| boolean_attr | Attribute with a boolean value. | Any Bool | Recommended |
### system.cpu.time
Monotonic cumulative sum int metric enabled by default.
The metric will be become optional soon.
| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | Semantic Convention |
| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | ------------------- |
| s | Sum | Int | Cumulative | true | Beta | [system.cpu.time](https://github.com/open-telemetry/semantic-conventions/blob/v1.38.0/docs/system/system-metrics.md#metric-systemcputime) |
## Optional Metrics
The following metrics are not emitted by default. Each of them can be enabled by applying the following configuration:
```yaml
metrics:
:
enabled: true
```
### optional.metric
[DEPRECATED] Gauge double metric disabled by default.
| Unit | Metric Type | Value Type | Stability |
| ---- | ----------- | ---------- | --------- |
| 1 | Gauge | Double | Deprecated since 1.0.0 |
**Deprecation note**: This metric will be removed
#### Attributes
| Name | Description | Values | Requirement Level |
| ---- | ----------- | ------ | -------- |
| string_attr | Attribute with any string value. | Any Str | Recommended |
| boolean_attr | Attribute with a boolean value. | Any Bool | Recommended |
| boolean_attr2 | Another attribute with a boolean value. | Any Bool | Recommended |
| conditional_string_attr | A conditional attribute with any string value | Any Str | Conditionally Required |
### optional.metric.empty_unit
[DEPRECATED] Gauge double metric disabled by default.
| Unit | Metric Type | Value Type | Stability |
| ---- | ----------- | ---------- | --------- |
| | Gauge | Double | Deprecated since 1.0.0 |
**Deprecation note**: This metric will be removed
#### Attributes
| Name | Description | Values | Requirement Level |
| ---- | ----------- | ------ | -------- |
| string_attr | Attribute with any string value. | Any Str | Recommended |
| boolean_attr | Attribute with a boolean value. | Any Bool | Recommended |
## Default Events
The following events are emitted by default. Each of them can be disabled by applying the following configuration:
```yaml
events:
:
enabled: false
```
### default.event
Example event enabled by default.
#### Attributes
| Name | Description | Values |
| ---- | ----------- | ------ |
| string_attr | Attribute with any string value. | Any Str |
| state | Integer attribute with overridden name. | Any Int |
| enum_attr | Attribute with a known set of string values. | Str: ``red``, ``green``, ``blue`` |
| slice_attr | Attribute with a slice value. | Any Slice |
| map_attr | Attribute with a map value. | Any Map |
| conditional_int_attr | A conditional attribute with an integer value | Any Int |
| conditional_string_attr | A conditional attribute with any string value | Any Str |
| opt_in_bool_attr | An opt-in attribute with a boolean value | Any Bool |
### default.event.to_be_removed
[DEPRECATED] Example to-be-removed event enabled by default.
The event will be removed soon.
#### Attributes
| Name | Description | Values |
| ---- | ----------- | ------ |
| string_attr | Attribute with any string value. | Any Str |
| state | Integer attribute with overridden name. | Any Int |
| enum_attr | Attribute with a known set of string values. | Str: ``red``, ``green``, ``blue`` |
| slice_attr | Attribute with a slice value. | Any Slice |
| map_attr | Attribute with a map value. | Any Map |
## Optional Events
The following events are not emitted by default. Each of them can be enabled by applying the following configuration:
```yaml
events:
:
enabled: true
```
### default.event.to_be_renamed
[DEPRECATED] Example event disabled by default.
The event will be renamed soon.
#### Attributes
| Name | Description | Values |
| ---- | ----------- | ------ |
| string_attr | Attribute with any string value. | Any Str |
| boolean_attr | Attribute with a boolean value. | Any Bool |
| boolean_attr2 | Another attribute with a boolean value. | Any Bool |
| conditional_string_attr | A conditional attribute with any string value | Any Str |
## Resource Attributes
| Name | Description | Values | Enabled |
| ---- | ----------- | ------ | ------- |
| map.resource.attr | Resource attribute with a map value. | Any Map | true |
| optional.resource.attr | Explicitly disabled ResourceAttribute. | Any Str | false |
| slice.resource.attr | Resource attribute with a slice value. | Any Slice | true |
| string.enum.resource.attr | Resource attribute with a known set of string values. | Str: ``one``, ``two`` | true |
| string.resource.attr | Resource attribute with any string value. | Any Str | true |
| string.resource.attr_disable_warning | Resource attribute with any string value. | Any Str | true |
| string.resource.attr_remove_warning | Resource attribute with any string value. | Any Str | false |
| string.resource.attr_to_be_removed | Resource attribute with any string value. | Any Str | true |
## Internal Telemetry
The following telemetry is emitted by this component.
### otelcol_batch_size_trigger_send
Number of times the batch was sent due to a size trigger
> **Deprecated since 1.5.0**
> This metric will be removed in favor of batch_send_trigger_size
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {time} | Sum | Int | true | Deprecated since 1.5.0 |
**Deprecation note**: This metric will be removed in favor of batch_send_trigger_size
### otelcol_process_runtime_total_alloc_bytes
Cumulative bytes allocated for heap objects (see 'go doc runtime.MemStats.TotalAlloc')
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| By | Sum | Int | true | Stable |
### otelcol_queue_capacity
Queue capacity - sync gauge example.
| Unit | Metric Type | Value Type | Stability |
| ---- | ----------- | ---------- | --------- |
| {item} | Gauge | Int | Development |
### otelcol_queue_length
This metric is optional and therefore not initialized in NewTelemetryBuilder.
For example this metric only exists if feature A is enabled.
| Unit | Metric Type | Value Type | Stability |
| ---- | ----------- | ---------- | --------- |
| {item} | Gauge | Int | Alpha |
### otelcol_request_duration
Duration of request
| Unit | Metric Type | Value Type | Stability |
| ---- | ----------- | ---------- | --------- |
| s | Histogram | Double | Alpha |
## Feature Gates
This component has the following feature gates:
| Feature Gate | Stage | Description | From Version | To Version | Reference |
| ------------ | ----- | ----------- | ------------ | ---------- | --------- |
| `receiver.sample.featuregate.example` | alpha | This is an example feature gate for testing mdatagen code generation. | v0.100.0 | N/A | [Link](https://github.com/open-telemetry/opentelemetry-collector/issues/12345) |
For more information about feature gates, see the [Feature Gates](https://github.com/open-telemetry/opentelemetry-collector/blob/main/featuregate/README.md) documentation.
================================================
FILE: cmd/mdatagen/internal/samplereceiver/factory.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package samplereceiver // import "go.opentelemetry.io/collector/cmd/mdatagen/internal/samplereceiver"
import (
"context"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/collector/cmd/mdatagen/internal/samplereceiver/internal/metadata"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/xreceiver"
)
// NewFactory returns a receiver.Factory for sample receiver.
func NewFactory() xreceiver.Factory {
return xreceiver.NewFactory(
metadata.Type,
func() component.Config { return &struct{}{} },
xreceiver.WithTraces(createTraces, metadata.TracesStability),
xreceiver.WithMetrics(createMetrics, metadata.MetricsStability),
xreceiver.WithLogs(createLogs, metadata.LogsStability),
xreceiver.WithProfiles(createProfiles, metadata.ProfilesStability),
)
}
func createTraces(context.Context, receiver.Settings, component.Config, consumer.Traces) (receiver.Traces, error) {
return nopInstance, nil
}
func createMetrics(ctx context.Context, set receiver.Settings, _ component.Config, _ consumer.Metrics) (receiver.Metrics, error) {
telemetryBuilder, err := metadata.NewTelemetryBuilder(set.TelemetrySettings)
if err != nil {
return nil, err
}
err = telemetryBuilder.RegisterProcessRuntimeTotalAllocBytesCallback(func(_ context.Context, observer metric.Int64Observer) error {
observer.Observe(2)
return nil
})
if err != nil {
return nil, err
}
telemetryBuilder.BatchSizeTriggerSend.Add(ctx, 1)
return nopReceiver{telemetryBuilder: telemetryBuilder}, nil
}
func createLogs(context.Context, receiver.Settings, component.Config, consumer.Logs) (receiver.Logs, error) {
return nopInstance, nil
}
func createProfiles(context.Context, receiver.Settings, component.Config, xconsumer.Profiles) (xreceiver.Profiles, error) {
return nopInstance, nil
}
var nopInstance = &nopReceiver{}
type nopReceiver struct {
component.StartFunc
telemetryBuilder *metadata.TelemetryBuilder
}
func (r nopReceiver) initOptionalMetric() {
_ = r.telemetryBuilder.RegisterQueueLengthCallback(func(_ context.Context, observer metric.Int64Observer) error {
observer.Observe(3)
return nil
})
}
// Shutdown shuts down the component.
func (r nopReceiver) Shutdown(context.Context) error {
if r.telemetryBuilder != nil {
r.telemetryBuilder.Shutdown()
}
return nil
}
================================================
FILE: cmd/mdatagen/internal/samplereceiver/generated_component_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
//go:build !freebsd && !illumos
package samplereceiver
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/confmap/confmaptest"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/receivertest"
"go.opentelemetry.io/collector/receiver/xreceiver"
)
var typ = component.MustNewType("sample")
func TestComponentFactoryType(t *testing.T) {
require.Equal(t, typ, NewFactory().Type())
}
func TestComponentConfigStruct(t *testing.T) {
require.NoError(t, componenttest.CheckConfigStruct(NewFactory().CreateDefaultConfig()))
}
func TestComponentLifecycle(t *testing.T) {
factory := NewFactory()
tests := []struct {
createFn func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error)
name string
}{
{
name: "logs",
createFn: func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateLogs(ctx, set, cfg, consumertest.NewNop())
},
},
{
name: "metrics",
createFn: func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateMetrics(ctx, set, cfg, consumertest.NewNop())
},
},
{
name: "traces",
createFn: func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateTraces(ctx, set, cfg, consumertest.NewNop())
},
},
{
name: "profiles",
createFn: func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error) {
return factory.(xreceiver.Factory).CreateProfiles(ctx, set, cfg, consumertest.NewNop())
},
},
}
cm, err := confmaptest.LoadConf("metadata.yaml")
require.NoError(t, err)
cfg := factory.CreateDefaultConfig()
sub, err := cm.Sub("tests::config")
require.NoError(t, err)
require.NoError(t, sub.Unmarshal(&cfg))
for _, tt := range tests {
t.Run(tt.name+"-shutdown", func(t *testing.T) {
c, err := tt.createFn(context.Background(), receivertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
err = c.Shutdown(context.Background())
require.NoError(t, err)
})
t.Run(tt.name+"-lifecycle", func(t *testing.T) {
firstRcvr, err := tt.createFn(context.Background(), receivertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
host := newMdatagenNopHost()
require.NoError(t, err)
require.NoError(t, firstRcvr.Start(context.Background(), host))
require.NoError(t, firstRcvr.Shutdown(context.Background()))
secondRcvr, err := tt.createFn(context.Background(), receivertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
require.NoError(t, secondRcvr.Start(context.Background(), host))
require.NoError(t, secondRcvr.Shutdown(context.Background()))
})
}
}
var _ component.Host = (*mdatagenNopHost)(nil)
type mdatagenNopHost struct{}
func newMdatagenNopHost() component.Host {
return &mdatagenNopHost{}
}
func (mnh *mdatagenNopHost) GetExtensions() map[component.ID]component.Component {
return nil
}
func (mnh *mdatagenNopHost) GetFactory(_ component.Kind, _ component.Type) component.Factory {
return nil
}
================================================
FILE: cmd/mdatagen/internal/samplereceiver/generated_config.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package samplereceiver
import (
"time"
"go.opentelemetry.io/collector/cmd/mdatagen/internal/samplereceiver/internal/metadata"
)
// Config defines the configuration for Sample Receiver component.
type Config struct {
metadata.MetricsBuilderConfig `mapstructure:",squash"`
// The endpoint to scrape metrics from.
Endpoint string `mapstructure:"endpoint"`
// Timeout for scraping metrics.
Timeout time.Duration `mapstructure:"timeout"`
}
================================================
FILE: cmd/mdatagen/internal/samplereceiver/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package samplereceiver
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: cmd/mdatagen/internal/samplereceiver/internal/metadata/config.schema.yaml
================================================
# Code generated by mdatagen. DO NOT EDIT.
$defs:
metrics_config:
description: MetricsConfig provides config for sample metrics.
type: object
properties:
default.metric:
description: "DefaultMetricMetricConfig provides config for the default.metric metric."
type: object
properties:
enabled:
type: boolean
default: true
aggregation_strategy:
type: string
enum:
- "sum"
- "avg"
- "min"
- "max"
default: "sum"
attributes:
type: array
items:
type: string
enum:
- "string_attr"
- "state"
- "enum_attr"
- "slice_attr"
- "map_attr"
- "conditional_int_attr"
- "conditional_string_attr"
- "opt_in_bool_attr"
default:
- "string_attr"
- "state"
- "enum_attr"
- "slice_attr"
- "map_attr"
- "conditional_int_attr"
- "conditional_string_attr"
default.metric.to_be_removed:
description: "DefaultMetricToBeRemovedMetricConfig provides config for the default.metric.to_be_removed metric."
type: object
properties:
enabled:
type: boolean
default: true
metric.input_type:
description: "MetricInputTypeMetricConfig provides config for the metric.input_type metric."
type: object
properties:
enabled:
type: boolean
default: true
aggregation_strategy:
type: string
enum:
- "sum"
- "avg"
- "min"
- "max"
default: "sum"
attributes:
type: array
items:
type: string
enum:
- "string_attr"
- "state"
- "enum_attr"
- "slice_attr"
- "map_attr"
default:
- "string_attr"
- "state"
- "enum_attr"
- "slice_attr"
- "map_attr"
optional.metric:
description: "OptionalMetricMetricConfig provides config for the optional.metric metric."
type: object
properties:
enabled:
type: boolean
default: false
aggregation_strategy:
type: string
enum:
- "sum"
- "avg"
- "min"
- "max"
default: "avg"
attributes:
type: array
items:
type: string
enum:
- "string_attr"
- "boolean_attr"
- "boolean_attr2"
- "conditional_string_attr"
default:
- "string_attr"
- "boolean_attr"
- "boolean_attr2"
- "conditional_string_attr"
optional.metric.empty_unit:
description: "OptionalMetricEmptyUnitMetricConfig provides config for the optional.metric.empty_unit metric."
type: object
properties:
enabled:
type: boolean
default: false
aggregation_strategy:
type: string
enum:
- "sum"
- "avg"
- "min"
- "max"
default: "avg"
attributes:
type: array
items:
type: string
enum:
- "string_attr"
- "boolean_attr"
default:
- "string_attr"
- "boolean_attr"
reaggregate.metric:
description: "ReaggregateMetricMetricConfig provides config for the reaggregate.metric metric."
type: object
properties:
enabled:
type: boolean
default: true
aggregation_strategy:
type: string
enum:
- "sum"
- "avg"
- "min"
- "max"
default: "avg"
attributes:
type: array
items:
type: string
enum:
- "string_attr"
- "boolean_attr"
default:
- "string_attr"
- "boolean_attr"
reaggregate.metric.with_required:
description: "ReaggregateMetricWithRequiredMetricConfig provides config for the reaggregate.metric.with_required metric."
type: object
properties:
enabled:
type: boolean
default: true
aggregation_strategy:
type: string
enum:
- "sum"
- "avg"
- "min"
- "max"
default: "avg"
attributes:
type: array
items:
type: string
enum:
- "required_string_attr"
- "string_attr"
- "boolean_attr"
default:
- "required_string_attr"
- "string_attr"
- "boolean_attr"
system.cpu.time:
description: "SystemCPUTimeMetricConfig provides config for the system.cpu.time metric."
type: object
properties:
enabled:
type: boolean
default: true
events_config:
description: EventsConfig provides config for sample events.
type: object
properties:
default.event:
description: EventConfig provides common config for a default.event event.
type: object
properties:
enabled:
type: boolean
default: true
default.event.to_be_removed:
description: EventConfig provides common config for a default.event.to_be_removed event.
type: object
properties:
enabled:
type: boolean
default: true
default.event.to_be_renamed:
description: EventConfig provides common config for a default.event.to_be_renamed event.
type: object
properties:
enabled:
type: boolean
default: false
resource_attributes_config:
description: ResourceAttributesConfig provides config for sample resource attributes.
type: object
properties:
map.resource.attr:
description: ResourceAttributeConfig provides common config for a map.resource.attr resource attribute.
type: object
properties:
enabled:
type: boolean
default: true
metrics_include:
description: "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
metrics_exclude:
description: "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude."
type: array
items:
$ref: /filter.config
events_include:
description: "Experimental: EventsInclude defines a list of filters for attribute values. If the list is not empty, only events with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
events_exclude:
description: "Experimental: EventsExclude defines a list of filters for attribute values. If the list is not empty, events with matching resource attribute values will not be emitted. EventsInclude has higher priority than EventsExclude."
type: array
items:
$ref: /filter.config
optional.resource.attr:
description: ResourceAttributeConfig provides common config for a optional.resource.attr resource attribute.
type: object
properties:
enabled:
type: boolean
default: false
metrics_include:
description: "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
metrics_exclude:
description: "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude."
type: array
items:
$ref: /filter.config
events_include:
description: "Experimental: EventsInclude defines a list of filters for attribute values. If the list is not empty, only events with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
events_exclude:
description: "Experimental: EventsExclude defines a list of filters for attribute values. If the list is not empty, events with matching resource attribute values will not be emitted. EventsInclude has higher priority than EventsExclude."
type: array
items:
$ref: /filter.config
slice.resource.attr:
description: ResourceAttributeConfig provides common config for a slice.resource.attr resource attribute.
type: object
properties:
enabled:
type: boolean
default: true
metrics_include:
description: "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
metrics_exclude:
description: "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude."
type: array
items:
$ref: /filter.config
events_include:
description: "Experimental: EventsInclude defines a list of filters for attribute values. If the list is not empty, only events with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
events_exclude:
description: "Experimental: EventsExclude defines a list of filters for attribute values. If the list is not empty, events with matching resource attribute values will not be emitted. EventsInclude has higher priority than EventsExclude."
type: array
items:
$ref: /filter.config
string.enum.resource.attr:
description: ResourceAttributeConfig provides common config for a string.enum.resource.attr resource attribute.
type: object
properties:
enabled:
type: boolean
default: true
metrics_include:
description: "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
metrics_exclude:
description: "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude."
type: array
items:
$ref: /filter.config
events_include:
description: "Experimental: EventsInclude defines a list of filters for attribute values. If the list is not empty, only events with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
events_exclude:
description: "Experimental: EventsExclude defines a list of filters for attribute values. If the list is not empty, events with matching resource attribute values will not be emitted. EventsInclude has higher priority than EventsExclude."
type: array
items:
$ref: /filter.config
string.resource.attr:
description: ResourceAttributeConfig provides common config for a string.resource.attr resource attribute.
type: object
properties:
enabled:
type: boolean
default: true
metrics_include:
description: "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
metrics_exclude:
description: "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude."
type: array
items:
$ref: /filter.config
events_include:
description: "Experimental: EventsInclude defines a list of filters for attribute values. If the list is not empty, only events with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
events_exclude:
description: "Experimental: EventsExclude defines a list of filters for attribute values. If the list is not empty, events with matching resource attribute values will not be emitted. EventsInclude has higher priority than EventsExclude."
type: array
items:
$ref: /filter.config
string.resource.attr_disable_warning:
description: ResourceAttributeConfig provides common config for a string.resource.attr_disable_warning resource attribute.
type: object
properties:
enabled:
type: boolean
default: true
metrics_include:
description: "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
metrics_exclude:
description: "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude."
type: array
items:
$ref: /filter.config
events_include:
description: "Experimental: EventsInclude defines a list of filters for attribute values. If the list is not empty, only events with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
events_exclude:
description: "Experimental: EventsExclude defines a list of filters for attribute values. If the list is not empty, events with matching resource attribute values will not be emitted. EventsInclude has higher priority than EventsExclude."
type: array
items:
$ref: /filter.config
string.resource.attr_remove_warning:
description: ResourceAttributeConfig provides common config for a string.resource.attr_remove_warning resource attribute.
type: object
properties:
enabled:
type: boolean
default: false
metrics_include:
description: "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
metrics_exclude:
description: "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude."
type: array
items:
$ref: /filter.config
events_include:
description: "Experimental: EventsInclude defines a list of filters for attribute values. If the list is not empty, only events with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
events_exclude:
description: "Experimental: EventsExclude defines a list of filters for attribute values. If the list is not empty, events with matching resource attribute values will not be emitted. EventsInclude has higher priority than EventsExclude."
type: array
items:
$ref: /filter.config
string.resource.attr_to_be_removed:
description: ResourceAttributeConfig provides common config for a string.resource.attr_to_be_removed resource attribute.
type: object
properties:
enabled:
type: boolean
default: true
metrics_include:
description: "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
metrics_exclude:
description: "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude."
type: array
items:
$ref: /filter.config
events_include:
description: "Experimental: EventsInclude defines a list of filters for attribute values. If the list is not empty, only events with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
events_exclude:
description: "Experimental: EventsExclude defines a list of filters for attribute values. If the list is not empty, events with matching resource attribute values will not be emitted. EventsInclude has higher priority than EventsExclude."
type: array
items:
$ref: /filter.config
metrics_builder_config:
description: MetricsBuilderConfig is a configuration for sample metrics builder.
type: object
properties:
metrics:
$ref: metrics_config
resource_attributes:
$ref: resource_attributes_config
logs_builder_config:
description: LogsBuilderConfig is a configuration for sample logs builder.
type: object
properties:
events:
$ref: events_config
resource_attributes:
$ref: resource_attributes_config
================================================
FILE: cmd/mdatagen/internal/samplereceiver/internal/metadata/generated_config.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"fmt"
"slices"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/filter"
)
// DefaultMetricMetricAttributeKey specifies the key of an attribute for the default.metric metric.
type DefaultMetricMetricAttributeKey string
const (
DefaultMetricMetricAttributeKeyStringAttr DefaultMetricMetricAttributeKey = "string_attr"
DefaultMetricMetricAttributeKeyOverriddenIntAttr DefaultMetricMetricAttributeKey = "state"
DefaultMetricMetricAttributeKeyEnumAttr DefaultMetricMetricAttributeKey = "enum_attr"
DefaultMetricMetricAttributeKeySliceAttr DefaultMetricMetricAttributeKey = "slice_attr"
DefaultMetricMetricAttributeKeyMapAttr DefaultMetricMetricAttributeKey = "map_attr"
DefaultMetricMetricAttributeKeyConditionalIntAttr DefaultMetricMetricAttributeKey = "conditional_int_attr"
DefaultMetricMetricAttributeKeyConditionalStringAttr DefaultMetricMetricAttributeKey = "conditional_string_attr"
DefaultMetricMetricAttributeKeyOptInBoolAttr DefaultMetricMetricAttributeKey = "opt_in_bool_attr"
)
// DefaultMetricMetricConfig provides config for the default.metric metric.
type DefaultMetricMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []DefaultMetricMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *DefaultMetricMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *DefaultMetricMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case DefaultMetricMetricAttributeKeyStringAttr, DefaultMetricMetricAttributeKeyOverriddenIntAttr, DefaultMetricMetricAttributeKeyEnumAttr, DefaultMetricMetricAttributeKeySliceAttr, DefaultMetricMetricAttributeKeyMapAttr, DefaultMetricMetricAttributeKeyConditionalIntAttr, DefaultMetricMetricAttributeKeyConditionalStringAttr, DefaultMetricMetricAttributeKeyOptInBoolAttr:
default:
return fmt.Errorf("metric default.metric doesn't have an attribute %v, valid attributes: [string_attr, state, enum_attr, slice_attr, map_attr, conditional_int_attr, conditional_string_attr, opt_in_bool_attr]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// DefaultMetricToBeRemovedMetricConfig provides config for the default.metric.to_be_removed metric.
type DefaultMetricToBeRemovedMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
}
func (ms *DefaultMetricToBeRemovedMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
// MetricInputTypeMetricAttributeKey specifies the key of an attribute for the metric.input_type metric.
type MetricInputTypeMetricAttributeKey string
const (
MetricInputTypeMetricAttributeKeyStringAttr MetricInputTypeMetricAttributeKey = "string_attr"
MetricInputTypeMetricAttributeKeyOverriddenIntAttr MetricInputTypeMetricAttributeKey = "state"
MetricInputTypeMetricAttributeKeyEnumAttr MetricInputTypeMetricAttributeKey = "enum_attr"
MetricInputTypeMetricAttributeKeySliceAttr MetricInputTypeMetricAttributeKey = "slice_attr"
MetricInputTypeMetricAttributeKeyMapAttr MetricInputTypeMetricAttributeKey = "map_attr"
)
// MetricInputTypeMetricConfig provides config for the metric.input_type metric.
type MetricInputTypeMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MetricInputTypeMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MetricInputTypeMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MetricInputTypeMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MetricInputTypeMetricAttributeKeyStringAttr, MetricInputTypeMetricAttributeKeyOverriddenIntAttr, MetricInputTypeMetricAttributeKeyEnumAttr, MetricInputTypeMetricAttributeKeySliceAttr, MetricInputTypeMetricAttributeKeyMapAttr:
default:
return fmt.Errorf("metric metric.input_type doesn't have an attribute %v, valid attributes: [string_attr, state, enum_attr, slice_attr, map_attr]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// OptionalMetricMetricAttributeKey specifies the key of an attribute for the optional.metric metric.
type OptionalMetricMetricAttributeKey string
const (
OptionalMetricMetricAttributeKeyStringAttr OptionalMetricMetricAttributeKey = "string_attr"
OptionalMetricMetricAttributeKeyBooleanAttr OptionalMetricMetricAttributeKey = "boolean_attr"
OptionalMetricMetricAttributeKeyBooleanAttr2 OptionalMetricMetricAttributeKey = "boolean_attr2"
OptionalMetricMetricAttributeKeyConditionalStringAttr OptionalMetricMetricAttributeKey = "conditional_string_attr"
)
// OptionalMetricMetricConfig provides config for the optional.metric metric.
type OptionalMetricMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []OptionalMetricMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *OptionalMetricMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *OptionalMetricMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case OptionalMetricMetricAttributeKeyStringAttr, OptionalMetricMetricAttributeKeyBooleanAttr, OptionalMetricMetricAttributeKeyBooleanAttr2, OptionalMetricMetricAttributeKeyConditionalStringAttr:
default:
return fmt.Errorf("metric optional.metric doesn't have an attribute %v, valid attributes: [string_attr, boolean_attr, boolean_attr2, conditional_string_attr]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// OptionalMetricEmptyUnitMetricAttributeKey specifies the key of an attribute for the optional.metric.empty_unit metric.
type OptionalMetricEmptyUnitMetricAttributeKey string
const (
OptionalMetricEmptyUnitMetricAttributeKeyStringAttr OptionalMetricEmptyUnitMetricAttributeKey = "string_attr"
OptionalMetricEmptyUnitMetricAttributeKeyBooleanAttr OptionalMetricEmptyUnitMetricAttributeKey = "boolean_attr"
)
// OptionalMetricEmptyUnitMetricConfig provides config for the optional.metric.empty_unit metric.
type OptionalMetricEmptyUnitMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []OptionalMetricEmptyUnitMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *OptionalMetricEmptyUnitMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *OptionalMetricEmptyUnitMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case OptionalMetricEmptyUnitMetricAttributeKeyStringAttr, OptionalMetricEmptyUnitMetricAttributeKeyBooleanAttr:
default:
return fmt.Errorf("metric optional.metric.empty_unit doesn't have an attribute %v, valid attributes: [string_attr, boolean_attr]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// ReaggregateMetricMetricAttributeKey specifies the key of an attribute for the reaggregate.metric metric.
type ReaggregateMetricMetricAttributeKey string
const (
ReaggregateMetricMetricAttributeKeyStringAttr ReaggregateMetricMetricAttributeKey = "string_attr"
ReaggregateMetricMetricAttributeKeyBooleanAttr ReaggregateMetricMetricAttributeKey = "boolean_attr"
)
// ReaggregateMetricMetricConfig provides config for the reaggregate.metric metric.
type ReaggregateMetricMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []ReaggregateMetricMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *ReaggregateMetricMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *ReaggregateMetricMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case ReaggregateMetricMetricAttributeKeyStringAttr, ReaggregateMetricMetricAttributeKeyBooleanAttr:
default:
return fmt.Errorf("metric reaggregate.metric doesn't have an attribute %v, valid attributes: [string_attr, boolean_attr]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// ReaggregateMetricWithRequiredMetricAttributeKey specifies the key of an attribute for the reaggregate.metric.with_required metric.
type ReaggregateMetricWithRequiredMetricAttributeKey string
const (
ReaggregateMetricWithRequiredMetricAttributeKeyRequiredStringAttr ReaggregateMetricWithRequiredMetricAttributeKey = "required_string_attr"
ReaggregateMetricWithRequiredMetricAttributeKeyStringAttr ReaggregateMetricWithRequiredMetricAttributeKey = "string_attr"
ReaggregateMetricWithRequiredMetricAttributeKeyBooleanAttr ReaggregateMetricWithRequiredMetricAttributeKey = "boolean_attr"
)
// ReaggregateMetricWithRequiredMetricConfig provides config for the reaggregate.metric.with_required metric.
type ReaggregateMetricWithRequiredMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []ReaggregateMetricWithRequiredMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *ReaggregateMetricWithRequiredMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *ReaggregateMetricWithRequiredMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case ReaggregateMetricWithRequiredMetricAttributeKeyRequiredStringAttr, ReaggregateMetricWithRequiredMetricAttributeKeyStringAttr, ReaggregateMetricWithRequiredMetricAttributeKeyBooleanAttr:
default:
return fmt.Errorf("metric reaggregate.metric.with_required doesn't have an attribute %v, valid attributes: [required_string_attr, string_attr, boolean_attr]", val)
}
}
if !slices.Contains(ms.EnabledAttributes, ReaggregateMetricWithRequiredMetricAttributeKeyRequiredStringAttr) {
return fmt.Errorf("required_string_attr is a required attribute for reaggregate.metric.with_required metric and must be included")
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// SystemCPUTimeMetricConfig provides config for the system.cpu.time metric.
type SystemCPUTimeMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
}
func (ms *SystemCPUTimeMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
// MetricsConfig provides config for sample metrics.
type MetricsConfig struct {
DefaultMetric DefaultMetricMetricConfig `mapstructure:"default.metric"`
DefaultMetricToBeRemoved DefaultMetricToBeRemovedMetricConfig `mapstructure:"default.metric.to_be_removed"`
MetricInputType MetricInputTypeMetricConfig `mapstructure:"metric.input_type"`
OptionalMetric OptionalMetricMetricConfig `mapstructure:"optional.metric"`
OptionalMetricEmptyUnit OptionalMetricEmptyUnitMetricConfig `mapstructure:"optional.metric.empty_unit"`
ReaggregateMetric ReaggregateMetricMetricConfig `mapstructure:"reaggregate.metric"`
ReaggregateMetricWithRequired ReaggregateMetricWithRequiredMetricConfig `mapstructure:"reaggregate.metric.with_required"`
SystemCPUTime SystemCPUTimeMetricConfig `mapstructure:"system.cpu.time"`
}
func DefaultMetricsConfig() MetricsConfig {
return MetricsConfig{
DefaultMetric: DefaultMetricMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategySum,
EnabledAttributes: []DefaultMetricMetricAttributeKey{DefaultMetricMetricAttributeKeyStringAttr, DefaultMetricMetricAttributeKeyOverriddenIntAttr, DefaultMetricMetricAttributeKeyEnumAttr, DefaultMetricMetricAttributeKeySliceAttr, DefaultMetricMetricAttributeKeyMapAttr, DefaultMetricMetricAttributeKeyConditionalIntAttr, DefaultMetricMetricAttributeKeyConditionalStringAttr},
},
DefaultMetricToBeRemoved: DefaultMetricToBeRemovedMetricConfig{
Enabled: true,
},
MetricInputType: MetricInputTypeMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategySum,
EnabledAttributes: []MetricInputTypeMetricAttributeKey{MetricInputTypeMetricAttributeKeyStringAttr, MetricInputTypeMetricAttributeKeyOverriddenIntAttr, MetricInputTypeMetricAttributeKeyEnumAttr, MetricInputTypeMetricAttributeKeySliceAttr, MetricInputTypeMetricAttributeKeyMapAttr},
},
OptionalMetric: OptionalMetricMetricConfig{
Enabled: false,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []OptionalMetricMetricAttributeKey{OptionalMetricMetricAttributeKeyStringAttr, OptionalMetricMetricAttributeKeyBooleanAttr, OptionalMetricMetricAttributeKeyBooleanAttr2, OptionalMetricMetricAttributeKeyConditionalStringAttr},
},
OptionalMetricEmptyUnit: OptionalMetricEmptyUnitMetricConfig{
Enabled: false,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []OptionalMetricEmptyUnitMetricAttributeKey{OptionalMetricEmptyUnitMetricAttributeKeyStringAttr, OptionalMetricEmptyUnitMetricAttributeKeyBooleanAttr},
},
ReaggregateMetric: ReaggregateMetricMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []ReaggregateMetricMetricAttributeKey{ReaggregateMetricMetricAttributeKeyStringAttr, ReaggregateMetricMetricAttributeKeyBooleanAttr},
},
ReaggregateMetricWithRequired: ReaggregateMetricWithRequiredMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []ReaggregateMetricWithRequiredMetricAttributeKey{ReaggregateMetricWithRequiredMetricAttributeKeyRequiredStringAttr, ReaggregateMetricWithRequiredMetricAttributeKeyStringAttr, ReaggregateMetricWithRequiredMetricAttributeKeyBooleanAttr},
},
SystemCPUTime: SystemCPUTimeMetricConfig{
Enabled: true,
},
}
}
// EventConfig provides common config for a particular event.
type EventConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
}
func (ec *EventConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ec)
if err != nil {
return err
}
ec.enabledSetByUser = parser.IsSet("enabled")
return nil
}
// EventsConfig provides config for sample events.
type EventsConfig struct {
DefaultEvent EventConfig `mapstructure:"default.event"`
DefaultEventToBeRemoved EventConfig `mapstructure:"default.event.to_be_removed"`
DefaultEventToBeRenamed EventConfig `mapstructure:"default.event.to_be_renamed"`
}
func DefaultEventsConfig() EventsConfig {
return EventsConfig{
DefaultEvent: EventConfig{
Enabled: true,
},
DefaultEventToBeRemoved: EventConfig{
Enabled: true,
},
DefaultEventToBeRenamed: EventConfig{
Enabled: false,
},
}
}
// ResourceAttributeConfig provides common config for a particular resource attribute.
type ResourceAttributeConfig struct {
Enabled bool `mapstructure:"enabled"`
// Experimental: MetricsInclude defines a list of filters for attribute values.
// If the list is not empty, only metrics with matching resource attribute values will be emitted.
MetricsInclude []filter.Config `mapstructure:"metrics_include"`
// Experimental: MetricsExclude defines a list of filters for attribute values.
// If the list is not empty, metrics with matching resource attribute values will not be emitted.
// MetricsInclude has higher priority than MetricsExclude.
MetricsExclude []filter.Config `mapstructure:"metrics_exclude"`
// Experimental: EventsInclude defines a list of filters for attribute values.
// If the list is not empty, only events with matching resource attribute values will be emitted.
EventsInclude []filter.Config `mapstructure:"events_include"`
// Experimental: EventsExclude defines a list of filters for attribute values.
// If the list is not empty, events with matching resource attribute values will not be emitted.
// EventsInclude has higher priority than EventsExclude.
EventsExclude []filter.Config `mapstructure:"events_exclude"`
enabledSetByUser bool
}
func (rac *ResourceAttributeConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(rac)
if err != nil {
return err
}
rac.enabledSetByUser = parser.IsSet("enabled")
return nil
}
// ResourceAttributesConfig provides config for sample resource attributes.
type ResourceAttributesConfig struct {
MapResourceAttr ResourceAttributeConfig `mapstructure:"map.resource.attr"`
OptionalResourceAttr ResourceAttributeConfig `mapstructure:"optional.resource.attr"`
SliceResourceAttr ResourceAttributeConfig `mapstructure:"slice.resource.attr"`
StringEnumResourceAttr ResourceAttributeConfig `mapstructure:"string.enum.resource.attr"`
StringResourceAttr ResourceAttributeConfig `mapstructure:"string.resource.attr"`
StringResourceAttrDisableWarning ResourceAttributeConfig `mapstructure:"string.resource.attr_disable_warning"`
StringResourceAttrRemoveWarning ResourceAttributeConfig `mapstructure:"string.resource.attr_remove_warning"`
StringResourceAttrToBeRemoved ResourceAttributeConfig `mapstructure:"string.resource.attr_to_be_removed"`
}
func DefaultResourceAttributesConfig() ResourceAttributesConfig {
return ResourceAttributesConfig{
MapResourceAttr: ResourceAttributeConfig{
Enabled: true,
},
OptionalResourceAttr: ResourceAttributeConfig{
Enabled: false,
},
SliceResourceAttr: ResourceAttributeConfig{
Enabled: true,
},
StringEnumResourceAttr: ResourceAttributeConfig{
Enabled: true,
},
StringResourceAttr: ResourceAttributeConfig{
Enabled: true,
},
StringResourceAttrDisableWarning: ResourceAttributeConfig{
Enabled: true,
},
StringResourceAttrRemoveWarning: ResourceAttributeConfig{
Enabled: false,
},
StringResourceAttrToBeRemoved: ResourceAttributeConfig{
Enabled: true,
},
}
}
// MetricsBuilderConfig is a configuration for sample metrics builder.
type MetricsBuilderConfig struct {
Metrics MetricsConfig `mapstructure:"metrics"`
ResourceAttributes ResourceAttributesConfig `mapstructure:"resource_attributes"`
}
func DefaultMetricsBuilderConfig() MetricsBuilderConfig {
return MetricsBuilderConfig{
Metrics: DefaultMetricsConfig(),
ResourceAttributes: DefaultResourceAttributesConfig(),
}
}
// LogsBuilderConfig is a configuration for sample logs builder.
type LogsBuilderConfig struct {
Events EventsConfig `mapstructure:"events"`
ResourceAttributes ResourceAttributesConfig `mapstructure:"resource_attributes"`
}
func DefaultLogsBuilderConfig() LogsBuilderConfig {
return LogsBuilderConfig{
Events: DefaultEventsConfig(),
ResourceAttributes: DefaultResourceAttributesConfig(),
}
}
================================================
FILE: cmd/mdatagen/internal/samplereceiver/internal/metadata/generated_config_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"path/filepath"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/confmaptest"
)
func TestMetricsBuilderConfig(t *testing.T) {
tests := []struct {
name string
want MetricsBuilderConfig
}{
{
name: "default",
want: DefaultMetricsBuilderConfig(),
},
{
name: "all_set",
want: MetricsBuilderConfig{
Metrics: MetricsConfig{
DefaultMetric: DefaultMetricMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategySum,
EnabledAttributes: []DefaultMetricMetricAttributeKey{DefaultMetricMetricAttributeKeyStringAttr, DefaultMetricMetricAttributeKeyOverriddenIntAttr, DefaultMetricMetricAttributeKeyEnumAttr, DefaultMetricMetricAttributeKeySliceAttr, DefaultMetricMetricAttributeKeyMapAttr, DefaultMetricMetricAttributeKeyConditionalIntAttr, DefaultMetricMetricAttributeKeyConditionalStringAttr, DefaultMetricMetricAttributeKeyOptInBoolAttr},
},
DefaultMetricToBeRemoved: DefaultMetricToBeRemovedMetricConfig{
Enabled: true,
},
MetricInputType: MetricInputTypeMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategySum,
EnabledAttributes: []MetricInputTypeMetricAttributeKey{MetricInputTypeMetricAttributeKeyStringAttr, MetricInputTypeMetricAttributeKeyOverriddenIntAttr, MetricInputTypeMetricAttributeKeyEnumAttr, MetricInputTypeMetricAttributeKeySliceAttr, MetricInputTypeMetricAttributeKeyMapAttr},
},
OptionalMetric: OptionalMetricMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []OptionalMetricMetricAttributeKey{OptionalMetricMetricAttributeKeyStringAttr, OptionalMetricMetricAttributeKeyBooleanAttr, OptionalMetricMetricAttributeKeyBooleanAttr2, OptionalMetricMetricAttributeKeyConditionalStringAttr},
},
OptionalMetricEmptyUnit: OptionalMetricEmptyUnitMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []OptionalMetricEmptyUnitMetricAttributeKey{OptionalMetricEmptyUnitMetricAttributeKeyStringAttr, OptionalMetricEmptyUnitMetricAttributeKeyBooleanAttr},
},
ReaggregateMetric: ReaggregateMetricMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []ReaggregateMetricMetricAttributeKey{ReaggregateMetricMetricAttributeKeyStringAttr, ReaggregateMetricMetricAttributeKeyBooleanAttr},
},
ReaggregateMetricWithRequired: ReaggregateMetricWithRequiredMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []ReaggregateMetricWithRequiredMetricAttributeKey{ReaggregateMetricWithRequiredMetricAttributeKeyRequiredStringAttr, ReaggregateMetricWithRequiredMetricAttributeKeyStringAttr, ReaggregateMetricWithRequiredMetricAttributeKeyBooleanAttr},
},
SystemCPUTime: SystemCPUTimeMetricConfig{
Enabled: true,
},
},
ResourceAttributes: ResourceAttributesConfig{
MapResourceAttr: ResourceAttributeConfig{Enabled: true},
OptionalResourceAttr: ResourceAttributeConfig{Enabled: true},
SliceResourceAttr: ResourceAttributeConfig{Enabled: true},
StringEnumResourceAttr: ResourceAttributeConfig{Enabled: true},
StringResourceAttr: ResourceAttributeConfig{Enabled: true},
StringResourceAttrDisableWarning: ResourceAttributeConfig{Enabled: true},
StringResourceAttrRemoveWarning: ResourceAttributeConfig{Enabled: true},
StringResourceAttrToBeRemoved: ResourceAttributeConfig{Enabled: true},
},
},
},
{
name: "none_set",
want: MetricsBuilderConfig{
Metrics: MetricsConfig{
DefaultMetric: DefaultMetricMetricConfig{
Enabled: false,
AggregationStrategy: AggregationStrategySum,
EnabledAttributes: []DefaultMetricMetricAttributeKey{DefaultMetricMetricAttributeKeyStringAttr, DefaultMetricMetricAttributeKeyOverriddenIntAttr, DefaultMetricMetricAttributeKeyEnumAttr, DefaultMetricMetricAttributeKeySliceAttr, DefaultMetricMetricAttributeKeyMapAttr, DefaultMetricMetricAttributeKeyConditionalIntAttr, DefaultMetricMetricAttributeKeyConditionalStringAttr, DefaultMetricMetricAttributeKeyOptInBoolAttr},
},
DefaultMetricToBeRemoved: DefaultMetricToBeRemovedMetricConfig{
Enabled: false,
},
MetricInputType: MetricInputTypeMetricConfig{
Enabled: false,
AggregationStrategy: AggregationStrategySum,
EnabledAttributes: []MetricInputTypeMetricAttributeKey{MetricInputTypeMetricAttributeKeyStringAttr, MetricInputTypeMetricAttributeKeyOverriddenIntAttr, MetricInputTypeMetricAttributeKeyEnumAttr, MetricInputTypeMetricAttributeKeySliceAttr, MetricInputTypeMetricAttributeKeyMapAttr},
},
OptionalMetric: OptionalMetricMetricConfig{
Enabled: false,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []OptionalMetricMetricAttributeKey{OptionalMetricMetricAttributeKeyStringAttr, OptionalMetricMetricAttributeKeyBooleanAttr, OptionalMetricMetricAttributeKeyBooleanAttr2, OptionalMetricMetricAttributeKeyConditionalStringAttr},
},
OptionalMetricEmptyUnit: OptionalMetricEmptyUnitMetricConfig{
Enabled: false,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []OptionalMetricEmptyUnitMetricAttributeKey{OptionalMetricEmptyUnitMetricAttributeKeyStringAttr, OptionalMetricEmptyUnitMetricAttributeKeyBooleanAttr},
},
ReaggregateMetric: ReaggregateMetricMetricConfig{
Enabled: false,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []ReaggregateMetricMetricAttributeKey{ReaggregateMetricMetricAttributeKeyStringAttr, ReaggregateMetricMetricAttributeKeyBooleanAttr},
},
ReaggregateMetricWithRequired: ReaggregateMetricWithRequiredMetricConfig{
Enabled: false,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []ReaggregateMetricWithRequiredMetricAttributeKey{ReaggregateMetricWithRequiredMetricAttributeKeyRequiredStringAttr, ReaggregateMetricWithRequiredMetricAttributeKeyStringAttr, ReaggregateMetricWithRequiredMetricAttributeKeyBooleanAttr},
},
SystemCPUTime: SystemCPUTimeMetricConfig{
Enabled: false,
},
},
ResourceAttributes: ResourceAttributesConfig{
MapResourceAttr: ResourceAttributeConfig{Enabled: false},
OptionalResourceAttr: ResourceAttributeConfig{Enabled: false},
SliceResourceAttr: ResourceAttributeConfig{Enabled: false},
StringEnumResourceAttr: ResourceAttributeConfig{Enabled: false},
StringResourceAttr: ResourceAttributeConfig{Enabled: false},
StringResourceAttrDisableWarning: ResourceAttributeConfig{Enabled: false},
StringResourceAttrRemoveWarning: ResourceAttributeConfig{Enabled: false},
StringResourceAttrToBeRemoved: ResourceAttributeConfig{Enabled: false},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := loadMetricsBuilderConfig(t, tt.name)
diff := cmp.Diff(tt.want, cfg, cmpopts.IgnoreUnexported(DefaultMetricMetricConfig{}, DefaultMetricToBeRemovedMetricConfig{}, MetricInputTypeMetricConfig{}, OptionalMetricMetricConfig{}, OptionalMetricEmptyUnitMetricConfig{}, ReaggregateMetricMetricConfig{}, ReaggregateMetricWithRequiredMetricConfig{}, SystemCPUTimeMetricConfig{}, ResourceAttributeConfig{}))
require.Emptyf(t, diff, "Config mismatch (-expected +actual):\n%s", diff)
})
}
}
func loadMetricsBuilderConfig(t *testing.T, name string) MetricsBuilderConfig {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
require.NoError(t, err)
sub, err := cm.Sub(name)
require.NoError(t, err)
cfg := DefaultMetricsBuilderConfig()
require.NoError(t, sub.Unmarshal(&cfg, confmap.WithIgnoreUnused()))
return cfg
}
func loadLogsBuilderConfig(t *testing.T, name string) LogsBuilderConfig {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
require.NoError(t, err)
sub, err := cm.Sub(name)
require.NoError(t, err)
cfg := DefaultLogsBuilderConfig()
require.NoError(t, sub.Unmarshal(&cfg, confmap.WithIgnoreUnused()))
return cfg
}
func TestResourceAttributesConfig(t *testing.T) {
tests := []struct {
name string
want ResourceAttributesConfig
}{
{
name: "default",
want: DefaultResourceAttributesConfig(),
},
{
name: "all_set",
want: ResourceAttributesConfig{
MapResourceAttr: ResourceAttributeConfig{Enabled: true},
OptionalResourceAttr: ResourceAttributeConfig{Enabled: true},
SliceResourceAttr: ResourceAttributeConfig{Enabled: true},
StringEnumResourceAttr: ResourceAttributeConfig{Enabled: true},
StringResourceAttr: ResourceAttributeConfig{Enabled: true},
StringResourceAttrDisableWarning: ResourceAttributeConfig{Enabled: true},
StringResourceAttrRemoveWarning: ResourceAttributeConfig{Enabled: true},
StringResourceAttrToBeRemoved: ResourceAttributeConfig{Enabled: true},
},
},
{
name: "none_set",
want: ResourceAttributesConfig{
MapResourceAttr: ResourceAttributeConfig{Enabled: false},
OptionalResourceAttr: ResourceAttributeConfig{Enabled: false},
SliceResourceAttr: ResourceAttributeConfig{Enabled: false},
StringEnumResourceAttr: ResourceAttributeConfig{Enabled: false},
StringResourceAttr: ResourceAttributeConfig{Enabled: false},
StringResourceAttrDisableWarning: ResourceAttributeConfig{Enabled: false},
StringResourceAttrRemoveWarning: ResourceAttributeConfig{Enabled: false},
StringResourceAttrToBeRemoved: ResourceAttributeConfig{Enabled: false},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := loadResourceAttributesConfig(t, tt.name)
diff := cmp.Diff(tt.want, cfg, cmpopts.IgnoreUnexported(ResourceAttributeConfig{}))
require.Emptyf(t, diff, "Config mismatch (-expected +actual):\n%s", diff)
})
}
}
func loadResourceAttributesConfig(t *testing.T, name string) ResourceAttributesConfig {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
require.NoError(t, err)
sub, err := cm.Sub(name)
require.NoError(t, err)
sub, err = sub.Sub("resource_attributes")
require.NoError(t, err)
cfg := DefaultResourceAttributesConfig()
require.NoError(t, sub.Unmarshal(&cfg))
return cfg
}
================================================
FILE: cmd/mdatagen/internal/samplereceiver/internal/metadata/generated_feature_gates.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/featuregate"
)
var ReceiverSampleFeaturegateExampleFeatureGate = featuregate.GlobalRegistry().MustRegister(
"receiver.sample.featuregate.example",
featuregate.StageAlpha,
featuregate.WithRegisterDescription("This is an example feature gate for testing mdatagen code generation."),
featuregate.WithRegisterReferenceURL("https://github.com/open-telemetry/opentelemetry-collector/issues/12345"),
featuregate.WithRegisterFromVersion("v0.100.0"),
)
================================================
FILE: cmd/mdatagen/internal/samplereceiver/internal/metadata/generated_logs.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"context"
conventions "go.opentelemetry.io/otel/semconv/v1.38.0"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/filter"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/receiver"
)
type EventAttributeOption interface {
apply(plog.LogRecord)
}
type eventAttributeOptionFunc func(plog.LogRecord)
func (eaof eventAttributeOptionFunc) apply(lr plog.LogRecord) {
eaof(lr)
}
func WithConditionalIntAttrEventAttribute(conditionalIntAttrAttributeValue int64) EventAttributeOption {
return eventAttributeOptionFunc(func(dp plog.LogRecord) {
dp.Attributes().PutInt("conditional_int_attr", conditionalIntAttrAttributeValue)
})
}
func WithConditionalStringAttrEventAttribute(conditionalStringAttrAttributeValue string) EventAttributeOption {
return eventAttributeOptionFunc(func(dp plog.LogRecord) {
dp.Attributes().PutStr("conditional_string_attr", conditionalStringAttrAttributeValue)
})
}
type eventDefaultEvent struct {
data plog.LogRecordSlice // data buffer for generated log records.
config EventConfig // event config provided by user.
}
func (e *eventDefaultEvent) recordEvent(ctx context.Context, timestamp pcommon.Timestamp, stringAttrAttributeValue string, overriddenIntAttrAttributeValue int64, enumAttrAttributeValue string, sliceAttrAttributeValue []any, mapAttrAttributeValue map[string]any, optInBoolAttrAttributeValue bool, options ...EventAttributeOption) {
if !e.config.Enabled {
return
}
dp := e.data.AppendEmpty()
dp.SetEventName("default.event")
dp.SetTimestamp(timestamp)
if span := trace.SpanContextFromContext(ctx); span.IsValid() {
dp.SetTraceID(pcommon.TraceID(span.TraceID()))
dp.SetSpanID(pcommon.SpanID(span.SpanID()))
}
dp.Attributes().PutStr("string_attr", stringAttrAttributeValue)
dp.Attributes().PutInt("state", overriddenIntAttrAttributeValue)
dp.Attributes().PutStr("enum_attr", enumAttrAttributeValue)
dp.Attributes().PutEmptySlice("slice_attr").FromRaw(sliceAttrAttributeValue)
dp.Attributes().PutEmptyMap("map_attr").FromRaw(mapAttrAttributeValue)
dp.Attributes().PutBool("opt_in_bool_attr", optInBoolAttrAttributeValue)
for _, op := range options {
op.apply(dp)
}
}
// emit appends recorded event data to a events slice and prepares it for recording another set of log records.
func (e *eventDefaultEvent) emit(lrs plog.LogRecordSlice) {
if e.config.Enabled && e.data.Len() > 0 {
e.data.MoveAndAppendTo(lrs)
}
}
func newEventDefaultEvent(cfg EventConfig) eventDefaultEvent {
e := eventDefaultEvent{config: cfg}
if cfg.Enabled {
e.data = plog.NewLogRecordSlice()
}
return e
}
type eventDefaultEventToBeRemoved struct {
data plog.LogRecordSlice // data buffer for generated log records.
config EventConfig // event config provided by user.
}
func (e *eventDefaultEventToBeRemoved) recordEvent(ctx context.Context, timestamp pcommon.Timestamp, stringAttrAttributeValue string, overriddenIntAttrAttributeValue int64, enumAttrAttributeValue string, sliceAttrAttributeValue []any, mapAttrAttributeValue map[string]any) {
if !e.config.Enabled {
return
}
dp := e.data.AppendEmpty()
dp.SetEventName("default.event.to_be_removed")
dp.SetTimestamp(timestamp)
if span := trace.SpanContextFromContext(ctx); span.IsValid() {
dp.SetTraceID(pcommon.TraceID(span.TraceID()))
dp.SetSpanID(pcommon.SpanID(span.SpanID()))
}
dp.Attributes().PutStr("string_attr", stringAttrAttributeValue)
dp.Attributes().PutInt("state", overriddenIntAttrAttributeValue)
dp.Attributes().PutStr("enum_attr", enumAttrAttributeValue)
dp.Attributes().PutEmptySlice("slice_attr").FromRaw(sliceAttrAttributeValue)
dp.Attributes().PutEmptyMap("map_attr").FromRaw(mapAttrAttributeValue)
}
// emit appends recorded event data to a events slice and prepares it for recording another set of log records.
func (e *eventDefaultEventToBeRemoved) emit(lrs plog.LogRecordSlice) {
if e.config.Enabled && e.data.Len() > 0 {
e.data.MoveAndAppendTo(lrs)
}
}
func newEventDefaultEventToBeRemoved(cfg EventConfig) eventDefaultEventToBeRemoved {
e := eventDefaultEventToBeRemoved{config: cfg}
if cfg.Enabled {
e.data = plog.NewLogRecordSlice()
}
return e
}
type eventDefaultEventToBeRenamed struct {
data plog.LogRecordSlice // data buffer for generated log records.
config EventConfig // event config provided by user.
}
func (e *eventDefaultEventToBeRenamed) recordEvent(ctx context.Context, timestamp pcommon.Timestamp, stringAttrAttributeValue string, booleanAttrAttributeValue bool, booleanAttr2AttributeValue bool, options ...EventAttributeOption) {
if !e.config.Enabled {
return
}
dp := e.data.AppendEmpty()
dp.SetEventName("default.event.to_be_renamed")
dp.SetTimestamp(timestamp)
if span := trace.SpanContextFromContext(ctx); span.IsValid() {
dp.SetTraceID(pcommon.TraceID(span.TraceID()))
dp.SetSpanID(pcommon.SpanID(span.SpanID()))
}
dp.Attributes().PutStr("string_attr", stringAttrAttributeValue)
dp.Attributes().PutBool("boolean_attr", booleanAttrAttributeValue)
dp.Attributes().PutBool("boolean_attr2", booleanAttr2AttributeValue)
for _, op := range options {
op.apply(dp)
}
}
// emit appends recorded event data to a events slice and prepares it for recording another set of log records.
func (e *eventDefaultEventToBeRenamed) emit(lrs plog.LogRecordSlice) {
if e.config.Enabled && e.data.Len() > 0 {
e.data.MoveAndAppendTo(lrs)
}
}
func newEventDefaultEventToBeRenamed(cfg EventConfig) eventDefaultEventToBeRenamed {
e := eventDefaultEventToBeRenamed{config: cfg}
if cfg.Enabled {
e.data = plog.NewLogRecordSlice()
}
return e
}
// LogsBuilder provides an interface for scrapers to report logs while taking care of all the transformations
// required to produce log representation defined in metadata and user config.
type LogsBuilder struct {
config LogsBuilderConfig // config of the logs builder.
logsBuffer plog.Logs
logRecordsBuffer plog.LogRecordSlice
buildInfo component.BuildInfo // contains version information.
resourceAttributeIncludeFilter map[string]filter.Filter
resourceAttributeExcludeFilter map[string]filter.Filter
eventDefaultEvent eventDefaultEvent
eventDefaultEventToBeRemoved eventDefaultEventToBeRemoved
eventDefaultEventToBeRenamed eventDefaultEventToBeRenamed
}
// LogBuilderOption applies changes to default logs builder.
type LogBuilderOption interface {
apply(*LogsBuilder)
}
func NewLogsBuilder(lbc LogsBuilderConfig, settings receiver.Settings) *LogsBuilder {
if !lbc.Events.DefaultEvent.enabledSetByUser {
settings.Logger.Warn("[WARNING] Please set `enabled` field explicitly for `default.event`: This event will be disabled by default soon.")
}
if lbc.Events.DefaultEventToBeRemoved.Enabled {
settings.Logger.Warn("[WARNING] `default.event.to_be_removed` should not be enabled: This event is deprecated and will be removed soon.")
}
if lbc.Events.DefaultEventToBeRenamed.enabledSetByUser {
settings.Logger.Warn("[WARNING] `default.event.to_be_renamed` should not be configured: This event is deprecated and will be renamed soon.")
}
if !lbc.ResourceAttributes.StringResourceAttrDisableWarning.enabledSetByUser {
settings.Logger.Warn("[WARNING] Please set `enabled` field explicitly for `string.resource.attr_disable_warning`: This resource_attribute will be disabled by default soon.")
}
if lbc.ResourceAttributes.StringResourceAttrRemoveWarning.enabledSetByUser || lbc.ResourceAttributes.StringResourceAttrRemoveWarning.EventsInclude != nil || lbc.ResourceAttributes.StringResourceAttrRemoveWarning.EventsExclude != nil {
settings.Logger.Warn("[WARNING] `string.resource.attr_remove_warning` should not be configured: This resource_attribute is deprecated and will be removed soon.")
}
if lbc.ResourceAttributes.StringResourceAttrToBeRemoved.Enabled {
settings.Logger.Warn("[WARNING] `string.resource.attr_to_be_removed` should not be enabled: This resource_attribute is deprecated and will be removed soon.")
}
lb := &LogsBuilder{
config: lbc,
logsBuffer: plog.NewLogs(),
logRecordsBuffer: plog.NewLogRecordSlice(),
buildInfo: settings.BuildInfo,
eventDefaultEvent: newEventDefaultEvent(lbc.Events.DefaultEvent),
eventDefaultEventToBeRemoved: newEventDefaultEventToBeRemoved(lbc.Events.DefaultEventToBeRemoved),
eventDefaultEventToBeRenamed: newEventDefaultEventToBeRenamed(lbc.Events.DefaultEventToBeRenamed),
resourceAttributeIncludeFilter: make(map[string]filter.Filter),
resourceAttributeExcludeFilter: make(map[string]filter.Filter),
}
if lbc.ResourceAttributes.MapResourceAttr.EventsInclude != nil {
lb.resourceAttributeIncludeFilter["map.resource.attr"] = filter.CreateFilter(lbc.ResourceAttributes.MapResourceAttr.EventsInclude)
}
if lbc.ResourceAttributes.MapResourceAttr.EventsExclude != nil {
lb.resourceAttributeExcludeFilter["map.resource.attr"] = filter.CreateFilter(lbc.ResourceAttributes.MapResourceAttr.EventsExclude)
}
if lbc.ResourceAttributes.OptionalResourceAttr.EventsInclude != nil {
lb.resourceAttributeIncludeFilter["optional.resource.attr"] = filter.CreateFilter(lbc.ResourceAttributes.OptionalResourceAttr.EventsInclude)
}
if lbc.ResourceAttributes.OptionalResourceAttr.EventsExclude != nil {
lb.resourceAttributeExcludeFilter["optional.resource.attr"] = filter.CreateFilter(lbc.ResourceAttributes.OptionalResourceAttr.EventsExclude)
}
if lbc.ResourceAttributes.SliceResourceAttr.EventsInclude != nil {
lb.resourceAttributeIncludeFilter["slice.resource.attr"] = filter.CreateFilter(lbc.ResourceAttributes.SliceResourceAttr.EventsInclude)
}
if lbc.ResourceAttributes.SliceResourceAttr.EventsExclude != nil {
lb.resourceAttributeExcludeFilter["slice.resource.attr"] = filter.CreateFilter(lbc.ResourceAttributes.SliceResourceAttr.EventsExclude)
}
if lbc.ResourceAttributes.StringEnumResourceAttr.EventsInclude != nil {
lb.resourceAttributeIncludeFilter["string.enum.resource.attr"] = filter.CreateFilter(lbc.ResourceAttributes.StringEnumResourceAttr.EventsInclude)
}
if lbc.ResourceAttributes.StringEnumResourceAttr.EventsExclude != nil {
lb.resourceAttributeExcludeFilter["string.enum.resource.attr"] = filter.CreateFilter(lbc.ResourceAttributes.StringEnumResourceAttr.EventsExclude)
}
if lbc.ResourceAttributes.StringResourceAttr.EventsInclude != nil {
lb.resourceAttributeIncludeFilter["string.resource.attr"] = filter.CreateFilter(lbc.ResourceAttributes.StringResourceAttr.EventsInclude)
}
if lbc.ResourceAttributes.StringResourceAttr.EventsExclude != nil {
lb.resourceAttributeExcludeFilter["string.resource.attr"] = filter.CreateFilter(lbc.ResourceAttributes.StringResourceAttr.EventsExclude)
}
if lbc.ResourceAttributes.StringResourceAttrDisableWarning.EventsInclude != nil {
lb.resourceAttributeIncludeFilter["string.resource.attr_disable_warning"] = filter.CreateFilter(lbc.ResourceAttributes.StringResourceAttrDisableWarning.EventsInclude)
}
if lbc.ResourceAttributes.StringResourceAttrDisableWarning.EventsExclude != nil {
lb.resourceAttributeExcludeFilter["string.resource.attr_disable_warning"] = filter.CreateFilter(lbc.ResourceAttributes.StringResourceAttrDisableWarning.EventsExclude)
}
if lbc.ResourceAttributes.StringResourceAttrRemoveWarning.EventsInclude != nil {
lb.resourceAttributeIncludeFilter["string.resource.attr_remove_warning"] = filter.CreateFilter(lbc.ResourceAttributes.StringResourceAttrRemoveWarning.EventsInclude)
}
if lbc.ResourceAttributes.StringResourceAttrRemoveWarning.EventsExclude != nil {
lb.resourceAttributeExcludeFilter["string.resource.attr_remove_warning"] = filter.CreateFilter(lbc.ResourceAttributes.StringResourceAttrRemoveWarning.EventsExclude)
}
if lbc.ResourceAttributes.StringResourceAttrToBeRemoved.EventsInclude != nil {
lb.resourceAttributeIncludeFilter["string.resource.attr_to_be_removed"] = filter.CreateFilter(lbc.ResourceAttributes.StringResourceAttrToBeRemoved.EventsInclude)
}
if lbc.ResourceAttributes.StringResourceAttrToBeRemoved.EventsExclude != nil {
lb.resourceAttributeExcludeFilter["string.resource.attr_to_be_removed"] = filter.CreateFilter(lbc.ResourceAttributes.StringResourceAttrToBeRemoved.EventsExclude)
}
return lb
}
// NewResourceBuilder returns a new resource builder that should be used to build a resource associated with for the emitted logs.
func (lb *LogsBuilder) NewResourceBuilder() *ResourceBuilder {
return NewResourceBuilder(lb.config.ResourceAttributes)
}
// ResourceLogsOption applies changes to provided resource logs.
type ResourceLogsOption interface {
apply(plog.ResourceLogs)
}
type resourceLogsOptionFunc func(plog.ResourceLogs)
func (rlof resourceLogsOptionFunc) apply(rl plog.ResourceLogs) {
rlof(rl)
}
// WithLogsResource sets the provided resource on the emitted ResourceLogs.
// It's recommended to use ResourceBuilder to create the resource.
func WithLogsResource(res pcommon.Resource) ResourceLogsOption {
return resourceLogsOptionFunc(func(rl plog.ResourceLogs) {
res.CopyTo(rl.Resource())
})
}
// AppendLogRecord adds a log record to the logs builder.
func (lb *LogsBuilder) AppendLogRecord(lr plog.LogRecord) {
lr.MoveTo(lb.logRecordsBuffer.AppendEmpty())
}
// EmitForResource saves all the generated logs under a new resource and updates the internal state to be ready for
// recording another set of log records as part of another resource. This function can be helpful when one scraper
// needs to emit logs from several resources. Otherwise calling this function is not required,
// just `Emit` function can be called instead.
// Resource attributes should be provided as ResourceLogsOption arguments.
func (lb *LogsBuilder) EmitForResource(options ...ResourceLogsOption) {
rl := plog.NewResourceLogs()
rl.SetSchemaUrl(conventions.SchemaURL)
ils := rl.ScopeLogs().AppendEmpty()
ils.Scope().SetName(ScopeName)
ils.Scope().SetVersion(lb.buildInfo.Version)
lb.eventDefaultEvent.emit(ils.LogRecords())
lb.eventDefaultEventToBeRemoved.emit(ils.LogRecords())
lb.eventDefaultEventToBeRenamed.emit(ils.LogRecords())
for _, op := range options {
op.apply(rl)
}
if lb.logRecordsBuffer.Len() > 0 {
lb.logRecordsBuffer.MoveAndAppendTo(ils.LogRecords())
lb.logRecordsBuffer = plog.NewLogRecordSlice()
}
for attr, filter := range lb.resourceAttributeIncludeFilter {
if val, ok := rl.Resource().Attributes().Get(attr); ok && !filter.Matches(val.AsString()) {
return
}
}
for attr, filter := range lb.resourceAttributeExcludeFilter {
if val, ok := rl.Resource().Attributes().Get(attr); ok && filter.Matches(val.AsString()) {
return
}
}
if ils.LogRecords().Len() > 0 {
rl.MoveTo(lb.logsBuffer.ResourceLogs().AppendEmpty())
}
}
// Emit returns all the logs accumulated by the logs builder and updates the internal state to be ready for
// recording another set of logs. This function will be responsible for applying all the transformations required to
// produce logs representation defined in metadata and user config.
func (lb *LogsBuilder) Emit(options ...ResourceLogsOption) plog.Logs {
lb.EmitForResource(options...)
logs := lb.logsBuffer
lb.logsBuffer = plog.NewLogs()
return logs
}
// RecordDefaultEventEvent adds a log record of default.event event.
func (lb *LogsBuilder) RecordDefaultEventEvent(ctx context.Context, timestamp pcommon.Timestamp, stringAttrAttributeValue string, overriddenIntAttrAttributeValue int64, enumAttrAttributeValue AttributeEnumAttr, sliceAttrAttributeValue []any, mapAttrAttributeValue map[string]any, optInBoolAttrAttributeValue bool, options ...EventAttributeOption) {
lb.eventDefaultEvent.recordEvent(ctx, timestamp, stringAttrAttributeValue, overriddenIntAttrAttributeValue, enumAttrAttributeValue.String(), sliceAttrAttributeValue, mapAttrAttributeValue, optInBoolAttrAttributeValue, options...)
}
// RecordDefaultEventToBeRemovedEvent adds a log record of default.event.to_be_removed event.
func (lb *LogsBuilder) RecordDefaultEventToBeRemovedEvent(ctx context.Context, timestamp pcommon.Timestamp, stringAttrAttributeValue string, overriddenIntAttrAttributeValue int64, enumAttrAttributeValue AttributeEnumAttr, sliceAttrAttributeValue []any, mapAttrAttributeValue map[string]any) {
lb.eventDefaultEventToBeRemoved.recordEvent(ctx, timestamp, stringAttrAttributeValue, overriddenIntAttrAttributeValue, enumAttrAttributeValue.String(), sliceAttrAttributeValue, mapAttrAttributeValue)
}
// RecordDefaultEventToBeRenamedEvent adds a log record of default.event.to_be_renamed event.
func (lb *LogsBuilder) RecordDefaultEventToBeRenamedEvent(ctx context.Context, timestamp pcommon.Timestamp, stringAttrAttributeValue string, booleanAttrAttributeValue bool, booleanAttr2AttributeValue bool, options ...EventAttributeOption) {
lb.eventDefaultEventToBeRenamed.recordEvent(ctx, timestamp, stringAttrAttributeValue, booleanAttrAttributeValue, booleanAttr2AttributeValue, options...)
}
================================================
FILE: cmd/mdatagen/internal/samplereceiver/internal/metadata/generated_logs_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/receiver/receivertest"
)
type eventsTestDataSet int
const (
eventTestDataSetDefault eventsTestDataSet = iota
eventTestDataSetAll
eventTestDataSetNone
)
func TestLogsBuilderAppendLogRecord(t *testing.T) {
observedZapCore, _ := observer.New(zap.WarnLevel)
settings := receivertest.NewNopSettings(receivertest.NopType)
settings.Logger = zap.New(observedZapCore)
lb := NewLogsBuilder(loadLogsBuilderConfig(t, "all_set"), settings)
rb := lb.NewResourceBuilder()
rb.SetMapResourceAttr(map[string]any{"key1": "map.resource.attr-val1", "key2": "map.resource.attr-val2"})
rb.SetOptionalResourceAttr("optional.resource.attr-val")
rb.SetSliceResourceAttr([]any{"slice.resource.attr-item1", "slice.resource.attr-item2"})
rb.SetStringEnumResourceAttrOne()
rb.SetStringResourceAttr("string.resource.attr-val")
rb.SetStringResourceAttrDisableWarning("string.resource.attr_disable_warning-val")
rb.SetStringResourceAttrRemoveWarning("string.resource.attr_remove_warning-val")
rb.SetStringResourceAttrToBeRemoved("string.resource.attr_to_be_removed-val")
res := rb.Emit()
// append the first log record
lr := plog.NewLogRecord()
lr.SetTimestamp(pcommon.NewTimestampFromTime(time.Now()))
lr.Attributes().PutStr("type", "log")
lr.Body().SetStr("the first log record")
// append the second log record
lr2 := plog.NewLogRecord()
lr2.SetTimestamp(pcommon.NewTimestampFromTime(time.Now()))
lr2.Attributes().PutStr("type", "event")
lr2.Body().SetStr("the second log record")
lb.AppendLogRecord(lr)
lb.AppendLogRecord(lr2)
logs := lb.Emit(WithLogsResource(res))
assert.Equal(t, 1, logs.ResourceLogs().Len())
rl := logs.ResourceLogs().At(0)
assert.Equal(t, 1, rl.ScopeLogs().Len())
sl := rl.ScopeLogs().At(0)
assert.Equal(t, ScopeName, sl.Scope().Name())
assert.Equal(t, lb.buildInfo.Version, sl.Scope().Version())
assert.Equal(t, 2, sl.LogRecords().Len())
attrVal, ok := sl.LogRecords().At(0).Attributes().Get("type")
assert.True(t, ok)
assert.Equal(t, "log", attrVal.Str())
assert.Equal(t, pcommon.ValueTypeStr, sl.LogRecords().At(0).Body().Type())
assert.Equal(t, "the first log record", sl.LogRecords().At(0).Body().Str())
attrVal, ok = sl.LogRecords().At(1).Attributes().Get("type")
assert.True(t, ok)
assert.Equal(t, "event", attrVal.Str())
assert.Equal(t, pcommon.ValueTypeStr, sl.LogRecords().At(1).Body().Type())
assert.Equal(t, "the second log record", sl.LogRecords().At(1).Body().Str())
}
func TestLogsBuilder(t *testing.T) {
tests := []struct {
name string
eventsSet eventsTestDataSet
resAttrsSet eventsTestDataSet
expectEmpty bool
}{
{
name: "default",
},
{
name: "all_set",
eventsSet: eventTestDataSetAll,
resAttrsSet: eventTestDataSetAll,
},
{
name: "none_set",
eventsSet: eventTestDataSetNone,
resAttrsSet: eventTestDataSetNone,
expectEmpty: true,
},
{
name: "filter_set_include",
resAttrsSet: eventTestDataSetAll,
},
{
name: "filter_set_exclude",
resAttrsSet: eventTestDataSetAll,
expectEmpty: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
timestamp := pcommon.Timestamp(1_000_001_000)
traceID := [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
spanID := [8]byte{0, 1, 2, 3, 4, 5, 6, 7}
ctx := trace.ContextWithSpanContext(context.Background(), trace.NewSpanContext(trace.SpanContextConfig{
TraceID: trace.TraceID(traceID),
SpanID: trace.SpanID(spanID),
TraceFlags: trace.FlagsSampled,
}))
observedZapCore, observedLogs := observer.New(zap.WarnLevel)
settings := receivertest.NewNopSettings(receivertest.NopType)
settings.Logger = zap.New(observedZapCore)
lb := NewLogsBuilder(loadLogsBuilderConfig(t, tt.name), settings)
expectedWarnings := 0
if tt.eventsSet == eventTestDataSetDefault {
assert.Equal(t, "[WARNING] Please set `enabled` field explicitly for `default.event`: This event will be disabled by default soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
if tt.eventsSet == eventTestDataSetDefault || tt.eventsSet == eventTestDataSetAll {
assert.Equal(t, "[WARNING] `default.event.to_be_removed` should not be enabled: This event is deprecated and will be removed soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
if tt.eventsSet == eventTestDataSetAll || tt.eventsSet == eventTestDataSetNone {
assert.Equal(t, "[WARNING] `default.event.to_be_renamed` should not be configured: This event is deprecated and will be renamed soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
if tt.resAttrsSet == eventTestDataSetDefault {
assert.Equal(t, "[WARNING] Please set `enabled` field explicitly for `string.resource.attr_disable_warning`: This resource_attribute will be disabled by default soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
if tt.resAttrsSet == eventTestDataSetAll || tt.resAttrsSet == eventTestDataSetNone {
assert.Equal(t, "[WARNING] `string.resource.attr_remove_warning` should not be configured: This resource_attribute is deprecated and will be removed soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
if tt.resAttrsSet == eventTestDataSetDefault || tt.resAttrsSet == eventTestDataSetAll {
assert.Equal(t, "[WARNING] `string.resource.attr_to_be_removed` should not be enabled: This resource_attribute is deprecated and will be removed soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
assert.Equal(t, expectedWarnings, observedLogs.Len())
defaultEventsCount := 0
allEventsCount := 0
defaultEventsCount++
allEventsCount++
lb.RecordDefaultEventEvent(ctx, timestamp, "string_attr-val", 19, AttributeEnumAttrRed, []any{"slice_attr-item1", "slice_attr-item2"}, map[string]any{"key1": "map_attr-val1", "key2": "map_attr-val2"}, true, WithConditionalIntAttrEventAttribute(20), WithConditionalStringAttrEventAttribute("conditional_string_attr-val"))
defaultEventsCount++
allEventsCount++
lb.RecordDefaultEventToBeRemovedEvent(ctx, timestamp, "string_attr-val", 19, AttributeEnumAttrRed, []any{"slice_attr-item1", "slice_attr-item2"}, map[string]any{"key1": "map_attr-val1", "key2": "map_attr-val2"})
allEventsCount++
lb.RecordDefaultEventToBeRenamedEvent(ctx, timestamp, "string_attr-val", true, false, WithConditionalStringAttrEventAttribute("conditional_string_attr-val"))
rb := lb.NewResourceBuilder()
rb.SetMapResourceAttr(map[string]any{"key1": "map.resource.attr-val1", "key2": "map.resource.attr-val2"})
rb.SetOptionalResourceAttr("optional.resource.attr-val")
rb.SetSliceResourceAttr([]any{"slice.resource.attr-item1", "slice.resource.attr-item2"})
rb.SetStringEnumResourceAttrOne()
rb.SetStringResourceAttr("string.resource.attr-val")
rb.SetStringResourceAttrDisableWarning("string.resource.attr_disable_warning-val")
rb.SetStringResourceAttrRemoveWarning("string.resource.attr_remove_warning-val")
rb.SetStringResourceAttrToBeRemoved("string.resource.attr_to_be_removed-val")
res := rb.Emit()
logs := lb.Emit(WithLogsResource(res))
if tt.expectEmpty || ((tt.name == "default" || tt.name == "filter_set_include") && defaultEventsCount == 0) {
assert.Equal(t, 0, logs.ResourceLogs().Len())
return
}
assert.Equal(t, 1, logs.ResourceLogs().Len())
rl := logs.ResourceLogs().At(0)
assert.Equal(t, res, rl.Resource())
assert.Equal(t, 1, rl.ScopeLogs().Len())
lrs := rl.ScopeLogs().At(0).LogRecords()
if tt.eventsSet == eventTestDataSetDefault {
assert.Equal(t, defaultEventsCount, lrs.Len())
}
if tt.eventsSet == eventTestDataSetAll {
assert.Equal(t, allEventsCount, lrs.Len())
}
validatedEvents := make(map[string]bool)
for i := 0; i < lrs.Len(); i++ {
switch lrs.At(i).EventName() {
case "default.event":
assert.False(t, validatedEvents["default.event"], "Found a duplicate in the events slice: default.event")
validatedEvents["default.event"] = true
lr := lrs.At(i)
assert.Equal(t, timestamp, lr.Timestamp())
assert.Equal(t, pcommon.TraceID(traceID), lr.TraceID())
assert.Equal(t, pcommon.SpanID(spanID), lr.SpanID())
attrVal, ok := lr.Attributes().Get("string_attr")
assert.True(t, ok)
assert.Equal(t, "string_attr-val", attrVal.Str())
attrVal, ok = lr.Attributes().Get("state")
assert.True(t, ok)
assert.EqualValues(t, 19, attrVal.Int())
attrVal, ok = lr.Attributes().Get("enum_attr")
assert.True(t, ok)
assert.Equal(t, "red", attrVal.Str())
attrVal, ok = lr.Attributes().Get("slice_attr")
assert.True(t, ok)
assert.Equal(t, []any{"slice_attr-item1", "slice_attr-item2"}, attrVal.Slice().AsRaw())
attrVal, ok = lr.Attributes().Get("map_attr")
assert.True(t, ok)
assert.Equal(t, map[string]any{"key1": "map_attr-val1", "key2": "map_attr-val2"}, attrVal.Map().AsRaw())
attrVal, ok = lr.Attributes().Get("conditional_int_attr")
assert.True(t, ok)
assert.EqualValues(t, 20, attrVal.Int())
attrVal, ok = lr.Attributes().Get("conditional_string_attr")
assert.True(t, ok)
assert.Equal(t, "conditional_string_attr-val", attrVal.Str())
attrVal, ok = lr.Attributes().Get("opt_in_bool_attr")
assert.True(t, ok)
assert.True(t, attrVal.Bool())
case "default.event.to_be_removed":
assert.False(t, validatedEvents["default.event.to_be_removed"], "Found a duplicate in the events slice: default.event.to_be_removed")
validatedEvents["default.event.to_be_removed"] = true
lr := lrs.At(i)
assert.Equal(t, timestamp, lr.Timestamp())
assert.Equal(t, pcommon.TraceID(traceID), lr.TraceID())
assert.Equal(t, pcommon.SpanID(spanID), lr.SpanID())
attrVal, ok := lr.Attributes().Get("string_attr")
assert.True(t, ok)
assert.Equal(t, "string_attr-val", attrVal.Str())
attrVal, ok = lr.Attributes().Get("state")
assert.True(t, ok)
assert.EqualValues(t, 19, attrVal.Int())
attrVal, ok = lr.Attributes().Get("enum_attr")
assert.True(t, ok)
assert.Equal(t, "red", attrVal.Str())
attrVal, ok = lr.Attributes().Get("slice_attr")
assert.True(t, ok)
assert.Equal(t, []any{"slice_attr-item1", "slice_attr-item2"}, attrVal.Slice().AsRaw())
attrVal, ok = lr.Attributes().Get("map_attr")
assert.True(t, ok)
assert.Equal(t, map[string]any{"key1": "map_attr-val1", "key2": "map_attr-val2"}, attrVal.Map().AsRaw())
case "default.event.to_be_renamed":
assert.False(t, validatedEvents["default.event.to_be_renamed"], "Found a duplicate in the events slice: default.event.to_be_renamed")
validatedEvents["default.event.to_be_renamed"] = true
lr := lrs.At(i)
assert.Equal(t, timestamp, lr.Timestamp())
assert.Equal(t, pcommon.TraceID(traceID), lr.TraceID())
assert.Equal(t, pcommon.SpanID(spanID), lr.SpanID())
attrVal, ok := lr.Attributes().Get("string_attr")
assert.True(t, ok)
assert.Equal(t, "string_attr-val", attrVal.Str())
attrVal, ok = lr.Attributes().Get("boolean_attr")
assert.True(t, ok)
assert.True(t, attrVal.Bool())
attrVal, ok = lr.Attributes().Get("boolean_attr2")
assert.True(t, ok)
assert.False(t, attrVal.Bool())
attrVal, ok = lr.Attributes().Get("conditional_string_attr")
assert.True(t, ok)
assert.Equal(t, "conditional_string_attr-val", attrVal.Str())
}
}
})
}
}
================================================
FILE: cmd/mdatagen/internal/samplereceiver/internal/metadata/generated_metrics.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"fmt"
"slices"
"strconv"
"time"
conventions "go.opentelemetry.io/otel/semconv/v1.38.0"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/filter"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/receiver"
)
const (
AggregationStrategySum = "sum"
AggregationStrategyAvg = "avg"
AggregationStrategyMin = "min"
AggregationStrategyMax = "max"
)
// AttributeEnumAttr specifies the value enum_attr attribute.
type AttributeEnumAttr int
const (
_ AttributeEnumAttr = iota
AttributeEnumAttrRed
AttributeEnumAttrGreen
AttributeEnumAttrBlue
)
// String returns the string representation of the AttributeEnumAttr.
func (av AttributeEnumAttr) String() string {
switch av {
case AttributeEnumAttrRed:
return "red"
case AttributeEnumAttrGreen:
return "green"
case AttributeEnumAttrBlue:
return "blue"
}
return ""
}
// MapAttributeEnumAttr is a helper map of string to AttributeEnumAttr attribute value.
var MapAttributeEnumAttr = map[string]AttributeEnumAttr{
"red": AttributeEnumAttrRed,
"green": AttributeEnumAttrGreen,
"blue": AttributeEnumAttrBlue,
}
var MetricsInfo = metricsInfo{
DefaultMetric: metricInfo{
Name: "default.metric",
},
DefaultMetricToBeRemoved: metricInfo{
Name: "default.metric.to_be_removed",
},
MetricInputType: metricInfo{
Name: "metric.input_type",
},
OptionalMetric: metricInfo{
Name: "optional.metric",
},
OptionalMetricEmptyUnit: metricInfo{
Name: "optional.metric.empty_unit",
},
ReaggregateMetric: metricInfo{
Name: "reaggregate.metric",
},
ReaggregateMetricWithRequired: metricInfo{
Name: "reaggregate.metric.with_required",
},
SystemCPUTime: metricInfo{
Name: "system.cpu.time",
},
}
type metricsInfo struct {
DefaultMetric metricInfo
DefaultMetricToBeRemoved metricInfo
MetricInputType metricInfo
OptionalMetric metricInfo
OptionalMetricEmptyUnit metricInfo
ReaggregateMetric metricInfo
ReaggregateMetricWithRequired metricInfo
SystemCPUTime metricInfo
}
type metricInfo struct {
Name string
}
type MetricAttributeOption interface {
apply(pmetric.NumberDataPoint)
}
type metricAttributeOptionFunc func(pmetric.NumberDataPoint)
func (maof metricAttributeOptionFunc) apply(dp pmetric.NumberDataPoint) {
maof(dp)
}
func WithConditionalIntAttrMetricAttribute(conditionalIntAttrAttributeValue int64) MetricAttributeOption {
return metricAttributeOptionFunc(func(dp pmetric.NumberDataPoint) {
dp.Attributes().PutInt("conditional_int_attr", conditionalIntAttrAttributeValue)
})
}
func WithConditionalStringAttrMetricAttribute(conditionalStringAttrAttributeValue string) MetricAttributeOption {
return metricAttributeOptionFunc(func(dp pmetric.NumberDataPoint) {
dp.Attributes().PutStr("conditional_string_attr", conditionalStringAttrAttributeValue)
})
}
type metricDefaultMetric struct {
data pmetric.Metric // data buffer for generated metric.
config DefaultMetricMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []int64 // slice containing number of aggregated datapoints at each index
}
// init fills default.metric metric with initial data.
func (m *metricDefaultMetric) init() {
m.data.SetName("default.metric")
m.data.SetDescription("Monotonic cumulative sum int metric enabled by default.")
m.data.SetUnit("s")
m.data.SetEmptySum()
m.data.Sum().SetIsMonotonic(true)
m.data.Sum().SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
m.data.Sum().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
}
func (m *metricDefaultMetric) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val int64, stringAttrAttributeValue string, overriddenIntAttrAttributeValue int64, enumAttrAttributeValue string, sliceAttrAttributeValue []any, mapAttrAttributeValue map[string]any, optInBoolAttrAttributeValue bool, options ...MetricAttributeOption) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, DefaultMetricMetricAttributeKeyStringAttr) {
dp.Attributes().PutStr("string_attr", stringAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, DefaultMetricMetricAttributeKeyOverriddenIntAttr) {
dp.Attributes().PutInt("state", overriddenIntAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, DefaultMetricMetricAttributeKeyEnumAttr) {
dp.Attributes().PutStr("enum_attr", enumAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, DefaultMetricMetricAttributeKeySliceAttr) {
dp.Attributes().PutEmptySlice("slice_attr").FromRaw(sliceAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, DefaultMetricMetricAttributeKeyMapAttr) {
dp.Attributes().PutEmptyMap("map_attr").FromRaw(mapAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, DefaultMetricMetricAttributeKeyOptInBoolAttr) {
dp.Attributes().PutBool("opt_in_bool_attr", optInBoolAttrAttributeValue)
}
for _, op := range options {
op.apply(dp)
}
var s string
dps := m.data.Sum().DataPoints()
for i := 0; i < dps.Len(); i++ {
dpi := dps.At(i)
if dp.Attributes().Equal(dpi.Attributes()) && dp.StartTimestamp() == dpi.StartTimestamp() && dp.Timestamp() == dpi.Timestamp() {
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetIntValue(dpi.IntValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.IntValue() > val {
dpi.SetIntValue(val)
}
return
case AggregationStrategyMax:
if dpi.IntValue() < val {
dpi.SetIntValue(val)
}
return
}
}
}
dp.SetIntValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricDefaultMetric) updateCapacity() {
if m.data.Sum().DataPoints().Len() > m.capacity {
m.capacity = m.data.Sum().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricDefaultMetric) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Sum().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Sum().DataPoints().At(i).SetIntValue(m.data.Sum().DataPoints().At(i).IntValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricDefaultMetric(cfg DefaultMetricMetricConfig) metricDefaultMetric {
m := metricDefaultMetric{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricDefaultMetricToBeRemoved struct {
data pmetric.Metric // data buffer for generated metric.
config DefaultMetricToBeRemovedMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
}
// init fills default.metric.to_be_removed metric with initial data.
func (m *metricDefaultMetricToBeRemoved) init() {
m.data.SetName("default.metric.to_be_removed")
m.data.SetDescription("[DEPRECATED] Non-monotonic delta sum double metric enabled by default.")
m.data.SetUnit("s")
m.data.SetEmptySum()
m.data.Sum().SetIsMonotonic(false)
m.data.Sum().SetAggregationTemporality(pmetric.AggregationTemporalityDelta)
}
func (m *metricDefaultMetricToBeRemoved) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64) {
if !m.config.Enabled {
return
}
dp := m.data.Sum().DataPoints().AppendEmpty()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
dp.SetDoubleValue(val)
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricDefaultMetricToBeRemoved) updateCapacity() {
if m.data.Sum().DataPoints().Len() > m.capacity {
m.capacity = m.data.Sum().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricDefaultMetricToBeRemoved) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Sum().DataPoints().Len() > 0 {
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricDefaultMetricToBeRemoved(cfg DefaultMetricToBeRemovedMetricConfig) metricDefaultMetricToBeRemoved {
m := metricDefaultMetricToBeRemoved{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMetricInputType struct {
data pmetric.Metric // data buffer for generated metric.
config MetricInputTypeMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []int64 // slice containing number of aggregated datapoints at each index
}
// init fills metric.input_type metric with initial data.
func (m *metricMetricInputType) init() {
m.data.SetName("metric.input_type")
m.data.SetDescription("Monotonic cumulative sum int metric with string input_type enabled by default.")
m.data.SetUnit("s")
m.data.SetEmptySum()
m.data.Sum().SetIsMonotonic(true)
m.data.Sum().SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
m.data.Sum().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
}
func (m *metricMetricInputType) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val int64, stringAttrAttributeValue string, overriddenIntAttrAttributeValue int64, enumAttrAttributeValue string, sliceAttrAttributeValue []any, mapAttrAttributeValue map[string]any) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MetricInputTypeMetricAttributeKeyStringAttr) {
dp.Attributes().PutStr("string_attr", stringAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, MetricInputTypeMetricAttributeKeyOverriddenIntAttr) {
dp.Attributes().PutInt("state", overriddenIntAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, MetricInputTypeMetricAttributeKeyEnumAttr) {
dp.Attributes().PutStr("enum_attr", enumAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, MetricInputTypeMetricAttributeKeySliceAttr) {
dp.Attributes().PutEmptySlice("slice_attr").FromRaw(sliceAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, MetricInputTypeMetricAttributeKeyMapAttr) {
dp.Attributes().PutEmptyMap("map_attr").FromRaw(mapAttrAttributeValue)
}
var s string
dps := m.data.Sum().DataPoints()
for i := 0; i < dps.Len(); i++ {
dpi := dps.At(i)
if dp.Attributes().Equal(dpi.Attributes()) && dp.StartTimestamp() == dpi.StartTimestamp() && dp.Timestamp() == dpi.Timestamp() {
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetIntValue(dpi.IntValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.IntValue() > val {
dpi.SetIntValue(val)
}
return
case AggregationStrategyMax:
if dpi.IntValue() < val {
dpi.SetIntValue(val)
}
return
}
}
}
dp.SetIntValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMetricInputType) updateCapacity() {
if m.data.Sum().DataPoints().Len() > m.capacity {
m.capacity = m.data.Sum().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMetricInputType) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Sum().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Sum().DataPoints().At(i).SetIntValue(m.data.Sum().DataPoints().At(i).IntValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMetricInputType(cfg MetricInputTypeMetricConfig) metricMetricInputType {
m := metricMetricInputType{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricOptionalMetric struct {
data pmetric.Metric // data buffer for generated metric.
config OptionalMetricMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
}
// init fills optional.metric metric with initial data.
func (m *metricOptionalMetric) init() {
m.data.SetName("optional.metric")
m.data.SetDescription("[DEPRECATED] Gauge double metric disabled by default.")
m.data.SetUnit("1")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
}
func (m *metricOptionalMetric) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, stringAttrAttributeValue string, booleanAttrAttributeValue bool, booleanAttr2AttributeValue bool, options ...MetricAttributeOption) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, OptionalMetricMetricAttributeKeyStringAttr) {
dp.Attributes().PutStr("string_attr", stringAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, OptionalMetricMetricAttributeKeyBooleanAttr) {
dp.Attributes().PutBool("boolean_attr", booleanAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, OptionalMetricMetricAttributeKeyBooleanAttr2) {
dp.Attributes().PutBool("boolean_attr2", booleanAttr2AttributeValue)
}
for _, op := range options {
op.apply(dp)
}
var s string
dps := m.data.Gauge().DataPoints()
for i := 0; i < dps.Len(); i++ {
dpi := dps.At(i)
if dp.Attributes().Equal(dpi.Attributes()) && dp.StartTimestamp() == dpi.StartTimestamp() && dp.Timestamp() == dpi.Timestamp() {
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricOptionalMetric) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricOptionalMetric) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricOptionalMetric(cfg OptionalMetricMetricConfig) metricOptionalMetric {
m := metricOptionalMetric{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricOptionalMetricEmptyUnit struct {
data pmetric.Metric // data buffer for generated metric.
config OptionalMetricEmptyUnitMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
}
// init fills optional.metric.empty_unit metric with initial data.
func (m *metricOptionalMetricEmptyUnit) init() {
m.data.SetName("optional.metric.empty_unit")
m.data.SetDescription("[DEPRECATED] Gauge double metric disabled by default.")
m.data.SetUnit("")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
}
func (m *metricOptionalMetricEmptyUnit) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, stringAttrAttributeValue string, booleanAttrAttributeValue bool) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, OptionalMetricEmptyUnitMetricAttributeKeyStringAttr) {
dp.Attributes().PutStr("string_attr", stringAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, OptionalMetricEmptyUnitMetricAttributeKeyBooleanAttr) {
dp.Attributes().PutBool("boolean_attr", booleanAttrAttributeValue)
}
var s string
dps := m.data.Gauge().DataPoints()
for i := 0; i < dps.Len(); i++ {
dpi := dps.At(i)
if dp.Attributes().Equal(dpi.Attributes()) && dp.StartTimestamp() == dpi.StartTimestamp() && dp.Timestamp() == dpi.Timestamp() {
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricOptionalMetricEmptyUnit) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricOptionalMetricEmptyUnit) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricOptionalMetricEmptyUnit(cfg OptionalMetricEmptyUnitMetricConfig) metricOptionalMetricEmptyUnit {
m := metricOptionalMetricEmptyUnit{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricReaggregateMetric struct {
data pmetric.Metric // data buffer for generated metric.
config ReaggregateMetricMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
}
// init fills reaggregate.metric metric with initial data.
func (m *metricReaggregateMetric) init() {
m.data.SetName("reaggregate.metric")
m.data.SetDescription("Metric for testing spatial reaggregation")
m.data.SetUnit("1")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
}
func (m *metricReaggregateMetric) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, stringAttrAttributeValue string, booleanAttrAttributeValue bool) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, ReaggregateMetricMetricAttributeKeyStringAttr) {
dp.Attributes().PutStr("string_attr", stringAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, ReaggregateMetricMetricAttributeKeyBooleanAttr) {
dp.Attributes().PutBool("boolean_attr", booleanAttrAttributeValue)
}
var s string
dps := m.data.Gauge().DataPoints()
for i := 0; i < dps.Len(); i++ {
dpi := dps.At(i)
if dp.Attributes().Equal(dpi.Attributes()) && dp.StartTimestamp() == dpi.StartTimestamp() && dp.Timestamp() == dpi.Timestamp() {
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricReaggregateMetric) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricReaggregateMetric) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricReaggregateMetric(cfg ReaggregateMetricMetricConfig) metricReaggregateMetric {
m := metricReaggregateMetric{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricReaggregateMetricWithRequired struct {
data pmetric.Metric // data buffer for generated metric.
config ReaggregateMetricWithRequiredMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
}
// init fills reaggregate.metric.with_required metric with initial data.
func (m *metricReaggregateMetricWithRequired) init() {
m.data.SetName("reaggregate.metric.with_required")
m.data.SetDescription("Metric for testing spatial reaggregation with required attributes")
m.data.SetUnit("1")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
}
func (m *metricReaggregateMetricWithRequired) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, requiredStringAttrAttributeValue string, stringAttrAttributeValue string, booleanAttrAttributeValue bool) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, ReaggregateMetricWithRequiredMetricAttributeKeyRequiredStringAttr) {
dp.Attributes().PutStr("required_string_attr", requiredStringAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, ReaggregateMetricWithRequiredMetricAttributeKeyStringAttr) {
dp.Attributes().PutStr("string_attr", stringAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, ReaggregateMetricWithRequiredMetricAttributeKeyBooleanAttr) {
dp.Attributes().PutBool("boolean_attr", booleanAttrAttributeValue)
}
var s string
dps := m.data.Gauge().DataPoints()
for i := 0; i < dps.Len(); i++ {
dpi := dps.At(i)
if dp.Attributes().Equal(dpi.Attributes()) && dp.StartTimestamp() == dpi.StartTimestamp() && dp.Timestamp() == dpi.Timestamp() {
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricReaggregateMetricWithRequired) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricReaggregateMetricWithRequired) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricReaggregateMetricWithRequired(cfg ReaggregateMetricWithRequiredMetricConfig) metricReaggregateMetricWithRequired {
m := metricReaggregateMetricWithRequired{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricSystemCPUTime struct {
data pmetric.Metric // data buffer for generated metric.
config SystemCPUTimeMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
}
// init fills system.cpu.time metric with initial data.
func (m *metricSystemCPUTime) init() {
m.data.SetName("system.cpu.time")
m.data.SetDescription("Monotonic cumulative sum int metric enabled by default.")
m.data.SetUnit("s")
m.data.SetEmptySum()
m.data.Sum().SetIsMonotonic(true)
m.data.Sum().SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
}
func (m *metricSystemCPUTime) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val int64) {
if !m.config.Enabled {
return
}
dp := m.data.Sum().DataPoints().AppendEmpty()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
dp.SetIntValue(val)
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricSystemCPUTime) updateCapacity() {
if m.data.Sum().DataPoints().Len() > m.capacity {
m.capacity = m.data.Sum().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricSystemCPUTime) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Sum().DataPoints().Len() > 0 {
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricSystemCPUTime(cfg SystemCPUTimeMetricConfig) metricSystemCPUTime {
m := metricSystemCPUTime{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
// MetricsBuilder provides an interface for scrapers to report metrics while taking care of all the transformations
// required to produce metric representation defined in metadata and user config.
type MetricsBuilder struct {
config MetricsBuilderConfig // config of the metrics builder.
startTime pcommon.Timestamp // start time that will be applied to all recorded data points.
metricsCapacity int // maximum observed number of metrics per resource.
metricsBuffer pmetric.Metrics // accumulates metrics data before emitting.
buildInfo component.BuildInfo // contains version information.
resourceAttributeIncludeFilter map[string]filter.Filter
resourceAttributeExcludeFilter map[string]filter.Filter
metricDefaultMetric metricDefaultMetric
metricDefaultMetricToBeRemoved metricDefaultMetricToBeRemoved
metricMetricInputType metricMetricInputType
metricOptionalMetric metricOptionalMetric
metricOptionalMetricEmptyUnit metricOptionalMetricEmptyUnit
metricReaggregateMetric metricReaggregateMetric
metricReaggregateMetricWithRequired metricReaggregateMetricWithRequired
metricSystemCPUTime metricSystemCPUTime
}
// MetricBuilderOption applies changes to default metrics builder.
type MetricBuilderOption interface {
apply(*MetricsBuilder)
}
type metricBuilderOptionFunc func(mb *MetricsBuilder)
func (mbof metricBuilderOptionFunc) apply(mb *MetricsBuilder) {
mbof(mb)
}
// WithStartTime sets startTime on the metrics builder.
func WithStartTime(startTime pcommon.Timestamp) MetricBuilderOption {
return metricBuilderOptionFunc(func(mb *MetricsBuilder) {
mb.startTime = startTime
})
}
func NewMetricsBuilder(mbc MetricsBuilderConfig, settings receiver.Settings, options ...MetricBuilderOption) *MetricsBuilder {
if !mbc.Metrics.DefaultMetric.enabledSetByUser {
settings.Logger.Warn("[WARNING] Please set `enabled` field explicitly for `default.metric`: This metric will be disabled by default soon.")
}
if mbc.Metrics.DefaultMetricToBeRemoved.Enabled {
settings.Logger.Warn("[WARNING] `default.metric.to_be_removed` should not be enabled: This metric is deprecated and will be removed soon.")
}
if mbc.Metrics.OptionalMetric.enabledSetByUser {
settings.Logger.Warn("[WARNING] `optional.metric` should not be configured: This metric is deprecated and will be removed soon.")
}
if mbc.Metrics.OptionalMetricEmptyUnit.enabledSetByUser {
settings.Logger.Warn("[WARNING] `optional.metric.empty_unit` should not be configured: This metric is deprecated and will be removed soon.")
}
if !mbc.ResourceAttributes.StringResourceAttrDisableWarning.enabledSetByUser {
settings.Logger.Warn("[WARNING] Please set `enabled` field explicitly for `string.resource.attr_disable_warning`: This resource_attribute will be disabled by default soon.")
}
if mbc.ResourceAttributes.StringResourceAttrRemoveWarning.enabledSetByUser || mbc.ResourceAttributes.StringResourceAttrRemoveWarning.MetricsInclude != nil || mbc.ResourceAttributes.StringResourceAttrRemoveWarning.MetricsExclude != nil {
settings.Logger.Warn("[WARNING] `string.resource.attr_remove_warning` should not be configured: This resource_attribute is deprecated and will be removed soon.")
}
if mbc.ResourceAttributes.StringResourceAttrToBeRemoved.Enabled {
settings.Logger.Warn("[WARNING] `string.resource.attr_to_be_removed` should not be enabled: This resource_attribute is deprecated and will be removed soon.")
}
mb := &MetricsBuilder{
config: mbc,
startTime: pcommon.NewTimestampFromTime(time.Now()),
metricsBuffer: pmetric.NewMetrics(),
buildInfo: settings.BuildInfo,
metricDefaultMetric: newMetricDefaultMetric(mbc.Metrics.DefaultMetric),
metricDefaultMetricToBeRemoved: newMetricDefaultMetricToBeRemoved(mbc.Metrics.DefaultMetricToBeRemoved),
metricMetricInputType: newMetricMetricInputType(mbc.Metrics.MetricInputType),
metricOptionalMetric: newMetricOptionalMetric(mbc.Metrics.OptionalMetric),
metricOptionalMetricEmptyUnit: newMetricOptionalMetricEmptyUnit(mbc.Metrics.OptionalMetricEmptyUnit),
metricReaggregateMetric: newMetricReaggregateMetric(mbc.Metrics.ReaggregateMetric),
metricReaggregateMetricWithRequired: newMetricReaggregateMetricWithRequired(mbc.Metrics.ReaggregateMetricWithRequired),
metricSystemCPUTime: newMetricSystemCPUTime(mbc.Metrics.SystemCPUTime),
resourceAttributeIncludeFilter: make(map[string]filter.Filter),
resourceAttributeExcludeFilter: make(map[string]filter.Filter),
}
if mbc.ResourceAttributes.MapResourceAttr.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["map.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.MapResourceAttr.MetricsInclude)
}
if mbc.ResourceAttributes.MapResourceAttr.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["map.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.MapResourceAttr.MetricsExclude)
}
if mbc.ResourceAttributes.OptionalResourceAttr.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["optional.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.OptionalResourceAttr.MetricsInclude)
}
if mbc.ResourceAttributes.OptionalResourceAttr.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["optional.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.OptionalResourceAttr.MetricsExclude)
}
if mbc.ResourceAttributes.SliceResourceAttr.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["slice.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.SliceResourceAttr.MetricsInclude)
}
if mbc.ResourceAttributes.SliceResourceAttr.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["slice.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.SliceResourceAttr.MetricsExclude)
}
if mbc.ResourceAttributes.StringEnumResourceAttr.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["string.enum.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.StringEnumResourceAttr.MetricsInclude)
}
if mbc.ResourceAttributes.StringEnumResourceAttr.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["string.enum.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.StringEnumResourceAttr.MetricsExclude)
}
if mbc.ResourceAttributes.StringResourceAttr.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["string.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.StringResourceAttr.MetricsInclude)
}
if mbc.ResourceAttributes.StringResourceAttr.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["string.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.StringResourceAttr.MetricsExclude)
}
if mbc.ResourceAttributes.StringResourceAttrDisableWarning.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["string.resource.attr_disable_warning"] = filter.CreateFilter(mbc.ResourceAttributes.StringResourceAttrDisableWarning.MetricsInclude)
}
if mbc.ResourceAttributes.StringResourceAttrDisableWarning.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["string.resource.attr_disable_warning"] = filter.CreateFilter(mbc.ResourceAttributes.StringResourceAttrDisableWarning.MetricsExclude)
}
if mbc.ResourceAttributes.StringResourceAttrRemoveWarning.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["string.resource.attr_remove_warning"] = filter.CreateFilter(mbc.ResourceAttributes.StringResourceAttrRemoveWarning.MetricsInclude)
}
if mbc.ResourceAttributes.StringResourceAttrRemoveWarning.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["string.resource.attr_remove_warning"] = filter.CreateFilter(mbc.ResourceAttributes.StringResourceAttrRemoveWarning.MetricsExclude)
}
if mbc.ResourceAttributes.StringResourceAttrToBeRemoved.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["string.resource.attr_to_be_removed"] = filter.CreateFilter(mbc.ResourceAttributes.StringResourceAttrToBeRemoved.MetricsInclude)
}
if mbc.ResourceAttributes.StringResourceAttrToBeRemoved.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["string.resource.attr_to_be_removed"] = filter.CreateFilter(mbc.ResourceAttributes.StringResourceAttrToBeRemoved.MetricsExclude)
}
for _, op := range options {
op.apply(mb)
}
return mb
}
// NewResourceBuilder returns a new resource builder that should be used to build a resource associated with for the emitted metrics.
func (mb *MetricsBuilder) NewResourceBuilder() *ResourceBuilder {
return NewResourceBuilder(mb.config.ResourceAttributes)
}
// updateCapacity updates max length of metrics and resource attributes that will be used for the slice capacity.
func (mb *MetricsBuilder) updateCapacity(rm pmetric.ResourceMetrics) {
if mb.metricsCapacity < rm.ScopeMetrics().At(0).Metrics().Len() {
mb.metricsCapacity = rm.ScopeMetrics().At(0).Metrics().Len()
}
}
// ResourceMetricsOption applies changes to provided resource metrics.
type ResourceMetricsOption interface {
apply(pmetric.ResourceMetrics)
}
type resourceMetricsOptionFunc func(pmetric.ResourceMetrics)
func (rmof resourceMetricsOptionFunc) apply(rm pmetric.ResourceMetrics) {
rmof(rm)
}
// WithResource sets the provided resource on the emitted ResourceMetrics.
// It's recommended to use ResourceBuilder to create the resource.
func WithResource(res pcommon.Resource) ResourceMetricsOption {
return resourceMetricsOptionFunc(func(rm pmetric.ResourceMetrics) {
res.CopyTo(rm.Resource())
})
}
// WithStartTimeOverride overrides start time for all the resource metrics data points.
// This option should be only used if different start time has to be set on metrics coming from different resources.
func WithStartTimeOverride(start pcommon.Timestamp) ResourceMetricsOption {
return resourceMetricsOptionFunc(func(rm pmetric.ResourceMetrics) {
var dps pmetric.NumberDataPointSlice
metrics := rm.ScopeMetrics().At(0).Metrics()
for i := 0; i < metrics.Len(); i++ {
switch metrics.At(i).Type() {
case pmetric.MetricTypeGauge:
dps = metrics.At(i).Gauge().DataPoints()
case pmetric.MetricTypeSum:
dps = metrics.At(i).Sum().DataPoints()
}
for j := 0; j < dps.Len(); j++ {
dps.At(j).SetStartTimestamp(start)
}
}
})
}
// EmitForResource saves all the generated metrics under a new resource and updates the internal state to be ready for
// recording another set of data points as part of another resource. This function can be helpful when one scraper
// needs to emit metrics from several resources. Otherwise calling this function is not required,
// just `Emit` function can be called instead.
// Resource attributes should be provided as ResourceMetricsOption arguments.
func (mb *MetricsBuilder) EmitForResource(options ...ResourceMetricsOption) {
rm := pmetric.NewResourceMetrics()
rm.SetSchemaUrl(conventions.SchemaURL)
ils := rm.ScopeMetrics().AppendEmpty()
ils.Scope().SetName(ScopeName)
ils.Scope().SetVersion(mb.buildInfo.Version)
ils.Metrics().EnsureCapacity(mb.metricsCapacity)
mb.metricDefaultMetric.emit(ils.Metrics())
mb.metricDefaultMetricToBeRemoved.emit(ils.Metrics())
mb.metricMetricInputType.emit(ils.Metrics())
mb.metricOptionalMetric.emit(ils.Metrics())
mb.metricOptionalMetricEmptyUnit.emit(ils.Metrics())
mb.metricReaggregateMetric.emit(ils.Metrics())
mb.metricReaggregateMetricWithRequired.emit(ils.Metrics())
mb.metricSystemCPUTime.emit(ils.Metrics())
for _, op := range options {
op.apply(rm)
}
for attr, filter := range mb.resourceAttributeIncludeFilter {
if val, ok := rm.Resource().Attributes().Get(attr); ok && !filter.Matches(val.AsString()) {
return
}
}
for attr, filter := range mb.resourceAttributeExcludeFilter {
if val, ok := rm.Resource().Attributes().Get(attr); ok && filter.Matches(val.AsString()) {
return
}
}
if ils.Metrics().Len() > 0 {
mb.updateCapacity(rm)
rm.MoveTo(mb.metricsBuffer.ResourceMetrics().AppendEmpty())
}
}
// Emit returns all the metrics accumulated by the metrics builder and updates the internal state to be ready for
// recording another set of metrics. This function will be responsible for applying all the transformations required to
// produce metric representation defined in metadata and user config, e.g. delta or cumulative.
func (mb *MetricsBuilder) Emit(options ...ResourceMetricsOption) pmetric.Metrics {
mb.EmitForResource(options...)
metrics := mb.metricsBuffer
mb.metricsBuffer = pmetric.NewMetrics()
return metrics
}
// RecordDefaultMetricDataPoint adds a data point to default.metric metric.
func (mb *MetricsBuilder) RecordDefaultMetricDataPoint(ts pcommon.Timestamp, val int64, stringAttrAttributeValue string, overriddenIntAttrAttributeValue int64, enumAttrAttributeValue AttributeEnumAttr, sliceAttrAttributeValue []any, mapAttrAttributeValue map[string]any, optInBoolAttrAttributeValue bool, options ...MetricAttributeOption) {
mb.metricDefaultMetric.recordDataPoint(mb.startTime, ts, val, stringAttrAttributeValue, overriddenIntAttrAttributeValue, enumAttrAttributeValue.String(), sliceAttrAttributeValue, mapAttrAttributeValue, optInBoolAttrAttributeValue, options...)
}
// RecordDefaultMetricToBeRemovedDataPoint adds a data point to default.metric.to_be_removed metric.
func (mb *MetricsBuilder) RecordDefaultMetricToBeRemovedDataPoint(ts pcommon.Timestamp, val float64) {
mb.metricDefaultMetricToBeRemoved.recordDataPoint(mb.startTime, ts, val)
}
// RecordMetricInputTypeDataPoint adds a data point to metric.input_type metric.
func (mb *MetricsBuilder) RecordMetricInputTypeDataPoint(ts pcommon.Timestamp, inputVal string, stringAttrAttributeValue string, overriddenIntAttrAttributeValue int64, enumAttrAttributeValue AttributeEnumAttr, sliceAttrAttributeValue []any, mapAttrAttributeValue map[string]any) error {
val, err := strconv.ParseInt(inputVal, 10, 64)
if err != nil {
return fmt.Errorf("failed to parse int64 for MetricInputType, value was %s: %w", inputVal, err)
}
mb.metricMetricInputType.recordDataPoint(mb.startTime, ts, val, stringAttrAttributeValue, overriddenIntAttrAttributeValue, enumAttrAttributeValue.String(), sliceAttrAttributeValue, mapAttrAttributeValue)
return nil
}
// RecordOptionalMetricDataPoint adds a data point to optional.metric metric.
func (mb *MetricsBuilder) RecordOptionalMetricDataPoint(ts pcommon.Timestamp, val float64, stringAttrAttributeValue string, booleanAttrAttributeValue bool, booleanAttr2AttributeValue bool, options ...MetricAttributeOption) {
mb.metricOptionalMetric.recordDataPoint(mb.startTime, ts, val, stringAttrAttributeValue, booleanAttrAttributeValue, booleanAttr2AttributeValue, options...)
}
// RecordOptionalMetricEmptyUnitDataPoint adds a data point to optional.metric.empty_unit metric.
func (mb *MetricsBuilder) RecordOptionalMetricEmptyUnitDataPoint(ts pcommon.Timestamp, val float64, stringAttrAttributeValue string, booleanAttrAttributeValue bool) {
mb.metricOptionalMetricEmptyUnit.recordDataPoint(mb.startTime, ts, val, stringAttrAttributeValue, booleanAttrAttributeValue)
}
// RecordReaggregateMetricDataPoint adds a data point to reaggregate.metric metric.
func (mb *MetricsBuilder) RecordReaggregateMetricDataPoint(ts pcommon.Timestamp, val float64, stringAttrAttributeValue string, booleanAttrAttributeValue bool) {
mb.metricReaggregateMetric.recordDataPoint(mb.startTime, ts, val, stringAttrAttributeValue, booleanAttrAttributeValue)
}
// RecordReaggregateMetricWithRequiredDataPoint adds a data point to reaggregate.metric.with_required metric.
func (mb *MetricsBuilder) RecordReaggregateMetricWithRequiredDataPoint(ts pcommon.Timestamp, val float64, requiredStringAttrAttributeValue string, stringAttrAttributeValue string, booleanAttrAttributeValue bool) {
mb.metricReaggregateMetricWithRequired.recordDataPoint(mb.startTime, ts, val, requiredStringAttrAttributeValue, stringAttrAttributeValue, booleanAttrAttributeValue)
}
// RecordSystemCPUTimeDataPoint adds a data point to system.cpu.time metric.
func (mb *MetricsBuilder) RecordSystemCPUTimeDataPoint(ts pcommon.Timestamp, val int64) {
mb.metricSystemCPUTime.recordDataPoint(mb.startTime, ts, val)
}
// Reset resets metrics builder to its initial state. It should be used when external metrics source is restarted,
// and metrics builder should update its startTime and reset it's internal state accordingly.
func (mb *MetricsBuilder) Reset(options ...MetricBuilderOption) {
mb.startTime = pcommon.NewTimestampFromTime(time.Now())
for _, op := range options {
op.apply(mb)
}
}
================================================
FILE: cmd/mdatagen/internal/samplereceiver/internal/metadata/generated_metrics_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"testing"
"github.com/stretchr/testify/assert"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/receiver/receivertest"
)
type testDataSet int
const (
testDataSetDefault testDataSet = iota
testDataSetAll
testDataSetNone
testDataSetReag
)
func TestMetricsBuilder(t *testing.T) {
tests := []struct {
name string
metricsSet testDataSet
resAttrsSet testDataSet
expectEmpty bool
}{
{
name: "default",
},
{
name: "all_set",
metricsSet: testDataSetAll,
resAttrsSet: testDataSetAll,
},
{
name: "reaggregate_set",
metricsSet: testDataSetReag,
resAttrsSet: testDataSetReag,
},
{
name: "none_set",
metricsSet: testDataSetNone,
resAttrsSet: testDataSetNone,
expectEmpty: true,
},
{
name: "filter_set_include",
resAttrsSet: testDataSetAll,
},
{
name: "filter_set_exclude",
resAttrsSet: testDataSetAll,
expectEmpty: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
start := pcommon.Timestamp(1_000_000_000)
ts := pcommon.Timestamp(1_000_001_000)
observedZapCore, observedLogs := observer.New(zap.WarnLevel)
settings := receivertest.NewNopSettings(receivertest.NopType)
settings.Logger = zap.New(observedZapCore)
mb := NewMetricsBuilder(loadMetricsBuilderConfig(t, tt.name), settings, WithStartTime(start))
aggMap := make(map[string]string) // contains the aggregation strategies for each metric name
aggMap["DefaultMetric"] = mb.metricDefaultMetric.config.AggregationStrategy
aggMap["MetricInputType"] = mb.metricMetricInputType.config.AggregationStrategy
aggMap["OptionalMetric"] = mb.metricOptionalMetric.config.AggregationStrategy
aggMap["OptionalMetricEmptyUnit"] = mb.metricOptionalMetricEmptyUnit.config.AggregationStrategy
aggMap["ReaggregateMetric"] = mb.metricReaggregateMetric.config.AggregationStrategy
aggMap["ReaggregateMetricWithRequired"] = mb.metricReaggregateMetricWithRequired.config.AggregationStrategy
expectedWarnings := 0
if tt.metricsSet == testDataSetDefault {
assert.Equal(t, "[WARNING] Please set `enabled` field explicitly for `default.metric`: This metric will be disabled by default soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
if tt.metricsSet == testDataSetDefault || tt.metricsSet == testDataSetAll {
assert.Equal(t, "[WARNING] `default.metric.to_be_removed` should not be enabled: This metric is deprecated and will be removed soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
if tt.metricsSet == testDataSetAll || tt.metricsSet == testDataSetNone {
assert.Equal(t, "[WARNING] `optional.metric` should not be configured: This metric is deprecated and will be removed soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
if tt.metricsSet == testDataSetAll || tt.metricsSet == testDataSetNone {
assert.Equal(t, "[WARNING] `optional.metric.empty_unit` should not be configured: This metric is deprecated and will be removed soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
if tt.resAttrsSet == testDataSetDefault {
assert.Equal(t, "[WARNING] Please set `enabled` field explicitly for `string.resource.attr_disable_warning`: This resource_attribute will be disabled by default soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
if tt.resAttrsSet == testDataSetAll || tt.resAttrsSet == testDataSetNone {
assert.Equal(t, "[WARNING] `string.resource.attr_remove_warning` should not be configured: This resource_attribute is deprecated and will be removed soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
if tt.resAttrsSet == testDataSetDefault || tt.resAttrsSet == testDataSetAll {
assert.Equal(t, "[WARNING] `string.resource.attr_to_be_removed` should not be enabled: This resource_attribute is deprecated and will be removed soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
if tt.metricsSet != testDataSetReag {
assert.Equal(t, expectedWarnings, observedLogs.Len())
}
defaultMetricsCount := 0
allMetricsCount := 0
defaultMetricsCount++
allMetricsCount++
mb.RecordDefaultMetricDataPoint(ts, 1, "string_attr-val", 19, AttributeEnumAttrRed, []any{"slice_attr-item1", "slice_attr-item2"}, map[string]any{"key1": "map_attr-val1", "key2": "map_attr-val2"}, true, WithConditionalIntAttrMetricAttribute(20), WithConditionalStringAttrMetricAttribute("conditional_string_attr-val"))
if tt.name == "reaggregate_set" {
mb.RecordDefaultMetricDataPoint(ts, 3, "string_attr-val-2", 20, AttributeEnumAttrGreen, []any{"slice_attr-item3", "slice_attr-item4"}, map[string]any{"key3": "map_attr-val3", "key4": "map_attr-val4"}, false, WithConditionalIntAttrMetricAttribute(20), WithConditionalStringAttrMetricAttribute("conditional_string_attr-val"))
}
defaultMetricsCount++
allMetricsCount++
mb.RecordDefaultMetricToBeRemovedDataPoint(ts, 1)
defaultMetricsCount++
allMetricsCount++
mb.RecordMetricInputTypeDataPoint(ts, "1", "string_attr-val", 19, AttributeEnumAttrRed, []any{"slice_attr-item1", "slice_attr-item2"}, map[string]any{"key1": "map_attr-val1", "key2": "map_attr-val2"})
if tt.name == "reaggregate_set" {
mb.RecordMetricInputTypeDataPoint(ts, "3", "string_attr-val-2", 20, AttributeEnumAttrGreen, []any{"slice_attr-item3", "slice_attr-item4"}, map[string]any{"key3": "map_attr-val3", "key4": "map_attr-val4"})
}
allMetricsCount++
mb.RecordOptionalMetricDataPoint(ts, 1, "string_attr-val", true, false, WithConditionalStringAttrMetricAttribute("conditional_string_attr-val"))
if tt.name == "reaggregate_set" {
mb.RecordOptionalMetricDataPoint(ts, 3, "string_attr-val-2", false, true, WithConditionalStringAttrMetricAttribute("conditional_string_attr-val"))
}
allMetricsCount++
mb.RecordOptionalMetricEmptyUnitDataPoint(ts, 1, "string_attr-val", true)
if tt.name == "reaggregate_set" {
mb.RecordOptionalMetricEmptyUnitDataPoint(ts, 3, "string_attr-val-2", false)
}
defaultMetricsCount++
allMetricsCount++
mb.RecordReaggregateMetricDataPoint(ts, 1, "string_attr-val", true)
if tt.name == "reaggregate_set" {
mb.RecordReaggregateMetricDataPoint(ts, 3, "string_attr-val-2", false)
}
defaultMetricsCount++
allMetricsCount++
mb.RecordReaggregateMetricWithRequiredDataPoint(ts, 1, "required_string_attr-val", "string_attr-val", true)
if tt.name == "reaggregate_set" {
mb.RecordReaggregateMetricWithRequiredDataPoint(ts, 3, "required_string_attr-val", "string_attr-val-2", false)
}
defaultMetricsCount++
allMetricsCount++
mb.RecordSystemCPUTimeDataPoint(ts, 1)
rb := mb.NewResourceBuilder()
rb.SetMapResourceAttr(map[string]any{"key1": "map.resource.attr-val1", "key2": "map.resource.attr-val2"})
rb.SetOptionalResourceAttr("optional.resource.attr-val")
rb.SetSliceResourceAttr([]any{"slice.resource.attr-item1", "slice.resource.attr-item2"})
rb.SetStringEnumResourceAttrOne()
rb.SetStringResourceAttr("string.resource.attr-val")
rb.SetStringResourceAttrDisableWarning("string.resource.attr_disable_warning-val")
rb.SetStringResourceAttrRemoveWarning("string.resource.attr_remove_warning-val")
rb.SetStringResourceAttrToBeRemoved("string.resource.attr_to_be_removed-val")
res := rb.Emit()
metrics := mb.Emit(WithResource(res))
if tt.name == "reaggregate_set" {
assert.Empty(t, mb.metricDefaultMetric.aggDataPoints)
assert.Empty(t, mb.metricMetricInputType.aggDataPoints)
assert.Empty(t, mb.metricOptionalMetric.aggDataPoints)
assert.Empty(t, mb.metricOptionalMetricEmptyUnit.aggDataPoints)
assert.Empty(t, mb.metricReaggregateMetric.aggDataPoints)
assert.Empty(t, mb.metricReaggregateMetricWithRequired.aggDataPoints)
}
if tt.expectEmpty {
assert.Equal(t, 0, metrics.ResourceMetrics().Len())
return
}
var allMetricsList []pmetric.Metric
totalMetricsCount := 0
for ri := 0; ri < metrics.ResourceMetrics().Len(); ri++ {
rm := metrics.ResourceMetrics().At(ri)
assert.Equal(t, 1, rm.ScopeMetrics().Len())
ms := rm.ScopeMetrics().At(0).Metrics()
totalMetricsCount += ms.Len()
for mi := 0; mi < ms.Len(); mi++ {
allMetricsList = append(allMetricsList, ms.At(mi))
}
}
if tt.metricsSet == testDataSetDefault {
assert.Equal(t, defaultMetricsCount, totalMetricsCount)
}
if tt.metricsSet == testDataSetAll {
assert.Equal(t, allMetricsCount, totalMetricsCount)
}
validatedMetrics := make(map[string]bool)
for _, mi := range allMetricsList {
switch mi.Name() {
case "default.metric":
if tt.name != "reaggregate_set" {
assert.False(t, validatedMetrics["default.metric"], "Found a duplicate in the metrics slice: default.metric")
validatedMetrics["default.metric"] = true
assert.Equal(t, pmetric.MetricTypeSum, mi.Type())
assert.Equal(t, 1, mi.Sum().DataPoints().Len())
assert.Equal(t, "Monotonic cumulative sum int metric enabled by default.", mi.Description())
assert.Equal(t, "s", mi.Unit())
assert.True(t, mi.Sum().IsMonotonic())
assert.Equal(t, pmetric.AggregationTemporalityCumulative, mi.Sum().AggregationTemporality())
dp := mi.Sum().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeInt, dp.ValueType())
assert.Equal(t, int64(1), dp.IntValue())
stringAttrAttrVal, ok := dp.Attributes().Get("string_attr")
assert.True(t, ok)
assert.Equal(t, "string_attr-val", stringAttrAttrVal.Str())
overriddenIntAttrAttrVal, ok := dp.Attributes().Get("state")
assert.True(t, ok)
assert.EqualValues(t, 19, overriddenIntAttrAttrVal.Int())
enumAttrAttrVal, ok := dp.Attributes().Get("enum_attr")
assert.True(t, ok)
assert.Equal(t, "red", enumAttrAttrVal.Str())
sliceAttrAttrVal, ok := dp.Attributes().Get("slice_attr")
assert.True(t, ok)
assert.Equal(t, []any{"slice_attr-item1", "slice_attr-item2"}, sliceAttrAttrVal.Slice().AsRaw())
mapAttrAttrVal, ok := dp.Attributes().Get("map_attr")
assert.True(t, ok)
assert.Equal(t, map[string]any{"key1": "map_attr-val1", "key2": "map_attr-val2"}, mapAttrAttrVal.Map().AsRaw())
conditionalIntAttrAttrVal, ok := dp.Attributes().Get("conditional_int_attr")
assert.True(t, ok)
assert.EqualValues(t, 20, conditionalIntAttrAttrVal.Int())
conditionalStringAttrAttrVal, ok := dp.Attributes().Get("conditional_string_attr")
assert.True(t, ok)
assert.Equal(t, "conditional_string_attr-val", conditionalStringAttrAttrVal.Str())
} else {
assert.False(t, validatedMetrics["default.metric"], "Found a duplicate in the metrics slice: default.metric")
validatedMetrics["default.metric"] = true
assert.Equal(t, pmetric.MetricTypeSum, mi.Type())
assert.Equal(t, 1, mi.Sum().DataPoints().Len())
assert.Equal(t, "Monotonic cumulative sum int metric enabled by default.", mi.Description())
assert.Equal(t, "s", mi.Unit())
assert.True(t, mi.Sum().IsMonotonic())
assert.Equal(t, pmetric.AggregationTemporalityCumulative, mi.Sum().AggregationTemporality())
dp := mi.Sum().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeInt, dp.ValueType())
switch aggMap["default.metric"] {
case "sum":
assert.Equal(t, int64(4), dp.IntValue())
case "avg":
assert.Equal(t, int64(2), dp.IntValue())
case "min":
assert.Equal(t, int64(1), dp.IntValue())
case "max":
assert.Equal(t, int64(3), dp.IntValue())
}
_, ok := dp.Attributes().Get("string_attr")
assert.False(t, ok)
_, ok = dp.Attributes().Get("state")
assert.False(t, ok)
_, ok = dp.Attributes().Get("enum_attr")
assert.False(t, ok)
_, ok = dp.Attributes().Get("slice_attr")
assert.False(t, ok)
_, ok = dp.Attributes().Get("map_attr")
assert.False(t, ok)
_, ok = dp.Attributes().Get("opt_in_bool_attr")
assert.False(t, ok)
}
case "default.metric.to_be_removed":
assert.False(t, validatedMetrics["default.metric.to_be_removed"], "Found a duplicate in the metrics slice: default.metric.to_be_removed")
validatedMetrics["default.metric.to_be_removed"] = true
assert.Equal(t, pmetric.MetricTypeSum, mi.Type())
assert.Equal(t, 1, mi.Sum().DataPoints().Len())
assert.Equal(t, "[DEPRECATED] Non-monotonic delta sum double metric enabled by default.", mi.Description())
assert.Equal(t, "s", mi.Unit())
assert.False(t, mi.Sum().IsMonotonic())
assert.Equal(t, pmetric.AggregationTemporalityDelta, mi.Sum().AggregationTemporality())
dp := mi.Sum().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeDouble, dp.ValueType())
assert.InDelta(t, float64(1), dp.DoubleValue(), 0.01)
case "metric.input_type":
if tt.name != "reaggregate_set" {
assert.False(t, validatedMetrics["metric.input_type"], "Found a duplicate in the metrics slice: metric.input_type")
validatedMetrics["metric.input_type"] = true
assert.Equal(t, pmetric.MetricTypeSum, mi.Type())
assert.Equal(t, 1, mi.Sum().DataPoints().Len())
assert.Equal(t, "Monotonic cumulative sum int metric with string input_type enabled by default.", mi.Description())
assert.Equal(t, "s", mi.Unit())
assert.True(t, mi.Sum().IsMonotonic())
assert.Equal(t, pmetric.AggregationTemporalityCumulative, mi.Sum().AggregationTemporality())
dp := mi.Sum().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeInt, dp.ValueType())
assert.Equal(t, int64(1), dp.IntValue())
stringAttrAttrVal, ok := dp.Attributes().Get("string_attr")
assert.True(t, ok)
assert.Equal(t, "string_attr-val", stringAttrAttrVal.Str())
overriddenIntAttrAttrVal, ok := dp.Attributes().Get("state")
assert.True(t, ok)
assert.EqualValues(t, 19, overriddenIntAttrAttrVal.Int())
enumAttrAttrVal, ok := dp.Attributes().Get("enum_attr")
assert.True(t, ok)
assert.Equal(t, "red", enumAttrAttrVal.Str())
sliceAttrAttrVal, ok := dp.Attributes().Get("slice_attr")
assert.True(t, ok)
assert.Equal(t, []any{"slice_attr-item1", "slice_attr-item2"}, sliceAttrAttrVal.Slice().AsRaw())
mapAttrAttrVal, ok := dp.Attributes().Get("map_attr")
assert.True(t, ok)
assert.Equal(t, map[string]any{"key1": "map_attr-val1", "key2": "map_attr-val2"}, mapAttrAttrVal.Map().AsRaw())
} else {
assert.False(t, validatedMetrics["metric.input_type"], "Found a duplicate in the metrics slice: metric.input_type")
validatedMetrics["metric.input_type"] = true
assert.Equal(t, pmetric.MetricTypeSum, mi.Type())
assert.Equal(t, 1, mi.Sum().DataPoints().Len())
assert.Equal(t, "Monotonic cumulative sum int metric with string input_type enabled by default.", mi.Description())
assert.Equal(t, "s", mi.Unit())
assert.True(t, mi.Sum().IsMonotonic())
assert.Equal(t, pmetric.AggregationTemporalityCumulative, mi.Sum().AggregationTemporality())
dp := mi.Sum().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeInt, dp.ValueType())
switch aggMap["metric.input_type"] {
case "sum":
assert.Equal(t, int64(4), dp.IntValue())
case "avg":
assert.Equal(t, int64(2), dp.IntValue())
case "min":
assert.Equal(t, int64(1), dp.IntValue())
case "max":
assert.Equal(t, int64(3), dp.IntValue())
}
_, ok := dp.Attributes().Get("string_attr")
assert.False(t, ok)
_, ok = dp.Attributes().Get("state")
assert.False(t, ok)
_, ok = dp.Attributes().Get("enum_attr")
assert.False(t, ok)
_, ok = dp.Attributes().Get("slice_attr")
assert.False(t, ok)
_, ok = dp.Attributes().Get("map_attr")
assert.False(t, ok)
}
case "optional.metric":
if tt.name != "reaggregate_set" {
assert.False(t, validatedMetrics["optional.metric"], "Found a duplicate in the metrics slice: optional.metric")
validatedMetrics["optional.metric"] = true
assert.Equal(t, pmetric.MetricTypeGauge, mi.Type())
assert.Equal(t, 1, mi.Gauge().DataPoints().Len())
assert.Equal(t, "[DEPRECATED] Gauge double metric disabled by default.", mi.Description())
assert.Equal(t, "1", mi.Unit())
dp := mi.Gauge().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeDouble, dp.ValueType())
assert.InDelta(t, float64(1), dp.DoubleValue(), 0.01)
stringAttrAttrVal, ok := dp.Attributes().Get("string_attr")
assert.True(t, ok)
assert.Equal(t, "string_attr-val", stringAttrAttrVal.Str())
booleanAttrAttrVal, ok := dp.Attributes().Get("boolean_attr")
assert.True(t, ok)
assert.True(t, booleanAttrAttrVal.Bool())
booleanAttr2AttrVal, ok := dp.Attributes().Get("boolean_attr2")
assert.True(t, ok)
assert.False(t, booleanAttr2AttrVal.Bool())
conditionalStringAttrAttrVal, ok := dp.Attributes().Get("conditional_string_attr")
assert.True(t, ok)
assert.Equal(t, "conditional_string_attr-val", conditionalStringAttrAttrVal.Str())
} else {
assert.False(t, validatedMetrics["optional.metric"], "Found a duplicate in the metrics slice: optional.metric")
validatedMetrics["optional.metric"] = true
assert.Equal(t, pmetric.MetricTypeGauge, mi.Type())
assert.Equal(t, 1, mi.Gauge().DataPoints().Len())
assert.Equal(t, "[DEPRECATED] Gauge double metric disabled by default.", mi.Description())
assert.Equal(t, "1", mi.Unit())
dp := mi.Gauge().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeDouble, dp.ValueType())
switch aggMap["optional.metric"] {
case "sum":
assert.InDelta(t, float64(4), dp.DoubleValue(), 0.01)
case "avg":
assert.InDelta(t, float64(2), dp.DoubleValue(), 0.01)
case "min":
assert.InDelta(t, float64(1), dp.DoubleValue(), 0.01)
case "max":
assert.InDelta(t, float64(3), dp.DoubleValue(), 0.01)
}
_, ok := dp.Attributes().Get("string_attr")
assert.False(t, ok)
_, ok = dp.Attributes().Get("boolean_attr")
assert.False(t, ok)
_, ok = dp.Attributes().Get("boolean_attr2")
assert.False(t, ok)
}
case "optional.metric.empty_unit":
if tt.name != "reaggregate_set" {
assert.False(t, validatedMetrics["optional.metric.empty_unit"], "Found a duplicate in the metrics slice: optional.metric.empty_unit")
validatedMetrics["optional.metric.empty_unit"] = true
assert.Equal(t, pmetric.MetricTypeGauge, mi.Type())
assert.Equal(t, 1, mi.Gauge().DataPoints().Len())
assert.Equal(t, "[DEPRECATED] Gauge double metric disabled by default.", mi.Description())
assert.Empty(t, mi.Unit())
dp := mi.Gauge().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeDouble, dp.ValueType())
assert.InDelta(t, float64(1), dp.DoubleValue(), 0.01)
stringAttrAttrVal, ok := dp.Attributes().Get("string_attr")
assert.True(t, ok)
assert.Equal(t, "string_attr-val", stringAttrAttrVal.Str())
booleanAttrAttrVal, ok := dp.Attributes().Get("boolean_attr")
assert.True(t, ok)
assert.True(t, booleanAttrAttrVal.Bool())
} else {
assert.False(t, validatedMetrics["optional.metric.empty_unit"], "Found a duplicate in the metrics slice: optional.metric.empty_unit")
validatedMetrics["optional.metric.empty_unit"] = true
assert.Equal(t, pmetric.MetricTypeGauge, mi.Type())
assert.Equal(t, 1, mi.Gauge().DataPoints().Len())
assert.Equal(t, "[DEPRECATED] Gauge double metric disabled by default.", mi.Description())
assert.Empty(t, mi.Unit())
dp := mi.Gauge().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeDouble, dp.ValueType())
switch aggMap["optional.metric.empty_unit"] {
case "sum":
assert.InDelta(t, float64(4), dp.DoubleValue(), 0.01)
case "avg":
assert.InDelta(t, float64(2), dp.DoubleValue(), 0.01)
case "min":
assert.InDelta(t, float64(1), dp.DoubleValue(), 0.01)
case "max":
assert.InDelta(t, float64(3), dp.DoubleValue(), 0.01)
}
_, ok := dp.Attributes().Get("string_attr")
assert.False(t, ok)
_, ok = dp.Attributes().Get("boolean_attr")
assert.False(t, ok)
}
case "reaggregate.metric":
if tt.name != "reaggregate_set" {
assert.False(t, validatedMetrics["reaggregate.metric"], "Found a duplicate in the metrics slice: reaggregate.metric")
validatedMetrics["reaggregate.metric"] = true
assert.Equal(t, pmetric.MetricTypeGauge, mi.Type())
assert.Equal(t, 1, mi.Gauge().DataPoints().Len())
assert.Equal(t, "Metric for testing spatial reaggregation", mi.Description())
assert.Equal(t, "1", mi.Unit())
dp := mi.Gauge().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeDouble, dp.ValueType())
assert.InDelta(t, float64(1), dp.DoubleValue(), 0.01)
stringAttrAttrVal, ok := dp.Attributes().Get("string_attr")
assert.True(t, ok)
assert.Equal(t, "string_attr-val", stringAttrAttrVal.Str())
booleanAttrAttrVal, ok := dp.Attributes().Get("boolean_attr")
assert.True(t, ok)
assert.True(t, booleanAttrAttrVal.Bool())
} else {
assert.False(t, validatedMetrics["reaggregate.metric"], "Found a duplicate in the metrics slice: reaggregate.metric")
validatedMetrics["reaggregate.metric"] = true
assert.Equal(t, pmetric.MetricTypeGauge, mi.Type())
assert.Equal(t, 1, mi.Gauge().DataPoints().Len())
assert.Equal(t, "Metric for testing spatial reaggregation", mi.Description())
assert.Equal(t, "1", mi.Unit())
dp := mi.Gauge().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeDouble, dp.ValueType())
switch aggMap["reaggregate.metric"] {
case "sum":
assert.InDelta(t, float64(4), dp.DoubleValue(), 0.01)
case "avg":
assert.InDelta(t, float64(2), dp.DoubleValue(), 0.01)
case "min":
assert.InDelta(t, float64(1), dp.DoubleValue(), 0.01)
case "max":
assert.InDelta(t, float64(3), dp.DoubleValue(), 0.01)
}
_, ok := dp.Attributes().Get("string_attr")
assert.False(t, ok)
_, ok = dp.Attributes().Get("boolean_attr")
assert.False(t, ok)
}
case "reaggregate.metric.with_required":
if tt.name != "reaggregate_set" {
assert.False(t, validatedMetrics["reaggregate.metric.with_required"], "Found a duplicate in the metrics slice: reaggregate.metric.with_required")
validatedMetrics["reaggregate.metric.with_required"] = true
assert.Equal(t, pmetric.MetricTypeGauge, mi.Type())
assert.Equal(t, 1, mi.Gauge().DataPoints().Len())
assert.Equal(t, "Metric for testing spatial reaggregation with required attributes", mi.Description())
assert.Equal(t, "1", mi.Unit())
dp := mi.Gauge().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeDouble, dp.ValueType())
assert.InDelta(t, float64(1), dp.DoubleValue(), 0.01)
requiredStringAttrAttrVal, ok := dp.Attributes().Get("required_string_attr")
assert.True(t, ok)
assert.Equal(t, "required_string_attr-val", requiredStringAttrAttrVal.Str())
stringAttrAttrVal, ok := dp.Attributes().Get("string_attr")
assert.True(t, ok)
assert.Equal(t, "string_attr-val", stringAttrAttrVal.Str())
booleanAttrAttrVal, ok := dp.Attributes().Get("boolean_attr")
assert.True(t, ok)
assert.True(t, booleanAttrAttrVal.Bool())
} else {
assert.False(t, validatedMetrics["reaggregate.metric.with_required"], "Found a duplicate in the metrics slice: reaggregate.metric.with_required")
validatedMetrics["reaggregate.metric.with_required"] = true
assert.Equal(t, pmetric.MetricTypeGauge, mi.Type())
assert.Equal(t, 1, mi.Gauge().DataPoints().Len())
assert.Equal(t, "Metric for testing spatial reaggregation with required attributes", mi.Description())
assert.Equal(t, "1", mi.Unit())
dp := mi.Gauge().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeDouble, dp.ValueType())
switch aggMap["reaggregate.metric.with_required"] {
case "sum":
assert.InDelta(t, float64(4), dp.DoubleValue(), 0.01)
case "avg":
assert.InDelta(t, float64(2), dp.DoubleValue(), 0.01)
case "min":
assert.InDelta(t, float64(1), dp.DoubleValue(), 0.01)
case "max":
assert.InDelta(t, float64(3), dp.DoubleValue(), 0.01)
}
_, ok := dp.Attributes().Get("required_string_attr")
assert.True(t, ok)
_, ok = dp.Attributes().Get("string_attr")
assert.False(t, ok)
_, ok = dp.Attributes().Get("boolean_attr")
assert.False(t, ok)
}
case "system.cpu.time":
assert.False(t, validatedMetrics["system.cpu.time"], "Found a duplicate in the metrics slice: system.cpu.time")
validatedMetrics["system.cpu.time"] = true
assert.Equal(t, pmetric.MetricTypeSum, mi.Type())
assert.Equal(t, 1, mi.Sum().DataPoints().Len())
assert.Equal(t, "Monotonic cumulative sum int metric enabled by default.", mi.Description())
assert.Equal(t, "s", mi.Unit())
assert.True(t, mi.Sum().IsMonotonic())
assert.Equal(t, pmetric.AggregationTemporalityCumulative, mi.Sum().AggregationTemporality())
dp := mi.Sum().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeInt, dp.ValueType())
assert.Equal(t, int64(1), dp.IntValue())
}
}
})
}
}
================================================
FILE: cmd/mdatagen/internal/samplereceiver/internal/metadata/generated_resource.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/pdata/pcommon"
)
// ResourceBuilder is a helper struct to build resources predefined in metadata.yaml.
// The ResourceBuilder is not thread-safe and must not to be used in multiple goroutines.
type ResourceBuilder struct {
config ResourceAttributesConfig
res pcommon.Resource
}
// NewResourceBuilder creates a new ResourceBuilder. This method should be called on the start of the application.
func NewResourceBuilder(rac ResourceAttributesConfig) *ResourceBuilder {
return &ResourceBuilder{
config: rac,
res: pcommon.NewResource(),
}
}
// SetMapResourceAttr sets provided value as "map.resource.attr" attribute.
func (rb *ResourceBuilder) SetMapResourceAttr(val map[string]any) {
if rb.config.MapResourceAttr.Enabled {
rb.res.Attributes().PutEmptyMap("map.resource.attr").FromRaw(val)
}
}
// SetOptionalResourceAttr sets provided value as "optional.resource.attr" attribute.
func (rb *ResourceBuilder) SetOptionalResourceAttr(val string) {
if rb.config.OptionalResourceAttr.Enabled {
rb.res.Attributes().PutStr("optional.resource.attr", val)
}
}
// SetSliceResourceAttr sets provided value as "slice.resource.attr" attribute.
func (rb *ResourceBuilder) SetSliceResourceAttr(val []any) {
if rb.config.SliceResourceAttr.Enabled {
rb.res.Attributes().PutEmptySlice("slice.resource.attr").FromRaw(val)
}
}
// SetStringEnumResourceAttrOne sets "string.enum.resource.attr=one" attribute.
func (rb *ResourceBuilder) SetStringEnumResourceAttrOne() {
if rb.config.StringEnumResourceAttr.Enabled {
rb.res.Attributes().PutStr("string.enum.resource.attr", "one")
}
}
// SetStringEnumResourceAttrTwo sets "string.enum.resource.attr=two" attribute.
func (rb *ResourceBuilder) SetStringEnumResourceAttrTwo() {
if rb.config.StringEnumResourceAttr.Enabled {
rb.res.Attributes().PutStr("string.enum.resource.attr", "two")
}
}
// SetStringResourceAttr sets provided value as "string.resource.attr" attribute.
func (rb *ResourceBuilder) SetStringResourceAttr(val string) {
if rb.config.StringResourceAttr.Enabled {
rb.res.Attributes().PutStr("string.resource.attr", val)
}
}
// SetStringResourceAttrDisableWarning sets provided value as "string.resource.attr_disable_warning" attribute.
func (rb *ResourceBuilder) SetStringResourceAttrDisableWarning(val string) {
if rb.config.StringResourceAttrDisableWarning.Enabled {
rb.res.Attributes().PutStr("string.resource.attr_disable_warning", val)
}
}
// SetStringResourceAttrRemoveWarning sets provided value as "string.resource.attr_remove_warning" attribute.
func (rb *ResourceBuilder) SetStringResourceAttrRemoveWarning(val string) {
if rb.config.StringResourceAttrRemoveWarning.Enabled {
rb.res.Attributes().PutStr("string.resource.attr_remove_warning", val)
}
}
// SetStringResourceAttrToBeRemoved sets provided value as "string.resource.attr_to_be_removed" attribute.
func (rb *ResourceBuilder) SetStringResourceAttrToBeRemoved(val string) {
if rb.config.StringResourceAttrToBeRemoved.Enabled {
rb.res.Attributes().PutStr("string.resource.attr_to_be_removed", val)
}
}
// Emit returns the built resource and resets the internal builder state.
func (rb *ResourceBuilder) Emit() pcommon.Resource {
r := rb.res
rb.res = pcommon.NewResource()
return r
}
================================================
FILE: cmd/mdatagen/internal/samplereceiver/internal/metadata/generated_resource_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestResourceBuilder(t *testing.T) {
for _, tt := range []string{"default", "all_set", "none_set"} {
t.Run(tt, func(t *testing.T) {
cfg := loadResourceAttributesConfig(t, tt)
rb := NewResourceBuilder(cfg)
rb.SetMapResourceAttr(map[string]any{"key1": "map.resource.attr-val1", "key2": "map.resource.attr-val2"})
rb.SetOptionalResourceAttr("optional.resource.attr-val")
rb.SetSliceResourceAttr([]any{"slice.resource.attr-item1", "slice.resource.attr-item2"})
rb.SetStringEnumResourceAttrOne()
rb.SetStringResourceAttr("string.resource.attr-val")
rb.SetStringResourceAttrDisableWarning("string.resource.attr_disable_warning-val")
rb.SetStringResourceAttrRemoveWarning("string.resource.attr_remove_warning-val")
rb.SetStringResourceAttrToBeRemoved("string.resource.attr_to_be_removed-val")
res := rb.Emit()
assert.Equal(t, 0, rb.Emit().Attributes().Len()) // Second call should return empty Resource
switch tt {
case "default":
assert.Equal(t, 6, res.Attributes().Len())
case "all_set":
assert.Equal(t, 8, res.Attributes().Len())
case "none_set":
assert.Equal(t, 0, res.Attributes().Len())
return
default:
assert.Failf(t, "unexpected test case: %s", tt)
}
mapResourceAttrAttrVal, ok := res.Attributes().Get("map.resource.attr")
assert.True(t, ok)
if ok {
assert.Equal(t, map[string]any{"key1": "map.resource.attr-val1", "key2": "map.resource.attr-val2"}, mapResourceAttrAttrVal.Map().AsRaw())
}
optionalResourceAttrAttrVal, ok := res.Attributes().Get("optional.resource.attr")
assert.Equal(t, tt == "all_set", ok)
if ok {
assert.Equal(t, "optional.resource.attr-val", optionalResourceAttrAttrVal.Str())
}
sliceResourceAttrAttrVal, ok := res.Attributes().Get("slice.resource.attr")
assert.True(t, ok)
if ok {
assert.Equal(t, []any{"slice.resource.attr-item1", "slice.resource.attr-item2"}, sliceResourceAttrAttrVal.Slice().AsRaw())
}
stringEnumResourceAttrAttrVal, ok := res.Attributes().Get("string.enum.resource.attr")
assert.True(t, ok)
if ok {
assert.Equal(t, "one", stringEnumResourceAttrAttrVal.Str())
}
stringResourceAttrAttrVal, ok := res.Attributes().Get("string.resource.attr")
assert.True(t, ok)
if ok {
assert.Equal(t, "string.resource.attr-val", stringResourceAttrAttrVal.Str())
}
stringResourceAttrDisableWarningAttrVal, ok := res.Attributes().Get("string.resource.attr_disable_warning")
assert.True(t, ok)
if ok {
assert.Equal(t, "string.resource.attr_disable_warning-val", stringResourceAttrDisableWarningAttrVal.Str())
}
stringResourceAttrRemoveWarningAttrVal, ok := res.Attributes().Get("string.resource.attr_remove_warning")
assert.Equal(t, tt == "all_set", ok)
if ok {
assert.Equal(t, "string.resource.attr_remove_warning-val", stringResourceAttrRemoveWarningAttrVal.Str())
}
stringResourceAttrToBeRemovedAttrVal, ok := res.Attributes().Get("string.resource.attr_to_be_removed")
assert.True(t, ok)
if ok {
assert.Equal(t, "string.resource.attr_to_be_removed-val", stringResourceAttrToBeRemovedAttrVal.Str())
}
})
}
}
================================================
FILE: cmd/mdatagen/internal/samplereceiver/internal/metadata/generated_status.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/component"
)
var (
Type = component.MustNewType("sample")
ScopeName = "go.opentelemetry.io/collector/internal/receiver/samplereceiver"
)
const (
ProfilesStability = component.StabilityLevelDeprecated
LogsStability = component.StabilityLevelDevelopment
TracesStability = component.StabilityLevelBeta
MetricsStability = component.StabilityLevelStable
)
================================================
FILE: cmd/mdatagen/internal/samplereceiver/internal/metadata/generated_telemetry.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"context"
"errors"
"sync"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/metric/embedded"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/collector/component"
)
func Meter(settings component.TelemetrySettings) metric.Meter {
return settings.MeterProvider.Meter("go.opentelemetry.io/collector/internal/receiver/samplereceiver")
}
func Tracer(settings component.TelemetrySettings) trace.Tracer {
return settings.TracerProvider.Tracer("go.opentelemetry.io/collector/internal/receiver/samplereceiver")
}
// TelemetryBuilder provides an interface for components to report telemetry
// as defined in metadata and user config.
type TelemetryBuilder struct {
meter metric.Meter
mu sync.Mutex
registrations []metric.Registration
BatchSizeTriggerSend metric.Int64Counter
ProcessRuntimeTotalAllocBytes metric.Int64ObservableCounter
QueueCapacity metric.Int64Gauge
QueueLength metric.Int64ObservableGauge
RequestDuration metric.Float64Histogram
}
// TelemetryBuilderOption applies changes to default builder.
type TelemetryBuilderOption interface {
apply(*TelemetryBuilder)
}
type telemetryBuilderOptionFunc func(mb *TelemetryBuilder)
func (tbof telemetryBuilderOptionFunc) apply(mb *TelemetryBuilder) {
tbof(mb)
}
// RegisterProcessRuntimeTotalAllocBytesCallback sets callback for observable ProcessRuntimeTotalAllocBytes metric.
func (builder *TelemetryBuilder) RegisterProcessRuntimeTotalAllocBytesCallback(cb metric.Int64Callback) error {
reg, err := builder.meter.RegisterCallback(func(ctx context.Context, o metric.Observer) error {
cb(ctx, &observerInt64{inst: builder.ProcessRuntimeTotalAllocBytes, obs: o})
return nil
}, builder.ProcessRuntimeTotalAllocBytes)
if err != nil {
return err
}
builder.mu.Lock()
defer builder.mu.Unlock()
builder.registrations = append(builder.registrations, reg)
return nil
}
// RegisterQueueLengthCallback sets callback for observable QueueLength metric.
func (builder *TelemetryBuilder) RegisterQueueLengthCallback(cb metric.Int64Callback) error {
reg, err := builder.meter.RegisterCallback(func(ctx context.Context, o metric.Observer) error {
cb(ctx, &observerInt64{inst: builder.QueueLength, obs: o})
return nil
}, builder.QueueLength)
if err != nil {
return err
}
builder.mu.Lock()
defer builder.mu.Unlock()
builder.registrations = append(builder.registrations, reg)
return nil
}
type observerInt64 struct {
embedded.Int64Observer
inst metric.Int64Observable
obs metric.Observer
}
func (oi *observerInt64) Observe(value int64, opts ...metric.ObserveOption) {
oi.obs.ObserveInt64(oi.inst, value, opts...)
}
// Shutdown unregister all registered callbacks for async instruments.
func (builder *TelemetryBuilder) Shutdown() {
builder.mu.Lock()
defer builder.mu.Unlock()
for _, reg := range builder.registrations {
reg.Unregister()
}
}
// NewTelemetryBuilder provides a struct with methods to update all internal telemetry
// for a component
func NewTelemetryBuilder(settings component.TelemetrySettings, options ...TelemetryBuilderOption) (*TelemetryBuilder, error) {
builder := TelemetryBuilder{}
for _, op := range options {
op.apply(&builder)
}
builder.meter = Meter(settings)
var err, errs error
builder.BatchSizeTriggerSend, err = builder.meter.Int64Counter(
"otelcol_batch_size_trigger_send",
metric.WithDescription("Number of times the batch was sent due to a size trigger [Deprecated]"),
metric.WithUnit("{time}"),
)
errs = errors.Join(errs, err)
builder.ProcessRuntimeTotalAllocBytes, err = builder.meter.Int64ObservableCounter(
"otelcol_process_runtime_total_alloc_bytes",
metric.WithDescription("Cumulative bytes allocated for heap objects (see 'go doc runtime.MemStats.TotalAlloc') [Stable]"),
metric.WithUnit("By"),
)
errs = errors.Join(errs, err)
builder.QueueCapacity, err = builder.meter.Int64Gauge(
"otelcol_queue_capacity",
metric.WithDescription("Queue capacity - sync gauge example. [Development]"),
metric.WithUnit("{item}"),
)
errs = errors.Join(errs, err)
builder.QueueLength, err = builder.meter.Int64ObservableGauge(
"otelcol_queue_length",
metric.WithDescription("This metric is optional and therefore not initialized in NewTelemetryBuilder. [Alpha]"),
metric.WithUnit("{item}"),
)
errs = errors.Join(errs, err)
builder.RequestDuration, err = builder.meter.Float64Histogram(
"otelcol_request_duration",
metric.WithDescription("Duration of request [Alpha]"),
metric.WithUnit("s"),
metric.WithExplicitBucketBoundaries([]float64{1, 10, 100}...),
)
errs = errors.Join(errs, err)
return &builder, errs
}
================================================
FILE: cmd/mdatagen/internal/samplereceiver/internal/metadata/generated_telemetry_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/metric"
embeddedmetric "go.opentelemetry.io/otel/metric/embedded"
noopmetric "go.opentelemetry.io/otel/metric/noop"
"go.opentelemetry.io/otel/trace"
embeddedtrace "go.opentelemetry.io/otel/trace/embedded"
nooptrace "go.opentelemetry.io/otel/trace/noop"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
)
type mockMeter struct {
noopmetric.Meter
name string
}
type mockMeterProvider struct {
embeddedmetric.MeterProvider
}
func (m mockMeterProvider) Meter(name string, opts ...metric.MeterOption) metric.Meter {
return mockMeter{name: name}
}
type mockTracer struct {
nooptrace.Tracer
name string
}
type mockTracerProvider struct {
embeddedtrace.TracerProvider
}
func (m mockTracerProvider) Tracer(name string, opts ...trace.TracerOption) trace.Tracer {
return mockTracer{name: name}
}
func TestProviders(t *testing.T) {
set := component.TelemetrySettings{
MeterProvider: mockMeterProvider{},
TracerProvider: mockTracerProvider{},
}
meter := Meter(set)
if m, ok := meter.(mockMeter); ok {
require.Equal(t, "go.opentelemetry.io/collector/internal/receiver/samplereceiver", m.name)
} else {
require.Fail(t, "returned Meter not mockMeter")
}
tracer := Tracer(set)
if m, ok := tracer.(mockTracer); ok {
require.Equal(t, "go.opentelemetry.io/collector/internal/receiver/samplereceiver", m.name)
} else {
require.Fail(t, "returned Meter not mockTracer")
}
}
func TestNewTelemetryBuilder(t *testing.T) {
set := componenttest.NewNopTelemetrySettings()
applied := false
_, err := NewTelemetryBuilder(set, telemetryBuilderOptionFunc(func(b *TelemetryBuilder) {
applied = true
}))
require.NoError(t, err)
require.True(t, applied)
}
================================================
FILE: cmd/mdatagen/internal/samplereceiver/internal/metadata/testdata/config.yaml
================================================
default:
all_set:
metrics:
default.metric:
enabled: true
attributes: ["string_attr","state","enum_attr","slice_attr","map_attr","conditional_int_attr","conditional_string_attr","opt_in_bool_attr"]
default.metric.to_be_removed:
enabled: true
metric.input_type:
enabled: true
attributes: ["string_attr","state","enum_attr","slice_attr","map_attr"]
optional.metric:
enabled: true
attributes: ["string_attr","boolean_attr","boolean_attr2","conditional_string_attr"]
optional.metric.empty_unit:
enabled: true
attributes: ["string_attr","boolean_attr"]
reaggregate.metric:
enabled: true
attributes: ["string_attr","boolean_attr"]
reaggregate.metric.with_required:
enabled: true
attributes: ["required_string_attr","string_attr","boolean_attr"]
system.cpu.time:
enabled: true
events:
default.event:
enabled: true
default.event.to_be_removed:
enabled: true
default.event.to_be_renamed:
enabled: true
resource_attributes:
map.resource.attr:
enabled: true
optional.resource.attr:
enabled: true
slice.resource.attr:
enabled: true
string.enum.resource.attr:
enabled: true
string.resource.attr:
enabled: true
string.resource.attr_disable_warning:
enabled: true
string.resource.attr_remove_warning:
enabled: true
string.resource.attr_to_be_removed:
enabled: true
reaggregate_set:
metrics:
default.metric:
enabled: true
attributes: []
default.metric.to_be_removed:
enabled: true
metric.input_type:
enabled: true
attributes: []
optional.metric:
enabled: true
attributes: []
optional.metric.empty_unit:
enabled: true
attributes: []
reaggregate.metric:
enabled: true
attributes: []
reaggregate.metric.with_required:
enabled: true
attributes: ["required_string_attr"]
system.cpu.time:
enabled: true
events:
default.event:
enabled: true
default.event.to_be_removed:
enabled: true
default.event.to_be_renamed:
enabled: true
resource_attributes:
map.resource.attr:
enabled: true
optional.resource.attr:
enabled: true
slice.resource.attr:
enabled: true
string.enum.resource.attr:
enabled: true
string.resource.attr:
enabled: true
string.resource.attr_disable_warning:
enabled: true
string.resource.attr_remove_warning:
enabled: true
string.resource.attr_to_be_removed:
enabled: true
none_set:
metrics:
default.metric:
enabled: false
attributes: ["string_attr","state","enum_attr","slice_attr","map_attr","conditional_int_attr","conditional_string_attr","opt_in_bool_attr"]
default.metric.to_be_removed:
enabled: false
metric.input_type:
enabled: false
attributes: ["string_attr","state","enum_attr","slice_attr","map_attr"]
optional.metric:
enabled: false
attributes: ["string_attr","boolean_attr","boolean_attr2","conditional_string_attr"]
optional.metric.empty_unit:
enabled: false
attributes: ["string_attr","boolean_attr"]
reaggregate.metric:
enabled: false
attributes: ["string_attr","boolean_attr"]
reaggregate.metric.with_required:
enabled: false
attributes: ["required_string_attr","string_attr","boolean_attr"]
system.cpu.time:
enabled: false
events:
default.event:
enabled: false
default.event.to_be_removed:
enabled: false
default.event.to_be_renamed:
enabled: false
resource_attributes:
map.resource.attr:
enabled: false
optional.resource.attr:
enabled: false
slice.resource.attr:
enabled: false
string.enum.resource.attr:
enabled: false
string.resource.attr:
enabled: false
string.resource.attr_disable_warning:
enabled: false
string.resource.attr_remove_warning:
enabled: false
string.resource.attr_to_be_removed:
enabled: false
filter_set_include:
resource_attributes:
map.resource.attr:
enabled: true
metrics_include:
- regexp: ".*"
events_include:
- regexp: ".*"
optional.resource.attr:
enabled: true
metrics_include:
- regexp: ".*"
events_include:
- regexp: ".*"
slice.resource.attr:
enabled: true
metrics_include:
- regexp: ".*"
events_include:
- regexp: ".*"
string.enum.resource.attr:
enabled: true
metrics_include:
- regexp: ".*"
events_include:
- regexp: ".*"
string.resource.attr:
enabled: true
metrics_include:
- regexp: ".*"
events_include:
- regexp: ".*"
string.resource.attr_disable_warning:
enabled: true
metrics_include:
- regexp: ".*"
events_include:
- regexp: ".*"
string.resource.attr_remove_warning:
enabled: true
metrics_include:
- regexp: ".*"
events_include:
- regexp: ".*"
string.resource.attr_to_be_removed:
enabled: true
metrics_include:
- regexp: ".*"
events_include:
- regexp: ".*"
filter_set_exclude:
resource_attributes:
map.resource.attr:
enabled: true
metrics_exclude:
- regexp: ".*"
events_exclude:
- regexp: ".*"
optional.resource.attr:
enabled: true
metrics_exclude:
- strict: "optional.resource.attr-val"
events_exclude:
- strict: "optional.resource.attr-val"
slice.resource.attr:
enabled: true
metrics_exclude:
- regexp: ".*"
events_exclude:
- regexp: ".*"
string.enum.resource.attr:
enabled: true
metrics_exclude:
- strict: "one"
events_exclude:
- strict: "one"
string.resource.attr:
enabled: true
metrics_exclude:
- strict: "string.resource.attr-val"
events_exclude:
- strict: "string.resource.attr-val"
string.resource.attr_disable_warning:
enabled: true
metrics_exclude:
- strict: "string.resource.attr_disable_warning-val"
events_exclude:
- strict: "string.resource.attr_disable_warning-val"
string.resource.attr_remove_warning:
enabled: true
metrics_exclude:
- strict: "string.resource.attr_remove_warning-val"
events_exclude:
- strict: "string.resource.attr_remove_warning-val"
string.resource.attr_to_be_removed:
enabled: true
metrics_exclude:
- strict: "string.resource.attr_to_be_removed-val"
events_exclude:
- strict: "string.resource.attr_to_be_removed-val"
================================================
FILE: cmd/mdatagen/internal/samplereceiver/internal/metadatatest/generated_telemetrytest.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadatatest
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/receivertest"
)
func NewSettings(tt *componenttest.Telemetry) receiver.Settings {
set := receivertest.NewNopSettings(receivertest.NopType)
set.ID = component.NewID(component.MustNewType("sample"))
set.TelemetrySettings = tt.NewTelemetrySettings()
return set
}
func AssertEqualBatchSizeTriggerSend(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_batch_size_trigger_send",
Description: "Number of times the batch was sent due to a size trigger [Deprecated]",
Unit: "{time}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_batch_size_trigger_send")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualProcessRuntimeTotalAllocBytes(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_process_runtime_total_alloc_bytes",
Description: "Cumulative bytes allocated for heap objects (see 'go doc runtime.MemStats.TotalAlloc') [Stable]",
Unit: "By",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_process_runtime_total_alloc_bytes")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualQueueCapacity(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_queue_capacity",
Description: "Queue capacity - sync gauge example. [Development]",
Unit: "{item}",
Data: metricdata.Gauge[int64]{
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_queue_capacity")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualQueueLength(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_queue_length",
Description: "This metric is optional and therefore not initialized in NewTelemetryBuilder. [Alpha]",
Unit: "{item}",
Data: metricdata.Gauge[int64]{
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_queue_length")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualRequestDuration(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.HistogramDataPoint[float64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_request_duration",
Description: "Duration of request [Alpha]",
Unit: "s",
Data: metricdata.Histogram[float64]{
Temporality: metricdata.CumulativeTemporality,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_request_duration")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
================================================
FILE: cmd/mdatagen/internal/samplereceiver/internal/metadatatest/generated_telemetrytest_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadatatest
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"go.opentelemetry.io/collector/cmd/mdatagen/internal/samplereceiver/internal/metadata"
"go.opentelemetry.io/collector/component/componenttest"
)
func TestSetupTelemetry(t *testing.T) {
testTel := componenttest.NewTelemetry()
tb, err := metadata.NewTelemetryBuilder(testTel.NewTelemetrySettings())
require.NoError(t, err)
defer tb.Shutdown()
require.NoError(t, tb.RegisterProcessRuntimeTotalAllocBytesCallback(func(_ context.Context, observer metric.Int64Observer) error {
observer.Observe(1)
return nil
}))
require.NoError(t, tb.RegisterQueueLengthCallback(func(_ context.Context, observer metric.Int64Observer) error {
observer.Observe(1)
return nil
}))
tb.BatchSizeTriggerSend.Add(context.Background(), 1)
tb.QueueCapacity.Record(context.Background(), 1)
tb.RequestDuration.Record(context.Background(), 1)
AssertEqualBatchSizeTriggerSend(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualProcessRuntimeTotalAllocBytes(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualQueueCapacity(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualQueueLength(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualRequestDuration(t, testTel,
[]metricdata.HistogramDataPoint[float64]{{}}, metricdatatest.IgnoreValue(),
metricdatatest.IgnoreTimestamp())
require.NoError(t, testTel.Shutdown(context.Background()))
}
================================================
FILE: cmd/mdatagen/internal/samplereceiver/metadata.yaml
================================================
# Sample metadata file with all available configurations for a receiver.
type: sample
display_name: Sample Receiver
description: This receiver is used for testing purposes to check the output of mdatagen.
reaggregation_enabled: true
scope_name: go.opentelemetry.io/collector/internal/receiver/samplereceiver
github_project: open-telemetry/opentelemetry-collector
sem_conv_version: 1.38.0
feature_gates:
- id: receiver.sample.featuregate.example
description: This is an example feature gate for testing mdatagen code generation.
stage: alpha
from_version: v0.100.0
reference_url: https://github.com/open-telemetry/opentelemetry-collector/issues/12345
status:
disable_codecov_badge: true
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
deprecated: [profiles]
deprecation:
profiles:
migration: "no migration needed"
date: "2025-02-05"
distributions: []
unsupported_platforms: [freebsd, illumos]
codeowners:
active: [dmitryax]
warnings:
- Any additional information that should be brought to the consumer's attention
config:
type: object
allOf:
- $ref: ./internal/metadata.metrics_builder_config
properties:
endpoint:
description: The endpoint to scrape metrics from.
type: string
default: "localhost:12345"
timeout:
description: Timeout for scraping metrics.
type: string
format: duration
default: 10s
required: [endpoint]
resource_attributes:
map.resource.attr:
description: Resource attribute with a map value.
type: map
enabled: true
optional.resource.attr:
description: Explicitly disabled ResourceAttribute.
type: string
enabled: false
slice.resource.attr:
description: Resource attribute with a slice value.
type: slice
enabled: true
string.enum.resource.attr:
description: Resource attribute with a known set of string values.
type: string
enum: [one, two]
enabled: true
string.resource.attr:
description: Resource attribute with any string value.
type: string
enabled: true
string.resource.attr_disable_warning:
description: Resource attribute with any string value.
type: string
enabled: true
warnings:
if_enabled_not_set: This resource_attribute will be disabled by default soon.
string.resource.attr_remove_warning:
description: Resource attribute with any string value.
type: string
enabled: false
warnings:
if_configured: This resource_attribute is deprecated and will be removed soon.
string.resource.attr_to_be_removed:
description: Resource attribute with any string value.
type: string
enabled: true
warnings:
if_enabled: This resource_attribute is deprecated and will be removed soon.
attributes:
boolean_attr:
description: Attribute with a boolean value.
type: bool
# This 2nd boolean attribute allows us to test both values for boolean attributes,
# as test values are based on the parity of the attribute name length.
boolean_attr2:
description: Another attribute with a boolean value.
type: bool
conditional_int_attr:
description: A conditional attribute with an integer value
type: int
requirement_level: conditionally_required
conditional_string_attr:
description: A conditional attribute with any string value
type: string
requirement_level: conditionally_required
enum_attr:
description: Attribute with a known set of string values.
type: string
enum: [red, green, blue]
map_attr:
description: Attribute with a map value.
type: map
opt_in_bool_attr:
description: An opt-in attribute with a boolean value
type: bool
requirement_level: opt_in
overridden_int_attr:
name_override: state
description: Integer attribute with overridden name.
type: int
required_string_attr:
description: A required attribute with a string value
type: string
requirement_level: required
slice_attr:
description: Attribute with a slice value.
type: slice
string_attr:
description: Attribute with any string value.
type: string
events:
default.event:
enabled: true
description: Example event enabled by default.
attributes:
[
string_attr,
overridden_int_attr,
enum_attr,
slice_attr,
map_attr,
conditional_int_attr,
conditional_string_attr,
opt_in_bool_attr,
]
warnings:
if_enabled_not_set: This event will be disabled by default soon.
default.event.to_be_removed:
enabled: true
description: "[DEPRECATED] Example to-be-removed event enabled by default."
extended_documentation: The event will be removed soon.
warnings:
if_enabled: This event is deprecated and will be removed soon.
attributes:
[string_attr, overridden_int_attr, enum_attr, slice_attr, map_attr]
default.event.to_be_renamed:
enabled: false
description: "[DEPRECATED] Example event disabled by default."
extended_documentation: The event will be renamed soon.
attributes: [string_attr, boolean_attr, boolean_attr2, conditional_string_attr]
warnings:
if_configured: This event is deprecated and will be renamed soon.
metrics:
default.metric:
enabled: true
description: Monotonic cumulative sum int metric enabled by default.
extended_documentation: The metric will be become optional soon.
stability: deprecated
deprecated:
since: "1.0.0"
note: "This metric will be removed"
unit: s
sum:
value_type: int
monotonic: true
aggregation_temporality: cumulative
attributes:
[
string_attr,
overridden_int_attr,
enum_attr,
slice_attr,
map_attr,
conditional_int_attr,
conditional_string_attr,
opt_in_bool_attr,
]
warnings:
if_enabled_not_set: This metric will be disabled by default soon.
default.metric.to_be_removed:
enabled: true
description: "[DEPRECATED] Non-monotonic delta sum double metric enabled by default."
extended_documentation: The metric will be removed soon.
stability: deprecated
deprecated:
since: "1.0.0"
note: "This metric will be removed"
unit: s
sum:
value_type: double
monotonic: false
aggregation_temporality: delta
warnings:
if_enabled: This metric is deprecated and will be removed soon.
metric.input_type:
enabled: true
description: Monotonic cumulative sum int metric with string input_type enabled by default.
stability: development
unit: s
sum:
value_type: int
input_type: string
monotonic: true
aggregation_temporality: cumulative
attributes:
[string_attr, overridden_int_attr, enum_attr, slice_attr, map_attr]
optional.metric:
enabled: false
description: "[DEPRECATED] Gauge double metric disabled by default."
stability: deprecated
deprecated:
since: "1.0.0"
note: "This metric will be removed"
unit: "1"
gauge:
value_type: double
attributes: [string_attr, boolean_attr, boolean_attr2, conditional_string_attr]
warnings:
if_configured: This metric is deprecated and will be removed soon.
optional.metric.empty_unit:
enabled: false
description: "[DEPRECATED] Gauge double metric disabled by default."
stability: deprecated
deprecated:
since: "1.0.0"
note: "This metric will be removed"
unit: ""
gauge:
value_type: double
attributes: [string_attr, boolean_attr]
warnings:
if_configured: This metric is deprecated and will be removed soon.
reaggregate.metric:
enabled: true
description: Metric for testing spatial reaggregation
unit: "1"
stability: beta
gauge:
value_type: double
attributes: [string_attr, boolean_attr]
reaggregate.metric.with_required:
enabled: true
description: Metric for testing spatial reaggregation with required attributes
unit: "1"
stability: beta
gauge:
value_type: double
attributes: [required_string_attr, string_attr, boolean_attr]
system.cpu.time:
enabled: true
stability: beta
description: Monotonic cumulative sum int metric enabled by default.
extended_documentation: The metric will be become optional soon.
unit: s
sum:
value_type: int
monotonic: true
aggregation_temporality: cumulative
semantic_convention:
ref: https://github.com/open-telemetry/semantic-conventions/blob/v1.38.0/docs/system/system-metrics.md#metric-systemcputime
telemetry:
metrics:
batch_size_trigger_send:
enabled: true
stability: deprecated
deprecated:
since: "1.5.0"
note: "This metric will be removed in favor of batch_send_trigger_size"
description: Number of times the batch was sent due to a size trigger
unit: "{time}"
sum:
value_type: int
monotonic: true
process_runtime_total_alloc_bytes:
enabled: true
stability: stable
description: Cumulative bytes allocated for heap objects (see 'go doc runtime.MemStats.TotalAlloc')
unit: By
sum:
async: true
value_type: int
monotonic: true
queue_capacity:
enabled: true
description: Queue capacity - sync gauge example.
stability: development
unit: "{item}"
gauge:
value_type: int
queue_length:
enabled: true
stability: alpha
description: This metric is optional and therefore not initialized in NewTelemetryBuilder.
extended_documentation: For example this metric only exists if feature A is enabled.
unit: "{item}"
optional: true
gauge:
async: true
value_type: int
request_duration:
enabled: true
stability: alpha
description: Duration of request
unit: s
histogram:
value_type: double
bucket_boundaries: [1, 10, 100]
================================================
FILE: cmd/mdatagen/internal/samplereceiver/metrics_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package samplereceiver
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"go.opentelemetry.io/collector/cmd/mdatagen/internal/samplereceiver/internal/metadata"
"go.opentelemetry.io/collector/cmd/mdatagen/internal/samplereceiver/internal/metadatatest"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/receiver/receivertest"
)
// TestGeneratedMetrics verifies that the internal/metadata API is generated correctly.
func TestGeneratedMetrics(t *testing.T) {
mb := metadata.NewMetricsBuilder(metadata.DefaultMetricsBuilderConfig(), receivertest.NewNopSettings(metadata.Type))
m := mb.Emit()
require.Equal(t, 0, m.ResourceMetrics().Len())
}
func TestComponentTelemetry(t *testing.T) {
tt := componenttest.NewTelemetry()
factory := NewFactory()
receiver, err := factory.CreateMetrics(context.Background(), metadatatest.NewSettings(tt), newMdatagenNopHost(), new(consumertest.MetricsSink))
require.NoError(t, err)
metadatatest.AssertEqualBatchSizeTriggerSend(t, tt,
[]metricdata.DataPoint[int64]{
{
Value: 1,
},
}, metricdatatest.IgnoreTimestamp())
metadatatest.AssertEqualProcessRuntimeTotalAllocBytes(t, tt,
[]metricdata.DataPoint[int64]{
{
Value: 2,
},
}, metricdatatest.IgnoreTimestamp())
rcv, ok := receiver.(nopReceiver)
require.True(t, ok)
rcv.initOptionalMetric()
metadatatest.AssertEqualQueueLength(t, tt,
[]metricdata.DataPoint[int64]{
{
Value: 3,
},
}, metricdatatest.IgnoreTimestamp())
require.NoError(t, tt.Shutdown(context.Background()))
}
================================================
FILE: cmd/mdatagen/internal/samplescraper/README.md
================================================
# Sample Scraper
This scraper is used for testing purposes to check the output of mdatagen.
| Status | |
| ------------- |-----------|
| Stability | [development]: logs, profiles |
| | [stable]: metrics |
| Unsupported Platforms | freebsd, illumos |
| Distributions | [] |
| Warnings | [Any additional information that should be brought to the consumer's attention](#warnings) |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Ascraper%2Fsample) [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aclosed+is%3Aissue+label%3Ascraper%2Fsample) |
| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@dmitryax](https://www.github.com/dmitryax) |
[development]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#development
[stable]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#stable
## Warnings
This is where warnings are described.
================================================
FILE: cmd/mdatagen/internal/samplescraper/config.schema.json
================================================
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "go.opentelemetry.io/collector/cmd/mdatagen/internal/samplescraper",
"title": "scraper/sample",
"description": "Configuration for the Sample Scraper.",
"type": "object",
"allOf": [
{
"description": "ControllerConfig defines common settings for a scraper controller configuration. Scraper controller receivers can embed this struct, instead of receiver.Settings, and extend it with more fields if needed.",
"type": "object",
"properties": {
"collection_interval": {
"description": "CollectionInterval sets how frequently the scraper should be called and used as the context timeout to ensure that scrapers don't exceed the interval. (duration format, e.g., \"30s\", \"1h30m\")",
"type": "string",
"pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$"
},
"initial_delay": {
"description": "InitialDelay sets the initial start delay for the scraper, any non positive value is assumed to be immediately. (duration format, e.g., \"30s\", \"1h30m\")",
"type": "string",
"pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$"
},
"timeout": {
"description": "Timeout is an optional value used to set scraper's context deadline. (duration format, e.g., \"30s\", \"1h30m\")",
"type": "string",
"pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$"
}
}
}
],
"properties": {
"targets": {
"description": "Targets configuration for the scraper.",
"type": "array",
"items": {
"type": "object",
"properties": {
"http_client": {
"description": "ClientConfig defines settings for creating an HTTP client.",
"type": "object",
"properties": {
"auth": {
"description": "Auth configuration for outgoing HTTP calls.",
"type": "object",
"properties": {
"authenticator": {
"description": "AuthenticatorID specifies the name of the extension to use in order to authenticate the incoming data point.",
"type": "string"
}
}
},
"compression": {
"description": "The compression key for supported compression types within collector.",
"type": "string"
},
"compression_params": {
"description": "Advanced configuration options for the Compression",
"type": "object",
"properties": {
"level": {
"type": "integer"
}
}
},
"cookies": {
"description": "Cookies configures the cookie management of the HTTP client."
},
"disable_keep_alives": {
"description": "DisableKeepAlives, if true, disables HTTP keep-alives and will only use the connection to the server for a single HTTP request. WARNING: enabling this option can result in significant overhead establishing a new HTTP(S) connection for every request. Before enabling this option please consider whether changes to idle connection settings can achieve your goal.",
"type": "boolean"
},
"endpoint": {
"description": "The target URL to send data to (e.g.: http://some.url:9411/v1/traces).",
"type": "string"
},
"force_attempt_http2": {
"description": "Enabling ForceAttemptHTTP2 forces the HTTP transport to use the HTTP/2 protocol. By default, this is set to true. NOTE: HTTP/2 does not support settings such as MaxConnsPerHost, MaxIdleConnsPerHost and MaxIdleConns.",
"type": "boolean"
},
"headers": {
"description": "Additional headers attached to each HTTP request sent by the client. Existing header values are overwritten if collision happens. Header values are opaque since they may be sensitive.",
"type": "array",
"items": {
"description": "Pair is an element of a MapList, and consists of a name and an opaque value.",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"value": {
"description": "String alias that is marshaled and printed in an opaque way. To recover the original value, cast it to a string.",
"type": "string"
}
}
}
},
"http2_ping_timeout": {
"description": "HTTP2PingTimeout if there's no response to the ping within the configured value, the connection will be closed. If not set or set to 0, it defaults to 15s. (duration format, e.g., \"30s\", \"1h30m\")",
"type": "string",
"pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$"
},
"http2_read_idle_timeout": {
"description": "This is needed in case you run into https://github.com/golang/go/issues/59690 https://github.com/golang/go/issues/36026 HTTP2ReadIdleTimeout if the connection has been idle for the configured value send a ping frame for health check 0s means no health check will be performed. (duration format, e.g., \"30s\", \"1h30m\")",
"type": "string",
"pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$"
},
"idle_conn_timeout": {
"description": "IdleConnTimeout is the maximum amount of time a connection will remain open before closing itself. By default, it is set to 90 seconds. (duration format, e.g., \"30s\", \"1h30m\")",
"type": "string",
"pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$"
},
"max_conns_per_host": {
"description": "MaxConnsPerHost limits the total number of connections per host, including connections in the dialing, active, and idle states. Default is 0 (unlimited).",
"type": "integer"
},
"max_idle_conns": {
"description": "MaxIdleConns is used to set a limit to the maximum idle HTTP connections the client can keep open. By default, it is set to 100. Zero means no limit.",
"type": "integer"
},
"max_idle_conns_per_host": {
"description": "MaxIdleConnsPerHost is used to set a limit to the maximum idle HTTP connections the host can keep open. If zero, [net/http.DefaultMaxIdleConnsPerHost] is used.",
"type": "integer"
},
"middlewares": {
"description": "Middlewares are used to add custom functionality to the HTTP client. Middleware handlers are called in the order they appear in this list, with the first middleware becoming the outermost handler.",
"type": "array",
"items": {
"description": "Middleware defines the extension ID for a middleware component.",
"type": "object",
"properties": {
"id": {
"description": "ID specifies the name of the extension to use.",
"type": "string"
}
}
}
},
"proxy_url": {
"description": "ProxyURL setting for the collector",
"type": "string"
},
"read_buffer_size": {
"description": "ReadBufferSize for HTTP client. See http.Transport.ReadBufferSize. Default is 0.",
"type": "integer"
},
"timeout": {
"description": "Timeout parameter configures `http.Client.Timeout`. Default is 0 (unlimited). (duration format, e.g., \"30s\", \"1h30m\")",
"type": "string",
"pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$"
},
"tls": {
"description": "TLS struct exposes TLS client configuration.",
"type": "object",
"allOf": [
{
"description": "Config 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 Config.",
"type": "object",
"properties": {
"ca_file": {
"description": "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)",
"type": "string"
},
"ca_pem": {
"description": "In memory PEM encoded cert. (optional)",
"type": "string"
},
"cert_file": {
"description": "Path to the TLS cert to use for TLS required connections. (optional)",
"type": "string"
},
"cert_pem": {
"description": "In memory PEM encoded TLS cert to use for TLS required connections. (optional)",
"type": "string"
},
"cipher_suites": {
"description": "CipherSuites is a list of TLS cipher suites that the TLS transport can use. If left blank, a safe default list is used. See https://go.dev/src/crypto/tls/cipher_suites.go for a list of supported cipher suites.",
"type": "array",
"items": {
"type": "string"
}
},
"curve_preferences": {
"description": "contains the elliptic curves that will be used in an ECDHE handshake, in preference order Defaults to empty list and \"crypto/tls\" defaults are used, internally.",
"type": "array",
"items": {
"type": "string"
}
},
"include_system_ca_certs_pool": {
"description": "If true, load system CA certificates pool in addition to the certificates configured in this struct.",
"type": "boolean"
},
"key_file": {
"description": "Path to the TLS key to use for TLS required connections. (optional)",
"type": "string"
},
"key_pem": {
"description": "In memory PEM encoded TLS key to use for TLS required connections. (optional)",
"type": "string"
},
"max_version": {
"description": "MaxVersion sets the maximum TLS version that is acceptable. If not set, refer to crypto/tls for defaults. (optional)",
"type": "string"
},
"min_version": {
"description": "MinVersion sets the minimum TLS version that is acceptable. If not set, TLS 1.2 will be used. (optional)",
"type": "string"
},
"reload_interval": {
"description": "ReloadInterval specifies the duration after which the certificate will be reloaded If not set, it will never be reloaded (optional)",
"type": "string",
"pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$"
},
"tpm": {
"description": "Trusted platform module configuration",
"type": "object",
"properties": {
"auth": {
"type": "string"
},
"enabled": {
"type": "boolean"
},
"owner_auth": {
"type": "string"
},
"path": {
"description": "The path to the TPM device or Unix domain socket. For instance /dev/tpm0 or /dev/tpmrm0.",
"type": "string"
}
}
}
}
}
],
"properties": {
"insecure": {
"description": "In gRPC and HTTP when set to true, this is used to disable the client transport security. See https://godoc.org/google.golang.org/grpc#WithInsecure for gRPC. Please refer to https://godoc.org/crypto/tls#Config for more information. (optional, default false)",
"type": "boolean"
},
"insecure_skip_verify": {
"description": "InsecureSkipVerify will enable TLS but not verify the certificate.",
"type": "boolean"
},
"server_name_override": {
"description": "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)",
"type": "string"
}
}
},
"write_buffer_size": {
"description": "WriteBufferSize for HTTP client. See http.Transport.WriteBufferSize. Default is 0.",
"type": "integer"
}
}
},
"interval": {
"type": "string",
"pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$"
}
}
}
}
}
}
================================================
FILE: cmd/mdatagen/internal/samplescraper/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Generate a test metrics builder from a sample metrics set covering all configuration options.
//go:generate mdatagen metadata.yaml
package samplescraper // import "go.opentelemetry.io/collector/cmd/mdatagen/internal/samplescraper"
================================================
FILE: cmd/mdatagen/internal/samplescraper/documentation.md
================================================
[comment]: <> (Code generated by mdatagen. DO NOT EDIT.)
# sample
## Default Metrics
The following metrics are emitted by default. Each of them can be disabled by applying the following configuration:
```yaml
metrics:
:
enabled: false
```
### default.metric
Monotonic cumulative sum int metric enabled by default.
The metric will be become optional soon.
| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability |
| ---- | ----------- | ---------- | ----------------------- | --------- | --------- |
| s | Sum | Int | Cumulative | true | Development |
#### Attributes
| Name | Description | Values | Requirement Level |
| ---- | ----------- | ------ | -------- |
| string_attr | Attribute with any string value. | Any Str | Recommended |
| state | Integer attribute with overridden name. | Any Int | Recommended |
| enum_attr | Attribute with a known set of string values. | Str: ``red``, ``green``, ``blue`` | Recommended |
| slice_attr | Attribute with a slice value. | Any Slice | Recommended |
| map_attr | Attribute with a map value. | Any Map | Recommended |
### default.metric.to_be_removed
[DEPRECATED] Non-monotonic delta sum double metric enabled by default.
The metric will be removed soon.
| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability |
| ---- | ----------- | ---------- | ----------------------- | --------- | --------- |
| s | Sum | Double | Delta | false | Deprecated since 1.0.0 |
**Deprecation note**: This metric will be removed
### metric.input_type
Monotonic cumulative sum int metric with string input_type enabled by default.
| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability |
| ---- | ----------- | ---------- | ----------------------- | --------- | --------- |
| s | Sum | Int | Cumulative | true | Development |
#### Attributes
| Name | Description | Values | Requirement Level |
| ---- | ----------- | ------ | -------- |
| string_attr | Attribute with any string value. | Any Str | Recommended |
| state | Integer attribute with overridden name. | Any Int | Recommended |
| enum_attr | Attribute with a known set of string values. | Str: ``red``, ``green``, ``blue`` | Recommended |
| slice_attr | Attribute with a slice value. | Any Slice | Recommended |
| map_attr | Attribute with a map value. | Any Map | Recommended |
### reaggregate.metric
Metric for testing spatial reaggregation
| Unit | Metric Type | Value Type | Stability |
| ---- | ----------- | ---------- | --------- |
| 1 | Gauge | Double | Beta |
#### Attributes
| Name | Description | Values | Requirement Level |
| ---- | ----------- | ------ | -------- |
| string_attr | Attribute with any string value. | Any Str | Recommended |
| boolean_attr | Attribute with a boolean value. | Any Bool | Recommended |
### system.cpu.time
Monotonic cumulative sum int metric enabled by default.
The metric will be become optional soon.
| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | Semantic Convention |
| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | ------------------- |
| s | Sum | Int | Cumulative | true | Beta | [system.cpu.time](https://github.com/open-telemetry/semantic-conventions/blob/v1.38.0/docs/system/system-metrics.md#metric-systemcputime) |
## Optional Metrics
The following metrics are not emitted by default. Each of them can be enabled by applying the following configuration:
```yaml
metrics:
:
enabled: true
```
### optional.metric
[DEPRECATED] Gauge double metric disabled by default.
| Unit | Metric Type | Value Type | Stability |
| ---- | ----------- | ---------- | --------- |
| 1 | Gauge | Double | Deprecated since 1.0.0 |
**Deprecation note**: This metric will be removed
#### Attributes
| Name | Description | Values | Requirement Level |
| ---- | ----------- | ------ | -------- |
| string_attr | Attribute with any string value. | Any Str | Recommended |
| boolean_attr | Attribute with a boolean value. | Any Bool | Recommended |
| boolean_attr2 | Another attribute with a boolean value. | Any Bool | Recommended |
### optional.metric.empty_unit
[DEPRECATED] Gauge double metric disabled by default.
| Unit | Metric Type | Value Type | Stability |
| ---- | ----------- | ---------- | --------- |
| | Gauge | Double | Deprecated since 1.0.0 |
**Deprecation note**: This metric will be removed
#### Attributes
| Name | Description | Values | Requirement Level |
| ---- | ----------- | ------ | -------- |
| string_attr | Attribute with any string value. | Any Str | Recommended |
| boolean_attr | Attribute with a boolean value. | Any Bool | Recommended |
## Resource Attributes
| Name | Description | Values | Enabled |
| ---- | ----------- | ------ | ------- |
| map.resource.attr | Resource attribute with a map value. | Any Map | true |
| optional.resource.attr | Explicitly disabled ResourceAttribute. | Any Str | false |
| slice.resource.attr | Resource attribute with a slice value. | Any Slice | true |
| string.enum.resource.attr | Resource attribute with a known set of string values. | Str: ``one``, ``two`` | true |
| string.resource.attr | Resource attribute with any string value. | Any Str | true |
| string.resource.attr_disable_warning | Resource attribute with any string value. | Any Str | true |
| string.resource.attr_remove_warning | Resource attribute with any string value. | Any Str | false |
| string.resource.attr_to_be_removed | Resource attribute with any string value. | Any Str | true |
================================================
FILE: cmd/mdatagen/internal/samplescraper/factory.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package samplescraper // import "go.opentelemetry.io/collector/cmd/mdatagen/internal/samplescraper"
import (
"context"
"go.opentelemetry.io/collector/cmd/mdatagen/internal/samplescraper/internal/metadata"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/scraper"
"go.opentelemetry.io/collector/scraper/xscraper"
)
// NewFactory returns a receiver.Factory for sample receiver.
func NewFactory() scraper.Factory {
return xscraper.NewFactory(
metadata.Type,
func() component.Config { return &struct{}{} },
xscraper.WithMetrics(createMetrics, metadata.MetricsStability),
xscraper.WithLogs(createLogs, metadata.LogsStability),
xscraper.WithProfiles(createProfiles, metadata.ProfilesStability),
)
}
func createMetrics(context.Context, scraper.Settings, component.Config) (scraper.Metrics, error) {
return scraper.NewMetrics(func(context.Context) (pmetric.Metrics, error) {
return pmetric.NewMetrics(), nil
})
}
func createLogs(context.Context, scraper.Settings, component.Config) (scraper.Logs, error) {
return scraper.NewLogs(func(context.Context) (plog.Logs, error) { return plog.Logs{}, nil })
}
func createProfiles(context.Context, scraper.Settings, component.Config) (xscraper.Profiles, error) {
return xscraper.NewProfiles(func(context.Context) (pprofile.Profiles, error) { return pprofile.Profiles{}, nil })
}
================================================
FILE: cmd/mdatagen/internal/samplescraper/generated_component_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
//go:build !freebsd && !illumos
package samplescraper
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/confmap/confmaptest"
"go.opentelemetry.io/collector/scraper"
"go.opentelemetry.io/collector/scraper/scrapertest"
"go.opentelemetry.io/collector/scraper/xscraper"
)
var typ = component.MustNewType("sample")
func TestComponentFactoryType(t *testing.T) {
require.Equal(t, typ, NewFactory().Type())
}
func TestComponentConfigStruct(t *testing.T) {
require.NoError(t, componenttest.CheckConfigStruct(NewFactory().CreateDefaultConfig()))
}
func TestComponentLifecycle(t *testing.T) {
factory := NewFactory()
tests := []struct {
createFn func(ctx context.Context, set scraper.Settings, cfg component.Config) (component.Component, error)
name string
}{
{
name: "logs",
createFn: func(ctx context.Context, set scraper.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateLogs(ctx, set, cfg)
},
},
{
name: "metrics",
createFn: func(ctx context.Context, set scraper.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateMetrics(ctx, set, cfg)
},
},
{
name: "profiles",
createFn: func(ctx context.Context, set scraper.Settings, cfg component.Config) (component.Component, error) {
return factory.(xscraper.Factory).CreateProfiles(ctx, set, cfg)
},
},
}
cm, err := confmaptest.LoadConf("metadata.yaml")
require.NoError(t, err)
cfg := factory.CreateDefaultConfig()
sub, err := cm.Sub("tests::config")
require.NoError(t, err)
require.NoError(t, sub.Unmarshal(&cfg))
for _, tt := range tests {
t.Run(tt.name+"-shutdown", func(t *testing.T) {
c, err := tt.createFn(context.Background(), scrapertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
err = c.Shutdown(context.Background())
require.NoError(t, err)
})
t.Run(tt.name+"-lifecycle", func(t *testing.T) {
firstRcvr, err := tt.createFn(context.Background(), scrapertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
host := newMdatagenNopHost()
require.NoError(t, err)
require.NoError(t, firstRcvr.Start(context.Background(), host))
require.NoError(t, firstRcvr.Shutdown(context.Background()))
secondRcvr, err := tt.createFn(context.Background(), scrapertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
require.NoError(t, secondRcvr.Start(context.Background(), host))
require.NoError(t, secondRcvr.Shutdown(context.Background()))
})
}
}
var _ component.Host = (*mdatagenNopHost)(nil)
type mdatagenNopHost struct{}
func newMdatagenNopHost() component.Host {
return &mdatagenNopHost{}
}
func (mnh *mdatagenNopHost) GetExtensions() map[component.ID]component.Component {
return nil
}
func (mnh *mdatagenNopHost) GetFactory(_ component.Kind, _ component.Type) component.Factory {
return nil
}
================================================
FILE: cmd/mdatagen/internal/samplescraper/generated_config.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package samplescraper
import (
"time"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/scraper/scraperhelper"
)
type TargetsItem struct {
HTTPClient confighttp.ClientConfig `mapstructure:"http_client"`
Interval configoptional.Optional[time.Duration] `mapstructure:"interval"`
}
// Configuration for the Sample Scraper.
type Config struct {
scraperhelper.ControllerConfig `mapstructure:",squash"`
// Targets configuration for the scraper.
Targets *[]TargetsItem `mapstructure:"targets"`
}
================================================
FILE: cmd/mdatagen/internal/samplescraper/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package samplescraper
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: cmd/mdatagen/internal/samplescraper/internal/metadata/config.schema.yaml
================================================
# Code generated by mdatagen. DO NOT EDIT.
$defs:
metrics_config:
description: MetricsConfig provides config for sample metrics.
type: object
properties:
default.metric:
description: "DefaultMetricMetricConfig provides config for the default.metric metric."
type: object
properties:
enabled:
type: boolean
default: true
aggregation_strategy:
type: string
enum:
- "sum"
- "avg"
- "min"
- "max"
default: "sum"
attributes:
type: array
items:
type: string
enum:
- "string_attr"
- "state"
- "enum_attr"
- "slice_attr"
- "map_attr"
default:
- "string_attr"
- "state"
- "enum_attr"
- "slice_attr"
- "map_attr"
default.metric.to_be_removed:
description: "DefaultMetricToBeRemovedMetricConfig provides config for the default.metric.to_be_removed metric."
type: object
properties:
enabled:
type: boolean
default: true
metric.input_type:
description: "MetricInputTypeMetricConfig provides config for the metric.input_type metric."
type: object
properties:
enabled:
type: boolean
default: true
aggregation_strategy:
type: string
enum:
- "sum"
- "avg"
- "min"
- "max"
default: "sum"
attributes:
type: array
items:
type: string
enum:
- "string_attr"
- "state"
- "enum_attr"
- "slice_attr"
- "map_attr"
default:
- "string_attr"
- "state"
- "enum_attr"
- "slice_attr"
- "map_attr"
optional.metric:
description: "OptionalMetricMetricConfig provides config for the optional.metric metric."
type: object
properties:
enabled:
type: boolean
default: false
aggregation_strategy:
type: string
enum:
- "sum"
- "avg"
- "min"
- "max"
default: "avg"
attributes:
type: array
items:
type: string
enum:
- "string_attr"
- "boolean_attr"
- "boolean_attr2"
default:
- "string_attr"
- "boolean_attr"
- "boolean_attr2"
optional.metric.empty_unit:
description: "OptionalMetricEmptyUnitMetricConfig provides config for the optional.metric.empty_unit metric."
type: object
properties:
enabled:
type: boolean
default: false
aggregation_strategy:
type: string
enum:
- "sum"
- "avg"
- "min"
- "max"
default: "avg"
attributes:
type: array
items:
type: string
enum:
- "string_attr"
- "boolean_attr"
default:
- "string_attr"
- "boolean_attr"
reaggregate.metric:
description: "ReaggregateMetricMetricConfig provides config for the reaggregate.metric metric."
type: object
properties:
enabled:
type: boolean
default: true
aggregation_strategy:
type: string
enum:
- "sum"
- "avg"
- "min"
- "max"
default: "avg"
attributes:
type: array
items:
type: string
enum:
- "string_attr"
- "boolean_attr"
default:
- "string_attr"
- "boolean_attr"
system.cpu.time:
description: "SystemCPUTimeMetricConfig provides config for the system.cpu.time metric."
type: object
properties:
enabled:
type: boolean
default: true
resource_attributes_config:
description: ResourceAttributesConfig provides config for sample resource attributes.
type: object
properties:
map.resource.attr:
description: ResourceAttributeConfig provides common config for a map.resource.attr resource attribute.
type: object
properties:
enabled:
type: boolean
default: true
metrics_include:
description: "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
metrics_exclude:
description: "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude."
type: array
items:
$ref: /filter.config
optional.resource.attr:
description: ResourceAttributeConfig provides common config for a optional.resource.attr resource attribute.
type: object
properties:
enabled:
type: boolean
default: false
metrics_include:
description: "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
metrics_exclude:
description: "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude."
type: array
items:
$ref: /filter.config
slice.resource.attr:
description: ResourceAttributeConfig provides common config for a slice.resource.attr resource attribute.
type: object
properties:
enabled:
type: boolean
default: true
metrics_include:
description: "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
metrics_exclude:
description: "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude."
type: array
items:
$ref: /filter.config
string.enum.resource.attr:
description: ResourceAttributeConfig provides common config for a string.enum.resource.attr resource attribute.
type: object
properties:
enabled:
type: boolean
default: true
metrics_include:
description: "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
metrics_exclude:
description: "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude."
type: array
items:
$ref: /filter.config
string.resource.attr:
description: ResourceAttributeConfig provides common config for a string.resource.attr resource attribute.
type: object
properties:
enabled:
type: boolean
default: true
metrics_include:
description: "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
metrics_exclude:
description: "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude."
type: array
items:
$ref: /filter.config
string.resource.attr_disable_warning:
description: ResourceAttributeConfig provides common config for a string.resource.attr_disable_warning resource attribute.
type: object
properties:
enabled:
type: boolean
default: true
metrics_include:
description: "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
metrics_exclude:
description: "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude."
type: array
items:
$ref: /filter.config
string.resource.attr_remove_warning:
description: ResourceAttributeConfig provides common config for a string.resource.attr_remove_warning resource attribute.
type: object
properties:
enabled:
type: boolean
default: false
metrics_include:
description: "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
metrics_exclude:
description: "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude."
type: array
items:
$ref: /filter.config
string.resource.attr_to_be_removed:
description: ResourceAttributeConfig provides common config for a string.resource.attr_to_be_removed resource attribute.
type: object
properties:
enabled:
type: boolean
default: true
metrics_include:
description: "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted."
type: array
items:
$ref: /filter.config
metrics_exclude:
description: "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude."
type: array
items:
$ref: /filter.config
metrics_builder_config:
description: MetricsBuilderConfig is a configuration for sample metrics builder.
type: object
properties:
metrics:
$ref: metrics_config
resource_attributes:
$ref: resource_attributes_config
================================================
FILE: cmd/mdatagen/internal/samplescraper/internal/metadata/generated_config.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"fmt"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/filter"
)
// DefaultMetricMetricAttributeKey specifies the key of an attribute for the default.metric metric.
type DefaultMetricMetricAttributeKey string
const (
DefaultMetricMetricAttributeKeyStringAttr DefaultMetricMetricAttributeKey = "string_attr"
DefaultMetricMetricAttributeKeyOverriddenIntAttr DefaultMetricMetricAttributeKey = "state"
DefaultMetricMetricAttributeKeyEnumAttr DefaultMetricMetricAttributeKey = "enum_attr"
DefaultMetricMetricAttributeKeySliceAttr DefaultMetricMetricAttributeKey = "slice_attr"
DefaultMetricMetricAttributeKeyMapAttr DefaultMetricMetricAttributeKey = "map_attr"
)
// DefaultMetricMetricConfig provides config for the default.metric metric.
type DefaultMetricMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []DefaultMetricMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *DefaultMetricMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *DefaultMetricMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case DefaultMetricMetricAttributeKeyStringAttr, DefaultMetricMetricAttributeKeyOverriddenIntAttr, DefaultMetricMetricAttributeKeyEnumAttr, DefaultMetricMetricAttributeKeySliceAttr, DefaultMetricMetricAttributeKeyMapAttr:
default:
return fmt.Errorf("metric default.metric doesn't have an attribute %v, valid attributes: [string_attr, state, enum_attr, slice_attr, map_attr]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// DefaultMetricToBeRemovedMetricConfig provides config for the default.metric.to_be_removed metric.
type DefaultMetricToBeRemovedMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
}
func (ms *DefaultMetricToBeRemovedMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
// MetricInputTypeMetricAttributeKey specifies the key of an attribute for the metric.input_type metric.
type MetricInputTypeMetricAttributeKey string
const (
MetricInputTypeMetricAttributeKeyStringAttr MetricInputTypeMetricAttributeKey = "string_attr"
MetricInputTypeMetricAttributeKeyOverriddenIntAttr MetricInputTypeMetricAttributeKey = "state"
MetricInputTypeMetricAttributeKeyEnumAttr MetricInputTypeMetricAttributeKey = "enum_attr"
MetricInputTypeMetricAttributeKeySliceAttr MetricInputTypeMetricAttributeKey = "slice_attr"
MetricInputTypeMetricAttributeKeyMapAttr MetricInputTypeMetricAttributeKey = "map_attr"
)
// MetricInputTypeMetricConfig provides config for the metric.input_type metric.
type MetricInputTypeMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MetricInputTypeMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MetricInputTypeMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MetricInputTypeMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MetricInputTypeMetricAttributeKeyStringAttr, MetricInputTypeMetricAttributeKeyOverriddenIntAttr, MetricInputTypeMetricAttributeKeyEnumAttr, MetricInputTypeMetricAttributeKeySliceAttr, MetricInputTypeMetricAttributeKeyMapAttr:
default:
return fmt.Errorf("metric metric.input_type doesn't have an attribute %v, valid attributes: [string_attr, state, enum_attr, slice_attr, map_attr]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// OptionalMetricMetricAttributeKey specifies the key of an attribute for the optional.metric metric.
type OptionalMetricMetricAttributeKey string
const (
OptionalMetricMetricAttributeKeyStringAttr OptionalMetricMetricAttributeKey = "string_attr"
OptionalMetricMetricAttributeKeyBooleanAttr OptionalMetricMetricAttributeKey = "boolean_attr"
OptionalMetricMetricAttributeKeyBooleanAttr2 OptionalMetricMetricAttributeKey = "boolean_attr2"
)
// OptionalMetricMetricConfig provides config for the optional.metric metric.
type OptionalMetricMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []OptionalMetricMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *OptionalMetricMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *OptionalMetricMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case OptionalMetricMetricAttributeKeyStringAttr, OptionalMetricMetricAttributeKeyBooleanAttr, OptionalMetricMetricAttributeKeyBooleanAttr2:
default:
return fmt.Errorf("metric optional.metric doesn't have an attribute %v, valid attributes: [string_attr, boolean_attr, boolean_attr2]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// OptionalMetricEmptyUnitMetricAttributeKey specifies the key of an attribute for the optional.metric.empty_unit metric.
type OptionalMetricEmptyUnitMetricAttributeKey string
const (
OptionalMetricEmptyUnitMetricAttributeKeyStringAttr OptionalMetricEmptyUnitMetricAttributeKey = "string_attr"
OptionalMetricEmptyUnitMetricAttributeKeyBooleanAttr OptionalMetricEmptyUnitMetricAttributeKey = "boolean_attr"
)
// OptionalMetricEmptyUnitMetricConfig provides config for the optional.metric.empty_unit metric.
type OptionalMetricEmptyUnitMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []OptionalMetricEmptyUnitMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *OptionalMetricEmptyUnitMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *OptionalMetricEmptyUnitMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case OptionalMetricEmptyUnitMetricAttributeKeyStringAttr, OptionalMetricEmptyUnitMetricAttributeKeyBooleanAttr:
default:
return fmt.Errorf("metric optional.metric.empty_unit doesn't have an attribute %v, valid attributes: [string_attr, boolean_attr]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// ReaggregateMetricMetricAttributeKey specifies the key of an attribute for the reaggregate.metric metric.
type ReaggregateMetricMetricAttributeKey string
const (
ReaggregateMetricMetricAttributeKeyStringAttr ReaggregateMetricMetricAttributeKey = "string_attr"
ReaggregateMetricMetricAttributeKeyBooleanAttr ReaggregateMetricMetricAttributeKey = "boolean_attr"
)
// ReaggregateMetricMetricConfig provides config for the reaggregate.metric metric.
type ReaggregateMetricMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []ReaggregateMetricMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *ReaggregateMetricMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *ReaggregateMetricMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case ReaggregateMetricMetricAttributeKeyStringAttr, ReaggregateMetricMetricAttributeKeyBooleanAttr:
default:
return fmt.Errorf("metric reaggregate.metric doesn't have an attribute %v, valid attributes: [string_attr, boolean_attr]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// SystemCPUTimeMetricConfig provides config for the system.cpu.time metric.
type SystemCPUTimeMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
}
func (ms *SystemCPUTimeMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
// MetricsConfig provides config for sample metrics.
type MetricsConfig struct {
DefaultMetric DefaultMetricMetricConfig `mapstructure:"default.metric"`
DefaultMetricToBeRemoved DefaultMetricToBeRemovedMetricConfig `mapstructure:"default.metric.to_be_removed"`
MetricInputType MetricInputTypeMetricConfig `mapstructure:"metric.input_type"`
OptionalMetric OptionalMetricMetricConfig `mapstructure:"optional.metric"`
OptionalMetricEmptyUnit OptionalMetricEmptyUnitMetricConfig `mapstructure:"optional.metric.empty_unit"`
ReaggregateMetric ReaggregateMetricMetricConfig `mapstructure:"reaggregate.metric"`
SystemCPUTime SystemCPUTimeMetricConfig `mapstructure:"system.cpu.time"`
}
func DefaultMetricsConfig() MetricsConfig {
return MetricsConfig{
DefaultMetric: DefaultMetricMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategySum,
EnabledAttributes: []DefaultMetricMetricAttributeKey{DefaultMetricMetricAttributeKeyStringAttr, DefaultMetricMetricAttributeKeyOverriddenIntAttr, DefaultMetricMetricAttributeKeyEnumAttr, DefaultMetricMetricAttributeKeySliceAttr, DefaultMetricMetricAttributeKeyMapAttr},
},
DefaultMetricToBeRemoved: DefaultMetricToBeRemovedMetricConfig{
Enabled: true,
},
MetricInputType: MetricInputTypeMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategySum,
EnabledAttributes: []MetricInputTypeMetricAttributeKey{MetricInputTypeMetricAttributeKeyStringAttr, MetricInputTypeMetricAttributeKeyOverriddenIntAttr, MetricInputTypeMetricAttributeKeyEnumAttr, MetricInputTypeMetricAttributeKeySliceAttr, MetricInputTypeMetricAttributeKeyMapAttr},
},
OptionalMetric: OptionalMetricMetricConfig{
Enabled: false,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []OptionalMetricMetricAttributeKey{OptionalMetricMetricAttributeKeyStringAttr, OptionalMetricMetricAttributeKeyBooleanAttr, OptionalMetricMetricAttributeKeyBooleanAttr2},
},
OptionalMetricEmptyUnit: OptionalMetricEmptyUnitMetricConfig{
Enabled: false,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []OptionalMetricEmptyUnitMetricAttributeKey{OptionalMetricEmptyUnitMetricAttributeKeyStringAttr, OptionalMetricEmptyUnitMetricAttributeKeyBooleanAttr},
},
ReaggregateMetric: ReaggregateMetricMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []ReaggregateMetricMetricAttributeKey{ReaggregateMetricMetricAttributeKeyStringAttr, ReaggregateMetricMetricAttributeKeyBooleanAttr},
},
SystemCPUTime: SystemCPUTimeMetricConfig{
Enabled: true,
},
}
}
// ResourceAttributeConfig provides common config for a particular resource attribute.
type ResourceAttributeConfig struct {
Enabled bool `mapstructure:"enabled"`
// Experimental: MetricsInclude defines a list of filters for attribute values.
// If the list is not empty, only metrics with matching resource attribute values will be emitted.
MetricsInclude []filter.Config `mapstructure:"metrics_include"`
// Experimental: MetricsExclude defines a list of filters for attribute values.
// If the list is not empty, metrics with matching resource attribute values will not be emitted.
// MetricsInclude has higher priority than MetricsExclude.
MetricsExclude []filter.Config `mapstructure:"metrics_exclude"`
enabledSetByUser bool
}
func (rac *ResourceAttributeConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(rac)
if err != nil {
return err
}
rac.enabledSetByUser = parser.IsSet("enabled")
return nil
}
// ResourceAttributesConfig provides config for sample resource attributes.
type ResourceAttributesConfig struct {
MapResourceAttr ResourceAttributeConfig `mapstructure:"map.resource.attr"`
OptionalResourceAttr ResourceAttributeConfig `mapstructure:"optional.resource.attr"`
SliceResourceAttr ResourceAttributeConfig `mapstructure:"slice.resource.attr"`
StringEnumResourceAttr ResourceAttributeConfig `mapstructure:"string.enum.resource.attr"`
StringResourceAttr ResourceAttributeConfig `mapstructure:"string.resource.attr"`
StringResourceAttrDisableWarning ResourceAttributeConfig `mapstructure:"string.resource.attr_disable_warning"`
StringResourceAttrRemoveWarning ResourceAttributeConfig `mapstructure:"string.resource.attr_remove_warning"`
StringResourceAttrToBeRemoved ResourceAttributeConfig `mapstructure:"string.resource.attr_to_be_removed"`
}
func DefaultResourceAttributesConfig() ResourceAttributesConfig {
return ResourceAttributesConfig{
MapResourceAttr: ResourceAttributeConfig{
Enabled: true,
},
OptionalResourceAttr: ResourceAttributeConfig{
Enabled: false,
},
SliceResourceAttr: ResourceAttributeConfig{
Enabled: true,
},
StringEnumResourceAttr: ResourceAttributeConfig{
Enabled: true,
},
StringResourceAttr: ResourceAttributeConfig{
Enabled: true,
},
StringResourceAttrDisableWarning: ResourceAttributeConfig{
Enabled: true,
},
StringResourceAttrRemoveWarning: ResourceAttributeConfig{
Enabled: false,
},
StringResourceAttrToBeRemoved: ResourceAttributeConfig{
Enabled: true,
},
}
}
// MetricsBuilderConfig is a configuration for sample metrics builder.
type MetricsBuilderConfig struct {
Metrics MetricsConfig `mapstructure:"metrics"`
ResourceAttributes ResourceAttributesConfig `mapstructure:"resource_attributes"`
}
func DefaultMetricsBuilderConfig() MetricsBuilderConfig {
return MetricsBuilderConfig{
Metrics: DefaultMetricsConfig(),
ResourceAttributes: DefaultResourceAttributesConfig(),
}
}
================================================
FILE: cmd/mdatagen/internal/samplescraper/internal/metadata/generated_config_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"path/filepath"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/confmaptest"
)
func TestMetricsBuilderConfig(t *testing.T) {
tests := []struct {
name string
want MetricsBuilderConfig
}{
{
name: "default",
want: DefaultMetricsBuilderConfig(),
},
{
name: "all_set",
want: MetricsBuilderConfig{
Metrics: MetricsConfig{
DefaultMetric: DefaultMetricMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategySum,
EnabledAttributes: []DefaultMetricMetricAttributeKey{DefaultMetricMetricAttributeKeyStringAttr, DefaultMetricMetricAttributeKeyOverriddenIntAttr, DefaultMetricMetricAttributeKeyEnumAttr, DefaultMetricMetricAttributeKeySliceAttr, DefaultMetricMetricAttributeKeyMapAttr},
},
DefaultMetricToBeRemoved: DefaultMetricToBeRemovedMetricConfig{
Enabled: true,
},
MetricInputType: MetricInputTypeMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategySum,
EnabledAttributes: []MetricInputTypeMetricAttributeKey{MetricInputTypeMetricAttributeKeyStringAttr, MetricInputTypeMetricAttributeKeyOverriddenIntAttr, MetricInputTypeMetricAttributeKeyEnumAttr, MetricInputTypeMetricAttributeKeySliceAttr, MetricInputTypeMetricAttributeKeyMapAttr},
},
OptionalMetric: OptionalMetricMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []OptionalMetricMetricAttributeKey{OptionalMetricMetricAttributeKeyStringAttr, OptionalMetricMetricAttributeKeyBooleanAttr, OptionalMetricMetricAttributeKeyBooleanAttr2},
},
OptionalMetricEmptyUnit: OptionalMetricEmptyUnitMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []OptionalMetricEmptyUnitMetricAttributeKey{OptionalMetricEmptyUnitMetricAttributeKeyStringAttr, OptionalMetricEmptyUnitMetricAttributeKeyBooleanAttr},
},
ReaggregateMetric: ReaggregateMetricMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []ReaggregateMetricMetricAttributeKey{ReaggregateMetricMetricAttributeKeyStringAttr, ReaggregateMetricMetricAttributeKeyBooleanAttr},
},
SystemCPUTime: SystemCPUTimeMetricConfig{
Enabled: true,
},
},
ResourceAttributes: ResourceAttributesConfig{
MapResourceAttr: ResourceAttributeConfig{Enabled: true},
OptionalResourceAttr: ResourceAttributeConfig{Enabled: true},
SliceResourceAttr: ResourceAttributeConfig{Enabled: true},
StringEnumResourceAttr: ResourceAttributeConfig{Enabled: true},
StringResourceAttr: ResourceAttributeConfig{Enabled: true},
StringResourceAttrDisableWarning: ResourceAttributeConfig{Enabled: true},
StringResourceAttrRemoveWarning: ResourceAttributeConfig{Enabled: true},
StringResourceAttrToBeRemoved: ResourceAttributeConfig{Enabled: true},
},
},
},
{
name: "none_set",
want: MetricsBuilderConfig{
Metrics: MetricsConfig{
DefaultMetric: DefaultMetricMetricConfig{
Enabled: false,
AggregationStrategy: AggregationStrategySum,
EnabledAttributes: []DefaultMetricMetricAttributeKey{DefaultMetricMetricAttributeKeyStringAttr, DefaultMetricMetricAttributeKeyOverriddenIntAttr, DefaultMetricMetricAttributeKeyEnumAttr, DefaultMetricMetricAttributeKeySliceAttr, DefaultMetricMetricAttributeKeyMapAttr},
},
DefaultMetricToBeRemoved: DefaultMetricToBeRemovedMetricConfig{
Enabled: false,
},
MetricInputType: MetricInputTypeMetricConfig{
Enabled: false,
AggregationStrategy: AggregationStrategySum,
EnabledAttributes: []MetricInputTypeMetricAttributeKey{MetricInputTypeMetricAttributeKeyStringAttr, MetricInputTypeMetricAttributeKeyOverriddenIntAttr, MetricInputTypeMetricAttributeKeyEnumAttr, MetricInputTypeMetricAttributeKeySliceAttr, MetricInputTypeMetricAttributeKeyMapAttr},
},
OptionalMetric: OptionalMetricMetricConfig{
Enabled: false,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []OptionalMetricMetricAttributeKey{OptionalMetricMetricAttributeKeyStringAttr, OptionalMetricMetricAttributeKeyBooleanAttr, OptionalMetricMetricAttributeKeyBooleanAttr2},
},
OptionalMetricEmptyUnit: OptionalMetricEmptyUnitMetricConfig{
Enabled: false,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []OptionalMetricEmptyUnitMetricAttributeKey{OptionalMetricEmptyUnitMetricAttributeKeyStringAttr, OptionalMetricEmptyUnitMetricAttributeKeyBooleanAttr},
},
ReaggregateMetric: ReaggregateMetricMetricConfig{
Enabled: false,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []ReaggregateMetricMetricAttributeKey{ReaggregateMetricMetricAttributeKeyStringAttr, ReaggregateMetricMetricAttributeKeyBooleanAttr},
},
SystemCPUTime: SystemCPUTimeMetricConfig{
Enabled: false,
},
},
ResourceAttributes: ResourceAttributesConfig{
MapResourceAttr: ResourceAttributeConfig{Enabled: false},
OptionalResourceAttr: ResourceAttributeConfig{Enabled: false},
SliceResourceAttr: ResourceAttributeConfig{Enabled: false},
StringEnumResourceAttr: ResourceAttributeConfig{Enabled: false},
StringResourceAttr: ResourceAttributeConfig{Enabled: false},
StringResourceAttrDisableWarning: ResourceAttributeConfig{Enabled: false},
StringResourceAttrRemoveWarning: ResourceAttributeConfig{Enabled: false},
StringResourceAttrToBeRemoved: ResourceAttributeConfig{Enabled: false},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := loadMetricsBuilderConfig(t, tt.name)
diff := cmp.Diff(tt.want, cfg, cmpopts.IgnoreUnexported(DefaultMetricMetricConfig{}, DefaultMetricToBeRemovedMetricConfig{}, MetricInputTypeMetricConfig{}, OptionalMetricMetricConfig{}, OptionalMetricEmptyUnitMetricConfig{}, ReaggregateMetricMetricConfig{}, SystemCPUTimeMetricConfig{}, ResourceAttributeConfig{}))
require.Emptyf(t, diff, "Config mismatch (-expected +actual):\n%s", diff)
})
}
}
func loadMetricsBuilderConfig(t *testing.T, name string) MetricsBuilderConfig {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
require.NoError(t, err)
sub, err := cm.Sub(name)
require.NoError(t, err)
cfg := DefaultMetricsBuilderConfig()
require.NoError(t, sub.Unmarshal(&cfg, confmap.WithIgnoreUnused()))
return cfg
}
func TestResourceAttributesConfig(t *testing.T) {
tests := []struct {
name string
want ResourceAttributesConfig
}{
{
name: "default",
want: DefaultResourceAttributesConfig(),
},
{
name: "all_set",
want: ResourceAttributesConfig{
MapResourceAttr: ResourceAttributeConfig{Enabled: true},
OptionalResourceAttr: ResourceAttributeConfig{Enabled: true},
SliceResourceAttr: ResourceAttributeConfig{Enabled: true},
StringEnumResourceAttr: ResourceAttributeConfig{Enabled: true},
StringResourceAttr: ResourceAttributeConfig{Enabled: true},
StringResourceAttrDisableWarning: ResourceAttributeConfig{Enabled: true},
StringResourceAttrRemoveWarning: ResourceAttributeConfig{Enabled: true},
StringResourceAttrToBeRemoved: ResourceAttributeConfig{Enabled: true},
},
},
{
name: "none_set",
want: ResourceAttributesConfig{
MapResourceAttr: ResourceAttributeConfig{Enabled: false},
OptionalResourceAttr: ResourceAttributeConfig{Enabled: false},
SliceResourceAttr: ResourceAttributeConfig{Enabled: false},
StringEnumResourceAttr: ResourceAttributeConfig{Enabled: false},
StringResourceAttr: ResourceAttributeConfig{Enabled: false},
StringResourceAttrDisableWarning: ResourceAttributeConfig{Enabled: false},
StringResourceAttrRemoveWarning: ResourceAttributeConfig{Enabled: false},
StringResourceAttrToBeRemoved: ResourceAttributeConfig{Enabled: false},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := loadResourceAttributesConfig(t, tt.name)
diff := cmp.Diff(tt.want, cfg, cmpopts.IgnoreUnexported(ResourceAttributeConfig{}))
require.Emptyf(t, diff, "Config mismatch (-expected +actual):\n%s", diff)
})
}
}
func loadResourceAttributesConfig(t *testing.T, name string) ResourceAttributesConfig {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
require.NoError(t, err)
sub, err := cm.Sub(name)
require.NoError(t, err)
sub, err = sub.Sub("resource_attributes")
require.NoError(t, err)
cfg := DefaultResourceAttributesConfig()
require.NoError(t, sub.Unmarshal(&cfg))
return cfg
}
================================================
FILE: cmd/mdatagen/internal/samplescraper/internal/metadata/generated_logs.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
conventions "go.opentelemetry.io/otel/semconv/v1.38.0"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/scraper"
)
// LogsBuilder provides an interface for scrapers to report logs while taking care of all the transformations
// required to produce log representation defined in metadata and user config.
type LogsBuilder struct {
logsBuffer plog.Logs
logRecordsBuffer plog.LogRecordSlice
buildInfo component.BuildInfo // contains version information.
}
// LogBuilderOption applies changes to default logs builder.
type LogBuilderOption interface {
apply(*LogsBuilder)
}
func NewLogsBuilder(settings scraper.Settings) *LogsBuilder {
lb := &LogsBuilder{
logsBuffer: plog.NewLogs(),
logRecordsBuffer: plog.NewLogRecordSlice(),
buildInfo: settings.BuildInfo,
}
return lb
}
// NewResourceBuilder returns a new resource builder that should be used to build a resource associated with for the emitted logs.
func (lb *LogsBuilder) NewResourceBuilder() *ResourceBuilder {
return NewResourceBuilder(ResourceAttributesConfig{})
}
// ResourceLogsOption applies changes to provided resource logs.
type ResourceLogsOption interface {
apply(plog.ResourceLogs)
}
type resourceLogsOptionFunc func(plog.ResourceLogs)
func (rlof resourceLogsOptionFunc) apply(rl plog.ResourceLogs) {
rlof(rl)
}
// WithLogsResource sets the provided resource on the emitted ResourceLogs.
// It's recommended to use ResourceBuilder to create the resource.
func WithLogsResource(res pcommon.Resource) ResourceLogsOption {
return resourceLogsOptionFunc(func(rl plog.ResourceLogs) {
res.CopyTo(rl.Resource())
})
}
// AppendLogRecord adds a log record to the logs builder.
func (lb *LogsBuilder) AppendLogRecord(lr plog.LogRecord) {
lr.MoveTo(lb.logRecordsBuffer.AppendEmpty())
}
// EmitForResource saves all the generated logs under a new resource and updates the internal state to be ready for
// recording another set of log records as part of another resource. This function can be helpful when one scraper
// needs to emit logs from several resources. Otherwise calling this function is not required,
// just `Emit` function can be called instead.
// Resource attributes should be provided as ResourceLogsOption arguments.
func (lb *LogsBuilder) EmitForResource(options ...ResourceLogsOption) {
rl := plog.NewResourceLogs()
rl.SetSchemaUrl(conventions.SchemaURL)
ils := rl.ScopeLogs().AppendEmpty()
ils.Scope().SetName(ScopeName)
ils.Scope().SetVersion(lb.buildInfo.Version)
for _, op := range options {
op.apply(rl)
}
if lb.logRecordsBuffer.Len() > 0 {
lb.logRecordsBuffer.MoveAndAppendTo(ils.LogRecords())
lb.logRecordsBuffer = plog.NewLogRecordSlice()
}
if ils.LogRecords().Len() > 0 {
rl.MoveTo(lb.logsBuffer.ResourceLogs().AppendEmpty())
}
}
// Emit returns all the logs accumulated by the logs builder and updates the internal state to be ready for
// recording another set of logs. This function will be responsible for applying all the transformations required to
// produce logs representation defined in metadata and user config.
func (lb *LogsBuilder) Emit(options ...ResourceLogsOption) plog.Logs {
lb.EmitForResource(options...)
logs := lb.logsBuffer
lb.logsBuffer = plog.NewLogs()
return logs
}
================================================
FILE: cmd/mdatagen/internal/samplescraper/internal/metadata/generated_logs_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/scraper/scrapertest"
)
func TestLogsBuilderAppendLogRecord(t *testing.T) {
observedZapCore, _ := observer.New(zap.WarnLevel)
settings := scrapertest.NewNopSettings(scrapertest.NopType)
settings.Logger = zap.New(observedZapCore)
lb := NewLogsBuilder(settings)
rb := lb.NewResourceBuilder()
rb.SetMapResourceAttr(map[string]any{"key1": "map.resource.attr-val1", "key2": "map.resource.attr-val2"})
rb.SetOptionalResourceAttr("optional.resource.attr-val")
rb.SetSliceResourceAttr([]any{"slice.resource.attr-item1", "slice.resource.attr-item2"})
rb.SetStringEnumResourceAttrOne()
rb.SetStringResourceAttr("string.resource.attr-val")
rb.SetStringResourceAttrDisableWarning("string.resource.attr_disable_warning-val")
rb.SetStringResourceAttrRemoveWarning("string.resource.attr_remove_warning-val")
rb.SetStringResourceAttrToBeRemoved("string.resource.attr_to_be_removed-val")
res := rb.Emit()
// append the first log record
lr := plog.NewLogRecord()
lr.SetTimestamp(pcommon.NewTimestampFromTime(time.Now()))
lr.Attributes().PutStr("type", "log")
lr.Body().SetStr("the first log record")
// append the second log record
lr2 := plog.NewLogRecord()
lr2.SetTimestamp(pcommon.NewTimestampFromTime(time.Now()))
lr2.Attributes().PutStr("type", "event")
lr2.Body().SetStr("the second log record")
lb.AppendLogRecord(lr)
lb.AppendLogRecord(lr2)
logs := lb.Emit(WithLogsResource(res))
assert.Equal(t, 1, logs.ResourceLogs().Len())
rl := logs.ResourceLogs().At(0)
assert.Equal(t, 1, rl.ScopeLogs().Len())
sl := rl.ScopeLogs().At(0)
assert.Equal(t, ScopeName, sl.Scope().Name())
assert.Equal(t, lb.buildInfo.Version, sl.Scope().Version())
assert.Equal(t, 2, sl.LogRecords().Len())
attrVal, ok := sl.LogRecords().At(0).Attributes().Get("type")
assert.True(t, ok)
assert.Equal(t, "log", attrVal.Str())
assert.Equal(t, pcommon.ValueTypeStr, sl.LogRecords().At(0).Body().Type())
assert.Equal(t, "the first log record", sl.LogRecords().At(0).Body().Str())
attrVal, ok = sl.LogRecords().At(1).Attributes().Get("type")
assert.True(t, ok)
assert.Equal(t, "event", attrVal.Str())
assert.Equal(t, pcommon.ValueTypeStr, sl.LogRecords().At(1).Body().Type())
assert.Equal(t, "the second log record", sl.LogRecords().At(1).Body().Str())
}
================================================
FILE: cmd/mdatagen/internal/samplescraper/internal/metadata/generated_metrics.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"fmt"
"slices"
"strconv"
"time"
conventions "go.opentelemetry.io/otel/semconv/v1.38.0"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/filter"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/scraper"
)
const (
AggregationStrategySum = "sum"
AggregationStrategyAvg = "avg"
AggregationStrategyMin = "min"
AggregationStrategyMax = "max"
)
// AttributeEnumAttr specifies the value enum_attr attribute.
type AttributeEnumAttr int
const (
_ AttributeEnumAttr = iota
AttributeEnumAttrRed
AttributeEnumAttrGreen
AttributeEnumAttrBlue
)
// String returns the string representation of the AttributeEnumAttr.
func (av AttributeEnumAttr) String() string {
switch av {
case AttributeEnumAttrRed:
return "red"
case AttributeEnumAttrGreen:
return "green"
case AttributeEnumAttrBlue:
return "blue"
}
return ""
}
// MapAttributeEnumAttr is a helper map of string to AttributeEnumAttr attribute value.
var MapAttributeEnumAttr = map[string]AttributeEnumAttr{
"red": AttributeEnumAttrRed,
"green": AttributeEnumAttrGreen,
"blue": AttributeEnumAttrBlue,
}
var MetricsInfo = metricsInfo{
DefaultMetric: metricInfo{
Name: "default.metric",
},
DefaultMetricToBeRemoved: metricInfo{
Name: "default.metric.to_be_removed",
},
MetricInputType: metricInfo{
Name: "metric.input_type",
},
OptionalMetric: metricInfo{
Name: "optional.metric",
},
OptionalMetricEmptyUnit: metricInfo{
Name: "optional.metric.empty_unit",
},
ReaggregateMetric: metricInfo{
Name: "reaggregate.metric",
},
SystemCPUTime: metricInfo{
Name: "system.cpu.time",
},
}
type metricsInfo struct {
DefaultMetric metricInfo
DefaultMetricToBeRemoved metricInfo
MetricInputType metricInfo
OptionalMetric metricInfo
OptionalMetricEmptyUnit metricInfo
ReaggregateMetric metricInfo
SystemCPUTime metricInfo
}
type metricInfo struct {
Name string
}
type metricDefaultMetric struct {
data pmetric.Metric // data buffer for generated metric.
config DefaultMetricMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []int64 // slice containing number of aggregated datapoints at each index
}
// init fills default.metric metric with initial data.
func (m *metricDefaultMetric) init() {
m.data.SetName("default.metric")
m.data.SetDescription("Monotonic cumulative sum int metric enabled by default.")
m.data.SetUnit("s")
m.data.SetEmptySum()
m.data.Sum().SetIsMonotonic(true)
m.data.Sum().SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
m.data.Sum().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
}
func (m *metricDefaultMetric) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val int64, stringAttrAttributeValue string, overriddenIntAttrAttributeValue int64, enumAttrAttributeValue string, sliceAttrAttributeValue []any, mapAttrAttributeValue map[string]any) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, DefaultMetricMetricAttributeKeyStringAttr) {
dp.Attributes().PutStr("string_attr", stringAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, DefaultMetricMetricAttributeKeyOverriddenIntAttr) {
dp.Attributes().PutInt("state", overriddenIntAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, DefaultMetricMetricAttributeKeyEnumAttr) {
dp.Attributes().PutStr("enum_attr", enumAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, DefaultMetricMetricAttributeKeySliceAttr) {
dp.Attributes().PutEmptySlice("slice_attr").FromRaw(sliceAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, DefaultMetricMetricAttributeKeyMapAttr) {
dp.Attributes().PutEmptyMap("map_attr").FromRaw(mapAttrAttributeValue)
}
var s string
dps := m.data.Sum().DataPoints()
for i := 0; i < dps.Len(); i++ {
dpi := dps.At(i)
if dp.Attributes().Equal(dpi.Attributes()) && dp.StartTimestamp() == dpi.StartTimestamp() && dp.Timestamp() == dpi.Timestamp() {
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetIntValue(dpi.IntValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.IntValue() > val {
dpi.SetIntValue(val)
}
return
case AggregationStrategyMax:
if dpi.IntValue() < val {
dpi.SetIntValue(val)
}
return
}
}
}
dp.SetIntValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricDefaultMetric) updateCapacity() {
if m.data.Sum().DataPoints().Len() > m.capacity {
m.capacity = m.data.Sum().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricDefaultMetric) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Sum().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Sum().DataPoints().At(i).SetIntValue(m.data.Sum().DataPoints().At(i).IntValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricDefaultMetric(cfg DefaultMetricMetricConfig) metricDefaultMetric {
m := metricDefaultMetric{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricDefaultMetricToBeRemoved struct {
data pmetric.Metric // data buffer for generated metric.
config DefaultMetricToBeRemovedMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
}
// init fills default.metric.to_be_removed metric with initial data.
func (m *metricDefaultMetricToBeRemoved) init() {
m.data.SetName("default.metric.to_be_removed")
m.data.SetDescription("[DEPRECATED] Non-monotonic delta sum double metric enabled by default.")
m.data.SetUnit("s")
m.data.SetEmptySum()
m.data.Sum().SetIsMonotonic(false)
m.data.Sum().SetAggregationTemporality(pmetric.AggregationTemporalityDelta)
}
func (m *metricDefaultMetricToBeRemoved) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64) {
if !m.config.Enabled {
return
}
dp := m.data.Sum().DataPoints().AppendEmpty()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
dp.SetDoubleValue(val)
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricDefaultMetricToBeRemoved) updateCapacity() {
if m.data.Sum().DataPoints().Len() > m.capacity {
m.capacity = m.data.Sum().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricDefaultMetricToBeRemoved) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Sum().DataPoints().Len() > 0 {
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricDefaultMetricToBeRemoved(cfg DefaultMetricToBeRemovedMetricConfig) metricDefaultMetricToBeRemoved {
m := metricDefaultMetricToBeRemoved{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMetricInputType struct {
data pmetric.Metric // data buffer for generated metric.
config MetricInputTypeMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []int64 // slice containing number of aggregated datapoints at each index
}
// init fills metric.input_type metric with initial data.
func (m *metricMetricInputType) init() {
m.data.SetName("metric.input_type")
m.data.SetDescription("Monotonic cumulative sum int metric with string input_type enabled by default.")
m.data.SetUnit("s")
m.data.SetEmptySum()
m.data.Sum().SetIsMonotonic(true)
m.data.Sum().SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
m.data.Sum().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
}
func (m *metricMetricInputType) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val int64, stringAttrAttributeValue string, overriddenIntAttrAttributeValue int64, enumAttrAttributeValue string, sliceAttrAttributeValue []any, mapAttrAttributeValue map[string]any) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MetricInputTypeMetricAttributeKeyStringAttr) {
dp.Attributes().PutStr("string_attr", stringAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, MetricInputTypeMetricAttributeKeyOverriddenIntAttr) {
dp.Attributes().PutInt("state", overriddenIntAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, MetricInputTypeMetricAttributeKeyEnumAttr) {
dp.Attributes().PutStr("enum_attr", enumAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, MetricInputTypeMetricAttributeKeySliceAttr) {
dp.Attributes().PutEmptySlice("slice_attr").FromRaw(sliceAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, MetricInputTypeMetricAttributeKeyMapAttr) {
dp.Attributes().PutEmptyMap("map_attr").FromRaw(mapAttrAttributeValue)
}
var s string
dps := m.data.Sum().DataPoints()
for i := 0; i < dps.Len(); i++ {
dpi := dps.At(i)
if dp.Attributes().Equal(dpi.Attributes()) && dp.StartTimestamp() == dpi.StartTimestamp() && dp.Timestamp() == dpi.Timestamp() {
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetIntValue(dpi.IntValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.IntValue() > val {
dpi.SetIntValue(val)
}
return
case AggregationStrategyMax:
if dpi.IntValue() < val {
dpi.SetIntValue(val)
}
return
}
}
}
dp.SetIntValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMetricInputType) updateCapacity() {
if m.data.Sum().DataPoints().Len() > m.capacity {
m.capacity = m.data.Sum().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMetricInputType) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Sum().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Sum().DataPoints().At(i).SetIntValue(m.data.Sum().DataPoints().At(i).IntValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMetricInputType(cfg MetricInputTypeMetricConfig) metricMetricInputType {
m := metricMetricInputType{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricOptionalMetric struct {
data pmetric.Metric // data buffer for generated metric.
config OptionalMetricMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
}
// init fills optional.metric metric with initial data.
func (m *metricOptionalMetric) init() {
m.data.SetName("optional.metric")
m.data.SetDescription("[DEPRECATED] Gauge double metric disabled by default.")
m.data.SetUnit("1")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
}
func (m *metricOptionalMetric) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, stringAttrAttributeValue string, booleanAttrAttributeValue bool, booleanAttr2AttributeValue bool) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, OptionalMetricMetricAttributeKeyStringAttr) {
dp.Attributes().PutStr("string_attr", stringAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, OptionalMetricMetricAttributeKeyBooleanAttr) {
dp.Attributes().PutBool("boolean_attr", booleanAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, OptionalMetricMetricAttributeKeyBooleanAttr2) {
dp.Attributes().PutBool("boolean_attr2", booleanAttr2AttributeValue)
}
var s string
dps := m.data.Gauge().DataPoints()
for i := 0; i < dps.Len(); i++ {
dpi := dps.At(i)
if dp.Attributes().Equal(dpi.Attributes()) && dp.StartTimestamp() == dpi.StartTimestamp() && dp.Timestamp() == dpi.Timestamp() {
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricOptionalMetric) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricOptionalMetric) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricOptionalMetric(cfg OptionalMetricMetricConfig) metricOptionalMetric {
m := metricOptionalMetric{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricOptionalMetricEmptyUnit struct {
data pmetric.Metric // data buffer for generated metric.
config OptionalMetricEmptyUnitMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
}
// init fills optional.metric.empty_unit metric with initial data.
func (m *metricOptionalMetricEmptyUnit) init() {
m.data.SetName("optional.metric.empty_unit")
m.data.SetDescription("[DEPRECATED] Gauge double metric disabled by default.")
m.data.SetUnit("")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
}
func (m *metricOptionalMetricEmptyUnit) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, stringAttrAttributeValue string, booleanAttrAttributeValue bool) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, OptionalMetricEmptyUnitMetricAttributeKeyStringAttr) {
dp.Attributes().PutStr("string_attr", stringAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, OptionalMetricEmptyUnitMetricAttributeKeyBooleanAttr) {
dp.Attributes().PutBool("boolean_attr", booleanAttrAttributeValue)
}
var s string
dps := m.data.Gauge().DataPoints()
for i := 0; i < dps.Len(); i++ {
dpi := dps.At(i)
if dp.Attributes().Equal(dpi.Attributes()) && dp.StartTimestamp() == dpi.StartTimestamp() && dp.Timestamp() == dpi.Timestamp() {
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricOptionalMetricEmptyUnit) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricOptionalMetricEmptyUnit) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricOptionalMetricEmptyUnit(cfg OptionalMetricEmptyUnitMetricConfig) metricOptionalMetricEmptyUnit {
m := metricOptionalMetricEmptyUnit{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricReaggregateMetric struct {
data pmetric.Metric // data buffer for generated metric.
config ReaggregateMetricMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
}
// init fills reaggregate.metric metric with initial data.
func (m *metricReaggregateMetric) init() {
m.data.SetName("reaggregate.metric")
m.data.SetDescription("Metric for testing spatial reaggregation")
m.data.SetUnit("1")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
}
func (m *metricReaggregateMetric) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, stringAttrAttributeValue string, booleanAttrAttributeValue bool) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, ReaggregateMetricMetricAttributeKeyStringAttr) {
dp.Attributes().PutStr("string_attr", stringAttrAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, ReaggregateMetricMetricAttributeKeyBooleanAttr) {
dp.Attributes().PutBool("boolean_attr", booleanAttrAttributeValue)
}
var s string
dps := m.data.Gauge().DataPoints()
for i := 0; i < dps.Len(); i++ {
dpi := dps.At(i)
if dp.Attributes().Equal(dpi.Attributes()) && dp.StartTimestamp() == dpi.StartTimestamp() && dp.Timestamp() == dpi.Timestamp() {
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricReaggregateMetric) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricReaggregateMetric) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricReaggregateMetric(cfg ReaggregateMetricMetricConfig) metricReaggregateMetric {
m := metricReaggregateMetric{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricSystemCPUTime struct {
data pmetric.Metric // data buffer for generated metric.
config SystemCPUTimeMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
}
// init fills system.cpu.time metric with initial data.
func (m *metricSystemCPUTime) init() {
m.data.SetName("system.cpu.time")
m.data.SetDescription("Monotonic cumulative sum int metric enabled by default.")
m.data.SetUnit("s")
m.data.SetEmptySum()
m.data.Sum().SetIsMonotonic(true)
m.data.Sum().SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
}
func (m *metricSystemCPUTime) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val int64) {
if !m.config.Enabled {
return
}
dp := m.data.Sum().DataPoints().AppendEmpty()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
dp.SetIntValue(val)
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricSystemCPUTime) updateCapacity() {
if m.data.Sum().DataPoints().Len() > m.capacity {
m.capacity = m.data.Sum().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricSystemCPUTime) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Sum().DataPoints().Len() > 0 {
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricSystemCPUTime(cfg SystemCPUTimeMetricConfig) metricSystemCPUTime {
m := metricSystemCPUTime{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
// MetricsBuilder provides an interface for scrapers to report metrics while taking care of all the transformations
// required to produce metric representation defined in metadata and user config.
type MetricsBuilder struct {
config MetricsBuilderConfig // config of the metrics builder.
startTime pcommon.Timestamp // start time that will be applied to all recorded data points.
metricsCapacity int // maximum observed number of metrics per resource.
metricsBuffer pmetric.Metrics // accumulates metrics data before emitting.
buildInfo component.BuildInfo // contains version information.
resourceAttributeIncludeFilter map[string]filter.Filter
resourceAttributeExcludeFilter map[string]filter.Filter
metricDefaultMetric metricDefaultMetric
metricDefaultMetricToBeRemoved metricDefaultMetricToBeRemoved
metricMetricInputType metricMetricInputType
metricOptionalMetric metricOptionalMetric
metricOptionalMetricEmptyUnit metricOptionalMetricEmptyUnit
metricReaggregateMetric metricReaggregateMetric
metricSystemCPUTime metricSystemCPUTime
}
// MetricBuilderOption applies changes to default metrics builder.
type MetricBuilderOption interface {
apply(*MetricsBuilder)
}
type metricBuilderOptionFunc func(mb *MetricsBuilder)
func (mbof metricBuilderOptionFunc) apply(mb *MetricsBuilder) {
mbof(mb)
}
// WithStartTime sets startTime on the metrics builder.
func WithStartTime(startTime pcommon.Timestamp) MetricBuilderOption {
return metricBuilderOptionFunc(func(mb *MetricsBuilder) {
mb.startTime = startTime
})
}
func NewMetricsBuilder(mbc MetricsBuilderConfig, settings scraper.Settings, options ...MetricBuilderOption) *MetricsBuilder {
if !mbc.Metrics.DefaultMetric.enabledSetByUser {
settings.Logger.Warn("[WARNING] Please set `enabled` field explicitly for `default.metric`: This metric will be disabled by default soon.")
}
if mbc.Metrics.DefaultMetricToBeRemoved.Enabled {
settings.Logger.Warn("[WARNING] `default.metric.to_be_removed` should not be enabled: This metric is deprecated and will be removed soon.")
}
if mbc.Metrics.OptionalMetric.enabledSetByUser {
settings.Logger.Warn("[WARNING] `optional.metric` should not be configured: This metric is deprecated and will be removed soon.")
}
if mbc.Metrics.OptionalMetricEmptyUnit.enabledSetByUser {
settings.Logger.Warn("[WARNING] `optional.metric.empty_unit` should not be configured: This metric is deprecated and will be removed soon.")
}
if !mbc.ResourceAttributes.StringResourceAttrDisableWarning.enabledSetByUser {
settings.Logger.Warn("[WARNING] Please set `enabled` field explicitly for `string.resource.attr_disable_warning`: This resource_attribute will be disabled by default soon.")
}
if mbc.ResourceAttributes.StringResourceAttrRemoveWarning.enabledSetByUser || mbc.ResourceAttributes.StringResourceAttrRemoveWarning.MetricsInclude != nil || mbc.ResourceAttributes.StringResourceAttrRemoveWarning.MetricsExclude != nil {
settings.Logger.Warn("[WARNING] `string.resource.attr_remove_warning` should not be configured: This resource_attribute is deprecated and will be removed soon.")
}
if mbc.ResourceAttributes.StringResourceAttrToBeRemoved.Enabled {
settings.Logger.Warn("[WARNING] `string.resource.attr_to_be_removed` should not be enabled: This resource_attribute is deprecated and will be removed soon.")
}
mb := &MetricsBuilder{
config: mbc,
startTime: pcommon.NewTimestampFromTime(time.Now()),
metricsBuffer: pmetric.NewMetrics(),
buildInfo: settings.BuildInfo,
metricDefaultMetric: newMetricDefaultMetric(mbc.Metrics.DefaultMetric),
metricDefaultMetricToBeRemoved: newMetricDefaultMetricToBeRemoved(mbc.Metrics.DefaultMetricToBeRemoved),
metricMetricInputType: newMetricMetricInputType(mbc.Metrics.MetricInputType),
metricOptionalMetric: newMetricOptionalMetric(mbc.Metrics.OptionalMetric),
metricOptionalMetricEmptyUnit: newMetricOptionalMetricEmptyUnit(mbc.Metrics.OptionalMetricEmptyUnit),
metricReaggregateMetric: newMetricReaggregateMetric(mbc.Metrics.ReaggregateMetric),
metricSystemCPUTime: newMetricSystemCPUTime(mbc.Metrics.SystemCPUTime),
resourceAttributeIncludeFilter: make(map[string]filter.Filter),
resourceAttributeExcludeFilter: make(map[string]filter.Filter),
}
if mbc.ResourceAttributes.MapResourceAttr.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["map.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.MapResourceAttr.MetricsInclude)
}
if mbc.ResourceAttributes.MapResourceAttr.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["map.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.MapResourceAttr.MetricsExclude)
}
if mbc.ResourceAttributes.OptionalResourceAttr.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["optional.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.OptionalResourceAttr.MetricsInclude)
}
if mbc.ResourceAttributes.OptionalResourceAttr.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["optional.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.OptionalResourceAttr.MetricsExclude)
}
if mbc.ResourceAttributes.SliceResourceAttr.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["slice.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.SliceResourceAttr.MetricsInclude)
}
if mbc.ResourceAttributes.SliceResourceAttr.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["slice.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.SliceResourceAttr.MetricsExclude)
}
if mbc.ResourceAttributes.StringEnumResourceAttr.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["string.enum.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.StringEnumResourceAttr.MetricsInclude)
}
if mbc.ResourceAttributes.StringEnumResourceAttr.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["string.enum.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.StringEnumResourceAttr.MetricsExclude)
}
if mbc.ResourceAttributes.StringResourceAttr.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["string.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.StringResourceAttr.MetricsInclude)
}
if mbc.ResourceAttributes.StringResourceAttr.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["string.resource.attr"] = filter.CreateFilter(mbc.ResourceAttributes.StringResourceAttr.MetricsExclude)
}
if mbc.ResourceAttributes.StringResourceAttrDisableWarning.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["string.resource.attr_disable_warning"] = filter.CreateFilter(mbc.ResourceAttributes.StringResourceAttrDisableWarning.MetricsInclude)
}
if mbc.ResourceAttributes.StringResourceAttrDisableWarning.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["string.resource.attr_disable_warning"] = filter.CreateFilter(mbc.ResourceAttributes.StringResourceAttrDisableWarning.MetricsExclude)
}
if mbc.ResourceAttributes.StringResourceAttrRemoveWarning.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["string.resource.attr_remove_warning"] = filter.CreateFilter(mbc.ResourceAttributes.StringResourceAttrRemoveWarning.MetricsInclude)
}
if mbc.ResourceAttributes.StringResourceAttrRemoveWarning.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["string.resource.attr_remove_warning"] = filter.CreateFilter(mbc.ResourceAttributes.StringResourceAttrRemoveWarning.MetricsExclude)
}
if mbc.ResourceAttributes.StringResourceAttrToBeRemoved.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["string.resource.attr_to_be_removed"] = filter.CreateFilter(mbc.ResourceAttributes.StringResourceAttrToBeRemoved.MetricsInclude)
}
if mbc.ResourceAttributes.StringResourceAttrToBeRemoved.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["string.resource.attr_to_be_removed"] = filter.CreateFilter(mbc.ResourceAttributes.StringResourceAttrToBeRemoved.MetricsExclude)
}
for _, op := range options {
op.apply(mb)
}
return mb
}
// NewResourceBuilder returns a new resource builder that should be used to build a resource associated with for the emitted metrics.
func (mb *MetricsBuilder) NewResourceBuilder() *ResourceBuilder {
return NewResourceBuilder(mb.config.ResourceAttributes)
}
// updateCapacity updates max length of metrics and resource attributes that will be used for the slice capacity.
func (mb *MetricsBuilder) updateCapacity(rm pmetric.ResourceMetrics) {
if mb.metricsCapacity < rm.ScopeMetrics().At(0).Metrics().Len() {
mb.metricsCapacity = rm.ScopeMetrics().At(0).Metrics().Len()
}
}
// ResourceMetricsOption applies changes to provided resource metrics.
type ResourceMetricsOption interface {
apply(pmetric.ResourceMetrics)
}
type resourceMetricsOptionFunc func(pmetric.ResourceMetrics)
func (rmof resourceMetricsOptionFunc) apply(rm pmetric.ResourceMetrics) {
rmof(rm)
}
// WithResource sets the provided resource on the emitted ResourceMetrics.
// It's recommended to use ResourceBuilder to create the resource.
func WithResource(res pcommon.Resource) ResourceMetricsOption {
return resourceMetricsOptionFunc(func(rm pmetric.ResourceMetrics) {
res.CopyTo(rm.Resource())
})
}
// WithStartTimeOverride overrides start time for all the resource metrics data points.
// This option should be only used if different start time has to be set on metrics coming from different resources.
func WithStartTimeOverride(start pcommon.Timestamp) ResourceMetricsOption {
return resourceMetricsOptionFunc(func(rm pmetric.ResourceMetrics) {
var dps pmetric.NumberDataPointSlice
metrics := rm.ScopeMetrics().At(0).Metrics()
for i := 0; i < metrics.Len(); i++ {
switch metrics.At(i).Type() {
case pmetric.MetricTypeGauge:
dps = metrics.At(i).Gauge().DataPoints()
case pmetric.MetricTypeSum:
dps = metrics.At(i).Sum().DataPoints()
}
for j := 0; j < dps.Len(); j++ {
dps.At(j).SetStartTimestamp(start)
}
}
})
}
// EmitForResource saves all the generated metrics under a new resource and updates the internal state to be ready for
// recording another set of data points as part of another resource. This function can be helpful when one scraper
// needs to emit metrics from several resources. Otherwise calling this function is not required,
// just `Emit` function can be called instead.
// Resource attributes should be provided as ResourceMetricsOption arguments.
func (mb *MetricsBuilder) EmitForResource(options ...ResourceMetricsOption) {
rm := pmetric.NewResourceMetrics()
rm.SetSchemaUrl(conventions.SchemaURL)
ils := rm.ScopeMetrics().AppendEmpty()
ils.Scope().SetName(ScopeName)
ils.Scope().SetVersion(mb.buildInfo.Version)
ils.Metrics().EnsureCapacity(mb.metricsCapacity)
mb.metricDefaultMetric.emit(ils.Metrics())
mb.metricDefaultMetricToBeRemoved.emit(ils.Metrics())
mb.metricMetricInputType.emit(ils.Metrics())
mb.metricOptionalMetric.emit(ils.Metrics())
mb.metricOptionalMetricEmptyUnit.emit(ils.Metrics())
mb.metricReaggregateMetric.emit(ils.Metrics())
mb.metricSystemCPUTime.emit(ils.Metrics())
for _, op := range options {
op.apply(rm)
}
for attr, filter := range mb.resourceAttributeIncludeFilter {
if val, ok := rm.Resource().Attributes().Get(attr); ok && !filter.Matches(val.AsString()) {
return
}
}
for attr, filter := range mb.resourceAttributeExcludeFilter {
if val, ok := rm.Resource().Attributes().Get(attr); ok && filter.Matches(val.AsString()) {
return
}
}
if ils.Metrics().Len() > 0 {
mb.updateCapacity(rm)
rm.MoveTo(mb.metricsBuffer.ResourceMetrics().AppendEmpty())
}
}
// Emit returns all the metrics accumulated by the metrics builder and updates the internal state to be ready for
// recording another set of metrics. This function will be responsible for applying all the transformations required to
// produce metric representation defined in metadata and user config, e.g. delta or cumulative.
func (mb *MetricsBuilder) Emit(options ...ResourceMetricsOption) pmetric.Metrics {
mb.EmitForResource(options...)
metrics := mb.metricsBuffer
mb.metricsBuffer = pmetric.NewMetrics()
return metrics
}
// RecordDefaultMetricDataPoint adds a data point to default.metric metric.
func (mb *MetricsBuilder) RecordDefaultMetricDataPoint(ts pcommon.Timestamp, val int64, stringAttrAttributeValue string, overriddenIntAttrAttributeValue int64, enumAttrAttributeValue AttributeEnumAttr, sliceAttrAttributeValue []any, mapAttrAttributeValue map[string]any) {
mb.metricDefaultMetric.recordDataPoint(mb.startTime, ts, val, stringAttrAttributeValue, overriddenIntAttrAttributeValue, enumAttrAttributeValue.String(), sliceAttrAttributeValue, mapAttrAttributeValue)
}
// RecordDefaultMetricToBeRemovedDataPoint adds a data point to default.metric.to_be_removed metric.
func (mb *MetricsBuilder) RecordDefaultMetricToBeRemovedDataPoint(ts pcommon.Timestamp, val float64) {
mb.metricDefaultMetricToBeRemoved.recordDataPoint(mb.startTime, ts, val)
}
// RecordMetricInputTypeDataPoint adds a data point to metric.input_type metric.
func (mb *MetricsBuilder) RecordMetricInputTypeDataPoint(ts pcommon.Timestamp, inputVal string, stringAttrAttributeValue string, overriddenIntAttrAttributeValue int64, enumAttrAttributeValue AttributeEnumAttr, sliceAttrAttributeValue []any, mapAttrAttributeValue map[string]any) error {
val, err := strconv.ParseInt(inputVal, 10, 64)
if err != nil {
return fmt.Errorf("failed to parse int64 for MetricInputType, value was %s: %w", inputVal, err)
}
mb.metricMetricInputType.recordDataPoint(mb.startTime, ts, val, stringAttrAttributeValue, overriddenIntAttrAttributeValue, enumAttrAttributeValue.String(), sliceAttrAttributeValue, mapAttrAttributeValue)
return nil
}
// RecordOptionalMetricDataPoint adds a data point to optional.metric metric.
func (mb *MetricsBuilder) RecordOptionalMetricDataPoint(ts pcommon.Timestamp, val float64, stringAttrAttributeValue string, booleanAttrAttributeValue bool, booleanAttr2AttributeValue bool) {
mb.metricOptionalMetric.recordDataPoint(mb.startTime, ts, val, stringAttrAttributeValue, booleanAttrAttributeValue, booleanAttr2AttributeValue)
}
// RecordOptionalMetricEmptyUnitDataPoint adds a data point to optional.metric.empty_unit metric.
func (mb *MetricsBuilder) RecordOptionalMetricEmptyUnitDataPoint(ts pcommon.Timestamp, val float64, stringAttrAttributeValue string, booleanAttrAttributeValue bool) {
mb.metricOptionalMetricEmptyUnit.recordDataPoint(mb.startTime, ts, val, stringAttrAttributeValue, booleanAttrAttributeValue)
}
// RecordReaggregateMetricDataPoint adds a data point to reaggregate.metric metric.
func (mb *MetricsBuilder) RecordReaggregateMetricDataPoint(ts pcommon.Timestamp, val float64, stringAttrAttributeValue string, booleanAttrAttributeValue bool) {
mb.metricReaggregateMetric.recordDataPoint(mb.startTime, ts, val, stringAttrAttributeValue, booleanAttrAttributeValue)
}
// RecordSystemCPUTimeDataPoint adds a data point to system.cpu.time metric.
func (mb *MetricsBuilder) RecordSystemCPUTimeDataPoint(ts pcommon.Timestamp, val int64) {
mb.metricSystemCPUTime.recordDataPoint(mb.startTime, ts, val)
}
// Reset resets metrics builder to its initial state. It should be used when external metrics source is restarted,
// and metrics builder should update its startTime and reset it's internal state accordingly.
func (mb *MetricsBuilder) Reset(options ...MetricBuilderOption) {
mb.startTime = pcommon.NewTimestampFromTime(time.Now())
for _, op := range options {
op.apply(mb)
}
}
================================================
FILE: cmd/mdatagen/internal/samplescraper/internal/metadata/generated_metrics_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"testing"
"github.com/stretchr/testify/assert"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/scraper/scrapertest"
)
type testDataSet int
const (
testDataSetDefault testDataSet = iota
testDataSetAll
testDataSetNone
testDataSetReag
)
func TestMetricsBuilder(t *testing.T) {
tests := []struct {
name string
metricsSet testDataSet
resAttrsSet testDataSet
expectEmpty bool
}{
{
name: "default",
},
{
name: "all_set",
metricsSet: testDataSetAll,
resAttrsSet: testDataSetAll,
},
{
name: "reaggregate_set",
metricsSet: testDataSetReag,
resAttrsSet: testDataSetReag,
},
{
name: "none_set",
metricsSet: testDataSetNone,
resAttrsSet: testDataSetNone,
expectEmpty: true,
},
{
name: "filter_set_include",
resAttrsSet: testDataSetAll,
},
{
name: "filter_set_exclude",
resAttrsSet: testDataSetAll,
expectEmpty: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
start := pcommon.Timestamp(1_000_000_000)
ts := pcommon.Timestamp(1_000_001_000)
observedZapCore, observedLogs := observer.New(zap.WarnLevel)
settings := scrapertest.NewNopSettings(scrapertest.NopType)
settings.Logger = zap.New(observedZapCore)
mb := NewMetricsBuilder(loadMetricsBuilderConfig(t, tt.name), settings, WithStartTime(start))
aggMap := make(map[string]string) // contains the aggregation strategies for each metric name
aggMap["DefaultMetric"] = mb.metricDefaultMetric.config.AggregationStrategy
aggMap["MetricInputType"] = mb.metricMetricInputType.config.AggregationStrategy
aggMap["OptionalMetric"] = mb.metricOptionalMetric.config.AggregationStrategy
aggMap["OptionalMetricEmptyUnit"] = mb.metricOptionalMetricEmptyUnit.config.AggregationStrategy
aggMap["ReaggregateMetric"] = mb.metricReaggregateMetric.config.AggregationStrategy
expectedWarnings := 0
if tt.metricsSet == testDataSetDefault {
assert.Equal(t, "[WARNING] Please set `enabled` field explicitly for `default.metric`: This metric will be disabled by default soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
if tt.metricsSet == testDataSetDefault || tt.metricsSet == testDataSetAll {
assert.Equal(t, "[WARNING] `default.metric.to_be_removed` should not be enabled: This metric is deprecated and will be removed soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
if tt.metricsSet == testDataSetAll || tt.metricsSet == testDataSetNone {
assert.Equal(t, "[WARNING] `optional.metric` should not be configured: This metric is deprecated and will be removed soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
if tt.metricsSet == testDataSetAll || tt.metricsSet == testDataSetNone {
assert.Equal(t, "[WARNING] `optional.metric.empty_unit` should not be configured: This metric is deprecated and will be removed soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
if tt.resAttrsSet == testDataSetDefault {
assert.Equal(t, "[WARNING] Please set `enabled` field explicitly for `string.resource.attr_disable_warning`: This resource_attribute will be disabled by default soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
if tt.resAttrsSet == testDataSetAll || tt.resAttrsSet == testDataSetNone {
assert.Equal(t, "[WARNING] `string.resource.attr_remove_warning` should not be configured: This resource_attribute is deprecated and will be removed soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
if tt.resAttrsSet == testDataSetDefault || tt.resAttrsSet == testDataSetAll {
assert.Equal(t, "[WARNING] `string.resource.attr_to_be_removed` should not be enabled: This resource_attribute is deprecated and will be removed soon.", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
if tt.metricsSet != testDataSetReag {
assert.Equal(t, expectedWarnings, observedLogs.Len())
}
defaultMetricsCount := 0
allMetricsCount := 0
defaultMetricsCount++
allMetricsCount++
mb.RecordDefaultMetricDataPoint(ts, 1, "string_attr-val", 19, AttributeEnumAttrRed, []any{"slice_attr-item1", "slice_attr-item2"}, map[string]any{"key1": "map_attr-val1", "key2": "map_attr-val2"})
if tt.name == "reaggregate_set" {
mb.RecordDefaultMetricDataPoint(ts, 3, "string_attr-val-2", 20, AttributeEnumAttrGreen, []any{"slice_attr-item3", "slice_attr-item4"}, map[string]any{"key3": "map_attr-val3", "key4": "map_attr-val4"})
}
defaultMetricsCount++
allMetricsCount++
mb.RecordDefaultMetricToBeRemovedDataPoint(ts, 1)
defaultMetricsCount++
allMetricsCount++
mb.RecordMetricInputTypeDataPoint(ts, "1", "string_attr-val", 19, AttributeEnumAttrRed, []any{"slice_attr-item1", "slice_attr-item2"}, map[string]any{"key1": "map_attr-val1", "key2": "map_attr-val2"})
if tt.name == "reaggregate_set" {
mb.RecordMetricInputTypeDataPoint(ts, "3", "string_attr-val-2", 20, AttributeEnumAttrGreen, []any{"slice_attr-item3", "slice_attr-item4"}, map[string]any{"key3": "map_attr-val3", "key4": "map_attr-val4"})
}
allMetricsCount++
mb.RecordOptionalMetricDataPoint(ts, 1, "string_attr-val", true, false)
if tt.name == "reaggregate_set" {
mb.RecordOptionalMetricDataPoint(ts, 3, "string_attr-val-2", false, true)
}
allMetricsCount++
mb.RecordOptionalMetricEmptyUnitDataPoint(ts, 1, "string_attr-val", true)
if tt.name == "reaggregate_set" {
mb.RecordOptionalMetricEmptyUnitDataPoint(ts, 3, "string_attr-val-2", false)
}
defaultMetricsCount++
allMetricsCount++
mb.RecordReaggregateMetricDataPoint(ts, 1, "string_attr-val", true)
if tt.name == "reaggregate_set" {
mb.RecordReaggregateMetricDataPoint(ts, 3, "string_attr-val-2", false)
}
defaultMetricsCount++
allMetricsCount++
mb.RecordSystemCPUTimeDataPoint(ts, 1)
rb := mb.NewResourceBuilder()
rb.SetMapResourceAttr(map[string]any{"key1": "map.resource.attr-val1", "key2": "map.resource.attr-val2"})
rb.SetOptionalResourceAttr("optional.resource.attr-val")
rb.SetSliceResourceAttr([]any{"slice.resource.attr-item1", "slice.resource.attr-item2"})
rb.SetStringEnumResourceAttrOne()
rb.SetStringResourceAttr("string.resource.attr-val")
rb.SetStringResourceAttrDisableWarning("string.resource.attr_disable_warning-val")
rb.SetStringResourceAttrRemoveWarning("string.resource.attr_remove_warning-val")
rb.SetStringResourceAttrToBeRemoved("string.resource.attr_to_be_removed-val")
res := rb.Emit()
metrics := mb.Emit(WithResource(res))
if tt.name == "reaggregate_set" {
assert.Empty(t, mb.metricDefaultMetric.aggDataPoints)
assert.Empty(t, mb.metricMetricInputType.aggDataPoints)
assert.Empty(t, mb.metricOptionalMetric.aggDataPoints)
assert.Empty(t, mb.metricOptionalMetricEmptyUnit.aggDataPoints)
assert.Empty(t, mb.metricReaggregateMetric.aggDataPoints)
}
if tt.expectEmpty {
assert.Equal(t, 0, metrics.ResourceMetrics().Len())
return
}
var allMetricsList []pmetric.Metric
totalMetricsCount := 0
for ri := 0; ri < metrics.ResourceMetrics().Len(); ri++ {
rm := metrics.ResourceMetrics().At(ri)
assert.Equal(t, 1, rm.ScopeMetrics().Len())
ms := rm.ScopeMetrics().At(0).Metrics()
totalMetricsCount += ms.Len()
for mi := 0; mi < ms.Len(); mi++ {
allMetricsList = append(allMetricsList, ms.At(mi))
}
}
if tt.metricsSet == testDataSetDefault {
assert.Equal(t, defaultMetricsCount, totalMetricsCount)
}
if tt.metricsSet == testDataSetAll {
assert.Equal(t, allMetricsCount, totalMetricsCount)
}
validatedMetrics := make(map[string]bool)
for _, mi := range allMetricsList {
switch mi.Name() {
case "default.metric":
if tt.name != "reaggregate_set" {
assert.False(t, validatedMetrics["default.metric"], "Found a duplicate in the metrics slice: default.metric")
validatedMetrics["default.metric"] = true
assert.Equal(t, pmetric.MetricTypeSum, mi.Type())
assert.Equal(t, 1, mi.Sum().DataPoints().Len())
assert.Equal(t, "Monotonic cumulative sum int metric enabled by default.", mi.Description())
assert.Equal(t, "s", mi.Unit())
assert.True(t, mi.Sum().IsMonotonic())
assert.Equal(t, pmetric.AggregationTemporalityCumulative, mi.Sum().AggregationTemporality())
dp := mi.Sum().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeInt, dp.ValueType())
assert.Equal(t, int64(1), dp.IntValue())
stringAttrAttrVal, ok := dp.Attributes().Get("string_attr")
assert.True(t, ok)
assert.Equal(t, "string_attr-val", stringAttrAttrVal.Str())
overriddenIntAttrAttrVal, ok := dp.Attributes().Get("state")
assert.True(t, ok)
assert.EqualValues(t, 19, overriddenIntAttrAttrVal.Int())
enumAttrAttrVal, ok := dp.Attributes().Get("enum_attr")
assert.True(t, ok)
assert.Equal(t, "red", enumAttrAttrVal.Str())
sliceAttrAttrVal, ok := dp.Attributes().Get("slice_attr")
assert.True(t, ok)
assert.Equal(t, []any{"slice_attr-item1", "slice_attr-item2"}, sliceAttrAttrVal.Slice().AsRaw())
mapAttrAttrVal, ok := dp.Attributes().Get("map_attr")
assert.True(t, ok)
assert.Equal(t, map[string]any{"key1": "map_attr-val1", "key2": "map_attr-val2"}, mapAttrAttrVal.Map().AsRaw())
} else {
assert.False(t, validatedMetrics["default.metric"], "Found a duplicate in the metrics slice: default.metric")
validatedMetrics["default.metric"] = true
assert.Equal(t, pmetric.MetricTypeSum, mi.Type())
assert.Equal(t, 1, mi.Sum().DataPoints().Len())
assert.Equal(t, "Monotonic cumulative sum int metric enabled by default.", mi.Description())
assert.Equal(t, "s", mi.Unit())
assert.True(t, mi.Sum().IsMonotonic())
assert.Equal(t, pmetric.AggregationTemporalityCumulative, mi.Sum().AggregationTemporality())
dp := mi.Sum().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeInt, dp.ValueType())
switch aggMap["default.metric"] {
case "sum":
assert.Equal(t, int64(4), dp.IntValue())
case "avg":
assert.Equal(t, int64(2), dp.IntValue())
case "min":
assert.Equal(t, int64(1), dp.IntValue())
case "max":
assert.Equal(t, int64(3), dp.IntValue())
}
_, ok := dp.Attributes().Get("string_attr")
assert.False(t, ok)
_, ok = dp.Attributes().Get("state")
assert.False(t, ok)
_, ok = dp.Attributes().Get("enum_attr")
assert.False(t, ok)
_, ok = dp.Attributes().Get("slice_attr")
assert.False(t, ok)
_, ok = dp.Attributes().Get("map_attr")
assert.False(t, ok)
}
case "default.metric.to_be_removed":
assert.False(t, validatedMetrics["default.metric.to_be_removed"], "Found a duplicate in the metrics slice: default.metric.to_be_removed")
validatedMetrics["default.metric.to_be_removed"] = true
assert.Equal(t, pmetric.MetricTypeSum, mi.Type())
assert.Equal(t, 1, mi.Sum().DataPoints().Len())
assert.Equal(t, "[DEPRECATED] Non-monotonic delta sum double metric enabled by default.", mi.Description())
assert.Equal(t, "s", mi.Unit())
assert.False(t, mi.Sum().IsMonotonic())
assert.Equal(t, pmetric.AggregationTemporalityDelta, mi.Sum().AggregationTemporality())
dp := mi.Sum().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeDouble, dp.ValueType())
assert.InDelta(t, float64(1), dp.DoubleValue(), 0.01)
case "metric.input_type":
if tt.name != "reaggregate_set" {
assert.False(t, validatedMetrics["metric.input_type"], "Found a duplicate in the metrics slice: metric.input_type")
validatedMetrics["metric.input_type"] = true
assert.Equal(t, pmetric.MetricTypeSum, mi.Type())
assert.Equal(t, 1, mi.Sum().DataPoints().Len())
assert.Equal(t, "Monotonic cumulative sum int metric with string input_type enabled by default.", mi.Description())
assert.Equal(t, "s", mi.Unit())
assert.True(t, mi.Sum().IsMonotonic())
assert.Equal(t, pmetric.AggregationTemporalityCumulative, mi.Sum().AggregationTemporality())
dp := mi.Sum().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeInt, dp.ValueType())
assert.Equal(t, int64(1), dp.IntValue())
stringAttrAttrVal, ok := dp.Attributes().Get("string_attr")
assert.True(t, ok)
assert.Equal(t, "string_attr-val", stringAttrAttrVal.Str())
overriddenIntAttrAttrVal, ok := dp.Attributes().Get("state")
assert.True(t, ok)
assert.EqualValues(t, 19, overriddenIntAttrAttrVal.Int())
enumAttrAttrVal, ok := dp.Attributes().Get("enum_attr")
assert.True(t, ok)
assert.Equal(t, "red", enumAttrAttrVal.Str())
sliceAttrAttrVal, ok := dp.Attributes().Get("slice_attr")
assert.True(t, ok)
assert.Equal(t, []any{"slice_attr-item1", "slice_attr-item2"}, sliceAttrAttrVal.Slice().AsRaw())
mapAttrAttrVal, ok := dp.Attributes().Get("map_attr")
assert.True(t, ok)
assert.Equal(t, map[string]any{"key1": "map_attr-val1", "key2": "map_attr-val2"}, mapAttrAttrVal.Map().AsRaw())
} else {
assert.False(t, validatedMetrics["metric.input_type"], "Found a duplicate in the metrics slice: metric.input_type")
validatedMetrics["metric.input_type"] = true
assert.Equal(t, pmetric.MetricTypeSum, mi.Type())
assert.Equal(t, 1, mi.Sum().DataPoints().Len())
assert.Equal(t, "Monotonic cumulative sum int metric with string input_type enabled by default.", mi.Description())
assert.Equal(t, "s", mi.Unit())
assert.True(t, mi.Sum().IsMonotonic())
assert.Equal(t, pmetric.AggregationTemporalityCumulative, mi.Sum().AggregationTemporality())
dp := mi.Sum().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeInt, dp.ValueType())
switch aggMap["metric.input_type"] {
case "sum":
assert.Equal(t, int64(4), dp.IntValue())
case "avg":
assert.Equal(t, int64(2), dp.IntValue())
case "min":
assert.Equal(t, int64(1), dp.IntValue())
case "max":
assert.Equal(t, int64(3), dp.IntValue())
}
_, ok := dp.Attributes().Get("string_attr")
assert.False(t, ok)
_, ok = dp.Attributes().Get("state")
assert.False(t, ok)
_, ok = dp.Attributes().Get("enum_attr")
assert.False(t, ok)
_, ok = dp.Attributes().Get("slice_attr")
assert.False(t, ok)
_, ok = dp.Attributes().Get("map_attr")
assert.False(t, ok)
}
case "optional.metric":
if tt.name != "reaggregate_set" {
assert.False(t, validatedMetrics["optional.metric"], "Found a duplicate in the metrics slice: optional.metric")
validatedMetrics["optional.metric"] = true
assert.Equal(t, pmetric.MetricTypeGauge, mi.Type())
assert.Equal(t, 1, mi.Gauge().DataPoints().Len())
assert.Equal(t, "[DEPRECATED] Gauge double metric disabled by default.", mi.Description())
assert.Equal(t, "1", mi.Unit())
dp := mi.Gauge().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeDouble, dp.ValueType())
assert.InDelta(t, float64(1), dp.DoubleValue(), 0.01)
stringAttrAttrVal, ok := dp.Attributes().Get("string_attr")
assert.True(t, ok)
assert.Equal(t, "string_attr-val", stringAttrAttrVal.Str())
booleanAttrAttrVal, ok := dp.Attributes().Get("boolean_attr")
assert.True(t, ok)
assert.True(t, booleanAttrAttrVal.Bool())
booleanAttr2AttrVal, ok := dp.Attributes().Get("boolean_attr2")
assert.True(t, ok)
assert.False(t, booleanAttr2AttrVal.Bool())
} else {
assert.False(t, validatedMetrics["optional.metric"], "Found a duplicate in the metrics slice: optional.metric")
validatedMetrics["optional.metric"] = true
assert.Equal(t, pmetric.MetricTypeGauge, mi.Type())
assert.Equal(t, 1, mi.Gauge().DataPoints().Len())
assert.Equal(t, "[DEPRECATED] Gauge double metric disabled by default.", mi.Description())
assert.Equal(t, "1", mi.Unit())
dp := mi.Gauge().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeDouble, dp.ValueType())
switch aggMap["optional.metric"] {
case "sum":
assert.InDelta(t, float64(4), dp.DoubleValue(), 0.01)
case "avg":
assert.InDelta(t, float64(2), dp.DoubleValue(), 0.01)
case "min":
assert.InDelta(t, float64(1), dp.DoubleValue(), 0.01)
case "max":
assert.InDelta(t, float64(3), dp.DoubleValue(), 0.01)
}
_, ok := dp.Attributes().Get("string_attr")
assert.False(t, ok)
_, ok = dp.Attributes().Get("boolean_attr")
assert.False(t, ok)
_, ok = dp.Attributes().Get("boolean_attr2")
assert.False(t, ok)
}
case "optional.metric.empty_unit":
if tt.name != "reaggregate_set" {
assert.False(t, validatedMetrics["optional.metric.empty_unit"], "Found a duplicate in the metrics slice: optional.metric.empty_unit")
validatedMetrics["optional.metric.empty_unit"] = true
assert.Equal(t, pmetric.MetricTypeGauge, mi.Type())
assert.Equal(t, 1, mi.Gauge().DataPoints().Len())
assert.Equal(t, "[DEPRECATED] Gauge double metric disabled by default.", mi.Description())
assert.Empty(t, mi.Unit())
dp := mi.Gauge().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeDouble, dp.ValueType())
assert.InDelta(t, float64(1), dp.DoubleValue(), 0.01)
stringAttrAttrVal, ok := dp.Attributes().Get("string_attr")
assert.True(t, ok)
assert.Equal(t, "string_attr-val", stringAttrAttrVal.Str())
booleanAttrAttrVal, ok := dp.Attributes().Get("boolean_attr")
assert.True(t, ok)
assert.True(t, booleanAttrAttrVal.Bool())
} else {
assert.False(t, validatedMetrics["optional.metric.empty_unit"], "Found a duplicate in the metrics slice: optional.metric.empty_unit")
validatedMetrics["optional.metric.empty_unit"] = true
assert.Equal(t, pmetric.MetricTypeGauge, mi.Type())
assert.Equal(t, 1, mi.Gauge().DataPoints().Len())
assert.Equal(t, "[DEPRECATED] Gauge double metric disabled by default.", mi.Description())
assert.Empty(t, mi.Unit())
dp := mi.Gauge().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeDouble, dp.ValueType())
switch aggMap["optional.metric.empty_unit"] {
case "sum":
assert.InDelta(t, float64(4), dp.DoubleValue(), 0.01)
case "avg":
assert.InDelta(t, float64(2), dp.DoubleValue(), 0.01)
case "min":
assert.InDelta(t, float64(1), dp.DoubleValue(), 0.01)
case "max":
assert.InDelta(t, float64(3), dp.DoubleValue(), 0.01)
}
_, ok := dp.Attributes().Get("string_attr")
assert.False(t, ok)
_, ok = dp.Attributes().Get("boolean_attr")
assert.False(t, ok)
}
case "reaggregate.metric":
if tt.name != "reaggregate_set" {
assert.False(t, validatedMetrics["reaggregate.metric"], "Found a duplicate in the metrics slice: reaggregate.metric")
validatedMetrics["reaggregate.metric"] = true
assert.Equal(t, pmetric.MetricTypeGauge, mi.Type())
assert.Equal(t, 1, mi.Gauge().DataPoints().Len())
assert.Equal(t, "Metric for testing spatial reaggregation", mi.Description())
assert.Equal(t, "1", mi.Unit())
dp := mi.Gauge().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeDouble, dp.ValueType())
assert.InDelta(t, float64(1), dp.DoubleValue(), 0.01)
stringAttrAttrVal, ok := dp.Attributes().Get("string_attr")
assert.True(t, ok)
assert.Equal(t, "string_attr-val", stringAttrAttrVal.Str())
booleanAttrAttrVal, ok := dp.Attributes().Get("boolean_attr")
assert.True(t, ok)
assert.True(t, booleanAttrAttrVal.Bool())
} else {
assert.False(t, validatedMetrics["reaggregate.metric"], "Found a duplicate in the metrics slice: reaggregate.metric")
validatedMetrics["reaggregate.metric"] = true
assert.Equal(t, pmetric.MetricTypeGauge, mi.Type())
assert.Equal(t, 1, mi.Gauge().DataPoints().Len())
assert.Equal(t, "Metric for testing spatial reaggregation", mi.Description())
assert.Equal(t, "1", mi.Unit())
dp := mi.Gauge().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeDouble, dp.ValueType())
switch aggMap["reaggregate.metric"] {
case "sum":
assert.InDelta(t, float64(4), dp.DoubleValue(), 0.01)
case "avg":
assert.InDelta(t, float64(2), dp.DoubleValue(), 0.01)
case "min":
assert.InDelta(t, float64(1), dp.DoubleValue(), 0.01)
case "max":
assert.InDelta(t, float64(3), dp.DoubleValue(), 0.01)
}
_, ok := dp.Attributes().Get("string_attr")
assert.False(t, ok)
_, ok = dp.Attributes().Get("boolean_attr")
assert.False(t, ok)
}
case "system.cpu.time":
assert.False(t, validatedMetrics["system.cpu.time"], "Found a duplicate in the metrics slice: system.cpu.time")
validatedMetrics["system.cpu.time"] = true
assert.Equal(t, pmetric.MetricTypeSum, mi.Type())
assert.Equal(t, 1, mi.Sum().DataPoints().Len())
assert.Equal(t, "Monotonic cumulative sum int metric enabled by default.", mi.Description())
assert.Equal(t, "s", mi.Unit())
assert.True(t, mi.Sum().IsMonotonic())
assert.Equal(t, pmetric.AggregationTemporalityCumulative, mi.Sum().AggregationTemporality())
dp := mi.Sum().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueTypeInt, dp.ValueType())
assert.Equal(t, int64(1), dp.IntValue())
}
}
})
}
}
================================================
FILE: cmd/mdatagen/internal/samplescraper/internal/metadata/generated_resource.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/pdata/pcommon"
)
// ResourceBuilder is a helper struct to build resources predefined in metadata.yaml.
// The ResourceBuilder is not thread-safe and must not to be used in multiple goroutines.
type ResourceBuilder struct {
config ResourceAttributesConfig
res pcommon.Resource
}
// NewResourceBuilder creates a new ResourceBuilder. This method should be called on the start of the application.
func NewResourceBuilder(rac ResourceAttributesConfig) *ResourceBuilder {
return &ResourceBuilder{
config: rac,
res: pcommon.NewResource(),
}
}
// SetMapResourceAttr sets provided value as "map.resource.attr" attribute.
func (rb *ResourceBuilder) SetMapResourceAttr(val map[string]any) {
if rb.config.MapResourceAttr.Enabled {
rb.res.Attributes().PutEmptyMap("map.resource.attr").FromRaw(val)
}
}
// SetOptionalResourceAttr sets provided value as "optional.resource.attr" attribute.
func (rb *ResourceBuilder) SetOptionalResourceAttr(val string) {
if rb.config.OptionalResourceAttr.Enabled {
rb.res.Attributes().PutStr("optional.resource.attr", val)
}
}
// SetSliceResourceAttr sets provided value as "slice.resource.attr" attribute.
func (rb *ResourceBuilder) SetSliceResourceAttr(val []any) {
if rb.config.SliceResourceAttr.Enabled {
rb.res.Attributes().PutEmptySlice("slice.resource.attr").FromRaw(val)
}
}
// SetStringEnumResourceAttrOne sets "string.enum.resource.attr=one" attribute.
func (rb *ResourceBuilder) SetStringEnumResourceAttrOne() {
if rb.config.StringEnumResourceAttr.Enabled {
rb.res.Attributes().PutStr("string.enum.resource.attr", "one")
}
}
// SetStringEnumResourceAttrTwo sets "string.enum.resource.attr=two" attribute.
func (rb *ResourceBuilder) SetStringEnumResourceAttrTwo() {
if rb.config.StringEnumResourceAttr.Enabled {
rb.res.Attributes().PutStr("string.enum.resource.attr", "two")
}
}
// SetStringResourceAttr sets provided value as "string.resource.attr" attribute.
func (rb *ResourceBuilder) SetStringResourceAttr(val string) {
if rb.config.StringResourceAttr.Enabled {
rb.res.Attributes().PutStr("string.resource.attr", val)
}
}
// SetStringResourceAttrDisableWarning sets provided value as "string.resource.attr_disable_warning" attribute.
func (rb *ResourceBuilder) SetStringResourceAttrDisableWarning(val string) {
if rb.config.StringResourceAttrDisableWarning.Enabled {
rb.res.Attributes().PutStr("string.resource.attr_disable_warning", val)
}
}
// SetStringResourceAttrRemoveWarning sets provided value as "string.resource.attr_remove_warning" attribute.
func (rb *ResourceBuilder) SetStringResourceAttrRemoveWarning(val string) {
if rb.config.StringResourceAttrRemoveWarning.Enabled {
rb.res.Attributes().PutStr("string.resource.attr_remove_warning", val)
}
}
// SetStringResourceAttrToBeRemoved sets provided value as "string.resource.attr_to_be_removed" attribute.
func (rb *ResourceBuilder) SetStringResourceAttrToBeRemoved(val string) {
if rb.config.StringResourceAttrToBeRemoved.Enabled {
rb.res.Attributes().PutStr("string.resource.attr_to_be_removed", val)
}
}
// Emit returns the built resource and resets the internal builder state.
func (rb *ResourceBuilder) Emit() pcommon.Resource {
r := rb.res
rb.res = pcommon.NewResource()
return r
}
================================================
FILE: cmd/mdatagen/internal/samplescraper/internal/metadata/generated_resource_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestResourceBuilder(t *testing.T) {
for _, tt := range []string{"default", "all_set", "none_set"} {
t.Run(tt, func(t *testing.T) {
cfg := loadResourceAttributesConfig(t, tt)
rb := NewResourceBuilder(cfg)
rb.SetMapResourceAttr(map[string]any{"key1": "map.resource.attr-val1", "key2": "map.resource.attr-val2"})
rb.SetOptionalResourceAttr("optional.resource.attr-val")
rb.SetSliceResourceAttr([]any{"slice.resource.attr-item1", "slice.resource.attr-item2"})
rb.SetStringEnumResourceAttrOne()
rb.SetStringResourceAttr("string.resource.attr-val")
rb.SetStringResourceAttrDisableWarning("string.resource.attr_disable_warning-val")
rb.SetStringResourceAttrRemoveWarning("string.resource.attr_remove_warning-val")
rb.SetStringResourceAttrToBeRemoved("string.resource.attr_to_be_removed-val")
res := rb.Emit()
assert.Equal(t, 0, rb.Emit().Attributes().Len()) // Second call should return empty Resource
switch tt {
case "default":
assert.Equal(t, 6, res.Attributes().Len())
case "all_set":
assert.Equal(t, 8, res.Attributes().Len())
case "none_set":
assert.Equal(t, 0, res.Attributes().Len())
return
default:
assert.Failf(t, "unexpected test case: %s", tt)
}
mapResourceAttrAttrVal, ok := res.Attributes().Get("map.resource.attr")
assert.True(t, ok)
if ok {
assert.Equal(t, map[string]any{"key1": "map.resource.attr-val1", "key2": "map.resource.attr-val2"}, mapResourceAttrAttrVal.Map().AsRaw())
}
optionalResourceAttrAttrVal, ok := res.Attributes().Get("optional.resource.attr")
assert.Equal(t, tt == "all_set", ok)
if ok {
assert.Equal(t, "optional.resource.attr-val", optionalResourceAttrAttrVal.Str())
}
sliceResourceAttrAttrVal, ok := res.Attributes().Get("slice.resource.attr")
assert.True(t, ok)
if ok {
assert.Equal(t, []any{"slice.resource.attr-item1", "slice.resource.attr-item2"}, sliceResourceAttrAttrVal.Slice().AsRaw())
}
stringEnumResourceAttrAttrVal, ok := res.Attributes().Get("string.enum.resource.attr")
assert.True(t, ok)
if ok {
assert.Equal(t, "one", stringEnumResourceAttrAttrVal.Str())
}
stringResourceAttrAttrVal, ok := res.Attributes().Get("string.resource.attr")
assert.True(t, ok)
if ok {
assert.Equal(t, "string.resource.attr-val", stringResourceAttrAttrVal.Str())
}
stringResourceAttrDisableWarningAttrVal, ok := res.Attributes().Get("string.resource.attr_disable_warning")
assert.True(t, ok)
if ok {
assert.Equal(t, "string.resource.attr_disable_warning-val", stringResourceAttrDisableWarningAttrVal.Str())
}
stringResourceAttrRemoveWarningAttrVal, ok := res.Attributes().Get("string.resource.attr_remove_warning")
assert.Equal(t, tt == "all_set", ok)
if ok {
assert.Equal(t, "string.resource.attr_remove_warning-val", stringResourceAttrRemoveWarningAttrVal.Str())
}
stringResourceAttrToBeRemovedAttrVal, ok := res.Attributes().Get("string.resource.attr_to_be_removed")
assert.True(t, ok)
if ok {
assert.Equal(t, "string.resource.attr_to_be_removed-val", stringResourceAttrToBeRemovedAttrVal.Str())
}
})
}
}
================================================
FILE: cmd/mdatagen/internal/samplescraper/internal/metadata/generated_status.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/component"
)
var (
Type = component.MustNewType("sample")
ScopeName = "go.opentelemetry.io/collector/cmd/mdatagen/internal/samplescraper"
)
const (
LogsStability = component.StabilityLevelDevelopment
ProfilesStability = component.StabilityLevelDevelopment
MetricsStability = component.StabilityLevelStable
)
================================================
FILE: cmd/mdatagen/internal/samplescraper/internal/metadata/testdata/config.yaml
================================================
default:
all_set:
metrics:
default.metric:
enabled: true
attributes: ["string_attr","state","enum_attr","slice_attr","map_attr"]
default.metric.to_be_removed:
enabled: true
metric.input_type:
enabled: true
attributes: ["string_attr","state","enum_attr","slice_attr","map_attr"]
optional.metric:
enabled: true
attributes: ["string_attr","boolean_attr","boolean_attr2"]
optional.metric.empty_unit:
enabled: true
attributes: ["string_attr","boolean_attr"]
reaggregate.metric:
enabled: true
attributes: ["string_attr","boolean_attr"]
system.cpu.time:
enabled: true
resource_attributes:
map.resource.attr:
enabled: true
optional.resource.attr:
enabled: true
slice.resource.attr:
enabled: true
string.enum.resource.attr:
enabled: true
string.resource.attr:
enabled: true
string.resource.attr_disable_warning:
enabled: true
string.resource.attr_remove_warning:
enabled: true
string.resource.attr_to_be_removed:
enabled: true
reaggregate_set:
metrics:
default.metric:
enabled: true
attributes: []
default.metric.to_be_removed:
enabled: true
metric.input_type:
enabled: true
attributes: []
optional.metric:
enabled: true
attributes: []
optional.metric.empty_unit:
enabled: true
attributes: []
reaggregate.metric:
enabled: true
attributes: []
system.cpu.time:
enabled: true
resource_attributes:
map.resource.attr:
enabled: true
optional.resource.attr:
enabled: true
slice.resource.attr:
enabled: true
string.enum.resource.attr:
enabled: true
string.resource.attr:
enabled: true
string.resource.attr_disable_warning:
enabled: true
string.resource.attr_remove_warning:
enabled: true
string.resource.attr_to_be_removed:
enabled: true
none_set:
metrics:
default.metric:
enabled: false
attributes: ["string_attr","state","enum_attr","slice_attr","map_attr"]
default.metric.to_be_removed:
enabled: false
metric.input_type:
enabled: false
attributes: ["string_attr","state","enum_attr","slice_attr","map_attr"]
optional.metric:
enabled: false
attributes: ["string_attr","boolean_attr","boolean_attr2"]
optional.metric.empty_unit:
enabled: false
attributes: ["string_attr","boolean_attr"]
reaggregate.metric:
enabled: false
attributes: ["string_attr","boolean_attr"]
system.cpu.time:
enabled: false
resource_attributes:
map.resource.attr:
enabled: false
optional.resource.attr:
enabled: false
slice.resource.attr:
enabled: false
string.enum.resource.attr:
enabled: false
string.resource.attr:
enabled: false
string.resource.attr_disable_warning:
enabled: false
string.resource.attr_remove_warning:
enabled: false
string.resource.attr_to_be_removed:
enabled: false
filter_set_include:
resource_attributes:
map.resource.attr:
enabled: true
metrics_include:
- regexp: ".*"
optional.resource.attr:
enabled: true
metrics_include:
- regexp: ".*"
slice.resource.attr:
enabled: true
metrics_include:
- regexp: ".*"
string.enum.resource.attr:
enabled: true
metrics_include:
- regexp: ".*"
string.resource.attr:
enabled: true
metrics_include:
- regexp: ".*"
string.resource.attr_disable_warning:
enabled: true
metrics_include:
- regexp: ".*"
string.resource.attr_remove_warning:
enabled: true
metrics_include:
- regexp: ".*"
string.resource.attr_to_be_removed:
enabled: true
metrics_include:
- regexp: ".*"
filter_set_exclude:
resource_attributes:
map.resource.attr:
enabled: true
metrics_exclude:
- regexp: ".*"
optional.resource.attr:
enabled: true
metrics_exclude:
- strict: "optional.resource.attr-val"
slice.resource.attr:
enabled: true
metrics_exclude:
- regexp: ".*"
string.enum.resource.attr:
enabled: true
metrics_exclude:
- strict: "one"
string.resource.attr:
enabled: true
metrics_exclude:
- strict: "string.resource.attr-val"
string.resource.attr_disable_warning:
enabled: true
metrics_exclude:
- strict: "string.resource.attr_disable_warning-val"
string.resource.attr_remove_warning:
enabled: true
metrics_exclude:
- strict: "string.resource.attr_remove_warning-val"
string.resource.attr_to_be_removed:
enabled: true
metrics_exclude:
- strict: "string.resource.attr_to_be_removed-val"
================================================
FILE: cmd/mdatagen/internal/samplescraper/metadata.yaml
================================================
# Sample metadata file with all available configurations for a scraper.
type: sample
display_name: Sample Scraper
description: This scraper is used for testing purposes to check the output of mdatagen.
reaggregation_enabled: true
github_project: open-telemetry/opentelemetry-collector
sem_conv_version: 1.38.0
status:
disable_codecov_badge: true
class: scraper
stability:
stable: [metrics]
development: [logs, profiles]
distributions: []
unsupported_platforms: [freebsd, illumos]
codeowners:
active: [dmitryax]
warnings:
- Any additional information that should be brought to the consumer's attention
config:
type: object
description: Configuration for the Sample Scraper.
allOf:
- $ref: /scraper/scraperhelper.controller_config
$defs:
properties:
targets:
description: Targets configuration for the scraper.
x-pointer: true
type: array
items:
type: object
properties:
http_client:
$ref: /config/confighttp.client_config
interval:
type: string
format: duration
x-optional: true
resource_attributes:
map.resource.attr:
description: Resource attribute with a map value.
type: map
enabled: true
optional.resource.attr:
description: Explicitly disabled ResourceAttribute.
type: string
enabled: false
slice.resource.attr:
description: Resource attribute with a slice value.
type: slice
enabled: true
string.enum.resource.attr:
description: Resource attribute with a known set of string values.
type: string
enum: [one, two]
enabled: true
string.resource.attr:
description: Resource attribute with any string value.
type: string
enabled: true
string.resource.attr_disable_warning:
description: Resource attribute with any string value.
type: string
enabled: true
warnings:
if_enabled_not_set: This resource_attribute will be disabled by default soon.
string.resource.attr_remove_warning:
description: Resource attribute with any string value.
type: string
enabled: false
warnings:
if_configured: This resource_attribute is deprecated and will be removed soon.
string.resource.attr_to_be_removed:
description: Resource attribute with any string value.
type: string
enabled: true
warnings:
if_enabled: This resource_attribute is deprecated and will be removed soon.
attributes:
boolean_attr:
description: Attribute with a boolean value.
type: bool
# This 2nd boolean attribute allows us to test both values for boolean attributes,
# as test values are based on the parity of the attribute name length.
boolean_attr2:
description: Another attribute with a boolean value.
type: bool
enum_attr:
description: Attribute with a known set of string values.
type: string
enum: [red, green, blue]
map_attr:
description: Attribute with a map value.
type: map
overridden_int_attr:
name_override: state
description: Integer attribute with overridden name.
type: int
slice_attr:
description: Attribute with a slice value.
type: slice
string_attr:
description: Attribute with any string value.
type: string
metrics:
default.metric:
enabled: true
description: Monotonic cumulative sum int metric enabled by default.
extended_documentation: The metric will be become optional soon.
stability: development
unit: s
sum:
value_type: int
monotonic: true
aggregation_temporality: cumulative
attributes:
[string_attr, overridden_int_attr, enum_attr, slice_attr, map_attr]
warnings:
if_enabled_not_set: This metric will be disabled by default soon.
default.metric.to_be_removed:
enabled: true
description: "[DEPRECATED] Non-monotonic delta sum double metric enabled by default."
extended_documentation: The metric will be removed soon.
stability: deprecated
deprecated:
since: "1.0.0"
note: "This metric will be removed"
unit: s
sum:
value_type: double
monotonic: false
aggregation_temporality: delta
warnings:
if_enabled: This metric is deprecated and will be removed soon.
metric.input_type:
enabled: true
stability: development
description: Monotonic cumulative sum int metric with string input_type enabled by default.
unit: s
sum:
value_type: int
input_type: string
monotonic: true
aggregation_temporality: cumulative
attributes:
[string_attr, overridden_int_attr, enum_attr, slice_attr, map_attr]
optional.metric:
enabled: false
description: "[DEPRECATED] Gauge double metric disabled by default."
stability: deprecated
deprecated:
since: "1.0.0"
note: "This metric will be removed"
unit: "1"
gauge:
value_type: double
attributes: [string_attr, boolean_attr, boolean_attr2]
warnings:
if_configured: This metric is deprecated and will be removed soon.
optional.metric.empty_unit:
enabled: false
description: "[DEPRECATED] Gauge double metric disabled by default."
stability: deprecated
deprecated:
since: "1.0.0"
note: "This metric will be removed"
unit: ""
gauge:
value_type: double
attributes: [string_attr, boolean_attr]
warnings:
if_configured: This metric is deprecated and will be removed soon.
reaggregate.metric:
enabled: true
description: Metric for testing spatial reaggregation
unit: "1"
stability: beta
gauge:
value_type: double
attributes: [string_attr, boolean_attr]
system.cpu.time:
enabled: true
stability: beta
description: Monotonic cumulative sum int metric enabled by default.
extended_documentation: The metric will be become optional soon.
unit: s
sum:
value_type: int
monotonic: true
aggregation_temporality: cumulative
semantic_convention:
ref: https://github.com/open-telemetry/semantic-conventions/blob/v1.38.0/docs/system/system-metrics.md#metric-systemcputime
================================================
FILE: cmd/mdatagen/internal/status.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/cmd/mdatagen/internal"
import (
"errors"
"fmt"
"slices"
"sort"
"time"
"go.opentelemetry.io/collector/component"
)
// distroURL returns the collection of distributions that can be referenced in the metadata.yaml files.
// The rules below apply to every distribution added to this list:
// - The distribution is open source and maintained by the OpenTelemetry project.
// - The link must point to a publicly accessible repository.
func distroURL(name string) string {
switch name {
case "core":
return "https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol"
case "contrib":
return "https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib"
case "k8s":
return "https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-k8s"
case "otlp":
return "https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-otlp"
default:
return ""
}
}
type Codeowners struct {
// Active codeowners
Active []string `mapstructure:"active"`
// Emeritus codeowners
Emeritus []string `mapstructure:"emeritus"`
// Whether new codeowners are being sought
SeekingNew bool `mapstructure:"seeking_new"`
}
type Status struct {
Stability StabilityMap `mapstructure:"stability"`
Distributions []string `mapstructure:"distributions"`
Class string `mapstructure:"class"`
Warnings []string `mapstructure:"warnings"`
Codeowners *Codeowners `mapstructure:"codeowners"`
UnsupportedPlatforms []string `mapstructure:"unsupported_platforms"`
Deprecation DeprecationMap `mapstructure:"deprecation"`
CodeCovComponentID string `mapstructure:"codecov_component_id"`
DisableCodeCov bool `mapstructure:"disable_codecov_badge"`
}
type DeprecationMap map[string]DeprecationInfo
type DeprecationInfo struct {
Date string `mapstructure:"date"`
Migration string `mapstructure:"migration"`
}
var validClasses = []string{
"cmd",
"connector",
"converter",
"exporter",
"extension",
"pkg",
"processor",
"provider",
"receiver",
"scraper",
}
var validStabilityKeys = []string{
"converter",
"extension",
"logs",
"logs_to_traces",
"logs_to_metrics",
"logs_to_logs",
"logs_to_profiles",
"metrics",
"metrics_to_traces",
"metrics_to_metrics",
"metrics_to_logs",
"metrics_to_profiles",
"profiles",
"profiles_to_profiles",
"profiles_to_traces",
"profiles_to_metrics",
"profiles_to_logs",
"provider",
"traces_to_traces",
"traces_to_metrics",
"traces_to_logs",
"traces_to_profiles",
"traces",
}
func (s *Status) SortedDistributions() []string {
sorted := s.Distributions
sort.Slice(sorted, func(i, j int) bool {
if s.Distributions[i] == "core" {
return true
}
if s.Distributions[i] == "contrib" {
return s.Distributions[j] != "core"
}
if s.Distributions[j] == "core" {
return false
}
if s.Distributions[j] == "contrib" {
return s.Distributions[i] == "core"
}
return s.Distributions[i] < s.Distributions[j]
})
return sorted
}
func (s *Status) Validate() error {
var errs error
if s == nil {
return errors.New("missing status")
}
if err := s.validateClass(); err != nil {
errs = errors.Join(errs, err)
}
if err := s.Stability.Validate(); err != nil {
errs = errors.Join(errs, err)
}
if err := s.Deprecation.Validate(s.Stability); err != nil {
errs = errors.Join(errs, err)
}
return errs
}
func (s *Status) validateClass() error {
if s.Class == "" {
return errors.New("missing class")
}
if !slices.Contains(validClasses, s.Class) {
return fmt.Errorf("invalid class: %v", s.Class)
}
return nil
}
type StabilityMap map[component.StabilityLevel][]string
func (ms StabilityMap) Validate() error {
var errs error
if len(ms) == 0 {
return errors.New("missing stability")
}
for stability, cmps := range ms {
if len(cmps) == 0 {
errs = errors.Join(errs, fmt.Errorf("missing component for stability: %v", stability))
}
for _, c := range cmps {
if !slices.Contains(validStabilityKeys, c) {
errs = errors.Join(errs, fmt.Errorf("invalid component: %v", c))
}
}
}
return errs
}
func (dm DeprecationMap) Validate(ms StabilityMap) error {
var errs error
for stability, cmps := range ms {
if stability != component.StabilityLevelDeprecated {
continue
}
for _, c := range cmps {
depInfo, found := dm[c]
if !found {
errs = errors.Join(errs, fmt.Errorf("deprecated component missing deprecation date and migration guide for %v", c))
continue
}
if depInfo.Migration == "" {
errs = errors.Join(errs, fmt.Errorf("deprecated component missing migration guide: %v", c))
}
if depInfo.Date == "" {
errs = errors.Join(errs, fmt.Errorf("deprecated component missing date in YYYY-MM-DD format: %v", c))
} else {
_, err := time.Parse("2006-01-02", depInfo.Date)
if err != nil {
errs = errors.Join(errs, fmt.Errorf("deprecated component missing valid date in YYYY-MM-DD format: %v", c))
}
}
}
}
return errs
}
================================================
FILE: cmd/mdatagen/internal/status_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestDistroURL(t *testing.T) {
tests := []struct {
input string
output string
}{
{
input: "core",
output: "https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol",
},
{
input: "contrib",
output: "https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib",
},
{
input: "k8s",
output: "https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-k8s",
},
{
input: "otlp",
output: "https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-otlp",
},
{
input: "not_found",
output: "",
},
}
for _, test := range tests {
t.Run(test.input, func(t *testing.T) {
assert.Equal(t, test.output, distroURL(test.input))
})
}
}
func TestSortedDistributions(t *testing.T) {
tests := []struct {
name string
s Status
result []string
}{
{
"all combined",
Status{Distributions: []string{"arm", "contrib", "core", "foo", "bar"}},
[]string{"core", "contrib", "arm", "bar", "foo"},
},
{
"core only",
Status{Distributions: []string{"core"}},
[]string{"core"},
},
{
"core and contrib only",
Status{Distributions: []string{"core", "contrib"}},
[]string{"core", "contrib"},
},
{
"core and contrib reversed",
Status{Distributions: []string{"contrib", "core"}},
[]string{"core", "contrib"},
},
{
"neither core nor contrib",
Status{Distributions: []string{"foo", "bar"}},
[]string{"bar", "foo"},
},
{
"no core, contrib, something else",
Status{Distributions: []string{"foo", "contrib", "bar"}},
[]string{"contrib", "bar", "foo"},
},
{
"core, no contrib, something else",
Status{Distributions: []string{"foo", "core", "bar"}},
[]string{"core", "bar", "foo"},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.result, test.s.SortedDistributions())
})
}
}
================================================
FILE: cmd/mdatagen/internal/telemetry.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/cmd/mdatagen/internal"
type Telemetry struct {
Metrics map[MetricName]Metric `mapstructure:"metrics"`
}
================================================
FILE: cmd/mdatagen/internal/templates/component_test.go.tmpl
================================================
// Code generated by mdatagen. DO NOT EDIT.
{{- if len .Status.UnsupportedPlatforms }}
//go:build {{ range $i, $v := .Status.UnsupportedPlatforms }}{{ if $i }} && {{ end }}!{{ . }}{{ end }}
{{- end }}
package {{ .Package }}
import (
{{- if not (and .Tests.SkipLifecycle .Tests.SkipShutdown) }}
"context"
{{- end }}
"testing"
{{- if and (not (and .Tests.SkipLifecycle .Tests.SkipShutdown)) (or isExporter isProcessor) }}
"time"
{{- end }}
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
{{- if not (and .Tests.SkipLifecycle .Tests.SkipShutdown) }}
"go.opentelemetry.io/collector/confmap/confmaptest"
{{- if isExporter }}
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exportertest"
{{- if supportsProfiles }}
"go.opentelemetry.io/collector/exporter/xexporter"
{{- end }}
{{- end }}
{{- if isProcessor }}
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/processortest"
{{- if supportsProfiles }}
"go.opentelemetry.io/collector/processor/xprocessor"
{{- end }}
{{- end }}
{{- if isReceiver }}
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/receivertest"
{{- if supportsProfiles }}
"go.opentelemetry.io/collector/receiver/xreceiver"
{{- end }}
{{- end }}
{{- if isScraper }}
"go.opentelemetry.io/collector/scraper"
"go.opentelemetry.io/collector/scraper/scrapertest"
{{- if supportsProfiles }}
"go.opentelemetry.io/collector/scraper/xscraper"
{{- end }}
{{- end }}
{{- if isExtension }}
"go.opentelemetry.io/collector/extension/extensiontest"
{{- end }}
{{- if isConnector }}
"go.opentelemetry.io/collector/connector"
"go.opentelemetry.io/collector/connector/connectortest"
{{- if supportsProfiles }}
"go.opentelemetry.io/collector/connector/xconnector"
{{- end }}
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/pipeline"
{{- if supportsProfiles }}
"go.opentelemetry.io/collector/pipeline/xpipeline"
{{- end }}
{{- end }}
{{- if or isExporter isProcessor }}
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
{{- end }}
{{- end }}
)
var typ = component.MustNewType("{{ .Type }}")
func TestComponentFactoryType(t *testing.T) {
require.Equal(t, typ, NewFactory().Type())
}
func TestComponentConfigStruct(t *testing.T) {
require.NoError(t, componenttest.CheckConfigStruct(NewFactory().CreateDefaultConfig()))
}
{{ if not (and .Tests.SkipLifecycle .Tests.SkipShutdown) -}}
{{ if isExporter -}}
func TestComponentLifecycle(t *testing.T) {
factory := NewFactory()
tests := []struct{
createFn func(ctx context.Context, set exporter.Settings, cfg component.Config) (component.Component, error)
name string
}{
{{ if supportsLogs }}
{
name: "logs",
createFn: func(ctx context.Context, set exporter.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateLogs(ctx, set, cfg)
},
},
{{ end }}
{{ if supportsMetrics }}
{
name: "metrics",
createFn: func(ctx context.Context, set exporter.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateMetrics(ctx, set, cfg)
},
},
{{ end }}
{{ if supportsTraces }}
{
name: "traces",
createFn: func(ctx context.Context, set exporter.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateTraces(ctx, set, cfg)
},
},
{{ end }}
}
cm, err := confmaptest.LoadConf("metadata.yaml")
require.NoError(t, err)
cfg := factory.CreateDefaultConfig()
sub, err := cm.Sub("tests::config")
require.NoError(t, err)
require.NoError(t, sub.Unmarshal(&cfg))
for _, tt := range tests {
{{- if not .Tests.SkipShutdown }}
t.Run(tt.name + "-shutdown", func(t *testing.T) {
c, err := tt.createFn(context.Background(), exportertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
err = c.Shutdown(context.Background())
require.NoError(t, err)
})
{{- end }}
{{- if not .Tests.SkipLifecycle }}
t.Run(tt.name + "-lifecycle", func(t *testing.T) {
c, err := tt.createFn(context.Background(), exportertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
host := {{ .Tests.Host }}
err = c.Start(context.Background(), host)
require.NoError(t, err)
require.NotPanics(t, func() {
switch tt.name {
case "logs":
e, ok := c.(exporter.Logs)
require.True(t, ok)
logs := generateLifecycleTestLogs()
if !e.Capabilities().MutatesData {
logs.MarkReadOnly()
}
err = e.ConsumeLogs(context.Background(), logs)
case "metrics":
e, ok := c.(exporter.Metrics)
require.True(t, ok)
metrics := generateLifecycleTestMetrics()
if !e.Capabilities().MutatesData {
metrics.MarkReadOnly()
}
err = e.ConsumeMetrics(context.Background(), metrics)
case "traces":
e, ok := c.(exporter.Traces)
require.True(t, ok)
traces := generateLifecycleTestTraces()
if !e.Capabilities().MutatesData {
traces.MarkReadOnly()
}
err = e.ConsumeTraces(context.Background(), traces)
}
})
{{ if not expectConsumerError }}
require.NoError(t, err)
{{ end }}
err = c.Shutdown(context.Background())
require.NoError(t, err)
})
{{- end }}
}
}
{{ end }}
{{ if isProcessor }}
func TestComponentLifecycle(t *testing.T) {
factory := NewFactory()
tests := []struct{
createFn func(ctx context.Context, set processor.Settings, cfg component.Config) (component.Component, error)
name string
}{
{{ if supportsLogs }}
{
name: "logs",
createFn: func(ctx context.Context, set processor.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateLogs(ctx, set, cfg, consumertest.NewNop())
},
},
{{ end }}
{{ if supportsMetrics }}
{
name: "metrics",
createFn: func(ctx context.Context, set processor.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateMetrics(ctx, set, cfg, consumertest.NewNop())
},
},
{{ end }}
{{ if supportsTraces }}
{
name: "traces",
createFn: func(ctx context.Context, set processor.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateTraces(ctx, set, cfg, consumertest.NewNop())
},
},
{{ end }}
{{ if supportsProfiles }}
{
name: "profiles",
createFn: func(ctx context.Context, set processor.Settings, cfg component.Config) (component.Component, error) {
return factory.(xprocessor.Factory).CreateProfiles(ctx, set, cfg, consumertest.NewNop())
},
},
{{ end }}
}
cm, err := confmaptest.LoadConf("metadata.yaml")
require.NoError(t, err)
cfg := factory.CreateDefaultConfig()
sub, err := cm.Sub("tests::config")
require.NoError(t, err)
require.NoError(t, sub.Unmarshal(&cfg))
for _, tt := range tests {
{{- if not .Tests.SkipShutdown }}
t.Run(tt.name + "-shutdown", func(t *testing.T) {
c, err := tt.createFn(context.Background(), processortest.NewNopSettings(typ), cfg)
require.NoError(t, err)
err = c.Shutdown(context.Background())
require.NoError(t, err)
})
{{- end }}
{{- if not .Tests.SkipLifecycle }}
t.Run(tt.name + "-lifecycle", func(t *testing.T) {
c, err := tt.createFn(context.Background(), processortest.NewNopSettings(typ), cfg)
require.NoError(t, err)
host := {{ .Tests.Host }}
err = c.Start(context.Background(), host)
require.NoError(t, err)
require.NotPanics(t, func() {
switch tt.name {
case "logs":
e, ok := c.(processor.Logs)
require.True(t, ok)
logs := generateLifecycleTestLogs()
if !e.Capabilities().MutatesData {
logs.MarkReadOnly()
}
err = e.ConsumeLogs(context.Background(), logs)
case "metrics":
e, ok := c.(processor.Metrics)
require.True(t, ok)
metrics := generateLifecycleTestMetrics()
if !e.Capabilities().MutatesData {
metrics.MarkReadOnly()
}
err = e.ConsumeMetrics(context.Background(), metrics)
case "traces":
e, ok := c.(processor.Traces)
require.True(t, ok)
traces := generateLifecycleTestTraces()
if !e.Capabilities().MutatesData {
traces.MarkReadOnly()
}
err = e.ConsumeTraces(context.Background(), traces)
}
})
require.NoError(t, err)
err = c.Shutdown(context.Background())
require.NoError(t, err)
})
{{- end }}
}
}
{{ end }}
{{ if isReceiver }}
func TestComponentLifecycle(t *testing.T) {
factory := NewFactory()
tests := []struct{
createFn func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error)
name string
}{
{{ if supportsLogs }}
{
name: "logs",
createFn: func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateLogs(ctx, set, cfg, consumertest.NewNop())
},
},
{{ end }}
{{ if supportsMetrics }}
{
name: "metrics",
createFn: func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateMetrics(ctx, set, cfg, consumertest.NewNop())
},
},
{{ end }}
{{ if supportsTraces }}
{
name: "traces",
createFn: func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateTraces(ctx, set, cfg, consumertest.NewNop())
},
},
{{ end }}
{{ if supportsProfiles }}
{
name: "profiles",
createFn: func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error) {
return factory.(xreceiver.Factory).CreateProfiles(ctx, set, cfg, consumertest.NewNop())
},
},
{{ end }}
}
cm, err := confmaptest.LoadConf("metadata.yaml")
require.NoError(t, err)
cfg := factory.CreateDefaultConfig()
sub, err := cm.Sub("tests::config")
require.NoError(t, err)
require.NoError(t, sub.Unmarshal(&cfg))
for _, tt := range tests {
{{- if not .Tests.SkipShutdown }}
t.Run(tt.name + "-shutdown", func(t *testing.T) {
c, err := tt.createFn(context.Background(), receivertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
err = c.Shutdown(context.Background())
require.NoError(t, err)
})
{{- end }}
{{- if not .Tests.SkipLifecycle }}
t.Run(tt.name + "-lifecycle", func(t *testing.T) {
firstRcvr, err := tt.createFn(context.Background(), receivertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
host := {{ .Tests.Host }}
require.NoError(t, err)
require.NoError(t, firstRcvr.Start(context.Background(), host))
require.NoError(t, firstRcvr.Shutdown(context.Background()))
secondRcvr, err := tt.createFn(context.Background(), receivertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
require.NoError(t, secondRcvr.Start(context.Background(), host))
require.NoError(t, secondRcvr.Shutdown(context.Background()))
})
{{- end }}
}
}
{{ end }}
{{ if isScraper }}
func TestComponentLifecycle(t *testing.T) {
factory := NewFactory()
tests := []struct{
createFn func(ctx context.Context, set scraper.Settings, cfg component.Config) (component.Component, error)
name string
}{
{{ if supportsLogs }}
{
name: "logs",
createFn: func(ctx context.Context, set scraper.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateLogs(ctx, set, cfg)
},
},
{{ end }}
{{ if supportsMetrics }}
{
name: "metrics",
createFn: func(ctx context.Context, set scraper.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateMetrics(ctx, set, cfg)
},
},
{{ end }}
{{ if supportsTraces }}
{
name: "traces",
createFn: func(ctx context.Context, set scraper.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateTraces(ctx, set, cfg)
},
},
{{ end }}
{{ if supportsProfiles }}
{
name: "profiles",
createFn: func(ctx context.Context, set scraper.Settings, cfg component.Config) (component.Component, error) {
return factory.(xscraper.Factory).CreateProfiles(ctx, set, cfg)
},
},
{{ end }}
}
cm, err := confmaptest.LoadConf("metadata.yaml")
require.NoError(t, err)
cfg := factory.CreateDefaultConfig()
sub, err := cm.Sub("tests::config")
require.NoError(t, err)
require.NoError(t, sub.Unmarshal(&cfg))
for _, tt := range tests {
{{- if not .Tests.SkipShutdown }}
t.Run(tt.name + "-shutdown", func(t *testing.T) {
c, err := tt.createFn(context.Background(), scrapertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
err = c.Shutdown(context.Background())
require.NoError(t, err)
})
{{- end }}
{{- if not .Tests.SkipLifecycle }}
t.Run(tt.name + "-lifecycle", func(t *testing.T) {
firstRcvr, err := tt.createFn(context.Background(), scrapertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
host := {{ .Tests.Host }}
require.NoError(t, err)
require.NoError(t, firstRcvr.Start(context.Background(), host))
require.NoError(t, firstRcvr.Shutdown(context.Background()))
secondRcvr, err := tt.createFn(context.Background(), scrapertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
require.NoError(t, secondRcvr.Start(context.Background(), host))
require.NoError(t, secondRcvr.Shutdown(context.Background()))
})
{{- end }}
}
}
{{ end }}
{{ if isExtension }}
func TestComponentLifecycle(t *testing.T) {
factory := NewFactory()
cm, err := confmaptest.LoadConf("metadata.yaml")
require.NoError(t, err)
cfg := factory.CreateDefaultConfig()
sub, err := cm.Sub("tests::config")
require.NoError(t, err)
require.NoError(t, sub.Unmarshal(&cfg))
{{- if not .Tests.SkipShutdown }}
t.Run("shutdown", func(t *testing.T) {
e, err := factory.Create(context.Background(), extensiontest.NewNopSettings(typ), cfg)
require.NoError(t, err)
err = e.Shutdown(context.Background())
require.NoError(t, err)
})
{{- end }}
{{- if not .Tests.SkipLifecycle }}
t.Run("lifecycle", func(t *testing.T) {
firstExt, err := factory.Create(context.Background(), extensiontest.NewNopSettings(typ), cfg)
require.NoError(t, err)
require.NoError(t, firstExt.Start(context.Background(), {{ .Tests.Host }}))
require.NoError(t, firstExt.Shutdown(context.Background()))
secondExt, err := factory.Create(context.Background(), extensiontest.NewNopSettings(typ), cfg)
require.NoError(t, err)
require.NoError(t, secondExt.Start(context.Background(), {{ .Tests.Host }}))
require.NoError(t, secondExt.Shutdown(context.Background()))
})
{{- end }}
}
{{ end }}
{{ if isConnector }}
func TestComponentLifecycle(t *testing.T) {
factory := NewFactory()
tests := []struct{
createFn func(ctx context.Context, set connector.Settings, cfg component.Config) (component.Component, error)
name string
}{
{{ if supportsLogsToLogs }}
{
name: "logs_to_logs",
createFn: func(ctx context.Context, set connector.Settings, cfg component.Config) (component.Component, error) {
router := connector.NewLogsRouter(map[pipeline.ID]consumer.Logs{pipeline.NewID(pipeline.SignalLogs): consumertest.NewNop()})
return factory.CreateLogsToLogs(ctx, set, cfg, router)
},
},
{{ end }}
{{ if supportsLogsToMetrics }}
{
name: "logs_to_metrics",
createFn: func(ctx context.Context, set connector.Settings, cfg component.Config) (component.Component, error) {
router := connector.NewMetricsRouter(map[pipeline.ID]consumer.Metrics{pipeline.NewID(pipeline.SignalMetrics): consumertest.NewNop()})
return factory.CreateLogsToMetrics(ctx, set, cfg, router)
},
},
{{ end }}
{{ if supportsLogsToTraces }}
{
name: "logs_to_traces",
createFn: func(ctx context.Context, set connector.Settings, cfg component.Config) (component.Component, error) {
router := connector.NewTracesRouter(map[pipeline.ID]consumer.Traces{pipeline.NewID(pipeline.SignalTraces): consumertest.NewNop()})
return factory.CreateLogsToTraces(ctx, set, cfg, router)
},
},
{{ end }}
{{ if supportsLogsToProfiles }}
{
name: "logs_to_profiles",
createFn: func(ctx context.Context, set connector.Settings, cfg component.Config) (component.Component, error) {
router := connector.NewTracesRouter(map[pipeline.ID]xconsumer.Profiles{pipeline.NewID(xpipeline.SignalProfiles): consumertest.NewNop()})
return factory.(xconnector.Factory).CreateLogsToProfiles(ctx, set, cfg, router)
},
},
{{ end }}
{{ if supportsMetricsToLogs }}
{
name: "metrics_to_logs",
createFn: func(ctx context.Context, set connector.Settings, cfg component.Config) (component.Component, error) {
router := connector.NewLogsRouter(map[pipeline.ID]consumer.Logs{pipeline.NewID(pipeline.SignalLogs): consumertest.NewNop()})
return factory.CreateMetricsToLogs(ctx, set, cfg, router)
},
},
{{ end }}
{{ if supportsMetricsToMetrics }}
{
name: "metrics_to_metrics",
createFn: func(ctx context.Context, set connector.Settings, cfg component.Config) (component.Component, error) {
router := connector.NewMetricsRouter(map[pipeline.ID]consumer.Metrics{pipeline.NewID(pipeline.SignalMetrics): consumertest.NewNop()})
return factory.CreateMetricsToMetrics(ctx, set, cfg, router)
},
},
{{ end }}
{{ if supportsMetricsToTraces }}
{
name: "metrics_to_traces",
createFn: func(ctx context.Context, set connector.Settings, cfg component.Config) (component.Component, error) {
router := connector.NewTracesRouter(map[pipeline.ID]consumer.Traces{pipeline.NewID(pipeline.SignalTraces): consumertest.NewNop()})
return factory.CreateMetricsToTraces(ctx, set, cfg, router)
},
},
{{ end }}
{{ if supportsMetricsToProfiles }}
{
name: "metrics_to_profiles",
createFn: func(ctx context.Context, set connector.Settings, cfg component.Config) (component.Component, error) {
router := connector.NewTracesRouter(map[pipeline.ID]xconsumer.Profiles{pipeline.NewID(xpipeline.SignalProfiles): consumertest.NewNop()})
return factory.(xconnector.Factory).CreateMetricsToTraces(ctx, set, cfg, router)
},
},
{{ end }}
{{ if supportsTracesToLogs }}
{
name: "traces_to_logs",
createFn: func(ctx context.Context, set connector.Settings, cfg component.Config) (component.Component, error) {
router := connector.NewLogsRouter(map[pipeline.ID]consumer.Logs{pipeline.NewID(pipeline.SignalLogs): consumertest.NewNop()})
return factory.CreateTracesToLogs(ctx, set, cfg, router)
},
},
{{ end }}
{{ if supportsTracesToMetrics }}
{
name: "traces_to_metrics",
createFn: func(ctx context.Context, set connector.Settings, cfg component.Config) (component.Component, error) {
router := connector.NewMetricsRouter(map[pipeline.ID]consumer.Metrics{pipeline.NewID(pipeline.SignalMetrics): consumertest.NewNop()})
return factory.CreateTracesToMetrics(ctx, set, cfg, router)
},
},
{{ end }}
{{ if supportsTracesToTraces }}
{
name: "traces_to_traces",
createFn: func(ctx context.Context, set connector.Settings, cfg component.Config) (component.Component, error) {
router := connector.NewTracesRouter(map[pipeline.ID]consumer.Traces{pipeline.NewID(pipeline.SignalTraces): consumertest.NewNop()})
return factory.CreateTracesToTraces(ctx, set, cfg, router)
},
},
{{ end }}
{{ if supportsTracesToProfiles }}
{
name: "traces_to_profiles",
createFn: func(ctx context.Context, set connector.Settings, cfg component.Config) (component.Component, error) {
router := connector.NewTracesRouter(map[pipeline.ID]xconsumer.Profiles{pipeline.NewID(xpipeline.SignalProfiles): consumertest.NewNop()})
return factory.(xconnector.Factory).CreateTracesToProfiles(ctx, set, cfg, router)
},
},
{{ end }}
{{ if supportsProfilesToLogs }}
{
name: "profiles_to_logs",
createFn: func(ctx context.Context, set connector.Settings, cfg component.Config) (component.Component, error) {
router := connector.NewLogsRouter(map[pipeline.ID]consumer.Logs{pipeline.NewID(pipeline.SignalLogs): consumertest.NewNop()})
return factory.(xconnector.Factory).CreateProfilesToLogs(ctx, set, cfg, router)
},
},
{{ end }}
{{ if supportsProfilesToMetrics }}
{
name: "profiles_to_metrics",
createFn: func(ctx context.Context, set connector.Settings, cfg component.Config) (component.Component, error) {
router := connector.NewMetricsRouter(map[pipeline.ID]consumer.Metrics{pipeline.NewID(pipeline.SignalMetrics): consumertest.NewNop()})
return factory.(xconnector.Factory).CreateProfilesToMetrics(ctx, set, cfg, router)
},
},
{{ end }}
{{ if supportsProfilesToTraces }}
{
name: "profiles_to_traces",
createFn: func(ctx context.Context, set connector.Settings, cfg component.Config) (component.Component, error) {
router := connector.NewTracesRouter(map[pipeline.ID]consumer.Traces{pipeline.NewID(pipeline.SignalTraces): consumertest.NewNop()})
return factory.(xconnector.Factory).CreateProfilesToTraces(ctx, set, cfg, router)
},
},
{{ end }}
{{ if supportsProfilesToProfiles }}
{
name: "profiles_to_profiles",
createFn: func(ctx context.Context, set connector.Settings, cfg component.Config) (component.Component, error) {
router := xconnector.NewProfilesRouter(map[pipeline.ID]xconsumer.Profiles{pipeline.NewID(xpipeline.SignalProfiles): consumertest.NewNop()})
return factory.(xconnector.Factory).CreateProfilesToProfiles(ctx, set, cfg, router)
},
},
{{ end }}
}
cm, err := confmaptest.LoadConf("metadata.yaml")
require.NoError(t, err)
cfg := factory.CreateDefaultConfig()
sub, err := cm.Sub("tests::config")
require.NoError(t, err)
require.NoError(t, sub.Unmarshal(&cfg))
for _, tt := range tests {
{{- if not .Tests.SkipShutdown }}
t.Run(tt.name + "-shutdown", func(t *testing.T) {
c, err := tt.createFn(context.Background(), connectortest.NewNopSettings(typ), cfg)
require.NoError(t, err)
err = c.Shutdown(context.Background())
require.NoError(t, err)
})
{{- end }}
{{- if not .Tests.SkipLifecycle }}
t.Run(tt.name + "-lifecycle", func(t *testing.T) {
firstConnector, err := tt.createFn(context.Background(), connectortest.NewNopSettings(typ), cfg)
require.NoError(t, err)
host := {{ .Tests.Host }}
require.NoError(t, err)
require.NoError(t, firstConnector.Start(context.Background(), host))
require.NoError(t, firstConnector.Shutdown(context.Background()))
secondConnector, err := tt.createFn(context.Background(), connectortest.NewNopSettings(typ), cfg)
require.NoError(t, err)
require.NoError(t, secondConnector.Start(context.Background(), host))
require.NoError(t, secondConnector.Shutdown(context.Background()))
})
{{- end }}
}
}
{{ end }}
{{ if or isExporter isProcessor -}}
func generateLifecycleTestLogs() plog.Logs {
logs := plog.NewLogs()
rl := logs.ResourceLogs().AppendEmpty()
rl.Resource().Attributes().PutStr("resource", "R1")
l := rl.ScopeLogs().AppendEmpty().LogRecords().AppendEmpty()
l.Body().SetStr("test log message")
l.SetTimestamp(pcommon.NewTimestampFromTime(time.Now()))
return logs
}
func generateLifecycleTestMetrics() pmetric.Metrics {
metrics := pmetric.NewMetrics()
rm := metrics.ResourceMetrics().AppendEmpty()
rm.Resource().Attributes().PutStr("resource", "R1")
m := rm.ScopeMetrics().AppendEmpty().Metrics().AppendEmpty()
m.SetName("test_metric")
dp := m.SetEmptyGauge().DataPoints().AppendEmpty()
dp.Attributes().PutStr("test_attr", "value_1")
dp.SetIntValue(123)
dp.SetTimestamp(pcommon.NewTimestampFromTime(time.Now()))
return metrics
}
func generateLifecycleTestTraces() ptrace.Traces {
traces := ptrace.NewTraces()
rs := traces.ResourceSpans().AppendEmpty()
rs.Resource().Attributes().PutStr("resource", "R1")
span := rs.ScopeSpans().AppendEmpty().Spans().AppendEmpty()
span.Attributes().PutStr("test_attr", "value_1")
span.SetName("test_span")
span.SetStartTimestamp(pcommon.NewTimestampFromTime(time.Now().Add(-1 * time.Second)))
span.SetEndTimestamp(pcommon.NewTimestampFromTime(time.Now()))
return traces
}
{{- end }}
{{- end }}
{{- if not .Tests.SkipLifecycle }}
var _ component.Host = (*mdatagenNopHost)(nil)
type mdatagenNopHost struct{}
func newMdatagenNopHost() component.Host {
return &mdatagenNopHost{}
}
func (mnh *mdatagenNopHost) GetExtensions() map[component.ID]component.Component {
return nil
}
func (mnh *mdatagenNopHost) GetFactory(_ component.Kind, _ component.Type) component.Factory {
return nil
}
{{- end }}
================================================
FILE: cmd/mdatagen/internal/templates/config.go.tmpl
================================================
// Code generated by mdatagen. DO NOT EDIT.
package {{ .Package }}
{{ $reag := .ReaggregationEnabled }}
{{- $hasReagMetrics := false }}
{{- range $name, $metric := .Metrics }}{{- if and $reag (hasAggregatableAttributes $metric.Attributes) }}{{- $hasReagMetrics = true }}{{- end }}{{- end }}
import (
{{- if $hasReagMetrics }}
"fmt"
"slices"
{{- end }}
"go.opentelemetry.io/collector/confmap"
{{ if and .Metrics .ResourceAttributes -}}
"go.opentelemetry.io/collector/filter"
{{- end }}
)
{{ if .Metrics -}}
{{- if not $reag }}
// MetricConfig provides common config for a particular metric.
type MetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
}
func (ms *MetricConfig) Unmarshal(parser *confmap.Conf) error {
{{- template "metricUnmarshal" "ms" }}
}
{{- else }}
{{- range $name, $metric := .Metrics }}
{{- if hasAggregatableAttributes $metric.Attributes }}
// {{ $name.Render }}MetricAttributeKey specifies the key of an attribute for the {{ $name }} metric.
type {{ $name.Render }}MetricAttributeKey string
const (
{{- range $metric.Attributes }}
{{ $name.Render }}MetricAttributeKey{{ .Render }} {{ $name.Render }}MetricAttributeKey = "{{ (attributeInfo .).Name }}"
{{- end }}
)
{{- end }}
// {{ $name.Render }}MetricConfig provides config for the {{ $name }} metric.
type {{ $name.Render }}MetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
{{- if hasAggregatableAttributes $metric.Attributes }}
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []{{ $name.Render }}MetricAttributeKey `mapstructure:"attributes"`
{{- end }}
}
func (ms *{{ $name.Render }}MetricConfig) Unmarshal(parser *confmap.Conf) error {
{{- template "metricUnmarshal" "ms" }}
}
{{ if hasAggregatableAttributes $metric.Attributes -}}
func (ms *{{ $name.Render }}MetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case {{ range $index, $element := $metric.Attributes -}}{{ if $index }}, {{ end }}{{ $name.Render }}MetricAttributeKey{{ $element.Render }}{{ end }}:
default:
return fmt.Errorf("metric {{ $name }} doesn't have an attribute %v, valid attributes: [{{ range $index, $element := $metric.Attributes -}}{{ if $index }}, {{ end }}{{ (attributeInfo $element).Name }}{{ end }}]", val)
}
}
{{- range $element := $metric.Attributes }}
{{- if (attributeInfo $element).IsRequired }}
if !slices.Contains(ms.EnabledAttributes, {{ $name.Render }}MetricAttributeKey{{ $element.Render }}) {
return fmt.Errorf("{{ (attributeInfo $element).Name }} is a required attribute for {{ $name }} metric and must be included")
}
{{- end }}
{{- end }}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
{{- end }}
{{- end }}
{{- end }}
// MetricsConfig provides config for {{ .Type }} metrics.
type MetricsConfig struct {
{{- range $name, $metric := .Metrics }}
{{ $name.Render }} {{ if $reag }}{{ $name.Render }}{{ end }}MetricConfig `mapstructure:"{{ $name }}"`
{{- end }}
}
func DefaultMetricsConfig() MetricsConfig {
return MetricsConfig{
{{- range $name, $metric := .Metrics }}
{{- $metricReag := and $reag (hasAggregatableAttributes $metric.Attributes) }}
{{ $name.Render }}: {{ if $reag }}{{ $name.Render }}{{ end }}MetricConfig{
Enabled: {{ $metric.Enabled }},
{{- if $metricReag }}
AggregationStrategy: AggregationStrategy{{ if eq $metric.Data.Type "Sum" }}Sum{{ else }}Avg{{ end }},
EnabledAttributes: []{{ $name.Render }}MetricAttributeKey{ {{- range $element := $metric.Attributes -}}{{- if (attributeInfo $element).IsNotOptIn }}{{ $name.Render }}MetricAttributeKey{{ $element.Render }}, {{ end }}{{- end -}} },
{{- end }}
},
{{- end }}
}
}
{{- end }}
{{ if .Events }}
// EventConfig provides common config for a particular event.
type EventConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
}
func (ec *EventConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ec)
if err != nil {
return err
}
ec.enabledSetByUser = parser.IsSet("enabled")
return nil
}
// EventsConfig provides config for {{ .Type }} events.
type EventsConfig struct {
{{- range $name, $event := .Events }}
{{ $name.Render }} EventConfig `mapstructure:"{{ $name }}"`
{{- end }}
}
func DefaultEventsConfig() EventsConfig {
return EventsConfig{
{{- range $name, $event := .Events }}
{{ $name.Render }}: EventConfig{
Enabled: {{ $event.Enabled }},
},
{{- end }}
}
}
{{- end }}
{{ if .ResourceAttributes -}}
// ResourceAttributeConfig provides common config for a particular resource attribute.
type ResourceAttributeConfig struct {
Enabled bool `mapstructure:"enabled"`
{{- if .Metrics }}
// Experimental: MetricsInclude defines a list of filters for attribute values.
// If the list is not empty, only metrics with matching resource attribute values will be emitted.
MetricsInclude []filter.Config `mapstructure:"metrics_include"`
// Experimental: MetricsExclude defines a list of filters for attribute values.
// If the list is not empty, metrics with matching resource attribute values will not be emitted.
// MetricsInclude has higher priority than MetricsExclude.
MetricsExclude []filter.Config `mapstructure:"metrics_exclude"`
{{- end }}
{{- if .Events }}
// Experimental: EventsInclude defines a list of filters for attribute values.
// If the list is not empty, only events with matching resource attribute values will be emitted.
EventsInclude []filter.Config `mapstructure:"events_include"`
// Experimental: EventsExclude defines a list of filters for attribute values.
// If the list is not empty, events with matching resource attribute values will not be emitted.
// EventsInclude has higher priority than EventsExclude.
EventsExclude []filter.Config `mapstructure:"events_exclude"`
{{- end }}
enabledSetByUser bool
}
func (rac *ResourceAttributeConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(rac)
if err != nil {
return err
}
rac.enabledSetByUser = parser.IsSet("enabled")
return nil
}
// ResourceAttributesConfig provides config for {{ .Type }} resource attributes.
type ResourceAttributesConfig struct {
{{- range $name, $attr := .ResourceAttributes }}
{{ $name.Render }} ResourceAttributeConfig `mapstructure:"{{ $name }}"`
{{- end }}
}
func DefaultResourceAttributesConfig() ResourceAttributesConfig {
return ResourceAttributesConfig{
{{- range $name, $attr := .ResourceAttributes }}
{{ $name.Render }}: ResourceAttributeConfig {
Enabled: {{ $attr.Enabled }},
},
{{- end }}
}
}
{{- end }}
{{ if .Metrics -}}
// MetricsBuilderConfig is a configuration for {{ .Type }} metrics builder.
type MetricsBuilderConfig struct {
Metrics MetricsConfig `mapstructure:"metrics"`
{{- if .ResourceAttributes }}
ResourceAttributes ResourceAttributesConfig `mapstructure:"resource_attributes"`
{{- end }}
}
func DefaultMetricsBuilderConfig() MetricsBuilderConfig {
return MetricsBuilderConfig {
Metrics: DefaultMetricsConfig(),
{{- if .ResourceAttributes }}
ResourceAttributes: DefaultResourceAttributesConfig(),
{{- end }}
}
}
{{- end }}
{{ if .Events -}}
// LogsBuilderConfig is a configuration for {{ .Type }} logs builder.
type LogsBuilderConfig struct {
Events EventsConfig `mapstructure:"events"`
{{- if .ResourceAttributes }}
ResourceAttributes ResourceAttributesConfig `mapstructure:"resource_attributes"`
{{- end }}
}
func DefaultLogsBuilderConfig() LogsBuilderConfig {
return LogsBuilderConfig {
Events: DefaultEventsConfig(),
{{- if .ResourceAttributes }}
ResourceAttributes: DefaultResourceAttributesConfig(),
{{- end }}
}
}
{{- end }}
================================================
FILE: cmd/mdatagen/internal/templates/config.schema.yaml.tmpl
================================================
# Code generated by mdatagen. DO NOT EDIT.
{{- $reag := .ReaggregationEnabled }}
$defs:
{{- if .Metrics }}
metrics_config:
description: MetricsConfig provides config for {{ .Type }} metrics.
type: object
properties:
{{- range $name, $metric := .Metrics }}
{{- $metricReag := and $reag (hasAggregatableAttributes $metric.Attributes) }}
{{ $name }}:
description: "{{ $name.Render }}MetricConfig provides config for the {{ $name }} metric."
type: object
properties:
enabled:
type: boolean
default: {{ $metric.Enabled }}
{{- if $metricReag }}
aggregation_strategy:
type: string
enum:
- "sum"
- "avg"
- "min"
- "max"
{{- if eq $metric.Data.Type "Sum" }}
default: "sum"
{{- else }}
default: "avg"
{{- end }}
attributes:
type: array
items:
type: string
enum:
{{- range $metric.Attributes }}
- "{{ (attributeInfo .).Name }}"
{{- end }}
default:
{{- range $metric.Attributes }}
{{- if (attributeInfo .).IsNotOptIn }}
- "{{ (attributeInfo .).Name }}"
{{- end }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
{{- if .Events }}
events_config:
description: EventsConfig provides config for {{ .Type }} events.
type: object
properties:
{{- range $name, $event := .Events }}
{{ $name }}:
description: EventConfig provides common config for a {{ $name }} event.
type: object
properties:
enabled:
type: boolean
default: {{ $event.Enabled }}
{{- end }}
{{- end }}
{{- if .ResourceAttributes }}
resource_attributes_config:
description: ResourceAttributesConfig provides config for {{ .Type }} resource attributes.
type: object
properties:
{{- range $name, $attr := .ResourceAttributes }}
{{ $name }}:
description: ResourceAttributeConfig provides common config for a {{ $name }} resource attribute.
type: object
properties:
enabled:
type: boolean
default: {{ $attr.Enabled }}
{{- if $.Metrics }}
metrics_include:
description: "Experimental: MetricsInclude defines a list of filters for attribute values. If the list is not empty, only metrics with matching resource attribute values will be emitted."
type: array
items:
$ref: {{ schemaRef "go.opentelemetry.io/collector/filter.config" }}
metrics_exclude:
description: "Experimental: MetricsExclude defines a list of filters for attribute values. If the list is not empty, metrics with matching resource attribute values will not be emitted. MetricsInclude has higher priority than MetricsExclude."
type: array
items:
$ref: {{ schemaRef "go.opentelemetry.io/collector/filter.config" }}
{{- end }}
{{- if $.Events }}
events_include:
description: "Experimental: EventsInclude defines a list of filters for attribute values. If the list is not empty, only events with matching resource attribute values will be emitted."
type: array
items:
$ref: {{ schemaRef "go.opentelemetry.io/collector/filter.config" }}
events_exclude:
description: "Experimental: EventsExclude defines a list of filters for attribute values. If the list is not empty, events with matching resource attribute values will not be emitted. EventsInclude has higher priority than EventsExclude."
type: array
items:
$ref: {{ schemaRef "go.opentelemetry.io/collector/filter.config" }}
{{- end }}
{{- end }}
{{- end }}
{{- if .Metrics }}
metrics_builder_config:
description: MetricsBuilderConfig is a configuration for {{ .Type }} metrics builder.
type: object
properties:
metrics:
$ref: metrics_config
{{- if .ResourceAttributes }}
resource_attributes:
$ref: resource_attributes_config
{{- end }}
{{- end }}
{{- if .Events }}
logs_builder_config:
description: LogsBuilderConfig is a configuration for {{ .Type }} logs builder.
type: object
properties:
events:
$ref: events_config
{{- if .ResourceAttributes }}
resource_attributes:
$ref: resource_attributes_config
{{- end }}
{{- end }}
================================================
FILE: cmd/mdatagen/internal/templates/config_from_cfggen.go.tmpl
================================================
// Code generated by mdatagen. DO NOT EDIT.
package {{ .Package }}
{{ $imports := extractImports .Metadata.Config }}
{{- if $imports }}
import (
{{- range $imports }}
"{{ . }}"
{{- end }}
)
{{- end }}
{{ define "struct" }}
{{- if .AllOf }}
{{- range .AllOf }}
{{- if .Description }}
// {{ .Description }}
{{- end }}
{{ .Ref | publicType }} `mapstructure:",squash"`
{{- end }}
{{- end }}
{{- range $propName, $prop := .Properties }}
{{- if $prop.Description }}
// {{ $prop.Description }}
{{- end }}
{{ $propName | publicVar }} {{ mapGoType $prop $propName }} `mapstructure:"{{ $propName }}"`
{{- end }}
{{ end }}
{{- $defs := extractDefs .Metadata.Config }}
{{ range $defName, $def := $defs }}
{{- if $def.Description }}
// {{ $defName | publicVar }} {{ $def.Description }}
{{- end }}
type {{ $defName | publicVar }} struct {
{{ template "struct" $def }}
}
{{ end }}
{{- if .Metadata.Config.Description }}
// {{ .Metadata.Config.Description }}
{{- else }}
// Config defines the configuration for {{ .Metadata.DisplayName }} component.
{{- end }}
type Config struct {
{{ template "struct" .Metadata.Config }}
}
================================================
FILE: cmd/mdatagen/internal/templates/config_test.go.tmpl
================================================
// Code generated by mdatagen. DO NOT EDIT.
package {{ .Package }}
import (
"path/filepath"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/confmaptest"
)
{{ $reag := .ReaggregationEnabled }}
{{ if .Metrics }}
func TestMetricsBuilderConfig(t *testing.T) {
tests := []struct {
name string
want MetricsBuilderConfig
}{
{
name: "default",
want: DefaultMetricsBuilderConfig(),
},
{
name: "all_set",
want: MetricsBuilderConfig{
Metrics: MetricsConfig{
{{- range $name, $metric := .Metrics }}
{{- $metricReag := and $reag (hasAggregatableAttributes $metric.Attributes) }}
{{ $name.Render }}: {{ if $reag }}{{ $name.Render }}{{ end }}MetricConfig{
Enabled: true,
{{- if $metricReag }}
AggregationStrategy: AggregationStrategy{{ if eq $metric.Data.Type "Sum" }}Sum{{ else }}Avg{{ end }},
EnabledAttributes: []{{ $name.Render }}MetricAttributeKey{ {{- range $index, $element := $metric.Attributes -}}{{ if $index }}, {{ end }}{{ $name.Render }}MetricAttributeKey{{ $element.Render }}{{ end -}} },
{{- end }}
},
{{- end }}
},
{{- if .ResourceAttributes }}
ResourceAttributes: ResourceAttributesConfig{
{{- range $name, $_ := .ResourceAttributes }}
{{ $name.Render }}: ResourceAttributeConfig{Enabled: true},
{{- end }}
},
{{- end }}
},
},
{
name: "none_set",
want: MetricsBuilderConfig{
Metrics: MetricsConfig{
{{- range $name, $metric := .Metrics }}
{{- $metricReag := and $reag (hasAggregatableAttributes $metric.Attributes) }}
{{ $name.Render }}: {{ if $reag }}{{ $name.Render }}{{ end }}MetricConfig{
Enabled: false,
{{- if $metricReag }}
AggregationStrategy: AggregationStrategy{{ if eq $metric.Data.Type "Sum" }}Sum{{ else }}Avg{{ end }},
EnabledAttributes: []{{ $name.Render }}MetricAttributeKey{ {{- range $index, $element := $metric.Attributes -}}{{ if $index }}, {{ end }}{{ $name.Render }}MetricAttributeKey{{ $element.Render }}{{ end -}} },
{{- end }}
},
{{- end }}
},
{{- if .ResourceAttributes }}
ResourceAttributes: ResourceAttributesConfig{
{{- range $name, $_ := .ResourceAttributes }}
{{ $name.Render }}: ResourceAttributeConfig{Enabled: false},
{{- end }}
},
{{- end }}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := loadMetricsBuilderConfig(t, tt.name)
diff := cmp.Diff(tt.want, cfg, cmpopts.IgnoreUnexported(
{{- if $reag }}
{{- range $name, $metric := .Metrics }}{{ $name.Render }}MetricConfig{}, {{ end }}
{{- else }}MetricConfig{},{{ end }}
{{- if .ResourceAttributes }}ResourceAttributeConfig{},{{ end }}
))
require.Emptyf(t, diff, "Config mismatch (-expected +actual):\n%s", diff)
})
}
}
func loadMetricsBuilderConfig(t *testing.T, name string) MetricsBuilderConfig {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
require.NoError(t, err)
sub, err := cm.Sub(name)
require.NoError(t, err)
cfg := DefaultMetricsBuilderConfig()
require.NoError(t, sub.Unmarshal(&cfg, confmap.WithIgnoreUnused()))
return cfg
}
{{- end }}
{{ if .Events }}
func loadLogsBuilderConfig(t *testing.T, name string) LogsBuilderConfig {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
require.NoError(t, err)
sub, err := cm.Sub(name)
require.NoError(t, err)
cfg := DefaultLogsBuilderConfig()
require.NoError(t, sub.Unmarshal(&cfg, confmap.WithIgnoreUnused()))
return cfg
}
{{- end }}
{{ if .ResourceAttributes -}}
func TestResourceAttributesConfig(t *testing.T) {
tests := []struct {
name string
want ResourceAttributesConfig
}{
{
name: "default",
want: DefaultResourceAttributesConfig(),
},
{
name: "all_set",
want: ResourceAttributesConfig{
{{- range $name, $_ := .ResourceAttributes }}
{{ $name.Render }}: ResourceAttributeConfig{Enabled: true},
{{- end }}
},
},
{
name: "none_set",
want: ResourceAttributesConfig{
{{- range $name, $_ := .ResourceAttributes }}
{{ $name.Render }}: ResourceAttributeConfig{Enabled: false},
{{- end }}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := loadResourceAttributesConfig(t, tt.name)
diff := cmp.Diff(tt.want, cfg, cmpopts.IgnoreUnexported(ResourceAttributeConfig{}))
require.Emptyf(t, diff, "Config mismatch (-expected +actual):\n%s", diff)
})
}
}
func loadResourceAttributesConfig(t *testing.T, name string) ResourceAttributesConfig {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
require.NoError(t, err)
sub, err := cm.Sub(name)
require.NoError(t, err)
sub, err = sub.Sub("resource_attributes")
require.NoError(t, err)
cfg := DefaultResourceAttributesConfig()
require.NoError(t, sub.Unmarshal(&cfg))
return cfg
}
{{- end }}
================================================
FILE: cmd/mdatagen/internal/templates/documentation.md.tmpl
================================================
{{- define "metric-documentation" -}}
{{- $metricName := . }}
{{- $metric := $metricName | metricInfo -}}
### {{ $metricName }}
{{ $metric.Description }}
{{- if $metric.ExtendedDocumentation }}
{{ $metric.ExtendedDocumentation }}
{{- end }}
| Unit | Metric Type | Value Type |{{ if $metric.Data.HasAggregated }} Aggregation Temporality |{{ end }}{{ if $metric.Data.HasMonotonic }} Monotonic |{{ end }}{{ if $metric.Stability }} Stability |{{ end }}{{ if $metric.SemanticConvention }} Semantic Convention |{{ end }}
| ---- | ----------- | ---------- |{{ if $metric.Data.HasAggregated }} ----------------------- |{{ end }}{{ if $metric.Data.HasMonotonic }} --------- |{{ end }}{{ if $metric.Stability }} --------- |{{ end }}{{ if $metric.SemanticConvention }} ------------------- |{{ end }}
| {{ $metric.Unit }} | {{ $metric.Data.Type }} | {{ $metric.Data.MetricValueType }} |
{{- if $metric.Data.HasAggregated }} {{ $metric.Data.AggregationTemporality }} |{{ end }}
{{- if $metric.Data.HasMonotonic }} {{ $metric.Data.Monotonic }} |{{ end }}
{{- if $metric.Stability }} {{ $metric.Stability }}{{ if $metric.Deprecated }} since {{ $metric.Deprecated.Since }}{{ end }} |{{ end }}
{{- if $metric.SemanticConvention }} [{{ $metricName }}]({{ $metric.SemanticConvention.SemanticConventionRef }}) |{{ end }}
{{- if $metric.Deprecated }}{{ if $metric.Deprecated.Note }}
**Deprecation note**: {{ $metric.Deprecated.Note }}
{{- end }}{{ end }}
{{- if $metric.Attributes }}
#### Attributes
| Name | Description | Values | Requirement Level |
| ---- | ----------- | ------ | -------- |
{{- range $metric.Attributes }}
{{- $attribute := . | attributeInfo }}
| {{ $attribute.Name }} | {{ $attribute.Description }} |
{{- if $attribute.Enum }} {{ $attribute.Type }}: ``{{ stringsJoin $attribute.Enum "``, ``" }}``{{ else }} Any {{ $attribute.Type }}{{ end }} | {{ $attribute.RequirementLevel }} |
{{- end }}
{{- end }}
{{- end -}}
{{- define "event-documentation" -}}
{{- $eventName := . }}
{{- $event := $eventName | eventInfo -}}
### {{ $eventName }}
{{ $event.Description }}
{{- if $event.ExtendedDocumentation }}
{{ $event.ExtendedDocumentation }}
{{- end }}
{{- if $event.Attributes }}
#### Attributes
| Name | Description | Values |
| ---- | ----------- | ------ |
{{- range $event.Attributes }}
{{- $attribute := . | attributeInfo }}
| {{ $attribute.Name }} | {{ $attribute.Description }} |
{{- if $attribute.Enum }} {{ $attribute.Type }}: ``{{ stringsJoin $attribute.Enum "``, ``" }}``{{ else }} Any {{ $attribute.Type }}{{ end }} |
{{- end }}
{{- end }}
{{- end -}}
{{- define "telemetry-documentation" -}}
{{- $metricName := . }}
{{- $metric := $metricName | telemetryInfo -}}
### {{ if $metric.Prefix -}}{{ $metric.Prefix }}{{- else -}}otelcol_{{- end -}}{{ $metricName }}
{{ $metric.Description }}
{{- if $metric.Deprecated }}
> **Deprecated since {{ $metric.Deprecated.Since}}**
{{- if $metric.Deprecated.Note }}
> {{ $metric.Deprecated.Note }}
{{- end }}
{{- end }}
{{- if $metric.ExtendedDocumentation }}
{{ $metric.ExtendedDocumentation }}
{{- end }}
| Unit | Metric Type | Value Type |{{ if $metric.Data.HasMonotonic }} Monotonic |{{ end }}{{ if $metric.Stability }} Stability |{{ end }}{{ if $metric.SemanticConvention }} Semantic Convention |{{ end }}
| ---- | ----------- | ---------- |{{ if $metric.Data.HasMonotonic }} --------- |{{ end }}{{ if $metric.Stability }} --------- |{{ end }}{{ if $metric.SemanticConvention }} ------------------- |{{ end }}
| {{ $metric.Unit }} | {{ $metric.Data.Type }} | {{ $metric.Data.MetricValueType }} |
{{- if $metric.Data.HasMonotonic }} {{ $metric.Data.Monotonic }} |{{ end }}
{{- if $metric.Stability }} {{ $metric.Stability }}{{ if $metric.Deprecated }} since {{ $metric.Deprecated.Since }}{{ end }} |{{ end }}
{{- if $metric.SemanticConvention }} [{{ $metricName }}]({{ $metric.SemanticConvention.SemanticConventionRef }}) |{{ end }}
{{- if $metric.Deprecated }}{{ if $metric.Deprecated.Note }}
**Deprecation note**: {{ $metric.Deprecated.Note }}
{{- end }}{{ end }}
{{- if $metric.Attributes }}
#### Attributes
| Name | Description | Values |
| ---- | ----------- | ------ |
{{- range $metric.Attributes }}
{{- $attribute := . | attributeInfo }}
| {{ $attribute.Name }} | {{ $attribute.Description }} |
{{- if $attribute.Enum }} {{ $attribute.Type }}: ``{{ stringsJoin $attribute.Enum "``, ``" }}``{{ else }} Any {{ $attribute.Type }}{{ end }} |
{{- end }}
{{- end }}
{{- end -}}
[comment]: <> (Code generated by mdatagen. DO NOT EDIT.)
# {{ .Type }}
{{- if .Parent }}
**Parent Component:** {{ .Parent }}
{{- end }}
{{- if .Metrics }}
## Default Metrics
The following metrics are emitted by default. Each of them can be disabled by applying the following configuration:
```yaml
metrics:
:
enabled: false
```
{{- end }}
{{- range $metricName, $metric := .Metrics }}
{{- if $metric.Enabled }}
{{ template "metric-documentation" $metricName }}
{{- end }}
{{- end }}
{{- $optionalMetricSeen := false }}
{{- range $metricName, $metric := .Metrics }}
{{- if not $metric.Enabled }}
{{- if not $optionalMetricSeen }}
## Optional Metrics
The following metrics are not emitted by default. Each of them can be enabled by applying the following configuration:
```yaml
metrics:
:
enabled: true
```
{{- end }}
{{- $optionalMetricSeen = true }}
{{ template "metric-documentation" $metricName }}
{{- end }}
{{- end }}
{{- if .Events }}
## Default Events
The following events are emitted by default. Each of them can be disabled by applying the following configuration:
```yaml
events:
:
enabled: false
```
{{- range $eventName, $event := .Events }}
{{- if $event.Enabled }}
{{ template "event-documentation" $eventName }}
{{- end }}
{{- end }}
{{- end }}
{{- $optionalEventSeen := false }}
{{- range $eventName, $event := .Events }}
{{- if not $event.Enabled }}
{{- if not $optionalEventSeen }}
## Optional Events
The following events are not emitted by default. Each of them can be enabled by applying the following configuration:
```yaml
events:
:
enabled: true
```
{{- end }}
{{- $optionalEventSeen = true }}
{{ template "event-documentation" $eventName }}
{{- end }}
{{- end }}
{{- if .ResourceAttributes }}
## Resource Attributes
| Name | Description | Values | Enabled |
| ---- | ----------- | ------ | ------- |
{{- range $attributeName, $attribute := .ResourceAttributes }}
| {{ $attributeName }} | {{ $attribute.Description }} |
{{- if $attribute.Enum }} {{ $attribute.Type }}: ``{{ stringsJoin $attribute.Enum "``, ``" }}``{{ else }} Any {{ $attribute.Type }}{{ end }} | {{ $attribute.Enabled }} |
{{- end }}
{{- end }}
{{- if .Entities }}
## Entities
The following entities are defined for this component:
{{- range $entity := .Entities }}
### {{ $entity.Type }}
{{ $entity.Brief }}
{{- if $entity.Stability }}
**Stability:** {{ $entity.Stability }}
{{- end }}
{{- if $entity.Identity }}
**Identifying Attributes:**
{{- range $entity.Identity }}
- `{{ .Ref }}`
{{- end }}
{{- end }}
{{- if $entity.Description }}
**Descriptive Attributes:**
{{- range $entity.Description }}
- `{{ .Ref }}`
{{- end }}
{{- end }}
{{- end }}
{{- end }}
{{- if .Telemetry.Metrics }}
## Internal Telemetry
The following telemetry is emitted by this component.
{{- range $metricName, $metric := .Telemetry.Metrics }}
{{- if $metric.Enabled }}
{{ template "telemetry-documentation" $metricName }}
{{- end }}
{{- end }}
{{- end }}
{{- if .FeatureGates }}
## Feature Gates
This component has the following feature gates:
| Feature Gate | Stage | Description | From Version | To Version | Reference |
| ------------ | ----- | ----------- | ------------ | ---------- | --------- |
{{- range .FeatureGates }}
| `{{ .ID }}` | {{ .Stage }} | {{ .Description }} | {{ if .FromVersion }}{{ .FromVersion }}{{ else }}N/A{{ end }} | {{ if .ToVersion }}{{ .ToVersion }}{{ else }}N/A{{ end }} | {{ if .ReferenceURL }}[Link]({{ .ReferenceURL }}){{ else }}N/A{{ end }} |
{{- end }}
For more information about feature gates, see the [Feature Gates](https://github.com/open-telemetry/opentelemetry-collector/blob/main/featuregate/README.md) documentation.
{{- end }}
================================================
FILE: cmd/mdatagen/internal/templates/entity_metrics.go.tmpl
================================================
// Code generated by mdatagen. DO NOT EDIT.
package {{ .Package }}
import (
{{- if .Metrics | parseImportsRequired }}
"fmt"
"strconv"
{{- end }}
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/xpdata/entity"
)
{{ $resourceAttributes := .ResourceAttributes }}
{{ $metrics := .Metrics }}
{{ range $ent := .Entities }}
{{ $entityType := $ent.Type }}
{{ $entityStructType := printf "%sEntity" (publicVar $entityType) }}
// {{ $entityStructType }} represents a {{ $entityType }} entity.
// Create one with New{{ $entityStructType }} and pass it to EmitForEntity.
type {{ $entityStructType }} struct {
{{- range $idAttr := $ent.Identity }}
{{- $attr := index $resourceAttributes $idAttr.Ref }}
{{ $idAttr.Ref.RenderUnexported }} {{ $attr.Type.Primitive }}
{{- end }}
{{- range $descAttr := $ent.Description }}
{{- $attr := index $resourceAttributes $descAttr.Ref }}
{{ $descAttr.Ref.RenderUnexported }} {{ $attr.Type.Primitive }}
{{- end }}
{{- range $extraAttr := $ent.ExtraAttributes }}
{{- $attr := index $resourceAttributes $extraAttr.Ref }}
{{ $extraAttr.Ref.RenderUnexported }} {{ $attr.Type.Primitive }}
{{- end }}
{{- range $rel := $ent.Relationships }}
{{ toLowerCamelCase $rel.Type }}{{ publicVar $rel.Target }} *{{ publicVar $rel.Target }}Entity
{{- end }}
}
// New{{ $entityStructType }} creates a new {{ $entityStructType }}.
// Identity attributes are required and must be provided at construction time.
func New{{ $entityStructType }}(
{{- range $i, $idAttr := $ent.Identity -}}
{{- if $i }}, {{ end -}}
{{- $attr := index $resourceAttributes $idAttr.Ref -}}
{{ $idAttr.Ref.RenderUnexported }} {{ $attr.Type.Primitive }}
{{- end -}}
) *{{ $entityStructType }} {
return &{{ $entityStructType }}{
{{- range $idAttr := $ent.Identity }}
{{ $idAttr.Ref.RenderUnexported }}: {{ $idAttr.Ref.RenderUnexported }},
{{- end }}
}
}
{{- if $ent.Description }}
// Description attribute setters for {{ $entityType }}.
{{- range $descAttr := $ent.Description }}
{{- $attr := index $resourceAttributes $descAttr.Ref }}
// Set{{ publicVar $descAttr.Ref.Render }} sets the {{ $descAttr.Ref }} description attribute.
func (e *{{ $entityStructType }}) Set{{ publicVar $descAttr.Ref.Render }}(val {{ $attr.Type.Primitive }}) {
e.{{ $descAttr.Ref.RenderUnexported }} = val
}
{{- end }}
{{- end }}
{{- if $ent.ExtraAttributes }}
// Extra attribute setters for {{ $entityType }}.
// These attributes are contextually relevant but are not part of the entity's identity or description.
{{- range $extraAttr := $ent.ExtraAttributes }}
{{- $attr := index $resourceAttributes $extraAttr.Ref }}
// Set{{ publicVar $extraAttr.Ref.Render }} sets the {{ $extraAttr.Ref }} extra attribute on the resource.
func (e *{{ $entityStructType }}) Set{{ publicVar $extraAttr.Ref.Render }}(val {{ $attr.Type.Primitive }}) {
e.{{ $extraAttr.Ref.RenderUnexported }} = val
}
{{- end }}
{{- end }}
{{- if $ent.Relationships }}
// Relationship setters for {{ $entityType }}.
{{- range $rel := $ent.Relationships }}
{{ $relMethod := printf "Set%s%s" (publicVar $rel.Type) (publicVar $rel.Target) }}
// {{ $relMethod }} sets the {{ $rel.Type }} relationship to a {{ $rel.Target }} entity.
// The related entity will be emitted alongside this entity's metrics.
func (e *{{ $entityStructType }}) {{ $relMethod }}(target *{{ publicVar $rel.Target }}Entity) {
e.{{ toLowerCamelCase $rel.Type }}{{ publicVar $rel.Target }} = target
}
{{- end }}
{{- end }}
// copyToResource populates res with the entity's attributes according to cfg.
// If all identity attributes are enabled, an entity ref is produced; otherwise
// the enabled attributes are written directly as plain resource attributes.
func (e *{{ $entityStructType }}) copyToResource(cfg ResourceAttributesConfig, res pcommon.Resource) {
if {{ range $i, $idAttr := $ent.Identity }}{{ if $i }} && {{ end }}cfg.{{ publicVar $idAttr.Ref.Render }}.Enabled{{ end }} {
ent := entity.ResourceEntities(res).PutEmpty("{{ $entityType }}")
{{- range $idAttr := $ent.Identity }}
{{- $attr := index $resourceAttributes $idAttr.Ref }}
{{- if eq $attr.Type.ValueType.String "Str" }}
ent.IdentifyingAttributes().PutStr("{{ $idAttr.Ref }}", e.{{ $idAttr.Ref.RenderUnexported }})
{{- else if or (eq $attr.Type.ValueType.String "Map") (eq $attr.Type.ValueType.String "Slice") (eq $attr.Type.ValueType.String "Bytes") }}
ent.IdentifyingAttributes().PutEmpty("{{ $idAttr.Ref }}").SetEmpty{{ $attr.Type.ValueType }}().FromRaw(e.{{ $idAttr.Ref.RenderUnexported }})
{{- else }}
ent.IdentifyingAttributes().PutEmpty("{{ $idAttr.Ref }}").Set{{ $attr.Type.ValueType }}(e.{{ $idAttr.Ref.RenderUnexported }})
{{- end }}
{{- end }}
{{- range $descAttr := $ent.Description }}
{{- $attr := index $resourceAttributes $descAttr.Ref }}
if cfg.{{ publicVar $descAttr.Ref.Render }}.Enabled {
{{- if eq $attr.Type.ValueType.String "Str" }}
ent.DescriptiveAttributes().PutStr("{{ $descAttr.Ref }}", e.{{ $descAttr.Ref.RenderUnexported }})
{{- else if or (eq $attr.Type.ValueType.String "Map") (eq $attr.Type.ValueType.String "Slice") (eq $attr.Type.ValueType.String "Bytes") }}
ent.DescriptiveAttributes().PutEmpty("{{ $descAttr.Ref }}").SetEmpty{{ $attr.Type.ValueType }}().FromRaw(e.{{ $descAttr.Ref.RenderUnexported }})
{{- else }}
ent.DescriptiveAttributes().PutEmpty("{{ $descAttr.Ref }}").Set{{ $attr.Type.ValueType }}(e.{{ $descAttr.Ref.RenderUnexported }})
{{- end }}
}
{{- end }}
{{- range $extraAttr := $ent.ExtraAttributes }}
{{- $attr := index $resourceAttributes $extraAttr.Ref }}
if cfg.{{ publicVar $extraAttr.Ref.Render }}.Enabled {
{{- if eq $attr.Type.ValueType.String "Str" }}
res.Attributes().PutStr("{{ $extraAttr.Ref }}", e.{{ $extraAttr.Ref.RenderUnexported }})
{{- else if or (eq $attr.Type.ValueType.String "Map") (eq $attr.Type.ValueType.String "Slice") (eq $attr.Type.ValueType.String "Bytes") }}
res.Attributes().PutEmpty("{{ $extraAttr.Ref }}").SetEmpty{{ $attr.Type.ValueType }}().FromRaw(e.{{ $extraAttr.Ref.RenderUnexported }})
{{- else }}
res.Attributes().PutEmpty("{{ $extraAttr.Ref }}").Set{{ $attr.Type.ValueType }}(e.{{ $extraAttr.Ref.RenderUnexported }})
{{- end }}
}
{{- end }}
} else {
{{- range $idAttr := $ent.Identity }}
{{- $attr := index $resourceAttributes $idAttr.Ref }}
if cfg.{{ publicVar $idAttr.Ref.Render }}.Enabled {
{{- if or (eq $attr.Type.String "Bytes") (eq $attr.Type.String "Slice") (eq $attr.Type.String "Map") }}
res.Attributes().PutEmpty{{ $attr.Type }}("{{ $idAttr.Ref }}").FromRaw(e.{{ $idAttr.Ref.RenderUnexported }})
{{- else }}
res.Attributes().Put{{ $attr.Type }}("{{ $idAttr.Ref }}", e.{{ $idAttr.Ref.RenderUnexported }})
{{- end }}
}
{{- end }}
{{- range $descAttr := $ent.Description }}
{{- $attr := index $resourceAttributes $descAttr.Ref }}
if cfg.{{ publicVar $descAttr.Ref.Render }}.Enabled {
{{- if or (eq $attr.Type.String "Bytes") (eq $attr.Type.String "Slice") (eq $attr.Type.String "Map") }}
res.Attributes().PutEmpty{{ $attr.Type }}("{{ $descAttr.Ref }}").FromRaw(e.{{ $descAttr.Ref.RenderUnexported }})
{{- else }}
res.Attributes().Put{{ $attr.Type }}("{{ $descAttr.Ref }}", e.{{ $descAttr.Ref.RenderUnexported }})
{{- end }}
}
{{- end }}
{{- range $extraAttr := $ent.ExtraAttributes }}
{{- $attr := index $resourceAttributes $extraAttr.Ref }}
if cfg.{{ publicVar $extraAttr.Ref.Render }}.Enabled {
{{- if or (eq $attr.Type.String "Bytes") (eq $attr.Type.String "Slice") (eq $attr.Type.String "Map") }}
res.Attributes().PutEmpty{{ $attr.Type }}("{{ $extraAttr.Ref }}").FromRaw(e.{{ $extraAttr.Ref.RenderUnexported }})
{{- else }}
res.Attributes().Put{{ $attr.Type }}("{{ $extraAttr.Ref }}", e.{{ $extraAttr.Ref.RenderUnexported }})
{{- end }}
}
{{- end }}
}
}
{{ end }}
{{ range $ent := .Entities }}
{{ $entityType := $ent.Type }}
{{ $entityStructType := printf "%sEntity" (publicVar $entityType) }}
{{ $builderType := printf "%sMetricsBuilder" (publicVar $entityType) }}
// {{ $builderType }} records metrics for the {{ $entityType }} entity.
// Obtain one via MetricsBuilder.For{{ publicVar $entityType }}().
type {{ $builderType }} struct {
mb *MetricsBuilder
entity *{{ $entityStructType }}
}
{{- range $name, $metric := $metrics }}
{{- if eq $metric.Entity $entityType }}
{{/* TODO: Consolidate with the root-level Record*DataPoint methods in metrics.go.tmpl. */}}
// Record{{ $name.Render }}DataPoint records a data point for the {{ $name }} metric.
func (eb *{{ $builderType }}) Record{{ $name.Render }}DataPoint(ts pcommon.Timestamp
{{- if $metric.Data.HasMetricInputType }}, inputVal {{ $metric.Data.MetricInputType.String }}
{{- else }}, val {{ $metric.Data.MetricValueType.BasicType }}
{{- end }}
{{- range $metric.Attributes -}}
{{- if not (attributeInfo .).IsConditional -}}
, {{ .RenderUnexported }}AttributeValue {{ if (attributeInfo .).Enum }}Attribute{{ .Render }}{{ else }}{{ (attributeInfo .).Type.Primitive }}{{ end }}
{{- end -}}
{{- end -}}
{{- if $metric.HasConditionalAttributes $.Attributes -}}
, options ...MetricAttributeOption
{{- end -}}
)
{{- if $metric.Data.HasMetricInputType }} error{{ end }} {
{{- if $metric.Data.HasMetricInputType }}
{{- if eq $metric.Data.MetricValueType.BasicType "float64" }}
val, err := strconv.ParseFloat(inputVal, 64)
{{- else if eq $metric.Data.MetricValueType.BasicType "int64" }}
val, err := strconv.ParseInt(inputVal, 10, 64)
{{- end }}
if err != nil {
return fmt.Errorf("failed to parse {{ $metric.Data.MetricValueType.BasicType }} for {{ $name.Render }}, value was %s: %w", inputVal, err)
}
{{- end }}
eb.mb.metric{{ $name.Render }}.recordDataPoint(eb.mb.startTime, ts, val
{{- range $metric.Attributes -}}
{{- if not (attributeInfo .).IsConditional -}}
, {{ .RenderUnexported }}AttributeValue{{ if (attributeInfo .).Enum }}.String(){{ end }}
{{- end -}}
{{- end -}}
{{- if $metric.HasConditionalAttributes $.Attributes -}}
, options...
{{- end -}}
)
{{- if $metric.Data.HasMetricInputType }}
return nil
{{- end }}
}
{{- end }}
{{- end }}
// Emit emits all pending metrics for the entity. Resource attributes are filtered by config:
// disabled identity attributes suppress the entity (other enabled attributes are added directly
// to the resource); disabled descriptive/extra attributes are omitted entirely.
func (eb *{{ $builderType }}) Emit() {
res := pcommon.NewResource()
cfg := eb.mb.config.ResourceAttributes
eb.entity.copyToResource(cfg, res)
{{- range $rel := $ent.Relationships }}
if eb.entity.{{ toLowerCamelCase $rel.Type }}{{ publicVar $rel.Target }} != nil {
eb.entity.{{ toLowerCamelCase $rel.Type }}{{ publicVar $rel.Target }}.copyToResource(cfg, res)
}
{{- end }}
eb.mb.EmitForResource(withResourceMoved(res))
}
{{- end }}
================================================
FILE: cmd/mdatagen/internal/templates/entity_metrics_test.go.tmpl
================================================
// Code generated by mdatagen. DO NOT EDIT.
package {{ .Package }}
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/xpdata/entity"
"go.opentelemetry.io/collector/{{ .Status.Class }}/{{ .Status.Class }}test"
)
{{ $resourceAttributes := .ResourceAttributes }}
func TestEntityBuilders(t *testing.T) {
start := pcommon.Timestamp(1_000_000_000)
ts := pcommon.Timestamp(1_000_001_000)
settings := {{ .Status.Class }}test.NewNopSettings({{ .Status.Class }}test.NopType)
mb := NewMetricsBuilder(DefaultMetricsBuilderConfig(), settings, WithStartTime(start))
{{- range $ent := .Entities }}
{{ $entityType := $ent.Type }}
{{ $entityStructType := printf "%sEntity" (publicVar $entityType) }}
t.Run("{{ $entityType }}", func(t *testing.T) {
e := New{{ $entityStructType }}(
{{- range $i, $idAttr := $ent.Identity -}}
{{- $attr := index $resourceAttributes $idAttr.Ref -}}
{{- if $i }}, {{ end -}}{{ $attr.TestValue }}
{{- end -}}
)
require.NotNil(t, e)
{{- if $ent.Description }}
{{- range $descAttr := $ent.Description }}
{{- $attr := index $resourceAttributes $descAttr.Ref }}
e.Set{{ publicVar $descAttr.Ref.Render }}({{ $attr.TestValue }})
{{- end }}
{{- end }}
{{- if $ent.ExtraAttributes }}
{{- range $extraAttr := $ent.ExtraAttributes }}
{{- $attr := index $resourceAttributes $extraAttr.Ref }}
e.Set{{ publicVar $extraAttr.Ref.Render }}({{ $attr.TestValue }})
{{- end }}
{{- end }}
{{- if $ent.Relationships }}
{{- range $rel := $ent.Relationships }}
{{- range $other := $.Entities }}
{{- if eq $other.Type $rel.Target }}
related{{ publicVar $rel.Target }} := New{{ publicVar $rel.Target }}Entity(
{{- range $i, $idAttr := $other.Identity -}}
{{- $attr := index $resourceAttributes $idAttr.Ref -}}
{{- if $i }}, {{ end -}}{{ $attr.TestValue }}
{{- end -}}
)
e.Set{{ publicVar $rel.Type }}{{ publicVar $rel.Target }}(related{{ publicVar $rel.Target }})
{{- end }}
{{- end }}
{{- end }}
{{- end }}
eb := mb.For{{ publicVar $entityType }}(e)
{{- range $name, $metric := $.Metrics }}
{{- if eq $metric.Entity $entityType }}
eb.Record{{ $name.Render }}DataPoint(ts, {{ if $metric.Data.HasMetricInputType }}"1"{{ else }}1{{ end }}
{{- range $metric.Attributes -}}
{{- if not (attributeInfo .).IsConditional -}}
, {{- template "getAttributeValue" . -}}
{{- end -}}
{{- end -}}
)
{{- end }}
{{- end }}
eb.Emit()
metrics := mb.Emit()
{{- $count := 0 }}
{{- range $name, $metric := $.Metrics }}
{{- if and (eq $metric.Entity $entityType) $metric.Enabled }}
{{- $count = inc $count }}
{{- end }}
{{- end }}
{{- if gt $count 0 }}
require.Equal(t, 1, metrics.ResourceMetrics().Len())
rm := metrics.ResourceMetrics().At(0)
{{- range $idAttr := $ent.Identity }}
{{- $attr := index $resourceAttributes $idAttr.Ref }}
entityVal, ok := entity.ResourceEntities(rm.Resource()).Get("{{ $entityType }}")
require.True(t, ok)
{{ $idAttr.Ref.RenderUnexported }}AttrVal, ok := entityVal.IdentifyingAttributes().Get("{{ $idAttr.Ref }}")
require.True(t, ok)
{{- template "assertResourceAttributeValue" $attr }}
{{- end }}
{{- range $descAttr := $ent.Description }}
{{- $attr := index $resourceAttributes $descAttr.Ref }}
{{- if $attr.Enabled }}
{{ $descAttr.Ref.RenderUnexported }}AttrVal, ok := entityVal.DescriptiveAttributes().Get("{{ $descAttr.Ref }}")
require.True(t, ok)
{{- template "assertResourceAttributeValue" $attr }}
{{- else }}
_, ok = entityVal.DescriptiveAttributes().Get("{{ $descAttr.Ref }}")
assert.False(t, ok)
{{- end }}
{{- end }}
{{- range $extraAttr := $ent.ExtraAttributes }}
{{- $attr := index $resourceAttributes $extraAttr.Ref }}
_, ok = entityVal.DescriptiveAttributes().Get("{{ $extraAttr.Ref }}")
assert.False(t, ok)
{{- if $attr.Enabled }}
{{ $extraAttr.Ref.RenderUnexported }}AttrVal, ok := rm.Resource().Attributes().Get("{{ $extraAttr.Ref }}")
require.True(t, ok)
{{- template "assertResourceAttributeValue" $attr }}
{{- else }}
_, ok = rm.Resource().Attributes().Get("{{ $extraAttr.Ref }}")
assert.False(t, ok)
{{- end }}
{{- end }}
require.Equal(t, 1, rm.ScopeMetrics().Len())
ms := rm.ScopeMetrics().At(0).Metrics()
assert.Equal(t, {{ $count }}, ms.Len())
{{- else }}
// All metrics for this entity are disabled by default.
assert.Equal(t, 0, metrics.ResourceMetrics().Len())
{{- end }}
})
{{- if gt $count 0 }}
t.Run("{{ $entityType }}/disabled_identity_attr", func(t *testing.T) {
// When an identity attribute is disabled, the entity is not produced but
// other enabled attributes are still added to the resource directly.
cfg := DefaultMetricsBuilderConfig()
{{- range $idAttr := $ent.Identity }}
cfg.ResourceAttributes.{{ publicVar $idAttr.Ref.Render }}.Enabled = false
{{- end }}
mb := NewMetricsBuilder(cfg, settings, WithStartTime(start))
e := New{{ $entityStructType }}(
{{- range $i, $idAttr := $ent.Identity -}}
{{- $attr := index $resourceAttributes $idAttr.Ref -}}
{{- if $i }}, {{ end -}}{{ $attr.TestValue }}
{{- end -}}
)
{{- if $ent.Description }}
{{- range $descAttr := $ent.Description }}
{{- $attr := index $resourceAttributes $descAttr.Ref }}
e.Set{{ publicVar $descAttr.Ref.Render }}({{ $attr.TestValue }})
{{- end }}
{{- end }}
eb := mb.For{{ publicVar $entityType }}(e)
{{- range $name, $metric := $.Metrics }}
{{- if eq $metric.Entity $entityType }}
eb.Record{{ $name.Render }}DataPoint(ts, {{ if $metric.Data.HasMetricInputType }}"1"{{ else }}1{{ end }}
{{- range $metric.Attributes -}}
{{- if not (attributeInfo .).IsConditional -}}
, {{- template "getAttributeValue" . -}}
{{- end -}}
{{- end -}}
)
{{- end }}
{{- end }}
eb.Emit()
metrics := mb.Emit()
require.Equal(t, 1, metrics.ResourceMetrics().Len())
rm := metrics.ResourceMetrics().At(0)
// Entity must not be present since its identity attribute is disabled.
_, ok := entity.ResourceEntities(rm.Resource()).Get("{{ $entityType }}")
assert.False(t, ok)
// Enabled descriptive attributes should still be on the resource directly.
{{- range $descAttr := $ent.Description }}
{{- $attr := index $resourceAttributes $descAttr.Ref }}
{{- if $attr.Enabled }}
_, ok = rm.Resource().Attributes().Get("{{ $descAttr.Ref }}")
assert.True(t, ok)
{{- end }}
{{- end }}
})
{{- if or $ent.Description $ent.ExtraAttributes }}
t.Run("{{ $entityType }}/disabled_descriptive_attr", func(t *testing.T) {
// When a descriptive attribute is disabled, the entity is still produced
// with its identity but the disabled attribute is not added.
cfg := DefaultMetricsBuilderConfig()
{{- range $descAttr := $ent.Description }}
cfg.ResourceAttributes.{{ publicVar $descAttr.Ref.Render }}.Enabled = false
{{- end }}
{{- range $extraAttr := $ent.ExtraAttributes }}
cfg.ResourceAttributes.{{ publicVar $extraAttr.Ref.Render }}.Enabled = false
{{- end }}
mb := NewMetricsBuilder(cfg, settings, WithStartTime(start))
e := New{{ $entityStructType }}(
{{- range $i, $idAttr := $ent.Identity -}}
{{- $attr := index $resourceAttributes $idAttr.Ref -}}
{{- if $i }}, {{ end -}}{{ $attr.TestValue }}
{{- end -}}
)
{{- if $ent.Description }}
{{- range $descAttr := $ent.Description }}
{{- $attr := index $resourceAttributes $descAttr.Ref }}
e.Set{{ publicVar $descAttr.Ref.Render }}({{ $attr.TestValue }})
{{- end }}
{{- end }}
{{- if $ent.ExtraAttributes }}
{{- range $extraAttr := $ent.ExtraAttributes }}
{{- $attr := index $resourceAttributes $extraAttr.Ref }}
e.Set{{ publicVar $extraAttr.Ref.Render }}({{ $attr.TestValue }})
{{- end }}
{{- end }}
eb := mb.For{{ publicVar $entityType }}(e)
{{- range $name, $metric := $.Metrics }}
{{- if eq $metric.Entity $entityType }}
eb.Record{{ $name.Render }}DataPoint(ts, {{ if $metric.Data.HasMetricInputType }}"1"{{ else }}1{{ end }}
{{- range $metric.Attributes -}}
{{- if not (attributeInfo .).IsConditional -}}
, {{- template "getAttributeValue" . -}}
{{- end -}}
{{- end -}}
)
{{- end }}
{{- end }}
eb.Emit()
metrics := mb.Emit()
require.Equal(t, 1, metrics.ResourceMetrics().Len())
rm := metrics.ResourceMetrics().At(0)
// Entity must still be produced since identity attributes are enabled.
entityVal, ok := entity.ResourceEntities(rm.Resource()).Get("{{ $entityType }}")
require.True(t, ok)
{{- range $idAttr := $ent.Identity }}
{{- $attr := index $resourceAttributes $idAttr.Ref }}
{{ $idAttr.Ref.RenderUnexported }}AttrVal, ok := entityVal.IdentifyingAttributes().Get("{{ $idAttr.Ref }}")
require.True(t, ok)
{{- template "assertResourceAttributeValue" $attr }}
{{- end }}
// Disabled descriptive/extra attributes must not be present.
{{- range $descAttr := $ent.Description }}
_, ok = entityVal.DescriptiveAttributes().Get("{{ $descAttr.Ref }}")
assert.False(t, ok)
{{- end }}
{{- range $extraAttr := $ent.ExtraAttributes }}
_, ok = entityVal.DescriptiveAttributes().Get("{{ $extraAttr.Ref }}")
assert.False(t, ok)
{{- end }}
})
{{- end }}
{{- end }}
{{- end }}
}
================================================
FILE: cmd/mdatagen/internal/templates/feature_gates.go.tmpl
================================================
// Code generated by mdatagen. DO NOT EDIT.
package {{ .Package }}
import (
"go.opentelemetry.io/collector/featuregate"
)
{{- range $idx, $gate := .FeatureGates }}
var {{ printf "%s" $gate.ID | publicVar }}FeatureGate = featuregate.GlobalRegistry().MustRegister(
"{{ $gate.ID }}",
featuregate.Stage{{ printf "%s" $gate.Stage | casesTitle }},
featuregate.WithRegisterDescription("{{ $gate.Description }}"),
featuregate.WithRegisterReferenceURL("{{ $gate.ReferenceURL }}"),
featuregate.WithRegisterFromVersion("{{ $gate.FromVersion }}"),
{{- if $gate.ToVersion }}
featuregate.WithRegisterToVersion("{{ $gate.ToVersion }}"),
{{- end }}
)
{{- end }}
================================================
FILE: cmd/mdatagen/internal/templates/feature_gates.md.tmpl
================================================
{{- if len .FeatureGates }}
## Feature Gates
This component has the following feature gates:
| Feature Gate | Stage | Description | From Version | To Version | Reference |
| ------------ | ----- | ----------- | ------------ | ---------- | --------- |
{{- range .FeatureGates }}
| `{{ .ID }}` | {{ .Stage }} | {{ .Description }} | {{ if .FromVersion }}{{ .FromVersion }}{{ else }}N/A{{ end }} | {{ if .ToVersion }}{{ .ToVersion }}{{ else }}N/A{{ end }} | {{ if .ReferenceURL }}[Link]({{ .ReferenceURL }}){{ else }}N/A{{ end }} |
{{- end }}
For more information about feature gates, see the [Feature Gates](https://github.com/open-telemetry/opentelemetry-collector/blob/main/featuregate/README.md) documentation.
{{- end }}
================================================
FILE: cmd/mdatagen/internal/templates/helper.tmpl
================================================
{{- define "putAttribute" -}}
{{- if eq (attributeInfo .).Type.Primitive "[]byte" }}
dp.Attributes().PutEmptyBytes("{{ (attributeInfo .).Name }}").FromRaw({{ .RenderUnexported }}AttributeValue)
{{- else if eq (attributeInfo .).Type.Primitive "[]any" }}
dp.Attributes().PutEmptySlice("{{ (attributeInfo .).Name }}").FromRaw({{ .RenderUnexported }}AttributeValue)
{{- else if eq (attributeInfo .).Type.Primitive "map[string]any" }}
dp.Attributes().PutEmptyMap("{{ (attributeInfo .).Name }}").FromRaw({{ .RenderUnexported }}AttributeValue)
{{- else }}
dp.Attributes().Put{{ (attributeInfo .).Type }}("{{ (attributeInfo .).Name }}", {{ .RenderUnexported }}AttributeValue)
{{- end }}
{{- end -}}
{{- define "putMetricAttribute" -}}
if slices.Contains(m.config.EnabledAttributes, "{{ (attributeInfo .).Name }}") {
{{- if eq (attributeInfo .).Type.Primitive "[]byte" }}
dp.Attributes().PutEmptyBytes("{{ (attributeInfo .).Name }}").FromRaw({{ .RenderUnexported }}AttributeValue)
{{- else if eq (attributeInfo .).Type.Primitive "[]any" }}
dp.Attributes().PutEmptySlice("{{ (attributeInfo .).Name }}").FromRaw({{ .RenderUnexported }}AttributeValue)
{{- else if eq (attributeInfo .).Type.Primitive "map[string]any" }}
dp.Attributes().PutEmptyMap("{{ (attributeInfo .).Name }}").FromRaw({{ .RenderUnexported }}AttributeValue)
{{- else }}
dp.Attributes().Put{{ (attributeInfo .).Type }}("{{ (attributeInfo .).Name }}", {{ .RenderUnexported }}AttributeValue)
{{- end }}
}
{{- end -}}
{{- define "getAttributeValue" -}}
{{ if (attributeInfo .).Enum }}Attribute{{ .Render }}{{ (index (attributeInfo .).Enum 0) | publicVar }}{{ else }}{{ (attributeInfo .).TestValue }}{{ end }}
{{- end -}}
{{- define "getAttributeValueTwo" -}}
{{ if (attributeInfo .).Enum }}Attribute{{ .Render }}{{ if gt (len (attributeInfo .).Enum) 1}}{{ (index (attributeInfo .).Enum 1) | publicVar}}{{ else }}{{ (index (attributeInfo .).Enum 0) | publicVar}}{{ end }}{{ else }}{{ (attributeInfo .).TestValueTwo }}{{ end }}
{{- end -}}
{{- define "assertResourceAttributeValue" -}}
{{- if eq .Type.String "Bool"}}
assert.{{- if eq .TestValue "true" }}True{{ else }}False{{- end }}(t, {{ .FullName.RenderUnexported }}AttrVal.{{ .Type }}())
{{- else if eq .Type.String "Int"}}
assert.EqualValues(t, {{ .TestValue }}, {{ .FullName.RenderUnexported }}AttrVal.{{ .Type }}())
{{- else }}
assert.Equal(t, {{ .TestValue }}, {{ .FullName.RenderUnexported }}AttrVal.{{ .Type }}()
{{- if or (eq .Type.String "Bytes") (eq .Type.String "Slice") (eq .Type.String "Map") -}}
.AsRaw()
{{- end -}}
)
{{- end }}
{{- end -}}
{{- define "assertMetricAttributeValue" -}}
{{- if eq (attributeInfo .).Type.String "Bool"}}
assert.{{- if eq (attributeInfo .).TestValue "true" }}True{{ else }}False{{- end }}(t, {{ .RenderUnexported }}AttrVal.{{ (attributeInfo .).Type }}())
{{- else if eq (attributeInfo .).Type.String "Int"}}
assert.EqualValues(t, {{ (attributeInfo .).TestValue }}, {{ .RenderUnexported }}AttrVal.{{ (attributeInfo .).Type }}())
{{- else }}
assert.Equal(t, {{ (attributeInfo .).TestValue }}, {{ .RenderUnexported }}AttrVal.{{ (attributeInfo .).Type }}()
{{- if or (eq (attributeInfo .).Type.String "Bytes") (eq (attributeInfo .).Type.String "Slice") (eq (attributeInfo .).Type.String "Map") -}}
.AsRaw()
{{- end -}}
)
{{- end }}
{{- end -}}
{{- define "metricUnmarshal" }}
if parser == nil {
return nil
}
err := parser.Unmarshal({{ . }})
if err != nil {
return err
}
{{ . }}.enabledSetByUser = parser.IsSet("enabled")
return nil
{{- end }}
================================================
FILE: cmd/mdatagen/internal/templates/logs.go.tmpl
================================================
// Code generated by mdatagen. DO NOT EDIT.
package {{ .Package }}
import (
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
{{- if or isReceiver isScraper }}
"go.opentelemetry.io/collector/{{ .Status.Class }}"
{{- end }}
{{- if .SemConvVersion }}
conventions "go.opentelemetry.io/otel/semconv/v{{ .SemConvVersion }}"
{{- end }}
{{- if .Events }}
"context"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/collector/filter"
{{- end }}
)
{{ if getEventConditionalAttributes .Attributes }}
type EventAttributeOption interface {
apply(plog.LogRecord)
}
type eventAttributeOptionFunc func(plog.LogRecord)
func (eaof eventAttributeOptionFunc) apply(lr plog.LogRecord) {
eaof(lr)
}
{{ range getEventConditionalAttributes .Attributes }}
func With{{ .Render }}EventAttribute({{ .RenderUnexported }}AttributeValue {{ (attributeInfo .).Type.Primitive }}) EventAttributeOption {
return eventAttributeOptionFunc(func(dp plog.LogRecord) {
{{- template "putAttribute" . }}
})
}
{{ end }}
{{ end }}
{{ range $name, $event := .Events -}}
type event{{ $name.Render }} struct {
data plog.LogRecordSlice // data buffer for generated log records.
config EventConfig // event config provided by user.
}
func (e *event{{ $name.Render }}) recordEvent(ctx context.Context, timestamp pcommon.Timestamp
{{- range $event.Attributes -}}{{- if not (attributeInfo .).IsConditional -}}, {{ .RenderUnexported }}AttributeValue {{ (attributeInfo .).Type.Primitive }}{{ end }}{{end}}{{- if $event.HasConditionalAttributes $.Attributes -}}, options ...EventAttributeOption{{- end -}}) {
if !e.config.Enabled {
return
}
dp := e.data.AppendEmpty()
dp.SetEventName("{{ $name }}")
dp.SetTimestamp(timestamp)
if span := trace.SpanContextFromContext(ctx); span.IsValid() {
dp.SetTraceID(pcommon.TraceID(span.TraceID()))
dp.SetSpanID(pcommon.SpanID(span.SpanID()))
}
{{- range $event.Attributes }}
{{- if not (attributeInfo .).IsConditional -}}
{{- template "putAttribute" . }}
{{- end }}
{{- end }}
{{ if $event.HasConditionalAttributes $.Attributes }}
for _, op := range options {
op.apply(dp)
}
{{- end }}
}
// emit appends recorded event data to a events slice and prepares it for recording another set of log records.
func (e *event{{ $name.Render }}) emit(lrs plog.LogRecordSlice) {
if e.config.Enabled && e.data.Len() > 0 {
e.data.MoveAndAppendTo(lrs)
}
}
func newEvent{{ $name.Render }}(cfg EventConfig) event{{ $name.Render }} {
e := event{{ $name.Render }}{config: cfg}
if cfg.Enabled {
e.data = plog.NewLogRecordSlice()
}
return e
}
{{ end -}}
// LogsBuilder provides an interface for scrapers to report logs while taking care of all the transformations
// required to produce log representation defined in metadata and user config.
type LogsBuilder struct {
{{- if .Events }}
config LogsBuilderConfig // config of the logs builder.
{{- end }}
logsBuffer plog.Logs
logRecordsBuffer plog.LogRecordSlice
buildInfo component.BuildInfo // contains version information.
{{- if and .Events .ResourceAttributes }}
resourceAttributeIncludeFilter map[string]filter.Filter
resourceAttributeExcludeFilter map[string]filter.Filter
{{- end }}
{{- range $name, $event := .Events }}
event{{ $name.Render }} event{{ $name.Render }}
{{- end }}
}
// LogBuilderOption applies changes to default logs builder.
type LogBuilderOption interface {
apply(*LogsBuilder)
}
{{- if or isReceiver isScraper }}
func NewLogsBuilder({{ if .Events }}lbc LogsBuilderConfig, {{ end }}settings {{ .Status.Class }}.Settings) *LogsBuilder {
{{- range $name, $event := .Events }}
{{- if $event.Warnings.IfEnabled }}
if lbc.Events.{{ $name.Render }}.Enabled {
settings.Logger.Warn("[WARNING] `{{ $name }}` should not be enabled: {{ $event.Warnings.IfEnabled }}")
}
{{- end }}
{{- if $event.Warnings.IfEnabledNotSet }}
if !lbc.Events.{{ $name.Render }}.enabledSetByUser {
settings.Logger.Warn("[WARNING] Please set `enabled` field explicitly for `{{ $name }}`: {{ $event.Warnings.IfEnabledNotSet }}")
}
{{- end }}
{{- if $event.Warnings.IfConfigured }}
if lbc.Events.{{ $name.Render }}.enabledSetByUser {
settings.Logger.Warn("[WARNING] `{{ $name }}` should not be configured: {{ $event.Warnings.IfConfigured }}")
}
{{- end }}
{{- end }}
{{- if .Events }}
{{- range $name, $attr := .ResourceAttributes }}
{{- if $attr.Warnings.IfEnabled }}
if lbc.ResourceAttributes.{{ $name.Render }}.Enabled {
settings.Logger.Warn("[WARNING] `{{ $name }}` should not be enabled: {{ $attr.Warnings.IfEnabled }}")
}
{{- end }}
{{- if $attr.Warnings.IfEnabledNotSet }}
if !lbc.ResourceAttributes.{{ $name.Render }}.enabledSetByUser {
settings.Logger.Warn("[WARNING] Please set `enabled` field explicitly for `{{ $name }}`: {{ $attr.Warnings.IfEnabledNotSet }}")
}
{{- end }}
{{- if $attr.Warnings.IfConfigured }}
if lbc.ResourceAttributes.{{ $name.Render }}.enabledSetByUser || lbc.ResourceAttributes.{{ $name.Render }}.EventsInclude != nil || lbc.ResourceAttributes.{{ $name.Render }}.EventsExclude != nil {
settings.Logger.Warn("[WARNING] `{{ $name }}` should not be configured: {{ $attr.Warnings.IfConfigured }}")
}
{{- end }}
{{- end }}
{{- end }}
lb := &LogsBuilder{
{{- if .Events }}
config: lbc,
{{- end }}
logsBuffer: plog.NewLogs(),
logRecordsBuffer: plog.NewLogRecordSlice(),
buildInfo: settings.BuildInfo,
{{- range $name, $event := .Events }}
event{{ $name.Render }}: newEvent{{ $name.Render }}(lbc.Events.{{ $name.Render }}),
{{- end }}
{{ if and .Events .ResourceAttributes -}}
resourceAttributeIncludeFilter: make(map[string]filter.Filter),
resourceAttributeExcludeFilter: make(map[string]filter.Filter),
{{- end }}
}
{{- if .Events }}
{{- range $name, $attr := .ResourceAttributes }}
if lbc.ResourceAttributes.{{ $name.Render }}.EventsInclude != nil {
lb.resourceAttributeIncludeFilter["{{ $name }}"] = filter.CreateFilter(lbc.ResourceAttributes.{{ $name.Render }}.EventsInclude)
}
if lbc.ResourceAttributes.{{ $name.Render }}.EventsExclude != nil {
lb.resourceAttributeExcludeFilter["{{ $name }}"] = filter.CreateFilter(lbc.ResourceAttributes.{{ $name.Render }}.EventsExclude)
}
{{- end }}
{{- end }}
return lb
}
{{- end }}
{{- if .ResourceAttributes }}
// NewResourceBuilder returns a new resource builder that should be used to build a resource associated with for the emitted logs.
func (lb *LogsBuilder) NewResourceBuilder() *ResourceBuilder {
return NewResourceBuilder({{ if .Events }}lb.config.ResourceAttributes{{ else }}ResourceAttributesConfig{}{{ end }})
}
{{- end }}
// ResourceLogsOption applies changes to provided resource logs.
type ResourceLogsOption interface {
apply(plog.ResourceLogs)
}
type resourceLogsOptionFunc func(plog.ResourceLogs)
func (rlof resourceLogsOptionFunc) apply(rl plog.ResourceLogs) {
rlof(rl)
}
// WithLogsResource sets the provided resource on the emitted ResourceLogs.
// It's recommended to use ResourceBuilder to create the resource.
func WithLogsResource(res pcommon.Resource) ResourceLogsOption {
return resourceLogsOptionFunc(func(rl plog.ResourceLogs) {
res.CopyTo(rl.Resource())
})
}
// AppendLogRecord adds a log record to the logs builder.
func (lb *LogsBuilder) AppendLogRecord(lr plog.LogRecord) {
lr.MoveTo(lb.logRecordsBuffer.AppendEmpty())
}
// EmitForResource saves all the generated logs under a new resource and updates the internal state to be ready for
// recording another set of log records as part of another resource. This function can be helpful when one scraper
// needs to emit logs from several resources. Otherwise calling this function is not required,
// just `Emit` function can be called instead.
// Resource attributes should be provided as ResourceLogsOption arguments.
func (lb *LogsBuilder) EmitForResource(options ...ResourceLogsOption) {
rl := plog.NewResourceLogs()
{{- if .SemConvVersion }}
rl.SetSchemaUrl(conventions.SchemaURL)
{{- end }}
ils := rl.ScopeLogs().AppendEmpty()
ils.Scope().SetName(ScopeName)
ils.Scope().SetVersion(lb.buildInfo.Version)
{{- range $name, $event := .Events }}
lb.event{{- $name.Render }}.emit(ils.LogRecords())
{{- end }}
for _, op := range options {
op.apply(rl)
}
if lb.logRecordsBuffer.Len() > 0 {
lb.logRecordsBuffer.MoveAndAppendTo(ils.LogRecords())
lb.logRecordsBuffer = plog.NewLogRecordSlice()
}
{{ if and .Events .ResourceAttributes -}}
for attr, filter := range lb.resourceAttributeIncludeFilter {
if val, ok := rl.Resource().Attributes().Get(attr); ok && !filter.Matches(val.AsString()) {
return
}
}
for attr, filter := range lb.resourceAttributeExcludeFilter {
if val, ok := rl.Resource().Attributes().Get(attr); ok && filter.Matches(val.AsString()) {
return
}
}
{{- end }}
if ils.LogRecords().Len() > 0 {
rl.MoveTo(lb.logsBuffer.ResourceLogs().AppendEmpty())
}
}
// Emit returns all the logs accumulated by the logs builder and updates the internal state to be ready for
// recording another set of logs. This function will be responsible for applying all the transformations required to
// produce logs representation defined in metadata and user config.
func (lb *LogsBuilder) Emit(options ...ResourceLogsOption) plog.Logs {
lb.EmitForResource(options...)
logs := lb.logsBuffer
lb.logsBuffer = plog.NewLogs()
return logs
}
{{ range $name, $event := .Events -}}
// Record{{ $name.Render }}Event adds a log record of {{ $name }} event.
func (lb *LogsBuilder) Record{{ $name.Render }}Event(ctx context.Context, timestamp pcommon.Timestamp
{{- range $event.Attributes -}}
{{- if not (attributeInfo .).IsConditional -}}
, {{ .RenderUnexported }}AttributeValue {{ if (attributeInfo .).Enum }}Attribute{{ .Render }}{{ else }}{{ (attributeInfo .).Type.Primitive }}{{ end }}
{{- end -}}
{{- end -}}
{{- if $event.HasConditionalAttributes $.Attributes -}}
, options... EventAttributeOption
{{- end -}}) {
lb.event{{ $name.Render }}.recordEvent(ctx, timestamp
{{- range $event.Attributes -}}
{{- if not (attributeInfo .).IsConditional -}}
, {{ .RenderUnexported }}AttributeValue{{ if (attributeInfo .).Enum }}.String(){{ end }}
{{- end -}}
{{- end -}}
{{- if $event.HasConditionalAttributes $.Attributes -}}
, options...
{{- end -}})
}
{{ end }}
================================================
FILE: cmd/mdatagen/internal/templates/logs_test.go.tmpl
================================================
// Code generated by mdatagen. DO NOT EDIT.
package {{ .Package }}
import (
"time"
"testing"
"github.com/stretchr/testify/assert"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
{{- if or isReceiver isScraper }}
"go.opentelemetry.io/collector/{{ .Status.Class }}/{{ .Status.Class }}test"
{{- end }}
{{- if .Events }}
"context"
"go.opentelemetry.io/otel/trace"
{{- end }}
)
{{- if .Events }}
type eventsTestDataSet int
const (
eventTestDataSetDefault eventsTestDataSet = iota
eventTestDataSetAll
eventTestDataSetNone
)
{{- end }}
func TestLogsBuilderAppendLogRecord(t *testing.T) {
observedZapCore, _ := observer.New(zap.WarnLevel)
{{- if or isReceiver isScraper }}
settings := {{ .Status.Class }}test.NewNopSettings({{ .Status.Class }}test.NopType)
{{- end }}
settings.Logger = zap.New(observedZapCore)
lb := NewLogsBuilder({{ if .Events }}loadLogsBuilderConfig(t, "all_set"), {{ end }}settings)
{{ if .ResourceAttributes }}
rb := lb.NewResourceBuilder()
{{- range $name, $attr := .ResourceAttributes }}
{{- if $attr.Enum }}
rb.Set{{ $attr.Name.Render }}{{ index $attr.Enum 0 | publicVar }}()
{{- else }}
rb.Set{{ $attr.Name.Render }}({{ $attr.TestValue }})
{{- end }}
{{- end }}
res := rb.Emit()
{{- else }}
res := pcommon.NewResource()
{{- end }}
// append the first log record
lr := plog.NewLogRecord()
lr.SetTimestamp(pcommon.NewTimestampFromTime(time.Now()))
lr.Attributes().PutStr("type", "log")
lr.Body().SetStr("the first log record")
// append the second log record
lr2 := plog.NewLogRecord()
lr2.SetTimestamp(pcommon.NewTimestampFromTime(time.Now()))
lr2.Attributes().PutStr("type", "event")
lr2.Body().SetStr("the second log record")
lb.AppendLogRecord(lr)
lb.AppendLogRecord(lr2)
logs := lb.Emit(WithLogsResource(res))
assert.Equal(t, 1, logs.ResourceLogs().Len())
rl := logs.ResourceLogs().At(0)
assert.Equal(t, 1, rl.ScopeLogs().Len())
sl := rl.ScopeLogs().At(0)
assert.Equal(t, ScopeName,sl.Scope().Name())
assert.Equal(t, lb.buildInfo.Version, sl.Scope().Version())
assert.Equal(t, 2, sl.LogRecords().Len())
attrVal, ok := sl.LogRecords().At(0).Attributes().Get("type")
assert.True(t, ok)
assert.Equal(t, "log", attrVal.Str())
assert.Equal(t, pcommon.ValueTypeStr, sl.LogRecords().At(0).Body().Type())
assert.Equal(t, "the first log record", sl.LogRecords().At(0).Body().Str())
attrVal, ok = sl.LogRecords().At(1).Attributes().Get("type")
assert.True(t, ok)
assert.Equal(t, "event", attrVal.Str())
assert.Equal(t, pcommon.ValueTypeStr, sl.LogRecords().At(1).Body().Type())
assert.Equal(t, "the second log record", sl.LogRecords().At(1).Body().Str())
}
{{- if .Events }}
func TestLogsBuilder(t *testing.T) {
tests := []struct {
name string
eventsSet eventsTestDataSet
resAttrsSet eventsTestDataSet
expectEmpty bool
}{
{
name: "default",
},
{
name: "all_set",
eventsSet: eventTestDataSetAll,
resAttrsSet: eventTestDataSetAll,
},
{
name: "none_set",
eventsSet: eventTestDataSetNone,
resAttrsSet: eventTestDataSetNone,
expectEmpty: true,
},
{{- if .ResourceAttributes }}
{
name: "filter_set_include",
resAttrsSet: eventTestDataSetAll,
},
{
name: "filter_set_exclude",
resAttrsSet: eventTestDataSetAll,
expectEmpty: true,
},
{{- end }}
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
timestamp := pcommon.Timestamp(1_000_001_000)
traceID := [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
spanID := [8]byte{0, 1, 2, 3, 4, 5, 6, 7}
ctx := trace.ContextWithSpanContext(context.Background(), trace.NewSpanContext(trace.SpanContextConfig{
TraceID: trace.TraceID(traceID),
SpanID: trace.SpanID(spanID),
TraceFlags: trace.FlagsSampled,
}))
observedZapCore, observedLogs := observer.New(zap.WarnLevel)
{{- if or isReceiver isScraper }}
settings := {{ .Status.Class }}test.NewNopSettings({{ .Status.Class }}test.NopType)
{{- end }}
settings.Logger = zap.New(observedZapCore)
lb := NewLogsBuilder(loadLogsBuilderConfig(t, tt.name), settings)
expectedWarnings := 0
{{- range $name, $event := .Events }}
{{- if and $event.Enabled $event.Warnings.IfEnabled }}
if tt.eventsSet == eventTestDataSetDefault || tt.eventsSet == eventTestDataSetAll {
assert.Equal(t, "[WARNING] `{{ $name }}` should not be enabled: {{ $event.Warnings.IfEnabled }}", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
{{- end }}
{{- if $event.Warnings.IfEnabledNotSet }}
if tt.eventsSet == eventTestDataSetDefault {
assert.Equal(t, "[WARNING] Please set `enabled` field explicitly for `{{ $name }}`: {{ $event.Warnings.IfEnabledNotSet }}", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
{{- end }}
{{- if $event.Warnings.IfConfigured }}
if tt.eventsSet == eventTestDataSetAll || tt.eventsSet == eventTestDataSetNone {
assert.Equal(t, "[WARNING] `{{ $name }}` should not be configured: {{ $event.Warnings.IfConfigured }}", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
{{- end }}
{{- end }}
{{- range $name, $attr := .ResourceAttributes }}
{{- if and $attr.Enabled $attr.Warnings.IfEnabled }}
if tt.resAttrsSet == eventTestDataSetDefault || tt.resAttrsSet == eventTestDataSetAll {
assert.Equal(t, "[WARNING] `{{ $name }}` should not be enabled: {{ $attr.Warnings.IfEnabled }}", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
{{- end }}
{{- if $attr.Warnings.IfEnabledNotSet }}
if tt.resAttrsSet == eventTestDataSetDefault {
assert.Equal(t, "[WARNING] Please set `enabled` field explicitly for `{{ $name }}`: {{ $attr.Warnings.IfEnabledNotSet }}", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
{{- end }}
{{- if $attr.Warnings.IfConfigured }}
if tt.resAttrsSet == eventTestDataSetAll || tt.resAttrsSet == eventTestDataSetNone {
assert.Equal(t, "[WARNING] `{{ $name }}` should not be configured: {{ $attr.Warnings.IfConfigured }}", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
{{- end }}
{{- end }}
assert.Equal(t, expectedWarnings, observedLogs.Len())
defaultEventsCount := 0
allEventsCount := 0
{{- range $name, $event := .Events }}
{{ if $event.Enabled }}defaultEventsCount++{{ end }}
allEventsCount++
lb.Record{{ $name.Render }}Event(ctx, timestamp
{{- range $event.Attributes -}}
{{- if not (attributeInfo .).IsConditional -}}
, {{- template "getAttributeValue" . -}}
{{- end -}}
{{- end -}}
{{- range $event.Attributes -}}
{{- if (attributeInfo .).IsConditional -}}
, With{{ .Render }}EventAttribute({{- template "getAttributeValue" . -}})
{{- end -}}
{{- end -}}
)
{{- end }}
{{ if .ResourceAttributes }}
rb := lb.NewResourceBuilder()
{{- range $name, $attr := .ResourceAttributes }}
{{- if $attr.Enum }}
rb.Set{{ $attr.Name.Render }}{{ index $attr.Enum 0 | publicVar }}()
{{- else }}
rb.Set{{ $attr.Name.Render }}({{ $attr.TestValue }})
{{- end }}
{{- end }}
res := rb.Emit()
{{- else }}
res := pcommon.NewResource()
{{- end }}
logs := lb.Emit(WithLogsResource(res))
if tt.expectEmpty || ((tt.name == "default" || tt.name == "filter_set_include") && defaultEventsCount == 0) {
assert.Equal(t, 0, logs.ResourceLogs().Len())
return
}
assert.Equal(t, 1, logs.ResourceLogs().Len())
rl := logs.ResourceLogs().At(0)
assert.Equal(t, res, rl.Resource())
assert.Equal(t, 1, rl.ScopeLogs().Len())
lrs := rl.ScopeLogs().At(0).LogRecords()
if tt.eventsSet == eventTestDataSetDefault {
assert.Equal(t, defaultEventsCount, lrs.Len())
}
if tt.eventsSet == eventTestDataSetAll {
assert.Equal(t, allEventsCount, lrs.Len())
}
validatedEvents := make(map[string]bool)
for i := 0; i < lrs.Len(); i++ {
switch lrs.At(i).EventName() {
{{- range $name, $event := .Events }}
case "{{ $name }}":
assert.False(t, validatedEvents["{{ $name }}"], "Found a duplicate in the events slice: {{ $name }}")
validatedEvents["{{ $name }}"] = true
lr := lrs.At(i)
assert.Equal(t, timestamp, lr.Timestamp())
assert.Equal(t, pcommon.TraceID(traceID), lr.TraceID())
assert.Equal(t, pcommon.SpanID(spanID), lr.SpanID())
{{- range $i, $attr := $event.Attributes }}
attrVal, ok {{ if eq $i 0 }}:{{ end }}= lr.Attributes().Get("{{ (attributeInfo $attr).Name }}")
assert.True(t, ok)
{{- if eq (attributeInfo $attr).Type.String "Bool"}}
assert.{{- if eq (attributeInfo $attr).TestValue "true" }}True{{ else }}False{{- end }}(t, attrVal.{{ (attributeInfo $attr).Type }}()
{{- else if eq (attributeInfo $attr).Type.String "Int"}}
assert.EqualValues(t, {{ (attributeInfo $attr).TestValue }}, attrVal.{{ (attributeInfo $attr).Type }}()
{{- else }}
assert.Equal(t, {{ (attributeInfo $attr).TestValue }}, attrVal.{{ (attributeInfo $attr).Type }}()
{{- end }}
{{- if or (eq (attributeInfo $attr).Type.String "Slice") (eq (attributeInfo $attr).Type.String "Map")}}.AsRaw(){{ end }})
{{- end }}
{{- end }}
}
}
})
}
}
{{- end }}
================================================
FILE: cmd/mdatagen/internal/templates/metrics.go.tmpl
================================================
// Code generated by mdatagen. DO NOT EDIT.
package {{ .Package }}
{{ $reag := .ReaggregationEnabled }}
{{- $hasReagMetrics := false }}
{{- range $name, $metric := .Metrics }}{{- if and $reag (hasAggregatableAttributes $metric.Attributes) }}{{- $hasReagMetrics = true }}{{- end }}{{- end }}
import (
{{- if .Metrics | parseImportsRequired }}
"strconv"
"fmt"
{{- end }}
"time"
{{- if $hasReagMetrics }}
"slices"
{{- end }}
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/pmetric"
{{- if or isReceiver isScraper isConnector }}
"go.opentelemetry.io/collector/{{ .Status.Class }}"
{{- end }}
{{- if .SemConvVersion }}
conventions "go.opentelemetry.io/otel/semconv/v{{ .SemConvVersion }}"
{{- end }}
{{ if .ResourceAttributes -}}
"go.opentelemetry.io/collector/filter"
{{- end }}
)
{{- if $reag }}
const (
AggregationStrategySum = "sum"
AggregationStrategyAvg = "avg"
AggregationStrategyMin = "min"
AggregationStrategyMax = "max"
)
{{- end }}
{{ range $name, $info := .Attributes }}
{{- if $info.Enum }}
// Attribute{{ $name.Render }} specifies the value {{ $name }} attribute.
type Attribute{{ $name.Render }} int
const (
_ Attribute{{ $name.Render }} = iota
{{- range $info.Enum }}
Attribute{{ $name.Render }}{{ . | publicVar }}
{{- end }}
)
// String returns the string representation of the Attribute{{ $name.Render }}.
func (av Attribute{{ $name.Render }}) String() string {
switch av {
{{- range $info.Enum }}
case Attribute{{ $name.Render }}{{ . | publicVar }}:
return "{{ . }}"
{{- end }}
}
return ""
}
// MapAttribute{{ $name.Render }} is a helper map of string to Attribute{{ $name.Render }} attribute value.
var MapAttribute{{ $name.Render }} = map[string]Attribute{{ $name.Render }}{
{{- range $info.Enum }}
"{{ . }}": Attribute{{ $name.Render }}{{ . | publicVar }},
{{- end }}
}
{{- end }}
{{- end }}
var MetricsInfo = metricsInfo{
{{- range $name, $metric := .Metrics }}
{{ $name.Render }}: metricInfo{
Name: "{{ $name }}",
},
{{- end }}
}
type metricsInfo struct {
{{- range $name, $metric := .Metrics }}
{{ $name.Render }} metricInfo
{{- end }}
}
type metricInfo struct {
Name string
}
{{ if getMetricConditionalAttributes .Attributes }}
type MetricAttributeOption interface {
apply(pmetric.NumberDataPoint)
}
type metricAttributeOptionFunc func(pmetric.NumberDataPoint)
func (maof metricAttributeOptionFunc) apply(dp pmetric.NumberDataPoint) {
maof(dp)
}
{{ range getMetricConditionalAttributes .Attributes }}
func With{{ .Render }}MetricAttribute({{ .RenderUnexported }}AttributeValue {{ (attributeInfo .).Type.Primitive }}) MetricAttributeOption {
return metricAttributeOptionFunc(func(dp pmetric.NumberDataPoint) {
{{- template "putAttribute" . }}
})
}
{{ end }}
{{ end }}
{{ range $name, $metric := .Metrics }}
{{- $metricReag := and $reag (hasAggregatableAttributes $metric.Attributes) -}}
type metric{{ $name.Render }} struct {
data pmetric.Metric // data buffer for generated metric.
config {{ if $reag }}{{ $name.Render }}{{ end }}MetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
{{- if $metricReag }}
aggDataPoints []{{ $metric.Data.MetricValueType.BasicType }} // slice containing number of aggregated datapoints at each index
{{- end }}
}
// init fills {{ $name }} metric with initial data.
func (m *metric{{ $name.Render }}) init() {
m.data.SetName("{{ $name }}")
m.data.SetDescription("{{ $metric.Description }}")
m.data.SetUnit("{{ $metric.Unit }}")
m.data.SetEmpty{{ $metric.Data.Type }}()
{{- if $metric.Data.HasMonotonic }}
m.data.{{ $metric.Data.Type }}().SetIsMonotonic({{ $metric.Data.Monotonic }})
{{- end }}
{{- if $metric.Data.HasAggregated }}
m.data.{{ $metric.Data.Type }}().SetAggregationTemporality(pmetric.AggregationTemporality{{ $metric.Data.AggregationTemporality }})
{{- end }}
{{- if $metric.Attributes }}
m.data.{{ $metric.Data.Type }}().DataPoints().EnsureCapacity(m.capacity)
{{- end }}
{{- if $metricReag }}
m.aggDataPoints = m.aggDataPoints[:0]
{{- end }}
}
{{/* This function changed in a major way, so instead of gating individual lines of code the original is being included */}}
{{- if $metricReag }}
func (m *metric{{ $name.Render }}) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val {{ $metric.Data.MetricValueType.BasicType }}
{{- range $metric.Attributes -}}{{- if not (attributeInfo .).IsConditional -}}, {{ .RenderUnexported }}AttributeValue {{ (attributeInfo .).Type.Primitive }}{{ end }}{{ end }}{{- if $metric.HasConditionalAttributes $.Attributes -}}, options ...MetricAttributeOption{{- end -}}) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
{{- range $metric.Attributes }}
{{- if not (attributeInfo .).IsConditional }}
if slices.Contains(m.config.EnabledAttributes, {{ $name.Render }}MetricAttributeKey{{ .Render }}) {
{{- template "putAttribute" . }}
}
{{- end }}
{{- end }}
{{- if $metric.HasConditionalAttributes $.Attributes }}
for _, op := range options {
op.apply(dp)
}
{{- end }}
var s string
dps := m.data.{{ $metric.Data.Type }}().DataPoints()
for i := 0; i < dps.Len(); i++ {
dpi := dps.At(i)
if dp.Attributes().Equal(dpi.Attributes()) && dp.StartTimestamp() == dpi.StartTimestamp() && dp.Timestamp() == dpi.Timestamp() {
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.Set{{ $metric.Data.MetricValueType }}Value(dpi.{{ $metric.Data.MetricValueType }}Value() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.{{ $metric.Data.MetricValueType }}Value() > val {
dpi.Set{{ $metric.Data.MetricValueType }}Value(val)
}
return
case AggregationStrategyMax:
if dpi.{{ $metric.Data.MetricValueType }}Value() < val {
dpi.Set{{ $metric.Data.MetricValueType }}Value(val)
}
return
}
}
}
dp.Set{{ $metric.Data.MetricValueType }}Value(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
dp.MoveTo(dps.AppendEmpty())
}
{{- else }}
func (m *metric{{ $name.Render }}) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val {{ $metric.Data.MetricValueType.BasicType }}
{{- range $metric.Attributes -}}{{- if not (attributeInfo .).IsConditional -}}, {{ .RenderUnexported }}AttributeValue {{ (attributeInfo .).Type.Primitive }}{{ end }}{{ end }}{{- if $metric.HasConditionalAttributes $.Attributes -}}, options ...MetricAttributeOption{{- end -}}) {
if !m.config.Enabled {
return
}
dp := m.data.{{ $metric.Data.Type }}().DataPoints().AppendEmpty()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
dp.Set{{ $metric.Data.MetricValueType }}Value(val)
{{- range $metric.Attributes }}
{{- if not (attributeInfo .).IsConditional -}}
{{- template "putAttribute" . -}}
{{- end }}
{{- end }}
{{- if $metric.HasConditionalAttributes $.Attributes }}
for _, op := range options {
op.apply(dp)
}
{{- end }}
}
{{- end }}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metric{{ $name.Render }}) updateCapacity() {
if m.data.{{ $metric.Data.Type }}().DataPoints().Len() > m.capacity {
m.capacity = m.data.{{ $metric.Data.Type }}().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metric{{ $name.Render }}) emit(metrics pmetric.MetricSlice) {
{{- if $metricReag }}
if m.config.Enabled && m.data.{{ $metric.Data.Type }}().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.{{ $metric.Data.Type }}().DataPoints().At(i).Set{{ $metric.Data.MetricValueType }}Value(m.data.{{ $metric.Data.Type }}().DataPoints().At(i).{{ $metric.Data.MetricValueType }}Value() / aggCount)
}
}
{{- else }}
if m.config.Enabled && m.data.{{ $metric.Data.Type }}().DataPoints().Len() > 0 {
{{- end }}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetric{{ $name.Render }}(cfg {{ if $reag }}{{ $name.Render }}{{ end }}MetricConfig) metric{{ $name.Render }} {
m := metric{{ $name.Render }}{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
{{ end -}}
// MetricsBuilder provides an interface for scrapers to report metrics while taking care of all the transformations
// required to produce metric representation defined in metadata and user config.
type MetricsBuilder struct {
config MetricsBuilderConfig // config of the metrics builder.
startTime pcommon.Timestamp // start time that will be applied to all recorded data points.
metricsCapacity int // maximum observed number of metrics per resource.
metricsBuffer pmetric.Metrics // accumulates metrics data before emitting.
buildInfo component.BuildInfo // contains version information.
{{- if .ResourceAttributes }}
resourceAttributeIncludeFilter map[string]filter.Filter
resourceAttributeExcludeFilter map[string]filter.Filter
{{- end }}
{{- range $name, $metric := .Metrics }}
metric{{ $name.Render }} metric{{ $name.Render }}
{{- end }}
}
// MetricBuilderOption applies changes to default metrics builder.
type MetricBuilderOption interface {
apply(*MetricsBuilder)
}
type metricBuilderOptionFunc func(mb *MetricsBuilder)
func (mbof metricBuilderOptionFunc) apply(mb *MetricsBuilder) {
mbof(mb)
}
// WithStartTime sets startTime on the metrics builder.
func WithStartTime(startTime pcommon.Timestamp) MetricBuilderOption {
return metricBuilderOptionFunc(func(mb *MetricsBuilder) {
mb.startTime = startTime
})
}
{{- if or isReceiver isScraper isConnector }}
func NewMetricsBuilder(mbc MetricsBuilderConfig, settings {{ .Status.Class }}.Settings, options ...MetricBuilderOption) *MetricsBuilder {
{{- range $name, $metric := .Metrics }}
{{- if $metric.Warnings.IfEnabled }}
if mbc.Metrics.{{ $name.Render }}.Enabled {
settings.Logger.Warn("[WARNING] `{{ $name }}` should not be enabled: {{ $metric.Warnings.IfEnabled }}")
}
{{- end }}
{{- if $metric.Warnings.IfEnabledNotSet }}
if !mbc.Metrics.{{ $name.Render }}.enabledSetByUser {
settings.Logger.Warn("[WARNING] Please set `enabled` field explicitly for `{{ $name }}`: {{ $metric.Warnings.IfEnabledNotSet }}")
}
{{- end }}
{{- if $metric.Warnings.IfConfigured }}
if mbc.Metrics.{{ $name.Render }}.enabledSetByUser {
settings.Logger.Warn("[WARNING] `{{ $name }}` should not be configured: {{ $metric.Warnings.IfConfigured }}")
}
{{- end }}
{{- end }}
{{- range $name, $attr := .ResourceAttributes }}
{{- if $attr.Warnings.IfEnabled }}
if mbc.ResourceAttributes.{{ $name.Render }}.Enabled {
settings.Logger.Warn("[WARNING] `{{ $name }}` should not be enabled: {{ $attr.Warnings.IfEnabled }}")
}
{{- end }}
{{- if $attr.Warnings.IfEnabledNotSet }}
if !mbc.ResourceAttributes.{{ $name.Render }}.enabledSetByUser {
settings.Logger.Warn("[WARNING] Please set `enabled` field explicitly for `{{ $name }}`: {{ $attr.Warnings.IfEnabledNotSet }}")
}
{{- end }}
{{- if $attr.Warnings.IfConfigured }}
if mbc.ResourceAttributes.{{ $name.Render }}.enabledSetByUser || mbc.ResourceAttributes.{{ $name.Render }}.MetricsInclude != nil || mbc.ResourceAttributes.{{ $name.Render }}.MetricsExclude != nil {
settings.Logger.Warn("[WARNING] `{{ $name }}` should not be configured: {{ $attr.Warnings.IfConfigured }}")
}
{{- end }}
{{- end }}
mb := &MetricsBuilder{
config: mbc,
startTime: pcommon.NewTimestampFromTime(time.Now()),
metricsBuffer: pmetric.NewMetrics(),
buildInfo: settings.BuildInfo,
{{- range $name, $metric := .Metrics }}
metric{{ $name.Render }}: newMetric{{ $name.Render }}(mbc.Metrics.{{ $name.Render }}),
{{- end }}
{{ if .ResourceAttributes -}}
resourceAttributeIncludeFilter: make(map[string]filter.Filter),
resourceAttributeExcludeFilter: make(map[string]filter.Filter),
{{- end }}
}
{{- range $name, $attr := .ResourceAttributes }}
if mbc.ResourceAttributes.{{ $name.Render }}.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["{{ $name }}"] = filter.CreateFilter(mbc.ResourceAttributes.{{ $name.Render }}.MetricsInclude)
}
if mbc.ResourceAttributes.{{ $name.Render }}.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["{{ $name }}"] = filter.CreateFilter(mbc.ResourceAttributes.{{ $name.Render }}.MetricsExclude)
}
{{- end }}
for _, op := range options {
op.apply(mb)
}
return mb
}
{{- end }}
{{- if .ResourceAttributes }}
// NewResourceBuilder returns a new resource builder that should be used to build a resource associated with for the emitted metrics.
func (mb *MetricsBuilder) NewResourceBuilder() *ResourceBuilder {
return NewResourceBuilder(mb.config.ResourceAttributes)
}
{{- end }}
// updateCapacity updates max length of metrics and resource attributes that will be used for the slice capacity.
func (mb *MetricsBuilder) updateCapacity(rm pmetric.ResourceMetrics) {
if mb.metricsCapacity < rm.ScopeMetrics().At(0).Metrics().Len() {
mb.metricsCapacity = rm.ScopeMetrics().At(0).Metrics().Len()
}
}
// ResourceMetricsOption applies changes to provided resource metrics.
type ResourceMetricsOption interface {
apply(pmetric.ResourceMetrics)
}
type resourceMetricsOptionFunc func(pmetric.ResourceMetrics)
func (rmof resourceMetricsOptionFunc) apply(rm pmetric.ResourceMetrics) {
rmof(rm)
}
// WithResource sets the provided resource on the emitted ResourceMetrics.
// It's recommended to use ResourceBuilder to create the resource.
func WithResource(res pcommon.Resource) ResourceMetricsOption {
return resourceMetricsOptionFunc(func(rm pmetric.ResourceMetrics) {
res.CopyTo(rm.Resource())
})
}
{{ if .HasEntities -}}
func withResourceMoved(res pcommon.Resource) ResourceMetricsOption {
return resourceMetricsOptionFunc(func(rm pmetric.ResourceMetrics) {
res.MoveTo(rm.Resource())
})
}
{{- end }}
// WithStartTimeOverride overrides start time for all the resource metrics data points.
// This option should be only used if different start time has to be set on metrics coming from different resources.
func WithStartTimeOverride(start pcommon.Timestamp) ResourceMetricsOption {
return resourceMetricsOptionFunc(func(rm pmetric.ResourceMetrics) {
var dps pmetric.NumberDataPointSlice
metrics := rm.ScopeMetrics().At(0).Metrics()
for i := 0; i < metrics.Len(); i++ {
switch metrics.At(i).Type() {
case pmetric.MetricTypeGauge:
dps = metrics.At(i).Gauge().DataPoints()
case pmetric.MetricTypeSum:
dps = metrics.At(i).Sum().DataPoints()
}
for j := 0; j < dps.Len(); j++ {
dps.At(j).SetStartTimestamp(start)
}
}
})
}
{{ range $ent := .Entities }}
{{ $entityType := $ent.Type }}
{{ $entityStructType := printf "%sEntity" (publicVar $entityType) }}
{{ $builderType := printf "%sMetricsBuilder" (publicVar $entityType) }}
// For{{ publicVar $entityType }} returns a {{ $builderType }} that restricts metric recording
// to metrics belonging to the {{ $entityType }} entity.
func (mb *MetricsBuilder) For{{ publicVar $entityType }}(e *{{ $entityStructType }}) *{{ $builderType }} {
return &{{ $builderType }}{mb: mb, entity: e}
}
{{ end }}
// EmitForResource saves all the generated metrics under a new resource and updates the internal state to be ready for
// recording another set of data points as part of another resource. This function can be helpful when one scraper
// needs to emit metrics from several resources. Otherwise calling this function is not required,
// just `Emit` function can be called instead.
// Resource attributes should be provided as ResourceMetricsOption arguments.
{{- if .HasEntities }}
//
// Deprecated: Use the For methods to get entity-scoped builders and call Emit() on them instead.
{{- end }}
func (mb *MetricsBuilder) EmitForResource(options ...ResourceMetricsOption) {
rm := pmetric.NewResourceMetrics()
{{- if .SemConvVersion }}
rm.SetSchemaUrl(conventions.SchemaURL)
{{- end }}
ils := rm.ScopeMetrics().AppendEmpty()
ils.Scope().SetName(ScopeName)
ils.Scope().SetVersion(mb.buildInfo.Version)
ils.Metrics().EnsureCapacity(mb.metricsCapacity)
{{- range $name, $metric := .Metrics }}
mb.metric{{- $name.Render }}.emit(ils.Metrics())
{{- end }}
for _, op := range options {
op.apply(rm)
}
{{ if .ResourceAttributes -}}
for attr, filter := range mb.resourceAttributeIncludeFilter {
if val, ok := rm.Resource().Attributes().Get(attr); ok && !filter.Matches(val.AsString()) {
return
}
}
for attr, filter := range mb.resourceAttributeExcludeFilter {
if val, ok := rm.Resource().Attributes().Get(attr); ok && filter.Matches(val.AsString()) {
return
}
}
{{- end }}
if ils.Metrics().Len() > 0 {
mb.updateCapacity(rm)
rm.MoveTo(mb.metricsBuffer.ResourceMetrics().AppendEmpty())
}
}
// Emit returns all the metrics accumulated by the metrics builder and updates the internal state to be ready for
// recording another set of metrics. This function will be responsible for applying all the transformations required to
// produce metric representation defined in metadata and user config, e.g. delta or cumulative.
func (mb *MetricsBuilder) Emit(options ...ResourceMetricsOption) pmetric.Metrics {
mb.EmitForResource(options...)
metrics := mb.metricsBuffer
mb.metricsBuffer = pmetric.NewMetrics()
return metrics
}
{{ range $name, $metric := .Metrics -}}
// Record{{ $name.Render }}DataPoint adds a data point to {{ $name }} metric.
{{- if $metric.Entity }}
//
// Deprecated: Use mb.For{{ publicVar $metric.Entity }}(entity).Record{{ $name.Render }}DataPoint(...) instead.
{{- end }}
func (mb *MetricsBuilder) Record{{ $name.Render }}DataPoint(ts pcommon.Timestamp
{{- if $metric.Data.HasMetricInputType }}, inputVal {{ $metric.Data.MetricInputType.String }}
{{- else }}, val {{ $metric.Data.MetricValueType.BasicType }}
{{- end }}
{{- range $metric.Attributes -}}
{{- if not (attributeInfo .).IsConditional -}}
, {{ .RenderUnexported }}AttributeValue {{ if (attributeInfo .).Enum }}Attribute{{ .Render }}{{ else }}{{ (attributeInfo .).Type.Primitive }}{{ end }}
{{- end -}}
{{- end -}}
{{- if $metric.HasConditionalAttributes $.Attributes -}}
, options... MetricAttributeOption
{{- end -}}
)
{{- if $metric.Data.HasMetricInputType }} error{{ end }} {
{{- if $metric.Data.HasMetricInputType }}
{{- if eq $metric.Data.MetricValueType.BasicType "float64" }}
val, err := strconv.ParseFloat(inputVal, 64)
{{- else if eq $metric.Data.MetricValueType.BasicType "int64" }}
val, err := strconv.ParseInt(inputVal, 10, 64)
{{- end }}
if err != nil {
return fmt.Errorf("failed to parse {{ $metric.Data.MetricValueType.BasicType }} for {{ $name.Render }}, value was %s: %w", inputVal, err)
}
{{- end }}
mb.metric{{ $name.Render }}.recordDataPoint(mb.startTime, ts, val
{{- range $metric.Attributes -}}
{{- if not (attributeInfo .).IsConditional -}}
, {{ .RenderUnexported }}AttributeValue{{ if (attributeInfo .).Enum }}.String(){{ end }}
{{- end -}}
{{- end -}}
{{- if $metric.HasConditionalAttributes $.Attributes -}}
, options...
{{- end -}}
)
{{- if $metric.Data.HasMetricInputType }}
return nil
{{- end }}
}
{{ end }}
// Reset resets metrics builder to its initial state. It should be used when external metrics source is restarted,
// and metrics builder should update its startTime and reset it's internal state accordingly.
func (mb *MetricsBuilder) Reset(options ...MetricBuilderOption) {
mb.startTime = pcommon.NewTimestampFromTime(time.Now())
for _, op := range options {
op.apply(mb)
}
}
================================================
FILE: cmd/mdatagen/internal/templates/metrics_test.go.tmpl
================================================
// Code generated by mdatagen. DO NOT EDIT.
package {{ .Package }}
{{ $reag := .ReaggregationEnabled }}
{{- $hasReagMetrics := false }}
{{- range $name, $metric := .Metrics }}{{- if and $reag (hasAggregatableAttributes $metric.Attributes) }}{{- $hasReagMetrics = true }}{{- end }}{{- end }}
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/pmetric"
{{- if or isReceiver isScraper isConnector }}
"go.opentelemetry.io/collector/{{ .Status.Class }}/{{ .Status.Class }}test"
{{- end }}
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
)
type testDataSet int
const (
testDataSetDefault testDataSet = iota
testDataSetAll
testDataSetNone
{{- if $reag }}
testDataSetReag
{{- end }}
)
func TestMetricsBuilder(t *testing.T) {
tests := []struct {
name string
metricsSet testDataSet
resAttrsSet testDataSet
expectEmpty bool
}{
{
name: "default",
},
{
name: "all_set",
metricsSet: testDataSetAll,
resAttrsSet: testDataSetAll,
},
{{- if $reag }}
{
name: "reaggregate_set",
metricsSet: testDataSetReag,
resAttrsSet: testDataSetReag,
},
{{- end }}
{
name: "none_set",
metricsSet: testDataSetNone,
resAttrsSet: testDataSetNone,
expectEmpty: true,
},
{{- if .ResourceAttributes }}
{
name: "filter_set_include",
resAttrsSet: testDataSetAll,
},
{
name: "filter_set_exclude",
resAttrsSet: testDataSetAll,
expectEmpty: true,
},
{{- end }}
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
start := pcommon.Timestamp(1_000_000_000)
ts := pcommon.Timestamp(1_000_001_000)
observedZapCore, observedLogs := observer.New(zap.WarnLevel)
{{- if or isReceiver isScraper isConnector }}
settings := {{ .Status.Class }}test.NewNopSettings({{ .Status.Class }}test.NopType)
{{- end }}
settings.Logger = zap.New(observedZapCore)
mb := NewMetricsBuilder(loadMetricsBuilderConfig(t, tt.name), settings, WithStartTime(start))
{{- if $hasReagMetrics }}
aggMap := make(map[string]string) // contains the aggregation strategies for each metric name
{{- range $name, $metric := .Metrics }}
{{- if hasAggregatableAttributes $metric.Attributes }}
aggMap["{{ $name.Render }}"] = mb.metric{{ $name.Render }}.config.AggregationStrategy
{{- end }}
{{- end }}
{{- end }}
expectedWarnings := 0
{{- range $name, $metric := .Metrics }}
{{- if and $metric.Enabled $metric.Warnings.IfEnabled }}
if tt.metricsSet == testDataSetDefault || tt.metricsSet == testDataSetAll {
assert.Equal(t, "[WARNING] `{{ $name }}` should not be enabled: {{ $metric.Warnings.IfEnabled }}", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
{{- end }}
{{- if $metric.Warnings.IfEnabledNotSet }}
if tt.metricsSet == testDataSetDefault {
assert.Equal(t, "[WARNING] Please set `enabled` field explicitly for `{{ $name }}`: {{ $metric.Warnings.IfEnabledNotSet }}", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
{{- end }}
{{- if $metric.Warnings.IfConfigured }}
if tt.metricsSet == testDataSetAll || tt.metricsSet == testDataSetNone {
assert.Equal(t, "[WARNING] `{{ $name }}` should not be configured: {{ $metric.Warnings.IfConfigured }}", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
{{- end }}
{{- end }}
{{- range $name, $attr := .ResourceAttributes }}
{{- if and $attr.Enabled $attr.Warnings.IfEnabled }}
if tt.resAttrsSet == testDataSetDefault || tt.resAttrsSet == testDataSetAll {
assert.Equal(t, "[WARNING] `{{ $name }}` should not be enabled: {{ $attr.Warnings.IfEnabled }}", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
{{- end }}
{{- if $attr.Warnings.IfEnabledNotSet }}
if tt.resAttrsSet == testDataSetDefault {
assert.Equal(t, "[WARNING] Please set `enabled` field explicitly for `{{ $name }}`: {{ $attr.Warnings.IfEnabledNotSet }}", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
{{- end }}
{{- if $attr.Warnings.IfConfigured }}
if tt.resAttrsSet == testDataSetAll || tt.resAttrsSet == testDataSetNone {
assert.Equal(t, "[WARNING] `{{ $name }}` should not be configured: {{ $attr.Warnings.IfConfigured }}", observedLogs.All()[expectedWarnings].Message)
expectedWarnings++
}
{{- end }}
{{- end }}
{{- if $reag }}
if tt.metricsSet != testDataSetReag {
assert.Equal(t, expectedWarnings, observedLogs.Len())
}
{{- else }}
assert.Equal(t, expectedWarnings, observedLogs.Len())
{{- end }}
defaultMetricsCount := 0
allMetricsCount := 0
{{- range $ent := .Entities }}
eb{{ publicVar $ent.Type }} := mb.For{{ publicVar $ent.Type }}(New{{ publicVar $ent.Type }}Entity(
{{- range $i, $idAttr := $ent.Identity -}}
{{- $attr := index $.ResourceAttributes $idAttr.Ref -}}
{{- if $i }}, {{ end -}}{{ $attr.TestValue }}
{{- end -}}
))
{{- end }}
{{- range $name, $metric := .Metrics }}
{{ if $metric.Enabled }}defaultMetricsCount++{{ end }}
allMetricsCount++
{{ if $metric.Entity }}eb{{ publicVar $metric.Entity }}{{ else }}mb{{ end }}.Record{{ $name.Render }}DataPoint(ts, {{ if $metric.Data.HasMetricInputType }}"1"{{ else }}1{{ end }}
{{- range $metric.Attributes -}}
{{- if not (attributeInfo .).IsConditional -}}
, {{- template "getAttributeValue" . -}}
{{- end -}}
{{- end -}}
{{- range $metric.Attributes -}}
{{- if (attributeInfo .).IsConditional -}}
, With{{ .Render }}MetricAttribute({{- template "getAttributeValue" . -}})
{{- end -}}
{{- end -}}
)
{{- if and $reag (hasAggregatableAttributes $metric.Attributes) }}
if tt.name == "reaggregate_set" {
{{ if $metric.Entity }}eb{{ publicVar $metric.Entity }}{{ else }}mb{{ end }}.Record{{ $name.Render }}DataPoint(ts, {{ if $metric.Data.HasMetricInputType }}"3"{{ else }}3{{ end }}
{{- range $metric.Attributes -}}
{{- if not (attributeInfo .).IsConditional -}}
{{- if (attributeInfo .).IsRequired -}}
, {{- template "getAttributeValue" . -}}
{{- else -}}
, {{- template "getAttributeValueTwo" . -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- range $metric.Attributes -}}
{{- if (attributeInfo .).IsConditional -}}
, With{{ .Render }}MetricAttribute({{- template "getAttributeValue" . -}})
{{- end -}}
{{- end -}}
)
}
{{- end }}
{{- end }}
{{- range $ent := .Entities }}
eb{{ publicVar $ent.Type }}.Emit()
{{- end }}
{{ if .ResourceAttributes }}
rb := mb.NewResourceBuilder()
{{- range $name, $attr := .ResourceAttributes }}
{{- if $attr.Enum }}
rb.Set{{ $attr.Name.Render }}{{ index $attr.Enum 0 | publicVar }}()
{{- else }}
rb.Set{{ $attr.Name.Render }}({{ $attr.TestValue }})
{{- end }}
{{- end }}
res := rb.Emit()
{{- else }}
res := pcommon.NewResource()
{{- end }}
metrics := mb.Emit(WithResource(res))
{{- if $reag }}
if tt.name == "reaggregate_set" {
{{- range $name, $metric := .Metrics }}
{{- if hasAggregatableAttributes $metric.Attributes }}
assert.Empty(t, mb.metric{{ $name.Render }}.aggDataPoints)
{{- end }}
{{- end }}
}
{{- end }}
if tt.expectEmpty {
assert.Equal(t, 0, metrics.ResourceMetrics().Len())
return
}
var allMetricsList []pmetric.Metric
totalMetricsCount := 0
for ri := 0; ri < metrics.ResourceMetrics().Len(); ri++ {
rm := metrics.ResourceMetrics().At(ri)
assert.Equal(t, 1, rm.ScopeMetrics().Len())
ms := rm.ScopeMetrics().At(0).Metrics()
totalMetricsCount += ms.Len()
for mi := 0; mi < ms.Len(); mi++ {
allMetricsList = append(allMetricsList, ms.At(mi))
}
}
if tt.metricsSet == testDataSetDefault {
assert.Equal(t, defaultMetricsCount, totalMetricsCount)
}
if tt.metricsSet == testDataSetAll {
assert.Equal(t, allMetricsCount, totalMetricsCount)
}
validatedMetrics := make(map[string]bool)
for _, mi := range allMetricsList {
switch mi.Name() {
{{- range $name, $metric := .Metrics }}
case "{{ $name }}":
{{- if and $reag (hasAggregatableAttributes $metric.Attributes) }}
if tt.name != "reaggregate_set" {
assert.False(t, validatedMetrics["{{ $name }}"], "Found a duplicate in the metrics slice: {{ $name }}")
validatedMetrics["{{ $name }}"] = true
assert.Equal(t, pmetric.MetricType{{ $metric.Data.Type }}, mi.Type())
assert.Equal(t, 1, mi.{{ $metric.Data.Type }}().DataPoints().Len())
assert.Equal(t, "{{ $metric.Description }}", mi.Description())
{{- if len $metric.Unit}}
assert.Equal(t, "{{ $metric.Unit }}", mi.Unit())
{{- else }}
assert.Empty(t, mi.Unit())
{{- end }}
{{- if $metric.Data.HasMonotonic }}
assert.{{- if $metric.Data.Monotonic }}True{{ else }}False{{ end }}(t, mi.{{ $metric.Data.Type }}().IsMonotonic())
{{- end }}
{{- if $metric.Data.HasAggregated }}
assert.Equal(t, pmetric.AggregationTemporality{{ $metric.Data.AggregationTemporality }}, mi.{{ $metric.Data.Type }}().AggregationTemporality())
{{- end }}
dp := mi.{{ $metric.Data.Type }}().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueType{{ $metric.Data.MetricValueType }}, dp.ValueType())
{{- if eq $metric.Data.MetricValueType.BasicType "float64" }}
assert.InDelta(t, {{ $metric.Data.MetricValueType.BasicType }}(1), dp.{{ $metric.Data.MetricValueType }}Value(), 0.01)
{{- else }}
assert.Equal(t, {{ $metric.Data.MetricValueType.BasicType }}(1), dp.{{ $metric.Data.MetricValueType }}Value())
{{- end }}
{{- range $i, $attr := $metric.Attributes }}
{{- if ne (attributeInfo $attr).RequirementLevel.String "Opt-In" }}
{{ $attr.RenderUnexported }}AttrVal, ok := dp.Attributes().Get("{{ (attributeInfo $attr).Name }}")
assert.True(t, ok)
{{- template "assertMetricAttributeValue" $attr }}
{{- end }}
{{- end }}
} else {
assert.False(t, validatedMetrics["{{ $name }}"], "Found a duplicate in the metrics slice: {{ $name }}")
validatedMetrics["{{ $name }}"] = true
assert.Equal(t, pmetric.MetricType{{ $metric.Data.Type }}, mi.Type())
assert.Equal(t, 1, mi.{{ $metric.Data.Type }}().DataPoints().Len())
assert.Equal(t, "{{ $metric.Description }}", mi.Description())
{{- if len $metric.Unit}}
assert.Equal(t, "{{ $metric.Unit }}", mi.Unit())
{{- else }}
assert.Empty(t, mi.Unit())
{{- end }}
{{- if $metric.Data.HasMonotonic }}
assert.{{- if $metric.Data.Monotonic }}True{{ else }}False{{ end }}(t, mi.{{ $metric.Data.Type }}().IsMonotonic())
{{- end }}
{{- if $metric.Data.HasAggregated }}
assert.Equal(t, pmetric.AggregationTemporality{{ $metric.Data.AggregationTemporality }}, mi.{{ $metric.Data.Type }}().AggregationTemporality())
{{- end }}
dp := mi.{{ $metric.Data.Type }}().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueType{{ $metric.Data.MetricValueType }}, dp.ValueType())
switch aggMap["{{ $name }}"] {
case "sum":
{{- if eq $metric.Data.MetricValueType.BasicType "float64" }}
assert.InDelta(t, {{ $metric.Data.MetricValueType.BasicType }}(4), dp.{{ $metric.Data.MetricValueType }}Value(), 0.01)
{{- else }}
assert.Equal(t, {{ $metric.Data.MetricValueType.BasicType }}(4), dp.{{ $metric.Data.MetricValueType }}Value())
{{- end }}
case "avg":
{{- if eq $metric.Data.MetricValueType.BasicType "float64" }}
assert.InDelta(t, {{ $metric.Data.MetricValueType.BasicType }}(2), dp.{{ $metric.Data.MetricValueType }}Value(), 0.01)
{{- else }}
assert.Equal(t, {{ $metric.Data.MetricValueType.BasicType }}(2), dp.{{ $metric.Data.MetricValueType }}Value())
{{- end }}
case "min":
{{- if eq $metric.Data.MetricValueType.BasicType "float64" }}
assert.InDelta(t, {{ $metric.Data.MetricValueType.BasicType }}(1), dp.{{ $metric.Data.MetricValueType }}Value(), 0.01)
{{- else }}
assert.Equal(t, {{ $metric.Data.MetricValueType.BasicType }}(1), dp.{{ $metric.Data.MetricValueType }}Value())
{{- end }}
case "max":
{{- if eq $metric.Data.MetricValueType.BasicType "float64" }}
assert.InDelta(t, {{ $metric.Data.MetricValueType.BasicType }}(3), dp.{{ $metric.Data.MetricValueType }}Value(), 0.01)
{{- else }}
assert.Equal(t, {{ $metric.Data.MetricValueType.BasicType }}(3), dp.{{ $metric.Data.MetricValueType }}Value())
{{- end }}
}
{{- range $i, $attr := $metric.Attributes }}
{{- if eq (attributeInfo $attr).RequirementLevel.String "Required" }}
_, ok {{ if eq $i 0 }}:{{ end }}= dp.Attributes().Get("{{ (attributeInfo $attr).Name }}")
assert.True(t, ok)
{{- else if eq (attributeInfo $attr).RequirementLevel.String "Conditionally Required" }}
{{- else }}
_, ok {{ if eq $i 0 }}:{{ end }}= dp.Attributes().Get("{{ (attributeInfo $attr).Name }}")
assert.False(t, ok)
{{- end }}
{{- end }}
}
{{- else }}
assert.False(t, validatedMetrics["{{ $name }}"], "Found a duplicate in the metrics slice: {{ $name }}")
validatedMetrics["{{ $name }}"] = true
assert.Equal(t, pmetric.MetricType{{ $metric.Data.Type }}, mi.Type())
assert.Equal(t, 1, mi.{{ $metric.Data.Type }}().DataPoints().Len())
assert.Equal(t, "{{ $metric.Description }}", mi.Description())
{{- if len $metric.Unit}}
assert.Equal(t, "{{ $metric.Unit }}", mi.Unit())
{{- else }}
assert.Empty(t, mi.Unit())
{{- end }}
{{- if $metric.Data.HasMonotonic }}
assert.{{- if $metric.Data.Monotonic }}True{{ else }}False{{ end }}(t, mi.{{ $metric.Data.Type }}().IsMonotonic())
{{- end }}
{{- if $metric.Data.HasAggregated }}
assert.Equal(t, pmetric.AggregationTemporality{{ $metric.Data.AggregationTemporality }}, mi.{{ $metric.Data.Type }}().AggregationTemporality())
{{- end }}
dp := mi.{{ $metric.Data.Type }}().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
assert.Equal(t, pmetric.NumberDataPointValueType{{ $metric.Data.MetricValueType }}, dp.ValueType())
{{- if eq $metric.Data.MetricValueType.BasicType "float64" }}
assert.InDelta(t, {{ $metric.Data.MetricValueType.BasicType }}(1), dp.{{ $metric.Data.MetricValueType }}Value(), 0.01)
{{- else }}
assert.Equal(t, {{ $metric.Data.MetricValueType.BasicType }}(1), dp.{{ $metric.Data.MetricValueType }}Value())
{{- end }}
{{- range $i, $attr := $metric.Attributes }}
{{ $attr.RenderUnexported }}AttrVal, ok := dp.Attributes().Get("{{ (attributeInfo $attr).Name }}")
assert.True(t, ok)
{{- template "assertMetricAttributeValue" $attr }}
{{- end }}
{{- end }}
{{- end }}
}
}
})
}
}
================================================
FILE: cmd/mdatagen/internal/templates/package_test.go.tmpl
================================================
// Code generated by mdatagen. DO NOT EDIT.
package {{ if isCommand -}}main{{ else }}{{ .Package }}{{- end }}
import (
{{- if .Tests.GoLeak.Skip }}
"os"
{{- end }}
"testing"
{{- if not .Tests.GoLeak.Skip }}
"go.uber.org/goleak"
{{- end }}
)
func TestMain(m *testing.M) {
{{- if .Tests.GoLeak.Setup }}
{{.Tests.GoLeak.Setup}}
{{- end }}
{{- if .Tests.GoLeak.Skip }}
// skipping goleak test as per metadata.yml configuration
os.Exit(m.Run())
{{- else }}
goleak.VerifyTestMain(m {{- range $val := .Tests.GoLeak.Ignore.Top}}, goleak.IgnoreTopFunction("{{$val}}"){{end}}{{- range $val := .Tests.GoLeak.Ignore.Any}}, goleak.IgnoreAnyFunction("{{$val}}"){{end}} )
{{- end }}
{{- if .Tests.GoLeak.Teardown }}
{{.Tests.GoLeak.Teardown}}
{{- end }}
}
================================================
FILE: cmd/mdatagen/internal/templates/readme.md.tmpl
================================================
{{- if .DisplayName }}
# {{ .DisplayName }}
{{- end }}
{{- if .Description }}
{{ .Description }}
{{ end -}}
{{- if len .Status.Stability }}
| Status | |
| ------------- |-----------|
{{- $class := .Status.Class }}
{{- $shortName := .ShortFolderName }}
{{- if ne $class "connector" }}
{{- $idx := 0 }}
{{- range $stability, $value := .Status.Stability }}
| {{ if not $idx }}Stability{{ else }} {{ end }} | [{{ toLowerCase $stability.String }}]{{ if and (ne $class "extension") (ne $class "converter") (ne $class "provider") }}: {{ stringsJoin $value ", " }} {{ end }} |
{{- $idx = inc $idx }}
{{- end }}
{{- if .Status.Deprecation }}
{{- range $deprecation, $value := .Status.Deprecation }}
| Deprecation of {{ toLowerCase $deprecation }} | [Date]: {{ $value.Date }}{{ if and (ne $class "extension") (ne $class "converter") (ne $class "provider") }} {{ end }} |
| | [Migration Note]: {{ $value.Migration }}{{ if ne $class "extension" }} {{ end }} |
{{- end }}
{{- end }}
{{- end}}
{{- if .Status.UnsupportedPlatforms }}
| Unsupported Platforms | {{ stringsJoin .Status.UnsupportedPlatforms ", " }} |
{{- end }}
{{- if and (ne $class "cmd") (ne $class "pkg") }}
| Distributions | [{{ stringsJoin .Status.SortedDistributions "], [" }}] |
{{- end }}
{{- if .Status.Warnings }}
| Warnings | [{{ stringsJoin .Status.Warnings ", " }}](#warnings) |
{{- end }}
{{- if ne $class "" }}
| Issues | [](https://github.com/{{ .GithubProject }}/issues?q=is%3Aopen+is%3Aissue+label%3A{{ $class }}%2F{{ $shortName }}) [](https://github.com/{{ .GithubProject }}/issues?q=is%3Aclosed+is%3Aissue+label%3A{{ $class }}%2F{{ $shortName }}) |
{{- if not .Status.DisableCodeCov }}
| Code coverage | [](https://app.codecov.io/gh/{{ .GithubProject}}/tree/main/?components%5B0%5D={{ .GetCodeCovComponentID }}&displayType=list) |
{{- end }}
{{- end }}
{{- if .Status.Codeowners }}
{{- $codeowners := userLinks .Status.Codeowners.Active }}
{{- $emeritus := userLinks .Status.Codeowners.Emeritus }}
| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | {{ stringsJoin $codeowners ", " }} {{ if .Status.Codeowners.SeekingNew }}\| Seeking more code owners! {{ end }}|
{{- if $emeritus }}
| Emeritus | {{ stringsJoin $emeritus ", " }} |
{{- end }}
{{- end }}
{{range $stability, $val := .Status.Stability}}
[{{ toLowerCase $stability.String }}]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#{{ toLowerCase $stability.String }}
{{- end }}
{{- if .Status.Deprecation }}
[Date]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#deprecation-information
[Migration Note]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#deprecation-information
{{- end }}
{{- range .Status.SortedDistributions }}
[{{.}}]: {{ distroURL . }}
{{- end }}
{{- if eq $class "connector"}}
## Supported Pipeline Types
| [Exporter Pipeline Type] | [Receiver Pipeline Type] | [Stability Level] |
| ------------------------ | ------------------------ | ----------------- |
{{- range $stability, $pipelines := .Status.Stability }}
{{- range $pipeline := $pipelines }}
{{- $parts := stringsSplit $pipeline "_to_" }}
| {{index $parts 0}} | {{index $parts 1}} | [{{ toLowerCase $stability.String }}] |
{{- end }}
{{- end }}
[Exporter Pipeline Type]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/connector/README.md#exporter-pipeline-type
[Receiver Pipeline Type]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/connector/README.md#receiver-pipeline-type
[Stability Level]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#stability-levels
{{- end }}
{{- end }}
================================================
FILE: cmd/mdatagen/internal/templates/resource.go.tmpl
================================================
// Code generated by mdatagen. DO NOT EDIT.
package {{ .Package }}
import (
"go.opentelemetry.io/collector/pdata/pcommon"
)
// ResourceBuilder is a helper struct to build resources predefined in metadata.yaml.
// The ResourceBuilder is not thread-safe and must not to be used in multiple goroutines.
type ResourceBuilder struct {
config ResourceAttributesConfig
res pcommon.Resource
}
// NewResourceBuilder creates a new ResourceBuilder. This method should be called on the start of the application.
func NewResourceBuilder(rac ResourceAttributesConfig) *ResourceBuilder {
return &ResourceBuilder{
config: rac,
res: pcommon.NewResource(),
}
}
{{- range $name, $attr := .ResourceAttributes }}
{{- range $attr.Enum }}
// Set{{ $name.Render }}{{ . | publicVar }} sets "{{ $name }}={{ . }}" attribute.
func (rb *ResourceBuilder) Set{{ $name.Render }}{{ . | publicVar }}() {
if rb.config.{{ $name.Render }}.Enabled {
rb.res.Attributes().PutStr("{{ $name }}", "{{ . }}")
}
}
{{- else }}
// Set{{ $name.Render }} sets provided value as "{{ $name }}" attribute.
func (rb *ResourceBuilder) Set{{ $name.Render }}(val {{ $attr.Type.Primitive }}) {
if rb.config.{{ $name.Render }}.Enabled {
{{- if or (eq $attr.Type.String "Bytes") (eq $attr.Type.String "Slice") (eq $attr.Type.String "Map") }}
rb.res.Attributes().PutEmpty{{ $attr.Type }}("{{ $name }}").FromRaw(val)
{{- else }}
rb.res.Attributes().Put{{ $attr.Type }}("{{ $name }}", val)
{{- end }}
}
}
{{- end }}
{{ end }}
// Emit returns the built resource and resets the internal builder state.
func (rb *ResourceBuilder) Emit() pcommon.Resource {
r := rb.res
rb.res = pcommon.NewResource()
return r
}
================================================
FILE: cmd/mdatagen/internal/templates/resource_test.go.tmpl
================================================
// Code generated by mdatagen. DO NOT EDIT.
package {{ .Package }}
import (
"testing"
"github.com/stretchr/testify/assert"
)
{{- $enabledAttrCount := 0 }}
{{- range $_, $attr := .ResourceAttributes }}
{{- if $attr.Enabled }}
{{- $enabledAttrCount = inc $enabledAttrCount }}
{{- end }}
{{- end }}
func TestResourceBuilder(t *testing.T) {
for _, tt := range []string{"default", "all_set", "none_set"} {
t.Run(tt, func(t *testing.T) {
cfg := loadResourceAttributesConfig(t, tt)
rb := NewResourceBuilder(cfg)
{{- range $name, $attr := .ResourceAttributes }}
{{- if $attr.Enum }}
rb.Set{{ $name.Render }}{{ index $attr.Enum 0 | publicVar }}()
{{- else }}
rb.Set{{ $name.Render }}({{ $attr.TestValue }})
{{- end }}
{{- end }}
res := rb.Emit()
assert.Equal(t, 0, rb.Emit().Attributes().Len()) // Second call should return empty Resource
switch tt {
case "default":
assert.Equal(t, {{ $enabledAttrCount }}, res.Attributes().Len())
case "all_set":
assert.Equal(t, {{ len .ResourceAttributes }}, res.Attributes().Len())
case "none_set":
assert.Equal(t, 0, res.Attributes().Len())
return
default:
assert.Failf(t, "unexpected test case: %s", tt)
}
{{- range $name, $attr := .ResourceAttributes }}
{{ $name.RenderUnexported }}AttrVal, ok := res.Attributes().Get("{{ $name }}")
{{- if $attr.Enabled }}
assert.True(t, ok)
{{- else }}
assert.Equal(t, tt == "all_set", ok)
{{- end }}
if ok {
{{- template "assertResourceAttributeValue" $attr }}
}
{{- end }}
})
}
}
================================================
FILE: cmd/mdatagen/internal/templates/status.go.tmpl
================================================
// Code generated by mdatagen. DO NOT EDIT.
package {{ .Package }}
import (
"go.opentelemetry.io/collector/component"
)
var (
Type = component.MustNewType("{{ .Type }}")
{{- if .DeprecatedType }}
DeprecatedType = component.MustNewType("{{ .DeprecatedType }}")
{{- end }}
ScopeName = "{{ .ScopeName }}"
)
const (
{{- range $stability, $signals := .Status.Stability }}
{{- range $signal := $signals }}
{{ toCamelCase $signal }}Stability = component.StabilityLevel{{ casesTitle $stability.String }}
{{- end }}
{{- end }}
)
================================================
FILE: cmd/mdatagen/internal/templates/telemetry.go.tmpl
================================================
// Code generated by mdatagen. DO NOT EDIT.
package {{ .Package }}
import (
{{- if .Telemetry.Metrics }}
{{- range $_, $metric := .Telemetry.Metrics }}
{{- if $metric.Data.Async }}
"context"
{{- break}}
{{- end }}
{{- end }}
"errors"
"sync"
{{- end }}
"go.opentelemetry.io/otel/metric"
{{- if .Telemetry.Metrics }}
{{- range $_, $metric := .Telemetry.Metrics }}
{{- if $metric.Data.Async }}
"go.opentelemetry.io/otel/metric/embedded"
{{- break}}
{{- end }}
{{- end }}
{{- end }}
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/collector/component"
)
func Meter(settings component.TelemetrySettings) metric.Meter {
return settings.MeterProvider.Meter("{{ .ScopeName }}")
}
func Tracer(settings component.TelemetrySettings) trace.Tracer {
return settings.TracerProvider.Tracer("{{ .ScopeName }}")
}
{{- if .Telemetry.Metrics }}
// TelemetryBuilder provides an interface for components to report telemetry
// as defined in metadata and user config.
type TelemetryBuilder struct {
meter metric.Meter
mu sync.Mutex
registrations []metric.Registration
{{- range $name, $metric := .Telemetry.Metrics }}
{{ $name.Render }} metric.{{ $metric.Data.Instrument }}
{{- if and ($metric.Data.Async) (not $metric.Optional) }}
{{- end }}
{{- end }}
}
// TelemetryBuilderOption applies changes to default builder.
type TelemetryBuilderOption interface {
apply(*TelemetryBuilder)
}
type telemetryBuilderOptionFunc func(mb *TelemetryBuilder)
func (tbof telemetryBuilderOptionFunc) apply(mb *TelemetryBuilder) {
tbof(mb)
}
{{- range $name, $metric := .Telemetry.Metrics }}
{{ if $metric.Data.Async -}}
// Register{{ $name.Render }}Callback sets callback for observable {{ $name.Render }} metric.
func (builder *TelemetryBuilder) Register{{ $name.Render }}Callback(cb metric.{{ casesTitle $metric.Data.BasicType }}Callback) error {
reg, err := builder.meter.RegisterCallback(func(ctx context.Context, o metric.Observer) error {
cb(ctx, &observer{{ casesTitle $metric.Data.BasicType }}{inst : builder.{{ $name.Render }}, obs: o})
return nil
}, builder.{{ $name.Render }})
if err != nil {
return err
}
builder.mu.Lock()
defer builder.mu.Unlock()
builder.registrations = append(builder.registrations, reg)
return nil
}
{{- end }}
{{- end }}
{{- range $name, $metric := .Telemetry.Metrics }}
{{- if $metric.Data.Async }}
{{ if eq $metric.Data.BasicType "int64" -}}
type observerInt64 struct {
embedded.Int64Observer
inst metric.Int64Observable
obs metric.Observer
}
func (oi *observerInt64) Observe(value int64, opts ...metric.ObserveOption) {
oi.obs.ObserveInt64(oi.inst, value, opts...)
}
{{ break }}
{{- end }}
{{- end }}
{{- end }}
{{- range $name, $metric := .Telemetry.Metrics }}
{{- if $metric.Data.Async }}
{{ if eq $metric.Data.BasicType "float64" -}}
type observerFloat64 struct {
embedded.Float64Observer
inst metric.Float64Observable
obs metric.Observer
}
func (oi *observerFloat64) Observe(value float64, opts ...metric.ObserveOption) {
oi.obs.ObserveFloat64(oi.inst, value, opts...)
}
{{ break }}
{{- end }}
{{- end }}
{{- end }}
// Shutdown unregister all registered callbacks for async instruments.
func (builder *TelemetryBuilder) Shutdown() {
builder.mu.Lock()
defer builder.mu.Unlock()
for _, reg := range builder.registrations {
reg.Unregister()
}
}
// NewTelemetryBuilder provides a struct with methods to update all internal telemetry
// for a component
func NewTelemetryBuilder(settings component.TelemetrySettings, options ...TelemetryBuilderOption) (*TelemetryBuilder, error) {
builder := TelemetryBuilder{}
for _, op := range options {
op.apply(&builder)
}
builder.meter = Meter(settings)
var err, errs error
{{- range $name, $metric := .Telemetry.Metrics }}
builder.{{ $name.Render }}, err = builder.meter.{{ $metric.Data.Instrument }}(
{{ if $metric.Prefix -}}
"{{ $metric.Prefix }}{{ $name }}",
{{ else -}}
"otelcol_{{ $name }}",
{{ end -}}
metric.WithDescription("{{ $metric.Description }} [{{ $metric.Stability }}]"),
metric.WithUnit("{{ $metric.Unit }}"),
{{ if eq $metric.Data.Type "Histogram" -}}
{{- if $metric.Data.Boundaries -}}metric.WithExplicitBucketBoundaries([]float64{ {{- range $metric.Data.Boundaries }} {{.}}, {{- end }} }...),{{- end }}
{{- end }}
)
errs = errors.Join(errs, err)
{{- end }}
return &builder, errs
}
{{- end }}
================================================
FILE: cmd/mdatagen/internal/templates/telemetry_test.go.tmpl
================================================
// Code generated by mdatagen. DO NOT EDIT.
package {{ .Package }}
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/metric"
embeddedmetric "go.opentelemetry.io/otel/metric/embedded"
noopmetric "go.opentelemetry.io/otel/metric/noop"
"go.opentelemetry.io/otel/trace"
embeddedtrace "go.opentelemetry.io/otel/trace/embedded"
nooptrace "go.opentelemetry.io/otel/trace/noop"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
)
type mockMeter struct {
noopmetric.Meter
name string
}
type mockMeterProvider struct {
embeddedmetric.MeterProvider
}
func (m mockMeterProvider) Meter(name string, opts ...metric.MeterOption) metric.Meter {
return mockMeter{name: name}
}
type mockTracer struct {
nooptrace.Tracer
name string
}
type mockTracerProvider struct {
embeddedtrace.TracerProvider
}
func (m mockTracerProvider) Tracer(name string, opts ...trace.TracerOption) trace.Tracer {
return mockTracer{name: name}
}
func TestProviders(t *testing.T) {
set := component.TelemetrySettings{
MeterProvider: mockMeterProvider{},
TracerProvider: mockTracerProvider{},
}
meter := Meter(set)
if m, ok := meter.(mockMeter); ok {
require.Equal(t, "{{ .ScopeName }}", m.name)
} else {
require.Fail(t, "returned Meter not mockMeter")
}
tracer := Tracer(set)
if m, ok := tracer.(mockTracer); ok {
require.Equal(t, "{{ .ScopeName }}", m.name)
} else {
require.Fail(t, "returned Meter not mockTracer")
}
}
{{- if .Telemetry.Metrics }}
func TestNewTelemetryBuilder(t *testing.T) {
set := componenttest.NewNopTelemetrySettings()
applied := false
_, err := NewTelemetryBuilder(set, telemetryBuilderOptionFunc(func(b *TelemetryBuilder) {
applied = true
}))
require.NoError(t, err)
require.True(t, applied)
}
{{- end }}
================================================
FILE: cmd/mdatagen/internal/templates/telemetrytest.go.tmpl
================================================
// Code generated by mdatagen. DO NOT EDIT.
package {{ .Package }}test
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
{{- if or isConnector isExporter isExtension isProcessor isReceiver isScraper }}
"go.opentelemetry.io/collector/component"
{{- end }}
"go.opentelemetry.io/collector/component/componenttest"
{{- if or isConnector isExporter isExtension isProcessor isReceiver isScraper }}
"go.opentelemetry.io/collector/{{ .Status.Class }}"
"go.opentelemetry.io/collector/{{ .Status.Class }}/{{ .Status.Class }}test"
{{- end }}
)
{{ if or isConnector isExporter isExtension isProcessor isReceiver isScraper }}
func NewSettings(tt *componenttest.Telemetry) {{ .Status.Class }}.Settings {
set := {{ .Status.Class }}test.NewNopSettings({{ .Status.Class }}test.NopType)
set.ID = component.NewID(component.MustNewType("{{ .Type }}"))
set.TelemetrySettings = tt.NewTelemetrySettings()
return set
}
{{- end }}
{{ range $name, $metric := .Telemetry.Metrics }}
func AssertEqual{{ $name.Render }}(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.{{- if eq $metric.Data.Type "Histogram" }} {{$metric.Data.Type}} {{- end }}DataPoint[{{ $metric.Data.BasicType }}], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
{{ if $metric.Prefix -}}
Name: "{{ $metric.Prefix }}{{ $name }}",
{{ else -}}
Name: "otelcol_{{ $name }}",
{{ end -}}
Description: "{{ $metric.Description }} [{{ $metric.Stability }}]",
Unit: "{{ $metric.Unit }}",
Data: metricdata.{{ $metric.Data.Type }}[{{ $metric.Data.BasicType }}]{
{{- if $metric.Data.HasAggregated }}
Temporality: metricdata.CumulativeTemporality,
{{- end }}
{{- if $metric.Data.HasMonotonic }}
IsMonotonic: {{ $metric.Data.Monotonic }},
{{- end }}
DataPoints: dps,
},
}
{{ if $metric.Prefix -}}
got, err := tt.GetMetric("{{ $metric.Prefix }}{{ $name }}")
{{ else -}}
got, err := tt.GetMetric("otelcol_{{ $name }}")
{{ end -}}
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
{{- end }}
================================================
FILE: cmd/mdatagen/internal/templates/telemetrytest_test.go.tmpl
================================================
// Code generated by mdatagen. DO NOT EDIT.
package {{ .Package }}test
import (
"context"
"testing"
"github.com/stretchr/testify/require"
{{- if .Telemetry.Metrics }}
{{- range $_, $metric := .Telemetry.Metrics }}
{{- if $metric.Data.Async }}
"go.opentelemetry.io/otel/metric"
{{- break}}
{{- end }}
{{- end }}
{{- end }}
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"go.opentelemetry.io/collector/component/componenttest"
"{{ .PackageName }}/internal/{{ .GeneratedPackageName }}"
)
func TestSetupTelemetry(t *testing.T) {
testTel := componenttest.NewTelemetry()
tb, err := {{ .Package }}.NewTelemetryBuilder(testTel.NewTelemetrySettings())
require.NoError(t, err)
defer tb.Shutdown()
{{- range $name, $metric := .Telemetry.Metrics }}
{{- if $metric.Data.Async }}
require.NoError(t, tb.Register{{ $name.Render }}Callback(func(_ context.Context, observer metric.{{ casesTitle $metric.Data.BasicType }}Observer) error {
observer.Observe(1)
return nil
}))
{{- end }}
{{- end }}
{{- range $name, $metric := .Telemetry.Metrics }}
{{- if not $metric.Data.Async }}
{{- if eq $metric.Data.Type "Sum" }}
tb.{{ $name.Render }}.Add(context.Background(), 1)
{{- else }}
tb.{{ $name.Render }}.Record(context.Background(), 1)
{{- end }}
{{- end }}
{{- end }}
{{- range $name, $metric := .Telemetry.Metrics }}
AssertEqual{{ $name.Render }}(t, testTel,
{{ if eq $metric.Data.Type "Gauge" -}}
[]metricdata.DataPoint[{{ $metric.Gauge.MetricValueType.BasicType }}]{{"{{Value: 1}}"}},
{{- else if eq $metric.Data.Type "Sum" -}}
[]metricdata.DataPoint[{{ $metric.Sum.MetricValueType.BasicType }}]{{"{{Value: 1}}"}},
{{- else if eq $metric.Data.Type "Histogram" -}}
[]metricdata.HistogramDataPoint[{{ $metric.Histogram.MetricValueType.BasicType }}]{{"{{}}"}}, metricdatatest.IgnoreValue(),
{{- end }}
metricdatatest.IgnoreTimestamp())
{{- end }}
require.NoError(t, testTel.Shutdown(context.Background()))
}
================================================
FILE: cmd/mdatagen/internal/templates/testdata/config.yaml.tmpl
================================================
{{- $reag := .ReaggregationEnabled -}}
default:
all_set:
{{- if $reag }}
{{- if .Metrics }}
metrics:
{{- range $name, $metric := .Metrics }}
{{- $metricReag := and $reag (hasAggregatableAttributes $metric.Attributes) }}
{{ $name }}:
enabled: true
{{- if $metricReag }}
attributes: [{{- range $index, $element := $metric.Attributes -}}{{ if $index }},{{ end }}"{{ (attributeInfo $element).Name }}"{{ end -}}]
{{- end }}
{{- end }}
{{- end }}
{{- if .Events }}
events:
{{- range $name, $_ := .Events }}
{{ $name }}:
enabled: true
{{- end }}
{{- end }}
{{- if .ResourceAttributes }}
resource_attributes:
{{- range $name, $_ := .ResourceAttributes }}
{{ $name }}:
enabled: true
{{- end }}
{{- end }}
reaggregate_set:
{{- end }}
{{- if .Metrics }}
metrics:
{{- range $name, $metric := .Metrics }}
{{- $metricReag := and $reag (hasAggregatableAttributes $metric.Attributes) }}
{{ $name }}:
enabled: true
{{- if $metricReag }}
attributes: [{{- range $index, $attr := requiredAttributes $metric.Attributes -}}{{ if $index }},{{ end }}"{{ $attr }}"{{- end -}}]
{{- end }}
{{- end }}
{{- end }}
{{- if .Events }}
events:
{{- range $name, $_ := .Events }}
{{ $name }}:
enabled: true
{{- end }}
{{- end }}
{{- if .ResourceAttributes }}
resource_attributes:
{{- range $name, $_ := .ResourceAttributes }}
{{ $name }}:
enabled: true
{{- end }}
{{- end }}
none_set:
{{- if .Metrics }}
metrics:
{{- range $name, $metric := .Metrics }}
{{- $metricReag := and $reag (hasAggregatableAttributes $metric.Attributes) }}
{{ $name }}:
enabled: false
{{- if $metricReag }}
attributes: [{{- range $index, $element := $metric.Attributes -}}{{ if $index }},{{ end }}"{{ (attributeInfo $element).Name }}"{{ end -}}]
{{- end }}
{{- end }}
{{- end }}
{{- if .Events }}
events:
{{- range $name, $_ := .Events }}
{{ $name }}:
enabled: false
{{- end }}
{{- end }}
{{- if .ResourceAttributes }}
resource_attributes:
{{- range $name, $_ := .ResourceAttributes }}
{{ $name }}:
enabled: false
{{- end }}
{{- end }}
{{- if and (or .Metrics .Events) .ResourceAttributes }}
filter_set_include:
resource_attributes:
{{- range $name, $attr := .ResourceAttributes }}
{{ $name }}:
enabled: true
{{- if $.Metrics }}
metrics_include:
- regexp: ".*"
{{- end }}
{{- if $.Events }}
events_include:
- regexp: ".*"
{{- end }}
{{- end }}
filter_set_exclude:
resource_attributes:
{{- range $name, $attr := .ResourceAttributes }}
{{ $name }}:
enabled: true
{{- if $.Metrics }}
metrics_exclude:
{{- if eq $attr.Type.String "Str" }}
- strict: {{ $attr.TestValue }}
{{- else }}
- regexp: ".*"
{{- end }}
{{- end }}
{{- if $.Events }}
events_exclude:
{{- if eq $attr.Type.String "Str" }}
- strict: {{ $attr.TestValue }}
{{- else }}
- regexp: ".*"
{{- end }}
{{- end }}
{{- end }}
{{- end }}
================================================
FILE: cmd/mdatagen/internal/testdata/async_metric.yaml
================================================
type: metricreceiver
status:
class: receiver
stability:
beta: [traces]
stable: [metrics]
distributions: [contrib]
warnings:
- Any additional information that should be brought to the consumer's attention
metrics:
metric:
enabled: true
description: Description.
stability: development
unit: s
gauge:
value_type: double
async: true
tests:
skip_lifecycle: true
skip_shutdown: true
================================================
FILE: cmd/mdatagen/internal/testdata/basic_connector.yaml
================================================
type: test
status:
class: connector
stability:
beta: [traces_to_traces]
================================================
FILE: cmd/mdatagen/internal/testdata/basic_pkg.yaml
================================================
type: test
status:
class: pkg
stability:
beta: [logs]
================================================
FILE: cmd/mdatagen/internal/testdata/basic_receiver.yaml
================================================
type: test
status:
class: receiver
stability:
beta: [logs]
================================================
FILE: cmd/mdatagen/internal/testdata/custom_generated_package_name.yaml
================================================
type: metricreceiver
generated_package_name: custom
status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
distributions: [contrib]
warnings:
- Any additional information that should be brought to the consumer's attention
tests:
skip_lifecycle: true
skip_shutdown: true
================================================
FILE: cmd/mdatagen/internal/testdata/deprecation_info_invalid_date.yaml
================================================
type: file
status:
class: receiver
stability:
beta: [logs]
stable: [metrics]
deprecated: [traces]
deprecation:
traces:
date: "05-09-2007"
migration: "no migration needed"
================================================
FILE: cmd/mdatagen/internal/testdata/display_name.yaml
================================================
type: test
display_name: Test Receiver
status:
class: receiver
stability:
beta: [logs]
================================================
FILE: cmd/mdatagen/internal/testdata/documentation.md
================================================
[comment]: <> (Code generated by mdatagen. DO NOT EDIT.)
# sample
## Resource Attributes
| Name | Description | Values | Enabled |
| ---- | ----------- | ------ | ------- |
| host.id | The unique host identifier | Any Str | true |
| host.name | The hostname | Any Str | true |
| process.pid | The process identifier | Any Int | true |
================================================
FILE: cmd/mdatagen/internal/testdata/empty.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package testdata
// this file allows `go list -f` to run in tests and get the scope name.
================================================
FILE: cmd/mdatagen/internal/testdata/empty_test_config.yaml
================================================
type: test
status:
class: receiver
stability:
beta: [logs]
tests:
config:
================================================
FILE: cmd/mdatagen/internal/testdata/entity_duplicate_attributes.yaml
================================================
type: sample
status:
class: receiver
stability:
stable: [metrics]
resource_attributes:
host.id:
description: The host identifier
type: string
enabled: true
host.name:
description: The hostname
type: string
enabled: true
process.pid:
description: The process identifier
type: int
enabled: true
entities:
- type: host
brief: A host instance.
stability: stable
identity:
- ref: host.id
description:
- ref: host.name
- type: process
brief: A process instance.
stability: stable
identity:
- ref: process.pid
description:
- ref: host.name
================================================
FILE: cmd/mdatagen/internal/testdata/entity_duplicate_types.yaml
================================================
type: sample
status:
class: receiver
stability:
stable: [metrics]
resource_attributes:
host.id:
description: The host identifier
type: string
enabled: true
host.name:
description: The hostname
type: string
enabled: true
entities:
- type: host
brief: A host instance.
stability: stable
identity:
- ref: host.id
- type: host
brief: Another host instance.
stability: stable
identity:
- ref: host.name
================================================
FILE: cmd/mdatagen/internal/testdata/entity_empty_id_attributes.yaml
================================================
type: sample
status:
class: receiver
stability:
stable: [metrics]
resource_attributes:
host.name:
description: The hostname
type: string
enabled: true
entities:
- type: host
brief: A host instance.
stability: stable
identity: []
description:
- ref: host.name
================================================
FILE: cmd/mdatagen/internal/testdata/entity_event_missing_association.yaml
================================================
type: sample
status:
class: receiver
stability:
stable: [metrics]
resource_attributes:
host.id:
description: The unique host identifier
type: string
enabled: true
process.pid:
description: The process identifier
type: int
enabled: true
entities:
- type: host
brief: A host instance.
stability: stable
identity:
- ref: host.id
- type: process
brief: A process instance.
stability: stable
identity:
- ref: process.pid
attributes:
test.attr:
description: Test attribute
type: string
events:
host.restart:
enabled: true
description: Host restart event
attributes: [test.attr]
================================================
FILE: cmd/mdatagen/internal/testdata/entity_metric_missing_association.yaml
================================================
type: sample
status:
class: receiver
stability:
stable: [metrics]
resource_attributes:
host.id:
description: The unique host identifier
type: string
enabled: true
process.pid:
description: The process identifier
type: int
enabled: true
entities:
- type: host
brief: A host instance.
stability: stable
identity:
- ref: host.id
- type: process
brief: A process instance.
stability: stable
identity:
- ref: process.pid
attributes:
test.attr:
description: Test attribute
type: string
metrics:
host.cpu.time:
enabled: true
description: Host CPU time
unit: s
sum:
value_type: int
monotonic: true
aggregation_temporality: cumulative
stability: stable
attributes: [test.attr]
================================================
FILE: cmd/mdatagen/internal/testdata/entity_metrics_events_valid.yaml
================================================
type: sample
status:
class: receiver
stability:
stable: [metrics]
resource_attributes:
host.id:
description: The unique host identifier
type: string
enabled: true
host.name:
description: The hostname
type: string
enabled: true
process.pid:
description: The process identifier
type: int
enabled: true
entities:
- type: host
brief: A host instance.
stability: stable
identity:
- ref: host.id
description:
- ref: host.name
- type: process
brief: A process instance.
stability: stable
identity:
- ref: process.pid
attributes:
test.attr:
description: Test attribute
type: string
metrics:
host.cpu.time:
enabled: true
description: Host CPU time
unit: s
sum:
value_type: int
monotonic: true
aggregation_temporality: cumulative
stability: stable
entity: host
attributes: [test.attr]
process.cpu.time:
enabled: true
description: Process CPU time
unit: s
sum:
value_type: int
monotonic: true
aggregation_temporality: cumulative
stability: stable
entity: process
attributes: [test.attr]
events:
host.restart:
enabled: true
description: Host restart event
entity: host
attributes: [test.attr]
process.start:
enabled: true
description: Process start event
entity: process
attributes: [test.attr]
================================================
FILE: cmd/mdatagen/internal/testdata/entity_relationships_bidirectional.yaml
================================================
type: sample
status:
class: receiver
stability:
stable: [metrics]
resource_attributes:
k8s.replicaset.uid:
description: The unique identifier of the Kubernetes ReplicaSet
type: string
enabled: true
k8s.pod.uid:
description: The unique identifier of the Kubernetes pod
type: string
enabled: true
entities:
- type: k8s.replicaset
brief: A Kubernetes ReplicaSet
stability: stable
identity:
- ref: k8s.replicaset.uid
relationships:
- type: controls
target: k8s.pod
- type: k8s.pod
brief: A Kubernetes pod
stability: stable
identity:
- ref: k8s.pod.uid
relationships:
- type: controlled_by
target: k8s.replicaset
================================================
FILE: cmd/mdatagen/internal/testdata/entity_relationships_empty_target.yaml
================================================
type: sample
status:
class: receiver
stability:
stable: [metrics]
resource_attributes:
k8s.replicaset.uid:
description: The unique identifier of the Kubernetes ReplicaSet
type: string
enabled: true
k8s.pod.uid:
description: The unique identifier of the Kubernetes pod
type: string
enabled: true
entities:
- type: k8s.replicaset
brief: A Kubernetes ReplicaSet
stability: stable
identity:
- ref: k8s.replicaset.uid
- type: k8s.pod
brief: A Kubernetes pod
stability: stable
identity:
- ref: k8s.pod.uid
relationships:
- type: controlled_by
================================================
FILE: cmd/mdatagen/internal/testdata/entity_relationships_empty_type.yaml
================================================
type: sample
status:
class: receiver
stability:
stable: [metrics]
resource_attributes:
k8s.replicaset.uid:
description: The unique identifier of the Kubernetes ReplicaSet
type: string
enabled: true
k8s.pod.uid:
description: The unique identifier of the Kubernetes pod
type: string
enabled: true
entities:
- type: k8s.replicaset
brief: A Kubernetes ReplicaSet
stability: stable
identity:
- ref: k8s.replicaset.uid
- type: k8s.pod
brief: A Kubernetes pod
stability: stable
identity:
- ref: k8s.pod.uid
relationships:
- target: k8s.replicaset
================================================
FILE: cmd/mdatagen/internal/testdata/entity_relationships_undefined_target.yaml
================================================
type: sample
status:
class: receiver
stability:
stable: [metrics]
resource_attributes:
k8s.pod.uid:
description: The unique identifier of the Kubernetes pod
type: string
enabled: true
entities:
- type: k8s.pod
brief: A Kubernetes pod
stability: stable
identity:
- ref: k8s.pod.uid
relationships:
- type: controlled_by
target: k8s.replicaset
================================================
FILE: cmd/mdatagen/internal/testdata/entity_relationships_valid.yaml
================================================
type: sample
status:
class: receiver
stability:
stable: [metrics]
resource_attributes:
k8s.replicaset.uid:
description: The unique identifier of the Kubernetes ReplicaSet
type: string
enabled: true
k8s.replicaset.name:
description: The name of the Kubernetes ReplicaSet
type: string
enabled: true
k8s.pod.uid:
description: The unique identifier of the Kubernetes pod
type: string
enabled: true
k8s.pod.name:
description: The name of the Kubernetes pod
type: string
enabled: true
entities:
- type: k8s.replicaset
brief: A Kubernetes ReplicaSet
stability: stable
identity:
- ref: k8s.replicaset.uid
description:
- ref: k8s.replicaset.name
- type: k8s.pod
brief: A Kubernetes pod
stability: stable
identity:
- ref: k8s.pod.uid
description:
- ref: k8s.pod.name
relationships:
- type: controlled_by
target: k8s.replicaset
================================================
FILE: cmd/mdatagen/internal/testdata/entity_single_metric_missing_association.yaml
================================================
type: sample
status:
class: receiver
stability:
stable: [metrics]
resource_attributes:
host.id:
description: The unique host identifier
type: string
enabled: true
entities:
- type: host
brief: A host instance.
stability: stable
identity:
- ref: host.id
attributes:
test.attr:
description: Test attribute
type: string
metrics:
host.cpu.time:
enabled: true
description: Host CPU time
unit: s
sum:
value_type: int
monotonic: true
aggregation_temporality: cumulative
stability: stable
attributes: [test.attr]
================================================
FILE: cmd/mdatagen/internal/testdata/entity_undefined_description_attribute.yaml
================================================
type: sample
status:
class: receiver
stability:
stable: [metrics]
resource_attributes:
host.id:
description: The host identifier
type: string
enabled: true
entities:
- type: host
brief: A host instance.
stability: stable
identity:
- ref: host.id
description:
- ref: host.missing
================================================
FILE: cmd/mdatagen/internal/testdata/entity_undefined_id_attribute.yaml
================================================
type: sample
status:
class: receiver
stability:
stable: [metrics]
resource_attributes:
host.name:
description: The hostname
type: string
enabled: true
entities:
- type: host
brief: A host instance.
stability: stable
identity:
- ref: host.missing
description:
- ref: host.name
================================================
FILE: cmd/mdatagen/internal/testdata/entity_undefined_reference.yaml
================================================
type: sample
status:
class: receiver
stability:
stable: [metrics]
resource_attributes:
host.id:
description: The unique host identifier
type: string
enabled: true
entities:
- type: host
brief: A host instance.
stability: stable
identity:
- ref: host.id
attributes:
test.attr:
description: Test attribute
type: string
metrics:
host.cpu.time:
enabled: true
description: Host CPU time
unit: s
sum:
value_type: int
monotonic: true
aggregation_temporality: cumulative
stability: stable
entity: undefined_entity
attributes: [test.attr]
================================================
FILE: cmd/mdatagen/internal/testdata/entity_valid.yaml
================================================
type: sample
status:
class: receiver
stability:
stable: [metrics]
resource_attributes:
host.id:
description: The unique host identifier
type: string
enabled: true
host.name:
description: The hostname
type: string
enabled: true
process.pid:
description: The process identifier
type: int
enabled: true
entities:
- type: host
brief: A host instance.
stability: stable
identity:
- ref: host.id
description:
- ref: host.name
- type: process
brief: A process instance.
stability: stable
identity:
- ref: process.pid
================================================
FILE: cmd/mdatagen/internal/testdata/events/basic_event.yaml
================================================
type: receiver
status:
class: receiver
stability:
development: [logs]
distributions: [contrib]
warnings:
- Any additional information that should be brought to the consumer's attention
events:
event:
enabled: true
description: Description.
================================================
FILE: cmd/mdatagen/internal/testdata/events/empty.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package events
// this file allows `go list -f` to run in tests and get the scope name.
================================================
FILE: cmd/mdatagen/internal/testdata/events/no_description.yaml
================================================
type: file
status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
distributions: [contrib]
warnings:
- Any additional information that should be brought to the consumer's attention
events:
default.event:
enabled: true
extended_documentation: The event will be renamed soon.
================================================
FILE: cmd/mdatagen/internal/testdata/events/no_enabled.yaml
================================================
type: receiver
status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
events:
system.event:
description: The system event collected by opentelemetry collector.
attributes:
================================================
FILE: cmd/mdatagen/internal/testdata/events/unknown_attribute.yaml
================================================
type: receiver
status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
events:
system.event:
enabled: true
description: The system event collected by opentelemetry collector.
attributes: [missing]
================================================
FILE: cmd/mdatagen/internal/testdata/feature_gates.yaml
================================================
type: sample
status:
class: receiver
stability:
beta: [metrics]
feature_gates:
- id: sample.feature.gate
description: 'This is a sample feature gate for testing purposes'
stage: alpha
from_version: 'v0.100.0'
reference_url: 'https://github.com/open-telemetry/opentelemetry-collector/issues/12345'
- id: stable.feature.gate
description: 'This is a stable feature gate'
stage: stable
from_version: 'v0.90.0'
to_version: 'v0.95.0'
reference_url: 'https://github.com/open-telemetry/opentelemetry-collector/issues/11111'
================================================
FILE: cmd/mdatagen/internal/testdata/generated_component_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package testdata
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/confmap/confmaptest"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/receivertest"
)
var typ = component.MustNewType("sample")
func TestComponentFactoryType(t *testing.T) {
require.Equal(t, typ, NewFactory().Type())
}
func TestComponentConfigStruct(t *testing.T) {
require.NoError(t, componenttest.CheckConfigStruct(NewFactory().CreateDefaultConfig()))
}
func TestComponentLifecycle(t *testing.T) {
factory := NewFactory()
tests := []struct {
createFn func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error)
name string
}{
{
name: "metrics",
createFn: func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateMetrics(ctx, set, cfg, consumertest.NewNop())
},
},
}
cm, err := confmaptest.LoadConf("metadata.yaml")
require.NoError(t, err)
cfg := factory.CreateDefaultConfig()
sub, err := cm.Sub("tests::config")
require.NoError(t, err)
require.NoError(t, sub.Unmarshal(&cfg))
for _, tt := range tests {
t.Run(tt.name+"-shutdown", func(t *testing.T) {
c, err := tt.createFn(context.Background(), receivertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
err = c.Shutdown(context.Background())
require.NoError(t, err)
})
t.Run(tt.name+"-lifecycle", func(t *testing.T) {
firstRcvr, err := tt.createFn(context.Background(), receivertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
host := newMdatagenNopHost()
require.NoError(t, err)
require.NoError(t, firstRcvr.Start(context.Background(), host))
require.NoError(t, firstRcvr.Shutdown(context.Background()))
secondRcvr, err := tt.createFn(context.Background(), receivertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
require.NoError(t, secondRcvr.Start(context.Background(), host))
require.NoError(t, secondRcvr.Shutdown(context.Background()))
})
}
}
var _ component.Host = (*mdatagenNopHost)(nil)
type mdatagenNopHost struct{}
func newMdatagenNopHost() component.Host {
return &mdatagenNopHost{}
}
func (mnh *mdatagenNopHost) GetExtensions() map[component.ID]component.Component {
return nil
}
func (mnh *mdatagenNopHost) GetFactory(_ component.Kind, _ component.Type) component.Factory {
return nil
}
================================================
FILE: cmd/mdatagen/internal/testdata/generated_package_name.yaml
================================================
type: custom
generated_package_name: customname
status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
================================================
FILE: cmd/mdatagen/internal/testdata/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package testdata
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: cmd/mdatagen/internal/testdata/internal/metadata/generated_status.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/component"
)
var (
Type = component.MustNewType("sample")
ScopeName = "go.opentelemetry.io/collector/cmd/mdatagen/internal/testdata"
)
const (
MetricsStability = component.StabilityLevelBeta
)
================================================
FILE: cmd/mdatagen/internal/testdata/invalid.yaml
================================================
invalid
================================================
FILE: cmd/mdatagen/internal/testdata/invalid_aggregation.yaml
================================================
type: metricreceiver
status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
distributions: [contrib]
warnings:
- Any additional information that should be brought to the consumer's attention
metrics:
default.metric:
enabled: true
description: Monotonic cumulative sum int metric enabled by default.
extended_documentation: The metric will be become optional soon.
stability: development
unit: s
sum:
value_type: int
monotonic: true
aggregation_temporality: invalidaggregation
================================================
FILE: cmd/mdatagen/internal/testdata/invalid_class.yaml
================================================
type: test
status:
class: incorrectclass
stability:
development: [logs]
beta: [traces]
stable: [metrics]
================================================
FILE: cmd/mdatagen/internal/testdata/invalid_config.yaml
================================================
type: receiver
status:
class: receiver
stability:
beta: [logs]
distributions: [contrib]
config:
type: string
properties:
endpoint:
type: string
================================================
FILE: cmd/mdatagen/internal/testdata/invalid_entity_stability.yaml
================================================
type: sample
status:
class: receiver
stability:
stable: [metrics]
resource_attributes:
host.id:
description: The unique host identifier
type: string
enabled: true
host.name:
description: The hostname
type: string
enabled: true
process.pid:
description: The process identifier
type: int
enabled: true
entities:
- type: host
brief: A host instance.
stability: stable42
identity:
- ref: host.id
description:
- ref: host.name
================================================
FILE: cmd/mdatagen/internal/testdata/invalid_input_type.yaml
================================================
type: metricreceiver
status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
metrics:
system.cpu.time:
enabled: true
description: Total CPU seconds broken down by different states.
stability: development
unit: s
sum:
value_type: double
monotonic: true
aggregation_temporality: cumulative
input_type: double
attributes:
================================================
FILE: cmd/mdatagen/internal/testdata/invalid_metric_semconvref.yaml
================================================
type: metricreceiver
status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
distributions: [contrib]
warnings:
- Any additional information that should be brought to the consumer's attention
sem_conv_version: 1.37.2
metrics:
default.metric:
enabled: true
description: Monotonic cumulative sum int metric enabled by default.
extended_documentation: The metric will be become optional soon.
stability: development
semantic_convention:
ref: https://github.com/open-telemetry/semantic-conventions/blob/v1.38.0/docs/system/system-metrics.md#metric-systemcputime
unit: s
sum:
value_type: int
monotonic: true
aggregation_temporality: cumulative
================================================
FILE: cmd/mdatagen/internal/testdata/invalid_metric_stability.yaml
================================================
type: metricreceiver
status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
distributions: [contrib]
warnings:
- Any additional information that should be brought to the consumer's attention
metrics:
default.metric:
enabled: true
description: Monotonic cumulative sum int metric enabled by default.
extended_documentation: The metric will be become optional soon.
stability: development42
unit: s
sum:
value_type: int
monotonic: true
aggregation_temporality: cumulative
================================================
FILE: cmd/mdatagen/internal/testdata/invalid_stability.yaml
================================================
type: file
status:
class: receiver
stability:
incorrectstability: [logs]
beta: [traces]
stable: [metrics]
================================================
FILE: cmd/mdatagen/internal/testdata/invalid_stability_component.yaml
================================================
type: file
status:
class: receiver
stability:
development: [incorrectcomponent]
beta: [traces]
stable: [metrics]
================================================
FILE: cmd/mdatagen/internal/testdata/invalid_telemetry_missing_value_type_for_histogram.yaml
================================================
type: metric
status:
class: receiver
stability:
beta: [traces, logs, metrics]
telemetry:
metrics:
sampling_decision_latency:
description: Latency (in microseconds) of a given sampling policy
unit: µs
enabled: true
histogram:
================================================
FILE: cmd/mdatagen/internal/testdata/invalid_type_attr.yaml
================================================
type: metricreceiver
sem_conv_version: 1.9.0
status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
attributes:
used_attr:
description: Used attribute.
type: invalidtype
metrics:
metric:
enabled: true
description: Metric.
unit: "1"
gauge:
value_type: double
attributes: [used_attr]
================================================
FILE: cmd/mdatagen/internal/testdata/invalid_type_rattr.yaml
================================================
type: file
sem_conv_version: 1.9.0
status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
distributions: [contrib]
warnings:
- Any additional information that should be brought to the consumer's attention
resource_attributes:
string.resource.attr:
description: Resource attribute with any string value.
type: invalidtype
enabled: true
================================================
FILE: cmd/mdatagen/internal/testdata/metrics_and_type.yaml
================================================
type: metricreceiver
status:
class: receiver
stability:
beta: [traces]
stable: [metrics]
distributions: [contrib]
warnings:
- Any additional information that should be brought to the consumer's attention
metrics:
metric:
enabled: true
description: Description.
stability: development
unit: s
gauge:
value_type: double
tests:
skip_lifecycle: true
skip_shutdown: true
================================================
FILE: cmd/mdatagen/internal/testdata/no_aggregation.yaml
================================================
type: file
status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
distributions: [contrib]
warnings:
- Any additional information that should be brought to the consumer's attention
metrics:
default.metric:
enabled: true
description: Monotonic cumulative sum int metric enabled by default.
extended_documentation: The metric will be become optional soon.
stability: development
unit: s
sum:
value_type: int
monotonic: false
================================================
FILE: cmd/mdatagen/internal/testdata/no_class.yaml
================================================
type: test
status:
stability:
development: [logs]
beta: [traces]
stable: [metrics]
================================================
FILE: cmd/mdatagen/internal/testdata/no_deprecation_date_info.yaml
================================================
type: file
status:
class: receiver
stability:
beta: [logs]
stable: [metrics]
deprecated: [traces]
deprecation:
traces:
migration: "no migration needed"
================================================
FILE: cmd/mdatagen/internal/testdata/no_deprecation_info.yaml
================================================
type: file
status:
class: receiver
stability:
beta: [logs]
stable: [metrics]
deprecated: [traces]
================================================
FILE: cmd/mdatagen/internal/testdata/no_deprecation_migration_info.yaml
================================================
type: file
status:
class: receiver
stability:
beta: [logs]
stable: [metrics]
deprecated: [traces]
deprecation:
traces:
date: "2006-05-09"
================================================
FILE: cmd/mdatagen/internal/testdata/no_description_attr.yaml
================================================
# Sample metric metadata file with all available configurations.
type: file
sem_conv_version: 1.9.0
status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
distributions: [contrib]
warnings:
- Any additional information that should be brought to the consumer's attention
attributes:
string_attr:
type: string
metrics:
default.metric:
enabled: true
description: Monotonic cumulative sum int metric enabled by default.
extended_documentation: The metric will be become optional soon.
stability: development
unit: s
sum:
value_type: int
monotonic: true
aggregation_temporality: cumulative
attributes: [string_attr]
warnings:
if_enabled_not_set: This metric will be disabled by default soon.
================================================
FILE: cmd/mdatagen/internal/testdata/no_description_rattr.yaml
================================================
type: file
status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
resource_attributes:
string.resource.attr:
type: string
enabled: true
================================================
FILE: cmd/mdatagen/internal/testdata/no_display_name.yaml
================================================
type: nodisplayname
status:
class: receiver
stability:
beta: [logs]
================================================
FILE: cmd/mdatagen/internal/testdata/no_enabled.yaml
================================================
type: metricreceiver
status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
metrics:
system.cpu.time:
description: Total CPU seconds broken down by different states.
stability: development
unit: s
sum:
value_type: double
monotonic: true
aggregation_temporality: cumulative
attributes:
================================================
FILE: cmd/mdatagen/internal/testdata/no_metric_description.yaml
================================================
type: file
status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
distributions: [contrib]
warnings:
- Any additional information that should be brought to the consumer's attention
metrics:
default.metric:
enabled: true
extended_documentation: The metric will be become optional soon.
stability: development
unit: s
sum:
value_type: int
monotonic: true
aggregation_temporality: cumulative
================================================
FILE: cmd/mdatagen/internal/testdata/no_metric_stability.yaml
================================================
type: metricreceiver
status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
distributions: [contrib]
warnings:
- Any additional information that should be brought to the consumer's attention
metrics:
default.metric:
enabled: true
description: Monotonic cumulative sum int metric enabled by default.
extended_documentation: The metric will be become optional soon.
stability: ~
unit: s
sum:
value_type: int
monotonic: true
aggregation_temporality: cumulative
================================================
FILE: cmd/mdatagen/internal/testdata/no_metric_type.yaml
================================================
type: metricreceiver
status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
metrics:
system.cpu.time:
enabled: true
description: Total CPU seconds broken down by different states.
stability: development
unit: s
attributes:
================================================
FILE: cmd/mdatagen/internal/testdata/no_metric_unit.yaml
================================================
type: file
status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
distributions: [contrib]
warnings:
- Any additional information that should be brought to the consumer's attention
metrics:
default.metric:
enabled: true
description: Monotonic cumulative sum int metric enabled by default.
extended_documentation: The metric will be become optional soon.
stability: development
sum:
value_type: int
monotonic: true
aggregation_temporality: cumulative
================================================
FILE: cmd/mdatagen/internal/testdata/no_monotonic.yaml
================================================
type: file
status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
distributions: [contrib]
warnings:
- Any additional information that should be brought to the consumer's attention
metrics:
default.metric:
enabled: true
description: Monotonic cumulative sum int metric enabled by default.
extended_documentation: The metric will be become optional soon.
stability: development
unit: s
sum:
value_type: int
aggregation_temporality: cumulative
================================================
FILE: cmd/mdatagen/internal/testdata/no_stability.yaml
================================================
type: test
status:
class: receiver
================================================
FILE: cmd/mdatagen/internal/testdata/no_stability_component.yaml
================================================
type: file
status:
class: receiver
stability:
beta:
stable: [metrics]
================================================
FILE: cmd/mdatagen/internal/testdata/no_status.yaml
================================================
type: test
================================================
FILE: cmd/mdatagen/internal/testdata/no_type.yaml
================================================
status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
================================================
FILE: cmd/mdatagen/internal/testdata/no_type_attr.yaml
================================================
type: metricreceiver
sem_conv_version: 1.9.0
status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
attributes:
used_attr:
description: Used attribute.
metrics:
metric:
enabled: true
description: Metric.
stability: development
unit: "1"
gauge:
value_type: double
attributes: [used_attr]
================================================
FILE: cmd/mdatagen/internal/testdata/no_type_rattr.yaml
================================================
type: file
sem_conv_version: 1.9.0
status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
distributions: [contrib]
warnings:
- Any additional information that should be brought to the consumer's attention
resource_attributes:
string.resource.attr:
description: Resource attribute with any string value.
enabled: true
================================================
FILE: cmd/mdatagen/internal/testdata/no_value_type.yaml
================================================
type: metricreceiver
status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
distributions: [contrib]
warnings:
- Any additional information that should be brought to the consumer's attention
metrics:
system.cpu.time:
enabled: true
description: Total CPU seconds broken down by different states.
stability: development
unit: s
sum:
monotonic: true
aggregation_temporality: cumulative
attributes:
================================================
FILE: cmd/mdatagen/internal/testdata/parent.yaml
================================================
type: subcomponent
parent: parentComponent
================================================
FILE: cmd/mdatagen/internal/testdata/readme_with_cmd_class.md
================================================
# Some component
| Status | |
| ------------- |-----------|
| Stability | [alpha]: logs |
| | [beta]: metrics |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aopen+is%3Aissue+label%3Acmd%2Ffoo) [](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aclosed+is%3Aissue+label%3Acmd%2Ffoo) |
[alpha]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#alpha
[beta]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#beta
Some info about a component
================================================
FILE: cmd/mdatagen/internal/testdata/readme_with_multiple_signals.md
================================================
# Some component
| Status | |
| ------------- |-----------|
| Stability | [alpha]: logs |
| | [beta]: metrics |
| Distributions | [contrib] |
[alpha]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#alpha
[beta]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#beta
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
Some info about a component
================================================
FILE: cmd/mdatagen/internal/testdata/readme_with_multiple_signals_and_deprecation.md
================================================
# Some component
| Status | |
| ------------- |-----------|
| Stability | [deprecated]: traces |
| | [alpha]: logs |
| | [beta]: metrics |
| Deprecation of traces | [Date]: 2025-02-05 |
| | [Migration Note]: no migration needed |
| Distributions | [contrib] |
[deprecated]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#deprecated
[alpha]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#alpha
[beta]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#beta
[Date]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#deprecation-information
[Migration Note]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#deprecation-information
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
Some info about a component
================================================
FILE: cmd/mdatagen/internal/testdata/readme_with_status.md
================================================
# Some component
| Status | |
| ------------- |-----------|
| Stability | [beta]: metrics |
| Distributions | [contrib] |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Areceiver%2Ffoo) [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aclosed+is%3Aissue+label%3Areceiver%2Ffoo) |
[beta]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#beta
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
Some info about a component
================================================
FILE: cmd/mdatagen/internal/testdata/readme_with_status_codeowners.md
================================================
# Some component
| Status | |
| ------------- |-----------|
| Stability | [beta]: metrics |
| Distributions | [contrib] |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aopen+is%3Aissue+label%3Areceiver%2Ffoo) [](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aclosed+is%3Aissue+label%3Areceiver%2Ffoo) |
| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@open-telemetry/collector-approvers](https://github.com/orgs/open-telemetry/teams/collector-approvers) |
[beta]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#beta
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
Some info about a component
================================================
FILE: cmd/mdatagen/internal/testdata/readme_with_status_codeowners_and_emeritus.md
================================================
# Some component
| Status | |
| ------------- |-----------|
| Stability | [beta]: metrics |
| Distributions | [contrib] |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aopen+is%3Aissue+label%3Areceiver%2Ffoo) [](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aclosed+is%3Aissue+label%3Areceiver%2Ffoo) |
| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@foo](https://www.github.com/foo) |
| Emeritus | [@bar](https://www.github.com/bar) |
[beta]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#beta
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
Some info about a component
================================================
FILE: cmd/mdatagen/internal/testdata/readme_with_status_codeowners_and_seeking_new.md
================================================
# Some component
| Status | |
| ------------- |-----------|
| Stability | [beta]: metrics |
| Distributions | [contrib] |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aopen+is%3Aissue+label%3Areceiver%2Ffoo) [](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aclosed+is%3Aissue+label%3Areceiver%2Ffoo) |
| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@foo](https://www.github.com/foo) \| Seeking more code owners! |
[beta]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#beta
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
Some info about a component
================================================
FILE: cmd/mdatagen/internal/testdata/readme_with_status_converter.md
================================================
# Some component
| Status | |
| ------------- |-----------|
| Stability | [beta] |
| Distributions | [contrib] |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aopen+is%3Aissue+label%3Aconverter%2Ffoo) [](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aclosed+is%3Aissue+label%3Aconverter%2Ffoo) |
[beta]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#beta
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
Some info about a component
================================================
FILE: cmd/mdatagen/internal/testdata/readme_with_status_extension.md
================================================
# Some component
| Status | |
| ------------- |-----------|
| Stability | [beta] |
| Distributions | [contrib] |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aopen+is%3Aissue+label%3Aextension%2Ffoo) [](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aclosed+is%3Aissue+label%3Aextension%2Ffoo) |
[beta]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#beta
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
Some info about a component
================================================
FILE: cmd/mdatagen/internal/testdata/readme_with_status_provider.md
================================================
# Some component
| Status | |
| ------------- |-----------|
| Stability | [beta] |
| Distributions | [contrib] |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aopen+is%3Aissue+label%3Aprovider%2Ffoo) [](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aclosed+is%3Aissue+label%3Aprovider%2Ffoo) |
[beta]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#beta
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
Some info about a component
================================================
FILE: cmd/mdatagen/internal/testdata/readme_with_warnings.md
================================================
# Some component
| Status | |
| ------------- |-----------|
| Stability | [beta]: metrics |
| Distributions | [contrib] |
| Warnings | [warning1](#warnings) |
[beta]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#beta
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
Some info about a component
### warnings
Some warning there.
================================================
FILE: cmd/mdatagen/internal/testdata/readme_without_status.md
================================================
# Some component
Some info about a component
================================================
FILE: cmd/mdatagen/internal/testdata/resource_attributes_only.yaml
================================================
type: test
status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
distributions: [contrib]
warnings:
- Any additional information that should be brought to the consumer's attention
resource_attributes:
res.attr1:
description: Resource attribute 1.
type: string
enabled: true
tests:
skip_lifecycle: true
skip_shutdown: true
================================================
FILE: cmd/mdatagen/internal/testdata/status_only.yaml
================================================
type: metricreceiver
status:
class: exporter
stability:
beta: [traces, metrics, logs]
distributions: [contrib]
tests:
skip_lifecycle: true
skip_shutdown: true
================================================
FILE: cmd/mdatagen/internal/testdata/two_metric_types.yaml
================================================
type: metricreceiver
status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
metrics:
system.cpu.time:
enabled: true
description: Total CPU seconds broken down by different states.
stability: development
unit: s
gauge:
value_type: double
sum:
value_type: double
monotonic: true
aggregation_temporality: cumulative
attributes:
================================================
FILE: cmd/mdatagen/internal/testdata/twopackages.yaml
================================================
type: sample
github_project: open-telemetry/opentelemetry-collector
status:
class: receiver
stability:
beta: [traces]
================================================
FILE: cmd/mdatagen/internal/testdata/undeprecated_with_deprecation.yaml
================================================
type: metricreceiver
status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
distributions: [contrib]
warnings:
- Any additional information that should be brought to the consumer's attention
metrics:
default.metric:
enabled: true
description: Monotonic cumulative sum int metric enabled by default.
extended_documentation: The metric will be become optional soon.
stability: development
deprecated:
note: this should not happen
unit: s
sum:
value_type: int
monotonic: true
aggregation_temporality: cumulative
================================================
FILE: cmd/mdatagen/internal/testdata/unknown_metric_attribute.yaml
================================================
type: metricreceiver
status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
metrics:
system.cpu.time:
enabled: true
description: Total CPU seconds broken down by different states.
stability: development
unit: s
sum:
value_type: double
monotonic: true
aggregation_temporality: cumulative
attributes: [missing]
================================================
FILE: cmd/mdatagen/internal/testdata/unknown_value_type.yaml
================================================
type: metricreceiver
status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
metrics:
system.cpu.time:
enabled: true
description: Total CPU seconds broken down by different states.
stability: development
unit: s
sum:
value_type: unknown
monotonic: true
aggregation_temporality: cumulative
================================================
FILE: cmd/mdatagen/internal/testdata/unsorted_rattr.yaml
================================================
type: sample
status:
class: receiver
stability:
beta: [logs]
resource_attributes:
cloud.region:
description: region
enabled: true
type: string
cloud.availability_zone:
description: az
enabled: true
type: string
================================================
FILE: cmd/mdatagen/internal/testdata/unused_attribute.yaml
================================================
type: metricreceiver
sem_conv_version: 1.9.0
status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
attributes:
used_attr_in_metrics_section:
description: Used attribute.
type: string
used_attr_in_telemetry_section:
description: Used attribute.
type: string
unused_attr:
name_override: state
description: Unused attribute.
type: string
metrics:
metric:
enabled: true
description: Metric.
stability: development
unit: "1"
gauge:
value_type: double
attributes: [used_attr_in_metrics_section]
telemetry:
metrics:
metric:
enabled: true
description: Metric.
stability: development
unit: "1"
gauge:
value_type: double
attributes: [used_attr_in_telemetry_section]
================================================
FILE: cmd/mdatagen/internal/testdata/with_conditional_attribute.yaml
================================================
type: receiver
status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
attributes:
conditional_int_attr:
description: Conditional int attr.
type: string
requirement_level: conditionally_required
opt_in_string_attr:
description: Opt-in string attr.
type: string
requirement_level: opt_in
metrics:
metric:
enabled: true
description: Metric.
stability: development
unit: "1"
gauge:
value_type: double
attributes: [conditional_int_attr, opt_in_string_attr]
events:
event:
enabled: true
description: Event.
attributes: [conditional_int_attr, opt_in_string_attr]
================================================
FILE: cmd/mdatagen/internal/testdata/with_config.yaml
================================================
type: receiver
status:
class: receiver
stability:
beta: [logs]
distributions: [contrib]
config:
type: object
properties:
endpoint:
description: The endpoint to connect to.
type: string
default: "localhost:4317"
timeout:
description: Timeout for requests.
type: string
format: duration
default: 10s
required: [endpoint]
tests:
skip_lifecycle: true
skip_shutdown: true
================================================
FILE: cmd/mdatagen/internal/testdata/with_description.yaml
================================================
type: testdesc
display_name: Test Component
description: This is a test component with a description.
status:
class: receiver
stability:
beta: [logs]
================================================
FILE: cmd/mdatagen/internal/testdata/with_goleak_ignores.yaml
================================================
type: foobar
status:
disable_codecov_badge: true
class: connector
stability:
beta: [traces_to_metrics, traces_to_traces, traces_to_logs, metrics_to_logs, metrics_to_metrics, metrics_to_traces, logs_to_logs, logs_to_metrics, logs_to_traces]
tests:
goleak:
ignore:
top:
- "testfunc1"
any:
- "testfunc2"
================================================
FILE: cmd/mdatagen/internal/testdata/with_goleak_setup.yaml
================================================
type: foobar
status:
disable_codecov_badge: true
class: connector
stability:
beta: [traces_to_metrics, traces_to_traces, traces_to_logs, metrics_to_logs, metrics_to_metrics, metrics_to_traces, logs_to_logs, logs_to_metrics, logs_to_traces]
tests:
goleak:
setup: "setupFunc()"
================================================
FILE: cmd/mdatagen/internal/testdata/with_goleak_skip.yaml
================================================
type: foobar
status:
disable_codecov_badge: true
class: connector
stability:
beta: [traces_to_metrics, traces_to_traces, traces_to_logs, metrics_to_logs, metrics_to_metrics, metrics_to_traces, logs_to_logs, logs_to_metrics, logs_to_traces]
tests:
goleak:
skip: true
================================================
FILE: cmd/mdatagen/internal/testdata/with_goleak_teardown.yaml
================================================
type: foobar
status:
disable_codecov_badge: true
class: connector
stability:
beta: [traces_to_metrics, traces_to_traces, traces_to_logs, metrics_to_logs, metrics_to_metrics, metrics_to_traces, logs_to_logs, logs_to_metrics, logs_to_traces]
tests:
goleak:
teardown: "teardownFunc()"
================================================
FILE: cmd/mdatagen/internal/testdata/with_invalid_config_ref.yaml
================================================
type: receiver
status:
class: receiver
stability:
beta: [logs]
distributions: [contrib]
# config has a local $ref without a definition name, which passes LoadMetadata
# validation but causes generateConfigFiles to return an error during schema resolution.
config:
type: object
properties:
sub:
$ref: "/config/configauth"
tests:
skip_lifecycle: true
skip_shutdown: true
================================================
FILE: cmd/mdatagen/internal/testdata/with_stability_from.yaml
================================================
type: test
status:
class: receiver
stability:
Beta: [metrics]
metrics:
test.metric:
enabled: true
description: Test metric with stability from field
unit: "1"
stability:
level: beta
from: "1.0.0"
sum:
value_type: int
aggregation_temporality: cumulative
monotonic: true
================================================
FILE: cmd/mdatagen/internal/testdata/with_telemetry.yaml
================================================
type: metric
status:
class: receiver
stability:
beta: [traces, logs, metrics]
attributes:
name:
description: Name of sampling decision
type: string
telemetry:
metrics:
sampling_decision_latency:
description: Latency (in microseconds) of a given sampling policy
unit: µs
enabled: true
stability: alpha
histogram:
value_type: int
attributes: [name]
================================================
FILE: cmd/mdatagen/internal/testdata/with_tests_connector.yaml
================================================
type: foobar
status:
disable_codecov_badge: true
class: connector
stability:
beta: [traces_to_metrics, traces_to_traces, traces_to_logs, metrics_to_logs, metrics_to_metrics, metrics_to_traces, logs_to_logs, logs_to_metrics, logs_to_traces]
================================================
FILE: cmd/mdatagen/internal/testdata/with_tests_exporter.yaml
================================================
type: metric
status:
class: exporter
stability:
beta: [traces, logs, metrics]
================================================
FILE: cmd/mdatagen/internal/testdata/with_tests_extension.yaml
================================================
type: metric
status:
class: extension
stability:
beta: [extension]
================================================
FILE: cmd/mdatagen/internal/testdata/with_tests_processor.yaml
================================================
type: metric
status:
class: processor
stability:
beta: [traces, logs, metrics]
================================================
FILE: cmd/mdatagen/internal/testdata/with_tests_profiles_connector.yaml
================================================
type: foobar
status:
disable_codecov_badge: true
class: connector
stability:
beta: [traces_to_profiles, metrics_to_profiles, logs_to_profiles, profiles_to_traces, profiles_to_metrics, profiles_to_logs, profiles_to_profiles]
================================================
FILE: cmd/mdatagen/internal/testdata/with_tests_receiver.yaml
================================================
type: metric
status:
class: receiver
stability:
beta: [traces, logs, metrics]
================================================
FILE: cmd/mdatagen/internal/testdata/with_underscore_in_semconv_ref_anchor_tag.yaml
================================================
type: metricreceiver
status:
class: receiver
stability:
development: [logs]
beta: [traces]
stable: [metrics]
distributions: [contrib]
warnings:
- Any additional information that should be brought to the consumer's attention
sem_conv_version: 1.38.0
metrics:
system.disk.io_time:
enabled: true
description: Time disk spent activated..
stability: development
semantic_convention:
ref: https://github.com/open-telemetry/semantic-conventions/blob/v1.38.0/docs/system/system-metrics.md#metric-systemdiskio_time
unit: s
sum:
value_type: double
monotonic: true
aggregation_temporality: cumulative
================================================
FILE: cmd/mdatagen/internal/tests.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/cmd/mdatagen/internal"
type Ignore struct {
Top []string `mapstructure:"top"`
Any []string `mapstructure:"any"`
}
type GoLeak struct {
Skip bool `mapstructure:"skip"`
Ignore Ignore `mapstructure:"ignore"`
Setup string `mapstructure:"setup"`
Teardown string `mapstructure:"teardown"`
}
type Tests struct {
Config any `mapstructure:"config"`
SkipLifecycle bool `mapstructure:"skip_lifecycle"`
SkipShutdown bool `mapstructure:"skip_shutdown"`
GoLeak GoLeak `mapstructure:"goleak"`
ExpectConsumerError bool `mapstructure:"expect_consumer_error"`
Host string `mapstructure:"host"`
}
================================================
FILE: cmd/mdatagen/main.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package main
//go:generate mdatagen metadata.yaml
import (
"os"
"github.com/spf13/cobra"
"go.opentelemetry.io/collector/cmd/mdatagen/internal"
)
func main() {
cmd, err := internal.NewCommand()
cobra.CheckErr(err)
if err := cmd.Execute(); err != nil {
os.Exit(1)
}
}
================================================
FILE: cmd/mdatagen/metadata-schema.yaml
================================================
# Required: The type of the component - Usually the name. The type and class combined uniquely identify the component (eg. receiver/otlp) or subcomponent (eg. receiver/hostmetricsreceiver/cpu)
type:
# Optional: A deprecated type that is still available as an alias.
deprecated_type: string
# Optional: Human-readable display name for the component. Used as the title in generated README files.
display_name: string
# Optional: Brief description of the component that will be included in the generated README.
description: string
# Required for subcomponents: The type of the parent component.
parent: string
# Optional: Scope name for the telemetry generated by the component. If not set, name of the go package will be used.
scope_name: string
# Optional: The name of the package that mdatagen generates. If not set, the name "metadata" will be used.
generated_package_name: string
# Optional: Enables per-metric reaggregation config generation for metrics with attributes.
# When enabled, generated metrics config includes `aggregation_strategy` and `attributes`
# fields that let users reduce metric cardinality by choosing which dimensions are kept.
reaggregation_enabled: bool
# Required for components (Optional for subcomponents): A high-level view of the development status and use of this component
status:
# Required: The class of the component (For example receiver)
class:
# Required: The stability of the component - See https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#stability-levels
stability:
development: []
alpha: []
beta: []
stable: []
deprecated: []
unmaintained: []
# Required for deprecated components: The deprecation information for the deprecated components - See https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#deprecation-information
deprecation:
:
date: string
migration: string
# Optional: The distributions that this component is bundled with (For example core or contrib). See statusdata.go for a list of common distros.
distributions: [string]
# Optional: A list of warnings that should be brought to the attention of users looking to use this component
warnings: [string]
# Optional: Metadata related to codeowners of the component
codeowners:
active: [string]
emeritus: [string]
unsupported_platforms: []
# Optional: OTel Semantic Conventions version that will be associated with the scraped metrics.
# This attribute should be set for metrics compliant with OTel Semantic Conventions.
sem_conv_version: 1.9.0
# Optional: JSON Schema based definition for the component's configuration structure.
# This section defines the schema that could be used to generate:
# - JSON Schema files that can be used for validation and documentation. (current)
# - Go structs representing the configuration with appropriate types and validation tags. (future)
# - Configuration reference documentation in the generated README files. (future)
config:
# Optional: Description of the configuration schema.
description: string
# Optional: Additional comments for developers (not included in JSON output).
$comment: string
# Required: The type of the configuration object. Typically "object" for component configs.
type:
# Optional: Map of configuration properties that define the component's configuration fields.
properties:
:
# Optional: Description of this configuration property.
description: string
# Optional: The JSON Schema type of this property.
type:
# Optional: Reference to another schema definition. Can be:
# - Internal reference: name of a type in $defs (e.g., "endpoint_config")
# - External reference: full package path with type (e.g., "go.opentelemetry.io/collector/config/confighttp.client_config")
# - Relative reference: local path (e.g., "./internal/metadata.config")
$ref: string
# Optional: Default value for this property if not specified.
default: any
# Optional: Example values for this property.
examples: [any]
# Optional: Indicates if this property is deprecated.
deprecated: bool
# Optional: For string types - allowed values.
enum: [string]
# Optional: Constant value - property must have exactly this value.
const: any
# Optional: For string types - regular expression pattern that the value must match.
pattern: string
# Optional: For string types - format specification (e.g., "uri", "email", "date-time", "duration").
format: string
# Optional: For string types - minimum length.
minLength: int
# Optional: For string types - maximum length.
maxLength: int
# Optional: For number/integer types - minimum value (inclusive).
minimum: number
# Optional: For number/integer types - maximum value (inclusive).
maximum: number
# Optional: For number/integer types - exclusive minimum value.
exclusiveMinimum: number
# Optional: For number/integer types - exclusive maximum value.
exclusiveMaximum: number
# Optional: For number types - value must be a multiple of this number.
multipleOf: number
# Optional: For object types - properties of the nested object.
properties: {} # Recursively follows same structure
# Optional: For object types - whether additional properties beyond those defined are allowed.
# Can be true, false, or an object schema defining the type of additional properties.
additionalProperties:
# Optional: For object types - minimum number of properties required.
minProperties: int
# Optional: For object types - maximum number of properties allowed.
maxProperties: int
# Optional: For array types - schema for array items.
items:
# Same structure as property definition
type: string
# ... other item properties
# Optional: For array types - minimum number of items.
minItems: int
# Optional: For array types - maximum number of items.
maxItems: int
# Optional: For array types - whether all items must be unique.
uniqueItems: bool
# Optional: All of these schemas must be satisfied (schema composition).
allOf:
- type: object
properties: {}
# Custom extension fields (not part of JSON Schema standard, used by mdatagen):
# Optional: Custom Go type name to use instead of generated type.
x-customType: string
# Optional: Whether this field should be a pointer in Go code.
x-pointer: bool
# Optional: Whether this field is optional in Go code (will use pointer or optional type).
x-optional: bool
# Optional: List of required property names. Properties in this list must be present in the config.
required: [string]
# Optional: Map of reusable schema definitions that can be referenced via $ref.
# These definitions are not directly part of the config but can be reused multiple times.
$defs:
:
# Same structure as property definition
type: object
properties: {}
# ... other schema properties
# Optional: map of resource attribute definitions with the key being the attribute name.
resource_attributes:
:
# Required: whether the resource attribute is added the emitted metrics by default.
enabled: bool
# Required: description of the attribute.
description:
# Optional: array of attribute values if they are static values (currently, only string type is supported).
enum: [string]
# Required: attribute value type.
type:
# Optional: warnings that will be shown to user under specified conditions.
warnings:
# A warning that will be displayed if the resource_attribute is enabled in user config.
# Should be used for deprecated default resource_attributes that will be removed soon.
if_enabled:
# A warning that will be displayed if `enabled` field is not set explicitly in user config.
# Should be used for resource_attributes that will be turned from default to optional or vice versa.
if_enabled_not_set:
# A warning that will be displayed if the resource_attribute is configured by user in any way.
# Should be used for deprecated optional resource_attributes that will be removed soon.
if_configured:
# Optional: array of entity definitions. Entities organize resource attributes into logical entities
# with identity and description attributes.
entities:
- # Required: the type of the entity.
type: string
# Required: a brief description of the entity.
brief: string
# Optional: the stability level of the entity.
stability:
# Required: array of references to resource attributes that uniquely identify this entity.
# All referenced attributes must be defined in the resource_attributes section.
identity:
- ref: string
# Optional: array of references to resource attributes that describe this entity.
# All referenced attributes must be defined in the resource_attributes section.
description:
- ref: string
# Optional: array of relationships to other entities. Relationships should be defined on only
# one end to avoid bidirectional definitions. It is recommended to define relationships on
# entities with lower lifespan (higher churn). For example, a pod should define its relationship
# to a replicaset, rather than the replicaset defining its relationship to pods.
relationships:
- # Required: the type of the relationship (e.g., "controlled_by", "parent", "peer").
type: string
# Required: the target entity type this entity relates to. Must reference an entity
# defined in the same metadata.yaml file.
target: string
# Optional: map of attribute definitions with the key being the attribute name and value
# being described below.
attributes:
:
# Optional: this field can be used to override the actual attribute name defined by the key.
# It should be used if multiple metrics have different attributes with the same name.
name_override:
# Required: description of the attribute.
description:
# Optional: array of attribute values if they are static values (currently, only string type is supported).
enum: [string]
# Required: attribute value type.
type:
# Optional: indicates requirement level of the attribute.
# - required: the attribute is always included and cannot be excluded.
# - conditionally_required: the attribute is included by default when certain conditions are met.
# - recommended (default behavior): the attribute is included by default but can be disabled via configuration.
# - opt_in: the attribute is not included unless explicitly enabled in user config.
requirement_level:
# Optional: map of metric names with the key being the metric name and value
# being described below.
metrics:
:
# Required: whether the metric is collected by default.
enabled: bool
# Required: metric description.
description:
# Optional: extended documentation of the metric.
extended_documentation:
# Optional: warnings that will be shown to user under specified conditions.
warnings:
# A warning that will be displayed if the metric is enabled in user config.
# Should be used for deprecated default metrics that will be removed soon.
if_enabled:
# A warning that will be displayed if `enabled` field is not set explicitly in user config.
# Should be used for metrics that will be turned from default to optional or vice versa.
if_enabled_not_set:
# A warning that will be displayed if the metrics is configured by user in any way.
# Should be used for deprecated optional metrics that will be removed soon.
if_configured:
# Required: metric unit as defined by https://ucum.org/ucum.html.
unit:
# Required: metric type with its settings.
:
# Required for sum and gauge metrics: type of number data point values.
value_type:
# Required for sum metric: whether the metric is monotonic (no negative delta values).
monotonic: bool
# Required for sum metric: whether reported values incorporate previous measurements
# (cumulative) or not (delta).
aggregation_temporality:
# Optional: Indicates the type the metric needs to be parsed from. If set, the generated
# functions will parse the value from string to value_type.
input_type: string
# Optional: array of attributes that were defined in the attributes section that are emitted by this metric.
attributes: [string]
# Optional: the entity type this metric is associated with.
# Required when entities are defined in the entities section.
# Must reference an entity type defined in the entities section.
entity: string
# Required: the metric stability
stability:
# Deprecation information for the metric. Required when stability is `deprecated`.
deprecated:
# Required: version when the metric was deprecated
since:
# Required: migration note
note:
# Optional: the reference to a semantic convention
semantic_convention:
ref:
# Optional: map of event names with the key being the event name and value
# being described below.
events:
:
# Required: whether the event is collected by default.
enabled: bool
# Required: event description.
description:
# Optional: extended documentation of the event.
extended_documentation:
# Optional: warnings that will be shown to user under specified conditions.
warnings:
# A warning that will be displayed if the event is enabled in user config.
# Should be used for deprecated default events that will be removed soon.
if_enabled:
# A warning that will be displayed if `enabled` field is not set explicitly in user config.
if_enabled_not_set:
# A warning that will be displayed if the event is configured by user in any way.
if_configured:
# Optional: array of attributes that were defined in the attributes section that are emitted by this event.
attributes: [string]
# Optional: the entity type this event is associated with.
# Required when entities are defined in the entities section.
# Must reference an entity type defined in the entities section.
entity: string
# Lifecycle tests generated for this component.
tests:
config: # {} by default, specific testing configuration for lifecycle tests.
# Skip lifecycle tests for this component. Not recommended for components that are not in development.
skip_lifecycle: false # false by default
# Skip shutdown tests for this component. Not recommended for components that are not in development.
skip_shutdown: false # false by default
# Whether it's expected that the Consume[Logs|Metrics|Traces] method will return an error with the given configuration.
expect_consumer_error: true # false by default
goleak: # {} by default generates a package_test to enable check for leaks
skip: false # set to true if goleak tests should be skipped
setup: string # Optional: supports configuring a setup function that runs before goleak checks
teardown: string # Optional: supports configuring a teardown function that runs before goleak checks
ignore:
top: [string] # Optional: array of strings representing functions that should be ignore via IgnoreTopFunction
any: [string] # Optional: array of strings representing functions that should be ignore via IgnoreAnyFunction
# Optional: map of metric names with the key being the metric name and value
# being described below.
telemetry:
metrics:
:
# Required: whether the metric is collected by default.
enabled: bool
# Required: metric description.
description:
# Optional: the stability of the metric. Set to alpha by default.
stability:
# Optional: the stability level of the metric. Set to alpha by default.
level: [alpha|stable|deprecated]
# Optional: the version current stability was introduced
from:
# Deprecation information for the metric. Required when stability is `deprecated`.
deprecated:
# Required: version when the metric was deprecated
since:
# Required: migration note
note:
# Optional: extended documentation of the metric.
extended_documentation:
# Optional: whether or not this metric is optional. Optional metrics may only be initialized
# if certain features are enabled or configured.
optional: bool
# Optional: warnings that will be shown to user under specified conditions.
warnings:
# A warning that will be displayed if the metric is enabled in user config.
# Should be used for deprecated default metrics that will be removed soon.
if_enabled:
# A warning that will be displayed if `enabled` field is not set explicitly in user config.
# Should be used for metrics that will be turned from default to optional or vice versa.
if_enabled_not_set:
# A warning that will be displayed if the metrics is configured by user in any way.
# Should be used for deprecated optional metrics that will be removed soon.
if_configured:
# Required: metric unit as defined by https://ucum.org/ucum.html.
unit:
# Required: metric type with its settings.
:
# Optional: Whether this metric is asynchronous. If async, a mechanism is required to be able to
# pass in options to the callbacks that are called when the metric is observed.
async: bool
# Required: type of number data point values.
value_type:
# Required for sum metric: whether the metric is monotonic (no negative delta values).
monotonic: bool
# Bucket boundaries are only available to set for histogram metrics.
bucket_boundaries: [double]
# Optional: array of attributes that were defined in the attributes section that are emitted by this metric.
# Note: Only the following attribute types are supported:
attributes: [string]
# Optional: list of feature gate definitions.
feature_gates:
- # Required: unique identifier for the feature gate.
id:
# Required: description of the feature gate.
description: string
# Required: lifecycle stage of the feature gate.
stage:
# Required: version when the feature gate was introduced.
from_version: string
# Required for stable/deprecated gates: version when the feature gate reached stable stage.
to_version: string
# Required: URL with contextual information about the feature gate.
reference_url: string
================================================
FILE: cmd/mdatagen/metadata.yaml
================================================
type: mdatagen
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: cmd
stability:
alpha: [metrics]
codeowners:
active: [dmitryax]
================================================
FILE: cmd/mdatagen/third_party/golint/LICENSE
================================================
Copyright (c) 2013 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: cmd/mdatagen/third_party/golint/golint.go
================================================
// Copyright (c) 2013 The Go Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
package golint // import "go.opentelemetry.io/collector/cmd/mdatagen/third_party/golint"
// See https://github.com/golang/lint/blob/d0100b6bd8b389f0385611eb39152c4d7c3a7905/lint.go#L771
// Acronyms is a list of known acronyms that should not be formatted when linting.
var Acronyms = map[string]bool{
"ACL": true,
"API": true,
"ASCII": true,
"CPU": true,
"CSS": true,
"DNS": true,
"EOF": true,
"GUID": true,
"HTML": true,
"HTTP": true,
"HTTPS": true,
"ID": true,
"IP": true,
"JSON": true,
"LHS": true,
"QPS": true,
"RAM": true,
"RHS": true,
"RPC": true,
"SLA": true,
"SMTP": true,
"SQL": true,
"SSH": true,
"TCP": true,
"TLS": true,
"TTL": true,
"UDP": true,
"UI": true,
"UID": true,
"UUID": true,
"URI": true,
"URL": true,
"UTF8": true,
"VM": true,
"XML": true,
"XMPP": true,
"XSRF": true,
"XSS": true,
}
================================================
FILE: cmd/otelcorecol/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: cmd/otelcorecol/README.md
================================================
# `otelcorecol` test binary
This folder contains the sources for the `otelcorecol` test binary. This binary is intended for internal **TEST PURPOSES ONLY**. The source files in this folder are **NOT** the ones used to build any official OpenTelemetry Collector releases.
Check [open-telemetry/opentelemetry-collector-releases](https://github.com/open-telemetry/opentelemetry-collector-releases) for the official releases. Check the [**`otelcol` folder**](https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol) on that repository for the official Collector core manifest.
================================================
FILE: cmd/otelcorecol/builder-config.yaml
================================================
# NOTE:
# This builder configuration is NOT used to build any official binary.
# To see the builder manifests used for official binaries,
# check https://github.com/open-telemetry/opentelemetry-collector-releases
#
# For the OpenTelemetry Collector Core official distribution sources, check
# https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol
dist:
module: go.opentelemetry.io/collector/cmd/otelcorecol
name: otelcorecol
description: Local OpenTelemetry Collector binary, testing only.
version: 0.148.0-dev
receivers:
- gomod: go.opentelemetry.io/collector/receiver/nopreceiver v0.148.0
- gomod: go.opentelemetry.io/collector/receiver/otlpreceiver v0.148.0
exporters:
- gomod: go.opentelemetry.io/collector/exporter/debugexporter v0.148.0
- gomod: go.opentelemetry.io/collector/exporter/nopexporter v0.148.0
- gomod: go.opentelemetry.io/collector/exporter/otlpexporter v0.148.0
- gomod: go.opentelemetry.io/collector/exporter/otlphttpexporter v0.148.0
extensions:
- gomod: go.opentelemetry.io/collector/extension/memorylimiterextension v0.148.0
- gomod: go.opentelemetry.io/collector/extension/zpagesextension v0.148.0
processors:
- gomod: go.opentelemetry.io/collector/processor/batchprocessor v0.148.0
- gomod: go.opentelemetry.io/collector/processor/memorylimiterprocessor v0.148.0
connectors:
- gomod: go.opentelemetry.io/collector/connector/forwardconnector v0.148.0
providers:
- gomod: go.opentelemetry.io/collector/confmap/provider/envprovider v1.54.0
- gomod: go.opentelemetry.io/collector/confmap/provider/fileprovider v1.54.0
- gomod: go.opentelemetry.io/collector/confmap/provider/httpprovider v1.54.0
- gomod: go.opentelemetry.io/collector/confmap/provider/httpsprovider v1.54.0
- gomod: go.opentelemetry.io/collector/confmap/provider/yamlprovider v1.54.0
telemetry:
gomod: go.opentelemetry.io/collector/service v0.148.0
import: go.opentelemetry.io/collector/service/telemetry/otelconftelemetry
replaces:
- go.opentelemetry.io/collector => ../../
- go.opentelemetry.io/collector/client => ../../client
- go.opentelemetry.io/collector/component => ../../component
- go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest
- go.opentelemetry.io/collector/component/componentstatus => ../../component/componentstatus
- go.opentelemetry.io/collector/config/configauth => ../../config/configauth
- go.opentelemetry.io/collector/config/configcompression => ../../config/configcompression
- go.opentelemetry.io/collector/config/configgrpc => ../../config/configgrpc
- go.opentelemetry.io/collector/config/confighttp => ../../config/confighttp
- go.opentelemetry.io/collector/config/configmiddleware => ../../config/configmiddleware
- go.opentelemetry.io/collector/config/confignet => ../../config/confignet
- go.opentelemetry.io/collector/config/configopaque => ../../config/configopaque
- go.opentelemetry.io/collector/config/configoptional => ../../config/configoptional
- go.opentelemetry.io/collector/config/configretry => ../../config/configretry
- go.opentelemetry.io/collector/config/configtelemetry => ../../config/configtelemetry
- go.opentelemetry.io/collector/config/configtls => ../../config/configtls
- go.opentelemetry.io/collector/confmap => ../../confmap
- go.opentelemetry.io/collector/confmap/xconfmap => ../../confmap/xconfmap
- go.opentelemetry.io/collector/confmap/provider/envprovider => ../../confmap/provider/envprovider
- go.opentelemetry.io/collector/confmap/provider/fileprovider => ../../confmap/provider/fileprovider
- go.opentelemetry.io/collector/confmap/provider/httpprovider => ../../confmap/provider/httpprovider
- go.opentelemetry.io/collector/confmap/provider/httpsprovider => ../../confmap/provider/httpsprovider
- go.opentelemetry.io/collector/confmap/provider/yamlprovider => ../../confmap/provider/yamlprovider
- go.opentelemetry.io/collector/consumer => ../../consumer
- go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer
- go.opentelemetry.io/collector/consumer/consumererror => ../../consumer/consumererror
- go.opentelemetry.io/collector/consumer/consumererror/xconsumererror => ../../consumer/consumererror/xconsumererror
- go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest
- go.opentelemetry.io/collector/connector => ../../connector
- go.opentelemetry.io/collector/connector/connectortest => ../../connector/connectortest
- go.opentelemetry.io/collector/connector/xconnector => ../../connector/xconnector
- go.opentelemetry.io/collector/connector/forwardconnector => ../../connector/forwardconnector
- go.opentelemetry.io/collector/exporter => ../../exporter
- go.opentelemetry.io/collector/exporter/debugexporter => ../../exporter/debugexporter
- go.opentelemetry.io/collector/exporter/exportertest => ../../exporter/exportertest
- go.opentelemetry.io/collector/exporter/xexporter => ../../exporter/xexporter
- go.opentelemetry.io/collector/exporter/exporterhelper => ../../exporter/exporterhelper
- go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper => ../../exporter/exporterhelper/xexporterhelper
- go.opentelemetry.io/collector/exporter/nopexporter => ../../exporter/nopexporter
- go.opentelemetry.io/collector/exporter/otlpexporter => ../../exporter/otlpexporter
- go.opentelemetry.io/collector/exporter/otlphttpexporter => ../../exporter/otlphttpexporter
- go.opentelemetry.io/collector/extension => ../../extension
- go.opentelemetry.io/collector/extension/extensionauth => ../../extension/extensionauth
- go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest => ../../extension/extensionauth/extensionauthtest
- go.opentelemetry.io/collector/extension/extensioncapabilities => ../../extension/extensioncapabilities
- go.opentelemetry.io/collector/extension/extensionmiddleware => ../../extension/extensionmiddleware
- go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest => ../../extension/extensionmiddleware/extensionmiddlewaretest
- go.opentelemetry.io/collector/extension/extensiontest => ../../extension/extensiontest
- go.opentelemetry.io/collector/extension/memorylimiterextension => ../../extension/memorylimiterextension
- go.opentelemetry.io/collector/extension/xextension => ../../extension/xextension
- go.opentelemetry.io/collector/extension/zpagesextension => ../../extension/zpagesextension
- go.opentelemetry.io/collector/featuregate => ../../featuregate
- go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
- go.opentelemetry.io/collector/internal/memorylimiter => ../../internal/memorylimiter
- go.opentelemetry.io/collector/internal/fanoutconsumer => ../../internal/fanoutconsumer
- go.opentelemetry.io/collector/internal/telemetry => ../../internal/telemetry
- go.opentelemetry.io/collector/internal/sharedcomponent => ../../internal/sharedcomponent
- go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
- go.opentelemetry.io/collector/otelcol => ../../otelcol
- go.opentelemetry.io/collector/pdata => ../../pdata
- go.opentelemetry.io/collector/pdata/testdata => ../../pdata/testdata
- go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile
- go.opentelemetry.io/collector/pdata/xpdata => ../../pdata/xpdata
- go.opentelemetry.io/collector/pipeline => ../../pipeline
- go.opentelemetry.io/collector/pipeline/xpipeline => ../../pipeline/xpipeline
- go.opentelemetry.io/collector/processor => ../../processor
- go.opentelemetry.io/collector/processor/processortest => ../../processor/processortest
- go.opentelemetry.io/collector/processor/batchprocessor => ../../processor/batchprocessor
- go.opentelemetry.io/collector/processor/memorylimiterprocessor => ../../processor/memorylimiterprocessor
- go.opentelemetry.io/collector/processor/xprocessor => ../../processor/xprocessor
- go.opentelemetry.io/collector/processor/processorhelper/xprocessorhelper => ../../processor/processorhelper/xprocessorhelper
- go.opentelemetry.io/collector/processor/processorhelper => ../../processor/processorhelper
- go.opentelemetry.io/collector/receiver => ../../receiver
- go.opentelemetry.io/collector/receiver/nopreceiver => ../../receiver/nopreceiver
- go.opentelemetry.io/collector/receiver/receiverhelper => ../../receiver/receiverhelper
- go.opentelemetry.io/collector/receiver/otlpreceiver => ../../receiver/otlpreceiver
- go.opentelemetry.io/collector/receiver/receivertest => ../../receiver/receivertest
- go.opentelemetry.io/collector/receiver/xreceiver => ../../receiver/xreceiver
- go.opentelemetry.io/collector/service => ../../service
- go.opentelemetry.io/collector/service/hostcapabilities => ../../service/hostcapabilities
- go.opentelemetry.io/collector/service/telemetry/telemetrytest => ../../service/telemetry/telemetrytest
================================================
FILE: cmd/otelcorecol/components.go
================================================
// Code generated by "go.opentelemetry.io/collector/cmd/builder". DO NOT EDIT.
package main
import (
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/connector"
forwardconnector "go.opentelemetry.io/collector/connector/forwardconnector"
"go.opentelemetry.io/collector/exporter"
debugexporter "go.opentelemetry.io/collector/exporter/debugexporter"
nopexporter "go.opentelemetry.io/collector/exporter/nopexporter"
otlpexporter "go.opentelemetry.io/collector/exporter/otlpexporter"
otlphttpexporter "go.opentelemetry.io/collector/exporter/otlphttpexporter"
"go.opentelemetry.io/collector/extension"
memorylimiterextension "go.opentelemetry.io/collector/extension/memorylimiterextension"
zpagesextension "go.opentelemetry.io/collector/extension/zpagesextension"
"go.opentelemetry.io/collector/otelcol"
"go.opentelemetry.io/collector/processor"
batchprocessor "go.opentelemetry.io/collector/processor/batchprocessor"
memorylimiterprocessor "go.opentelemetry.io/collector/processor/memorylimiterprocessor"
"go.opentelemetry.io/collector/receiver"
nopreceiver "go.opentelemetry.io/collector/receiver/nopreceiver"
otlpreceiver "go.opentelemetry.io/collector/receiver/otlpreceiver"
otelconftelemetry "go.opentelemetry.io/collector/service/telemetry/otelconftelemetry"
)
type aliasProvider interface{ DeprecatedAlias() component.Type }
func makeModulesMap[T component.Factory](factories map[component.Type]T, modules map[component.Type]string) map[component.Type]string {
for compType, factory := range factories {
if ap, ok := any(factory).(aliasProvider); ok {
alias := ap.DeprecatedAlias()
if alias.String() != "" {
modules[alias] = modules[compType]
}
}
}
return modules
}
func components() (otelcol.Factories, error) {
var err error
factories := otelcol.Factories{
Telemetry: otelconftelemetry.NewFactory(),
}
factories.Extensions, err = otelcol.MakeFactoryMap[extension.Factory](
memorylimiterextension.NewFactory(),
zpagesextension.NewFactory(),
)
if err != nil {
return otelcol.Factories{}, err
}
factories.ExtensionModules = makeModulesMap(factories.Extensions, map[component.Type]string{
memorylimiterextension.NewFactory().Type(): "go.opentelemetry.io/collector/extension/memorylimiterextension v0.148.0",
zpagesextension.NewFactory().Type(): "go.opentelemetry.io/collector/extension/zpagesextension v0.148.0",
})
factories.Receivers, err = otelcol.MakeFactoryMap[receiver.Factory](
nopreceiver.NewFactory(),
otlpreceiver.NewFactory(),
)
if err != nil {
return otelcol.Factories{}, err
}
factories.ReceiverModules = makeModulesMap(factories.Receivers, map[component.Type]string{
nopreceiver.NewFactory().Type(): "go.opentelemetry.io/collector/receiver/nopreceiver v0.148.0",
otlpreceiver.NewFactory().Type(): "go.opentelemetry.io/collector/receiver/otlpreceiver v0.148.0",
})
factories.Exporters, err = otelcol.MakeFactoryMap[exporter.Factory](
debugexporter.NewFactory(),
nopexporter.NewFactory(),
otlpexporter.NewFactory(),
otlphttpexporter.NewFactory(),
)
if err != nil {
return otelcol.Factories{}, err
}
factories.ExporterModules = makeModulesMap(factories.Exporters, map[component.Type]string{
debugexporter.NewFactory().Type(): "go.opentelemetry.io/collector/exporter/debugexporter v0.148.0",
nopexporter.NewFactory().Type(): "go.opentelemetry.io/collector/exporter/nopexporter v0.148.0",
otlpexporter.NewFactory().Type(): "go.opentelemetry.io/collector/exporter/otlpexporter v0.148.0",
otlphttpexporter.NewFactory().Type(): "go.opentelemetry.io/collector/exporter/otlphttpexporter v0.148.0",
})
factories.Processors, err = otelcol.MakeFactoryMap[processor.Factory](
batchprocessor.NewFactory(),
memorylimiterprocessor.NewFactory(),
)
if err != nil {
return otelcol.Factories{}, err
}
factories.ProcessorModules = makeModulesMap(factories.Processors, map[component.Type]string{
batchprocessor.NewFactory().Type(): "go.opentelemetry.io/collector/processor/batchprocessor v0.148.0",
memorylimiterprocessor.NewFactory().Type(): "go.opentelemetry.io/collector/processor/memorylimiterprocessor v0.148.0",
})
factories.Connectors, err = otelcol.MakeFactoryMap[connector.Factory](
forwardconnector.NewFactory(),
)
if err != nil {
return otelcol.Factories{}, err
}
factories.ConnectorModules = makeModulesMap(factories.Connectors, map[component.Type]string{
forwardconnector.NewFactory().Type(): "go.opentelemetry.io/collector/connector/forwardconnector v0.148.0",
})
return factories, nil
}
================================================
FILE: cmd/otelcorecol/go.mod
================================================
// Code generated by "go.opentelemetry.io/collector/cmd/builder". DO NOT EDIT.
module go.opentelemetry.io/collector/cmd/otelcorecol
go 1.25.0
require (
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/confmap v1.54.0
go.opentelemetry.io/collector/confmap/provider/envprovider v1.54.0
go.opentelemetry.io/collector/confmap/provider/fileprovider v1.54.0
go.opentelemetry.io/collector/confmap/provider/httpprovider v1.54.0
go.opentelemetry.io/collector/confmap/provider/httpsprovider v1.54.0
go.opentelemetry.io/collector/confmap/provider/yamlprovider v1.54.0
go.opentelemetry.io/collector/connector v0.148.0
go.opentelemetry.io/collector/connector/forwardconnector v0.148.0
go.opentelemetry.io/collector/exporter v1.54.0
go.opentelemetry.io/collector/exporter/debugexporter v0.148.0
go.opentelemetry.io/collector/exporter/nopexporter v0.148.0
go.opentelemetry.io/collector/exporter/otlpexporter v0.148.0
go.opentelemetry.io/collector/exporter/otlphttpexporter v0.148.0
go.opentelemetry.io/collector/extension v1.54.0
go.opentelemetry.io/collector/extension/memorylimiterextension v0.148.0
go.opentelemetry.io/collector/extension/zpagesextension v0.148.0
go.opentelemetry.io/collector/otelcol v0.148.0
go.opentelemetry.io/collector/processor v1.54.0
go.opentelemetry.io/collector/processor/batchprocessor v0.148.0
go.opentelemetry.io/collector/processor/memorylimiterprocessor v0.148.0
go.opentelemetry.io/collector/receiver v1.54.0
go.opentelemetry.io/collector/receiver/nopreceiver v0.148.0
go.opentelemetry.io/collector/receiver/otlpreceiver v0.148.0
go.opentelemetry.io/collector/service v0.148.0
golang.org/x/sys v0.42.0
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/ebitengine/purego v0.10.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/go-tpm v0.9.8 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.18.4 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/mostynb/go-grpc-compression v1.2.3 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pierrec/lz4/v4 v4.1.26 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.5 // indirect
github.com/prometheus/otlptranslator v1.0.0 // indirect
github.com/prometheus/procfs v0.20.1 // indirect
github.com/rs/cors v1.11.1 // indirect
github.com/shirou/gopsutil/v4 v4.26.2 // indirect
github.com/spf13/cobra v1.10.2 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/stretchr/testify v1.11.1 // indirect
github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.11.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector v0.148.0 // indirect
go.opentelemetry.io/collector/client v1.54.0 // indirect
go.opentelemetry.io/collector/component/componentstatus v0.148.0 // indirect
go.opentelemetry.io/collector/component/componenttest v0.148.0 // indirect
go.opentelemetry.io/collector/config/configauth v1.54.0 // indirect
go.opentelemetry.io/collector/config/configcompression v1.54.0 // indirect
go.opentelemetry.io/collector/config/configgrpc v0.148.0 // indirect
go.opentelemetry.io/collector/config/confighttp v0.148.0 // indirect
go.opentelemetry.io/collector/config/configmiddleware v1.54.0 // indirect
go.opentelemetry.io/collector/config/confignet v1.54.0 // indirect
go.opentelemetry.io/collector/config/configopaque v1.54.0 // indirect
go.opentelemetry.io/collector/config/configoptional v1.54.0 // indirect
go.opentelemetry.io/collector/config/configretry v1.54.0 // indirect
go.opentelemetry.io/collector/config/configtelemetry v0.148.0 // indirect
go.opentelemetry.io/collector/config/configtls v1.54.0 // indirect
go.opentelemetry.io/collector/confmap/xconfmap v0.148.0 // indirect
go.opentelemetry.io/collector/connector/connectortest v0.148.0 // indirect
go.opentelemetry.io/collector/connector/xconnector v0.148.0 // indirect
go.opentelemetry.io/collector/consumer v1.54.0 // indirect
go.opentelemetry.io/collector/consumer/consumererror v0.148.0 // indirect
go.opentelemetry.io/collector/consumer/consumererror/xconsumererror v0.148.0 // indirect
go.opentelemetry.io/collector/consumer/consumertest v0.148.0 // indirect
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0 // indirect
go.opentelemetry.io/collector/exporter/exporterhelper v0.148.0 // indirect
go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper v0.148.0 // indirect
go.opentelemetry.io/collector/exporter/exportertest v0.148.0 // indirect
go.opentelemetry.io/collector/exporter/xexporter v0.148.0 // indirect
go.opentelemetry.io/collector/extension/extensionauth v1.54.0 // indirect
go.opentelemetry.io/collector/extension/extensioncapabilities v0.148.0 // indirect
go.opentelemetry.io/collector/extension/extensionmiddleware v0.148.0 // indirect
go.opentelemetry.io/collector/extension/extensiontest v0.148.0 // indirect
go.opentelemetry.io/collector/extension/xextension v0.148.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
go.opentelemetry.io/collector/internal/fanoutconsumer v0.148.0 // indirect
go.opentelemetry.io/collector/internal/memorylimiter v0.148.0 // indirect
go.opentelemetry.io/collector/internal/sharedcomponent v0.148.0 // indirect
go.opentelemetry.io/collector/internal/telemetry v0.148.0 // indirect
go.opentelemetry.io/collector/pdata v1.54.0 // indirect
go.opentelemetry.io/collector/pdata/pprofile v0.148.0 // indirect
go.opentelemetry.io/collector/pdata/testdata v0.148.0 // indirect
go.opentelemetry.io/collector/pdata/xpdata v0.148.0 // indirect
go.opentelemetry.io/collector/pipeline v1.54.0 // indirect
go.opentelemetry.io/collector/pipeline/xpipeline v0.148.0 // indirect
go.opentelemetry.io/collector/processor/processorhelper v0.148.0 // indirect
go.opentelemetry.io/collector/processor/processorhelper/xprocessorhelper v0.148.0 // indirect
go.opentelemetry.io/collector/processor/processortest v0.148.0 // indirect
go.opentelemetry.io/collector/processor/xprocessor v0.148.0 // indirect
go.opentelemetry.io/collector/receiver/receiverhelper v0.148.0 // indirect
go.opentelemetry.io/collector/receiver/receivertest v0.148.0 // indirect
go.opentelemetry.io/collector/receiver/xreceiver v0.148.0 // indirect
go.opentelemetry.io/collector/service/hostcapabilities v0.148.0 // indirect
go.opentelemetry.io/contrib/bridges/otelzap v0.17.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect
go.opentelemetry.io/contrib/otelconf v0.22.0 // indirect
go.opentelemetry.io/contrib/propagators/b3 v1.42.0 // indirect
go.opentelemetry.io/contrib/zpages v0.67.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/prometheus v0.64.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 // indirect
go.opentelemetry.io/otel/log v0.18.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
go.opentelemetry.io/otel/sdk/log v0.18.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/text v0.34.0 // indirect
gonum.org/v1/gonum v0.17.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector => ../../
replace go.opentelemetry.io/collector/client => ../../client
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest
replace go.opentelemetry.io/collector/component/componentstatus => ../../component/componentstatus
replace go.opentelemetry.io/collector/config/configauth => ../../config/configauth
replace go.opentelemetry.io/collector/config/configcompression => ../../config/configcompression
replace go.opentelemetry.io/collector/config/configgrpc => ../../config/configgrpc
replace go.opentelemetry.io/collector/config/confighttp => ../../config/confighttp
replace go.opentelemetry.io/collector/config/configmiddleware => ../../config/configmiddleware
replace go.opentelemetry.io/collector/config/confignet => ../../config/confignet
replace go.opentelemetry.io/collector/config/configopaque => ../../config/configopaque
replace go.opentelemetry.io/collector/config/configoptional => ../../config/configoptional
replace go.opentelemetry.io/collector/config/configretry => ../../config/configretry
replace go.opentelemetry.io/collector/config/configtelemetry => ../../config/configtelemetry
replace go.opentelemetry.io/collector/config/configtls => ../../config/configtls
replace go.opentelemetry.io/collector/confmap => ../../confmap
replace go.opentelemetry.io/collector/confmap/xconfmap => ../../confmap/xconfmap
replace go.opentelemetry.io/collector/confmap/provider/envprovider => ../../confmap/provider/envprovider
replace go.opentelemetry.io/collector/confmap/provider/fileprovider => ../../confmap/provider/fileprovider
replace go.opentelemetry.io/collector/confmap/provider/httpprovider => ../../confmap/provider/httpprovider
replace go.opentelemetry.io/collector/confmap/provider/httpsprovider => ../../confmap/provider/httpsprovider
replace go.opentelemetry.io/collector/confmap/provider/yamlprovider => ../../confmap/provider/yamlprovider
replace go.opentelemetry.io/collector/consumer => ../../consumer
replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer
replace go.opentelemetry.io/collector/consumer/consumererror => ../../consumer/consumererror
replace go.opentelemetry.io/collector/consumer/consumererror/xconsumererror => ../../consumer/consumererror/xconsumererror
replace go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest
replace go.opentelemetry.io/collector/connector => ../../connector
replace go.opentelemetry.io/collector/connector/connectortest => ../../connector/connectortest
replace go.opentelemetry.io/collector/connector/xconnector => ../../connector/xconnector
replace go.opentelemetry.io/collector/connector/forwardconnector => ../../connector/forwardconnector
replace go.opentelemetry.io/collector/exporter => ../../exporter
replace go.opentelemetry.io/collector/exporter/debugexporter => ../../exporter/debugexporter
replace go.opentelemetry.io/collector/exporter/exportertest => ../../exporter/exportertest
replace go.opentelemetry.io/collector/exporter/xexporter => ../../exporter/xexporter
replace go.opentelemetry.io/collector/exporter/exporterhelper => ../../exporter/exporterhelper
replace go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper => ../../exporter/exporterhelper/xexporterhelper
replace go.opentelemetry.io/collector/exporter/nopexporter => ../../exporter/nopexporter
replace go.opentelemetry.io/collector/exporter/otlpexporter => ../../exporter/otlpexporter
replace go.opentelemetry.io/collector/exporter/otlphttpexporter => ../../exporter/otlphttpexporter
replace go.opentelemetry.io/collector/extension => ../../extension
replace go.opentelemetry.io/collector/extension/extensionauth => ../../extension/extensionauth
replace go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest => ../../extension/extensionauth/extensionauthtest
replace go.opentelemetry.io/collector/extension/extensioncapabilities => ../../extension/extensioncapabilities
replace go.opentelemetry.io/collector/extension/extensionmiddleware => ../../extension/extensionmiddleware
replace go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest => ../../extension/extensionmiddleware/extensionmiddlewaretest
replace go.opentelemetry.io/collector/extension/extensiontest => ../../extension/extensiontest
replace go.opentelemetry.io/collector/extension/memorylimiterextension => ../../extension/memorylimiterextension
replace go.opentelemetry.io/collector/extension/xextension => ../../extension/xextension
replace go.opentelemetry.io/collector/extension/zpagesextension => ../../extension/zpagesextension
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
replace go.opentelemetry.io/collector/internal/memorylimiter => ../../internal/memorylimiter
replace go.opentelemetry.io/collector/internal/fanoutconsumer => ../../internal/fanoutconsumer
replace go.opentelemetry.io/collector/internal/telemetry => ../../internal/telemetry
replace go.opentelemetry.io/collector/internal/sharedcomponent => ../../internal/sharedcomponent
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/otelcol => ../../otelcol
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/pdata/testdata => ../../pdata/testdata
replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile
replace go.opentelemetry.io/collector/pdata/xpdata => ../../pdata/xpdata
replace go.opentelemetry.io/collector/pipeline => ../../pipeline
replace go.opentelemetry.io/collector/pipeline/xpipeline => ../../pipeline/xpipeline
replace go.opentelemetry.io/collector/processor => ../../processor
replace go.opentelemetry.io/collector/processor/processortest => ../../processor/processortest
replace go.opentelemetry.io/collector/processor/batchprocessor => ../../processor/batchprocessor
replace go.opentelemetry.io/collector/processor/memorylimiterprocessor => ../../processor/memorylimiterprocessor
replace go.opentelemetry.io/collector/processor/xprocessor => ../../processor/xprocessor
replace go.opentelemetry.io/collector/processor/processorhelper/xprocessorhelper => ../../processor/processorhelper/xprocessorhelper
replace go.opentelemetry.io/collector/processor/processorhelper => ../../processor/processorhelper
replace go.opentelemetry.io/collector/receiver => ../../receiver
replace go.opentelemetry.io/collector/receiver/nopreceiver => ../../receiver/nopreceiver
replace go.opentelemetry.io/collector/receiver/receiverhelper => ../../receiver/receiverhelper
replace go.opentelemetry.io/collector/receiver/otlpreceiver => ../../receiver/otlpreceiver
replace go.opentelemetry.io/collector/receiver/receivertest => ../../receiver/receivertest
replace go.opentelemetry.io/collector/receiver/xreceiver => ../../receiver/xreceiver
replace go.opentelemetry.io/collector/service => ../../service
replace go.opentelemetry.io/collector/service/hostcapabilities => ../../service/hostcapabilities
replace go.opentelemetry.io/collector/service/telemetry/telemetrytest => ../../service/telemetry/telemetrytest
================================================
FILE: cmd/otelcorecol/go.sum
================================================
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/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f h1:RJ+BDPLSHQO7cSjKBqjPJSbi1qfk9WcsjQDtZiw3dZw=
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f/go.mod h1:VHbbch/X4roIY22jL1s3qRbZhCiRIgUAF/PdSUcx2io=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/go-tpm-tools v0.4.7 h1:J3ycC8umYxM9A4eF73EofRZu4BxY0jjQnUnkhIBbvws=
github.com/google/go-tpm-tools v0.4.7/go.mod h1:gSyXTZHe3fgbzb6WEGd90QucmsnT1SRdlye82gH8QjQ=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
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 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mostynb/go-grpc-compression v1.2.3 h1:42/BKWMy0KEJGSdWvzqIyOZ95YcR9mLPqKctH7Uo//I=
github.com/mostynb/go-grpc-compression v1.2.3/go.mod h1:AghIxF3P57umzqM9yz795+y1Vjs47Km/Y2FE6ouQ7Lg=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY=
github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos=
github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM=
github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shirou/gopsutil/v4 v4.26.2 h1:X8i6sicvUFih4BmYIGT1m2wwgw2VG9YgrDTi7cIRGUI=
github.com/shirou/gopsutil/v4 v4.26.2/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/bridges/otelzap v0.17.0 h1:oCltVHJcblcth2z9B9dRTeZIZTe2Sf9Ad9h8bcc+s8M=
go.opentelemetry.io/contrib/bridges/otelzap v0.17.0/go.mod h1:G/VE1A/hRn6mEWdfC8rMvSdQVGM64KUPi4XilLkwcQw=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
go.opentelemetry.io/contrib/otelconf v0.22.0 h1:+kpcfczGOFM85zDZyqQCzWefhovegfn24D0WwmQz0n4=
go.opentelemetry.io/contrib/otelconf v0.22.0/go.mod h1:ojdbOukO+JRDJQmJY2PRIZEg0UYVzcOuZR59hp7xffc=
go.opentelemetry.io/contrib/propagators/b3 v1.42.0 h1:B2Pew5ufEtgkjLF+tSkXjgYZXQr9m7aCm1wLKB0URbU=
go.opentelemetry.io/contrib/propagators/b3 v1.42.0/go.mod h1:iPgUcSEF5DORW6+yNbdw/YevUy+QqJ508ncjhrRSCjc=
go.opentelemetry.io/contrib/zpages v0.67.0 h1:cIUwWSVDovuLEbDIKreptjdxMuIhGiqwq0uL8YNaq1c=
go.opentelemetry.io/contrib/zpages v0.67.0/go.mod h1:vK8fsYHgPYg4Z/XDbFSEvItSGZDbjWTvjBOu8+AiDhc=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 h1:deI9UQMoGFgrg5iLPgzueqFPHevDl+28YKfSpPTI6rY=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0/go.mod h1:PFx9NgpNUKXdf7J4Q3agRxMs3Y07QhTCVipKmLsMKnU=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0 h1:icqq3Z34UrEFk2u+HMhTtRsvo7Ues+eiJVjaJt62njs=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0/go.mod h1:W2m8P+d5Wn5kipj4/xmbt9uMqezEKfBjzVJadfABSBE=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 h1:MdKucPl/HbzckWWEisiNqMPhRrAOQX8r4jTuGr636gk=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0/go.mod h1:RolT8tWtfHcjajEH5wFIZ4Dgh5jpPdFXYV9pTAk/qjc=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0 h1:H7O6RlGOMTizyl3R08Kn5pdM06bnH8oscSj7o11tmLA=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0/go.mod h1:mBFWu/WOVDkWWsR7Tx7h6EpQB8wsv7P0Yrh0Pb7othc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 h1:uLXP+3mghfMf7XmV4PkGfFhFKuNWoCvvx5wP/wOXo0o=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0/go.mod h1:v0Tj04armyT59mnURNUJf7RCKcKzq+lgJs6QSjHjaTc=
go.opentelemetry.io/otel/exporters/prometheus v0.64.0 h1:g0LRDXMX/G1SEZtK8zl8Chm4K6GBwRkjPKE36LxiTYs=
go.opentelemetry.io/otel/exporters/prometheus v0.64.0/go.mod h1:UrgcjnarfdlBDP3GjDIJWe6HTprwSazNjwsI+Ru6hro=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 h1:KJVjPD3rcPb98rIs3HznyJlrfx9ge5oJvxxlGR+P/7s=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0/go.mod h1:K3kRa2ckmHWQaTWQdPRHc7qGXASuVuoEQXzrvlA98Ws=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 h1:lSZHgNHfbmQTPfuTmWVkEu8J8qXaQwuV30pjCcAUvP8=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0/go.mod h1:so9ounLcuoRDu033MW/E0AD4hhUjVqswrMF5FoZlBcw=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1JF26knRneorus8aOwVIDhvYx9WoDw=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs=
go.opentelemetry.io/otel/log v0.18.0 h1:XgeQIIBjZZrliksMEbcwMZefoOSMI1hdjiLEiiB0bAg=
go.opentelemetry.io/otel/log v0.18.0/go.mod h1:KEV1kad0NofR3ycsiDH4Yjcoj0+8206I6Ox2QYFSNgI=
go.opentelemetry.io/otel/log/logtest v0.18.0 h1:2QeyoKJdIgK2LJhG1yn78o/zmpXx1EditeyRDREqVS8=
go.opentelemetry.io/otel/log/logtest v0.18.0/go.mod h1:v1vh3PYR9zIa5MK6HwkH2lMrLBg/Y9Of6Qc+krlesX0=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/log v0.18.0 h1:n8OyZr7t7otkeTnPTbDNom6rW16TBYGtvyy2Gk6buQw=
go.opentelemetry.io/otel/sdk/log v0.18.0/go.mod h1:C0+wxkTwKpOCZLrlJ3pewPiiQwpzycPI/u6W0Z9fuYk=
go.opentelemetry.io/otel/sdk/log/logtest v0.18.0 h1:l3mYuPsuBx6UKE47BVcPrZoZ0q/KER57vbj2qkgDLXA=
go.opentelemetry.io/otel/sdk/log/logtest v0.18.0/go.mod h1:7cHtiVJpZebB3wybTa4NG+FUo5NPe3PROz1FqB0+qdw=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 h1:tu/dtnW1o3wfaxCOjSLn5IRX4YDcJrtlpzYkhHhGaC4=
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: cmd/otelcorecol/main.go
================================================
// Code generated by "go.opentelemetry.io/collector/cmd/builder". DO NOT EDIT.
// Program otelcorecol is an OpenTelemetry Collector binary.
package main
import (
"os"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/confmap"
envprovider "go.opentelemetry.io/collector/confmap/provider/envprovider"
fileprovider "go.opentelemetry.io/collector/confmap/provider/fileprovider"
httpprovider "go.opentelemetry.io/collector/confmap/provider/httpprovider"
httpsprovider "go.opentelemetry.io/collector/confmap/provider/httpsprovider"
yamlprovider "go.opentelemetry.io/collector/confmap/provider/yamlprovider"
"go.opentelemetry.io/collector/otelcol"
)
func main() {
info := component.BuildInfo{
Command: "otelcorecol",
Description: "Local OpenTelemetry Collector binary, testing only.",
Version: "0.148.0-dev",
}
set := otelcol.CollectorSettings{
BuildInfo: info,
Factories: components,
ConfigProviderSettings: otelcol.ConfigProviderSettings{
ResolverSettings: confmap.ResolverSettings{
ProviderFactories: []confmap.ProviderFactory{
envprovider.NewFactory(),
fileprovider.NewFactory(),
httpprovider.NewFactory(),
httpsprovider.NewFactory(),
yamlprovider.NewFactory(),
},
},
},
ProviderModules: map[string]string{
envprovider.NewFactory().Create(confmap.ProviderSettings{}).Scheme(): "go.opentelemetry.io/collector/confmap/provider/envprovider v1.54.0",
fileprovider.NewFactory().Create(confmap.ProviderSettings{}).Scheme(): "go.opentelemetry.io/collector/confmap/provider/fileprovider v1.54.0",
httpprovider.NewFactory().Create(confmap.ProviderSettings{}).Scheme(): "go.opentelemetry.io/collector/confmap/provider/httpprovider v1.54.0",
httpsprovider.NewFactory().Create(confmap.ProviderSettings{}).Scheme(): "go.opentelemetry.io/collector/confmap/provider/httpsprovider v1.54.0",
yamlprovider.NewFactory().Create(confmap.ProviderSettings{}).Scheme(): "go.opentelemetry.io/collector/confmap/provider/yamlprovider v1.54.0",
},
ConverterModules: []string{},
}
if err := run(set); err != nil {
// The error message is logged by cobra, so we intentionally
// avoid logging it again here to prevent duplicate output.
os.Exit(1)
}
}
func runInteractive(params otelcol.CollectorSettings) error {
cmd := otelcol.NewCommand(params)
if err := cmd.Execute(); err != nil {
return err
}
return nil
}
================================================
FILE: cmd/otelcorecol/main_others.go
================================================
// Code generated by "go.opentelemetry.io/collector/cmd/builder". DO NOT EDIT.
//go:build !windows
package main
import "go.opentelemetry.io/collector/otelcol"
func run(params otelcol.CollectorSettings) error {
return runInteractive(params)
}
================================================
FILE: cmd/otelcorecol/main_windows.go
================================================
// Code generated by "go.opentelemetry.io/collector/cmd/builder". DO NOT EDIT.
//go:build windows
package main
import (
"errors"
"fmt"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/svc"
"go.opentelemetry.io/collector/otelcol"
)
func run(params otelcol.CollectorSettings) error {
// No need to supply service name when startup is invoked through
// the Service Control Manager directly.
if err := svc.Run("", otelcol.NewSvcHandler(params)); err != nil {
if errors.Is(err, windows.ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) {
// Per https://learn.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-startservicectrldispatchera#return-value
// this means that the process is not running as a service, so run interactively.
return runInteractive(params)
}
return fmt.Errorf("failed to start collector server: %w", err)
}
return nil
}
================================================
FILE: component/Makefile
================================================
include ../Makefile.Common
================================================
FILE: component/build_info.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package component // import "go.opentelemetry.io/collector/component"
// BuildInfo is the information that is logged at the application start and
// passed into each component. This information can be overridden in custom build.
type BuildInfo struct {
// Command is the executable file name, e.g. "otelcol".
Command string
// Description is the full name of the collector, e.g. "OpenTelemetry Collector".
Description string
// Version string.
Version string
// prevent unkeyed literal initialization
_ struct{}
}
// NewDefaultBuildInfo returns a default BuildInfo.
func NewDefaultBuildInfo() BuildInfo {
return BuildInfo{
Command: "otelcol",
Description: "OpenTelemetry Collector",
Version: "latest",
}
}
================================================
FILE: component/component.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package component outlines the abstraction of components within the OpenTelemetry Collector. It provides details on the component
// lifecycle as well as defining the interface that components must fulfill.
package component // import "go.opentelemetry.io/collector/component"
import (
"context"
"fmt"
"strings"
)
// Component is either a receiver, exporter, processor, connector, or an extension.
//
// A component's lifecycle has the following phases:
//
// 1. Creation: The component is created using its respective factory, via a Create* call.
// 2. Start: The component's Start method is called.
// 3. Running: The component is up and running.
// 4. Shutdown: The component's Shutdown method is called and the lifecycle is complete.
//
// Once the lifecycle is complete it may be repeated, in which case a new component
// is created, starts, runs and is shutdown again.
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 an 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 the background. Background operations should
// create their own context using context.WithCancel(context.Background())
// rather than using the passed context, which is intended only for the
// startup operation itself. The component should cancel this context in its
// Shutdown() method.
//
// Note: as of today, the context passed to Start() lives for the entire
// lifetime of the collector, but this may change in the future to include a
// startup timeout.
Start(ctx context.Context, host Host) error
// Shutdown is invoked during service shutdown. After Shutdown() is called, if the component
// accepted data in any way, it should not accept it anymore.
//
// This method must be safe to call:
// - without Start() having been called
// - if the component is in a shutdown state already
//
// If there are any background operations running by the component they must be aborted before
// this function returns. Remember that if you started any long-running background operations from
// the Start() method, those operations must be also cancelled. If there are any buffers in the
// component, they should be flushed with the data being sent immediately to the next component.
//
// The component's lifecycle is completed once the Shutdown() method returns. No other
// methods of the component are called after that. If necessary a new component with
// the same or different configuration may be created and started (this may happen
// for example if we want to restart the component).
Shutdown(ctx context.Context) error
}
// StartFunc specifies the function invoked when the component.Component is being started.
type StartFunc func(context.Context, Host) error
// Start starts the component.
func (f StartFunc) Start(ctx context.Context, host Host) error {
if f == nil {
return nil
}
return f(ctx, host)
}
// ShutdownFunc specifies the function invoked when the component.Component is being shutdown.
type ShutdownFunc func(context.Context) error
// Shutdown shuts down the component.
func (f ShutdownFunc) Shutdown(ctx context.Context) error {
if f == nil {
return nil
}
return f(ctx)
}
// Kind represents component kinds.
type Kind struct {
name string
}
var (
KindReceiver = Kind{name: "Receiver"}
KindProcessor = Kind{name: "Processor"}
KindExporter = Kind{name: "Exporter"}
KindExtension = Kind{name: "Extension"}
KindConnector = Kind{name: "Connector"}
)
func (k Kind) String() string {
return k.name
}
// StabilityLevel represents the stability level of the component created by the factory.
// The stability level is used to determine if the component should be used in production
// or not. For more details see:
// https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#stability-levels
type StabilityLevel int
const (
StabilityLevelUndefined StabilityLevel = iota // skip 0, start types from 1.
StabilityLevelUnmaintained
StabilityLevelDeprecated
StabilityLevelDevelopment
StabilityLevelAlpha
StabilityLevelBeta
StabilityLevelStable
)
func (sl *StabilityLevel) UnmarshalText(in []byte) error {
str := strings.ToLower(string(in))
switch str {
case "undefined":
*sl = StabilityLevelUndefined
case "unmaintained":
*sl = StabilityLevelUnmaintained
case "deprecated":
*sl = StabilityLevelDeprecated
case "development":
*sl = StabilityLevelDevelopment
case "alpha":
*sl = StabilityLevelAlpha
case "beta":
*sl = StabilityLevelBeta
case "stable":
*sl = StabilityLevelStable
default:
return fmt.Errorf("unsupported stability level: %q", string(in))
}
return nil
}
func (sl StabilityLevel) String() string {
switch sl {
case StabilityLevelUndefined:
return "Undefined"
case StabilityLevelUnmaintained:
return "Unmaintained"
case StabilityLevelDeprecated:
return "Deprecated"
case StabilityLevelDevelopment:
return "Development"
case StabilityLevelAlpha:
return "Alpha"
case StabilityLevelBeta:
return "Beta"
case StabilityLevelStable:
return "Stable"
}
return ""
}
func (sl StabilityLevel) LogMessage() string {
switch sl {
case StabilityLevelUnmaintained:
return "Unmaintained component. Actively looking for contributors. Component will become deprecated after 3 months of remaining unmaintained."
case StabilityLevelDeprecated:
return "Deprecated component. Will be removed in future releases."
case StabilityLevelDevelopment:
return "Development component. May change in the future."
case StabilityLevelAlpha:
return "Alpha component. May change in the future."
case StabilityLevelBeta:
return "Beta component. May change in the future."
case StabilityLevelStable:
return "Stable component."
default:
return "Stability level of component is undefined"
}
}
// Factory is implemented by all Component factories.
type Factory interface {
// Type gets the type of the component created by this factory.
Type() Type
// CreateDefaultConfig creates the default configuration for the Component.
// 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 Component.
// The object returned by this method needs to pass the checks implemented by
// 'componenttest.CheckConfigStruct'. It is recommended to have these checks in the
// tests of any implementation of the Factory interface.
CreateDefaultConfig() Config
}
// CreateDefaultConfigFunc is the equivalent of Factory.CreateDefaultConfig().
type CreateDefaultConfigFunc func() Config
// CreateDefaultConfig implements Factory.CreateDefaultConfig().
func (f CreateDefaultConfigFunc) CreateDefaultConfig() Config {
return f()
}
================================================
FILE: component/component_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package component
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestKindString(t *testing.T) {
assert.Empty(t, Kind{}.String())
assert.Equal(t, "Receiver", KindReceiver.String())
assert.Equal(t, "Processor", KindProcessor.String())
assert.Equal(t, "Exporter", KindExporter.String())
assert.Equal(t, "Extension", KindExtension.String())
assert.Equal(t, "Connector", KindConnector.String())
}
func TestStabilityLevelUnmarshal(t *testing.T) {
tests := []struct {
input string
output StabilityLevel
expectedErr string
}{
{
input: "Undefined",
output: StabilityLevelUndefined,
},
{
input: "UnmaintaineD",
output: StabilityLevelUnmaintained,
},
{
input: "DepreCated",
output: StabilityLevelDeprecated,
},
{
input: "Development",
output: StabilityLevelDevelopment,
},
{
input: "alpha",
output: StabilityLevelAlpha,
},
{
input: "BETA",
output: StabilityLevelBeta,
},
{
input: "sTABLe",
output: StabilityLevelStable,
},
{
input: "notfound",
expectedErr: "unsupported stability level: \"notfound\"",
},
}
for _, test := range tests {
t.Run(test.input, func(t *testing.T) {
var sl StabilityLevel
err := sl.UnmarshalText([]byte(test.input))
if test.expectedErr != "" {
assert.EqualError(t, err, test.expectedErr)
} else {
assert.Equal(t, test.output, sl)
}
})
}
}
func TestStabilityLevelString(t *testing.T) {
assert.Equal(t, "Undefined", StabilityLevelUndefined.String())
assert.Equal(t, "Unmaintained", StabilityLevelUnmaintained.String())
assert.Equal(t, "Deprecated", StabilityLevelDeprecated.String())
assert.Equal(t, "Development", StabilityLevelDevelopment.String())
assert.Equal(t, "Alpha", StabilityLevelAlpha.String())
assert.Equal(t, "Beta", StabilityLevelBeta.String())
assert.Equal(t, "Stable", StabilityLevelStable.String())
assert.Empty(t, StabilityLevel(100).String())
}
func TestStabilityLevelLogMessage(t *testing.T) {
assert.Equal(t, "Stability level of component is undefined", StabilityLevelUndefined.LogMessage())
assert.Equal(t, "Unmaintained component. Actively looking for contributors. Component will become deprecated after 3 months of remaining unmaintained.", StabilityLevelUnmaintained.LogMessage())
assert.Equal(t, "Deprecated component. Will be removed in future releases.", StabilityLevelDeprecated.LogMessage())
assert.Equal(t, "Development component. May change in the future.", StabilityLevelDevelopment.LogMessage())
assert.Equal(t, "Alpha component. May change in the future.", StabilityLevelAlpha.LogMessage())
assert.Equal(t, "Beta component. May change in the future.", StabilityLevelBeta.LogMessage())
assert.Equal(t, "Stable component.", StabilityLevelStable.LogMessage())
assert.Equal(t, "Stability level of component is undefined", StabilityLevel(100).LogMessage())
}
================================================
FILE: component/componentstatus/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: component/componentstatus/go.mod
================================================
module go.opentelemetry.io/collector/component/componentstatus
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/collector/pipeline v1.54.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/component => ../
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/pipeline => ../../pipeline
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
================================================
FILE: component/componentstatus/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: component/componentstatus/instance.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package componentstatus // import "go.opentelemetry.io/collector/component/componentstatus"
import (
"slices"
"sort"
"strings"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pipeline"
)
// pipelineDelim is the delimiter for internal representation of pipeline
// component IDs.
const pipelineDelim = byte(0x20)
// InstanceID uniquely identifies a component instance
//
// TODO: consider moving this struct to a new package/module like `extension/statuswatcher`
// https://github.com/open-telemetry/opentelemetry-collector/issues/10764
type InstanceID struct {
componentID component.ID
kind component.Kind
pipelineIDs string // IDs encoded as a string so InstanceID is Comparable.
}
// NewInstanceID returns an ID that uniquely identifies a component.
func NewInstanceID(componentID component.ID, kind component.Kind, pipelineIDs ...pipeline.ID) *InstanceID {
instanceID := &InstanceID{
componentID: componentID,
kind: kind,
}
instanceID.addPipelines(pipelineIDs)
return instanceID
}
// ComponentID returns the ComponentID associated with this instance.
func (id *InstanceID) ComponentID() component.ID {
return id.componentID
}
// Kind returns the component Kind associated with this instance.
func (id *InstanceID) Kind() component.Kind {
return id.kind
}
// AllPipelineIDs calls f for each pipeline this instance is associated with. If
// f returns false it will stop iteration.
func (id *InstanceID) AllPipelineIDs(f func(pipeline.ID) bool) {
var bs []byte
for _, b := range []byte(id.pipelineIDs) {
if b != pipelineDelim {
bs = append(bs, b)
continue
}
pipelineID := pipeline.ID{}
err := pipelineID.UnmarshalText(bs)
bs = bs[:0]
if err != nil {
continue
}
if !f(pipelineID) {
break
}
}
}
// WithPipelines returns a new InstanceID updated to include the given
// pipelineIDs.
func (id *InstanceID) WithPipelines(pipelineIDs ...pipeline.ID) *InstanceID {
instanceID := &InstanceID{
componentID: id.componentID,
kind: id.kind,
pipelineIDs: id.pipelineIDs,
}
instanceID.addPipelines(pipelineIDs)
return instanceID
}
func (id *InstanceID) addPipelines(pipelineIDs []pipeline.ID) {
delim := string(pipelineDelim)
strIDs := strings.Split(id.pipelineIDs, delim)
for _, pID := range pipelineIDs {
strIDs = append(strIDs, pID.String())
}
sort.Strings(strIDs)
strIDs = slices.Compact(strIDs)
id.pipelineIDs = strings.Join(strIDs, delim) + delim
}
================================================
FILE: component/componentstatus/instance_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package componentstatus
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pipeline"
)
func TestInstanceID(t *testing.T) {
traces := component.MustNewID("traces")
tracesA := pipeline.NewIDWithName(pipeline.SignalTraces, "a")
tracesB := pipeline.NewIDWithName(pipeline.SignalTraces, "b")
tracesC := pipeline.NewIDWithName(pipeline.SignalTraces, "c")
idTracesA := NewInstanceID(traces, component.KindReceiver, tracesA)
idTracesAll := NewInstanceID(traces, component.KindReceiver, tracesA, tracesB, tracesC)
assert.NotEqual(t, idTracesA, idTracesAll)
assertHasPipelines := func(t *testing.T, instanceID *InstanceID, expectedPipelineIDs []pipeline.ID) {
var pipelineIDs []pipeline.ID
instanceID.AllPipelineIDs(func(id pipeline.ID) bool {
pipelineIDs = append(pipelineIDs, id)
return true
})
assert.Equal(t, expectedPipelineIDs, pipelineIDs)
}
for _, tc := range []struct {
name string
id1 *InstanceID
id2 *InstanceID
pipelineIDs []pipeline.ID
}{
{
name: "equal instances",
id1: idTracesA,
id2: NewInstanceID(traces, component.KindReceiver, tracesA),
pipelineIDs: []pipeline.ID{tracesA},
},
{
name: "equal instances - out of order",
id1: idTracesAll,
id2: NewInstanceID(traces, component.KindReceiver, tracesC, tracesB, tracesA),
pipelineIDs: []pipeline.ID{tracesA, tracesB, tracesC},
},
{
name: "with pipelines",
id1: idTracesAll,
id2: idTracesA.WithPipelines(tracesB, tracesC),
pipelineIDs: []pipeline.ID{tracesA, tracesB, tracesC},
},
{
name: "with pipelines - out of order",
id1: idTracesAll,
id2: idTracesA.WithPipelines(tracesC, tracesB),
pipelineIDs: []pipeline.ID{tracesA, tracesB, tracesC},
},
} {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.id1, tc.id2)
assertHasPipelines(t, tc.id1, tc.pipelineIDs)
assertHasPipelines(t, tc.id2, tc.pipelineIDs)
})
}
}
func TestAllPipelineIDs(t *testing.T) {
instanceID := NewInstanceID(
component.MustNewID("traces"),
component.KindReceiver,
pipeline.NewIDWithName(pipeline.SignalTraces, "a"),
pipeline.NewIDWithName(pipeline.SignalTraces, "b"),
pipeline.NewIDWithName(pipeline.SignalTraces, "c"),
)
count := 0
instanceID.AllPipelineIDs(func(pipeline.ID) bool {
count++
return true
})
assert.Equal(t, 3, count)
count = 0
instanceID.AllPipelineIDs(func(pipeline.ID) bool {
count++
return false
})
assert.Equal(t, 1, count)
}
================================================
FILE: component/componentstatus/metadata.yaml
================================================
type: component/componentstatus
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: component/componentstatus/status.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package componentstatus is an experimental module that defines how components should
// report health statues, how collector hosts should facilitate component status reporting,
// and how extensions should watch for new component statuses.
//
// This package is currently under development and is exempt from the Collector SIG's
// breaking change policy.
package componentstatus // import "go.opentelemetry.io/collector/component/componentstatus"
import (
"time"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// Reporter is an extra interface for `component.Host` implementations.
// A Reporter defines how to report a `componentstatus.Event`.
type Reporter interface {
// Report allows a component to report runtime changes in status. The service
// will automatically report status for a component during startup and shutdown. Components can
// use this method to report status after start and before shutdown. For more details about
// component status reporting see: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-status.md
Report(*Event)
}
// Watcher is an extra interface for Extension hosted by the OpenTelemetry
// Collector that is to be implemented by extensions interested in changes to component
// status.
//
// TODO: consider moving this interface to a new package/module like `extension/statuswatcher`
// https://github.com/open-telemetry/opentelemetry-collector/issues/10764
type Watcher interface {
// ComponentStatusChanged notifies about a change in the source component status.
// Extensions that implement this interface must be ready that the ComponentStatusChanged
// may be called before, after or concurrently with calls to Component.Start() and Component.Shutdown().
// The function may be called concurrently with itself.
ComponentStatusChanged(source *InstanceID, event *Event)
}
type Status int32
// Enumeration of possible component statuses
const (
// StatusNone indicates absence of component status.
StatusNone Status = iota
// StatusStarting indicates the component is starting.
StatusStarting
// StatusOK indicates the component is running without issues.
StatusOK
// StatusRecoverableError indicates that the component has experienced a transient error and may recover.
StatusRecoverableError
// StatusPermanentError indicates that the component has detected a condition at runtime that will need human intervention to fix. The collector will continue to run in a degraded mode.
StatusPermanentError
// StatusFatalError indicates that the collector has experienced a fatal runtime error and will shut down.
StatusFatalError
// StatusStopping indicates that the component is in the process of shutting down.
StatusStopping
// StatusStopped indicates that the component has completed shutdown.
StatusStopped
)
// String returns a string representation of a Status
func (s Status) String() string {
switch s {
case StatusStarting:
return "StatusStarting"
case StatusOK:
return "StatusOK"
case StatusRecoverableError:
return "StatusRecoverableError"
case StatusPermanentError:
return "StatusPermanentError"
case StatusFatalError:
return "StatusFatalError"
case StatusStopping:
return "StatusStopping"
case StatusStopped:
return "StatusStopped"
}
return "StatusNone"
}
// Event contains a status, a timestamp, optional error information, and additional attributes
type Event struct {
// attributes provides additional context or metadata for the event.
attributes pcommon.Map
status Status
err error
// TODO: consider if a timestamp is necessary in the default Event struct or is needed only for the healthcheckv2 extension
// https://github.com/open-telemetry/opentelemetry-collector/issues/10763
timestamp time.Time
}
// Status returns the Status (enum) associated with the Event
func (ev *Event) Status() Status {
return ev.status
}
// Err returns the error associated with the Event.
func (ev *Event) Err() error {
return ev.err
}
// Attributes returns the attributes (pcommon.Map) associated with the Event.
func (ev *Event) Attributes() pcommon.Map {
return ev.attributes
}
// Timestamp returns the timestamp associated with the Event
func (ev *Event) Timestamp() time.Time {
return ev.timestamp
}
// EventBuilderOption is a sealed interface wrapping options for [NewEvent].
type EventBuilderOption interface {
applyOption(*Event)
}
type eventOptionFunc func(*Event)
func (f eventOptionFunc) applyOption(event *Event) {
f(event)
}
// NewEvent creates and returns an Event with the specified options and sets the timestamp
// time.Now(). To set an error on the event for an error status use the
// WithError with one of the dedicated status (e.g. StatusRecoverableError, StatusPermanentError, StatusFatalError)
func NewEvent(st Status, opts ...EventBuilderOption) *Event {
event := &Event{
timestamp: time.Now(),
attributes: pcommon.NewMap(),
status: st,
}
for _, opt := range opts {
opt.applyOption(event)
}
return event
}
// WithAttributes sets the Event attributes.
func WithAttributes(attributes pcommon.Map) EventBuilderOption {
return eventOptionFunc(func(e *Event) {
e.attributes = attributes
})
}
// WithError sets the Event error.
func WithError(err error) EventBuilderOption {
return eventOptionFunc(func(e *Event) {
e.err = err
})
}
// NewRecoverableErrorEvent wraps a transient error
// passed as argument as a Event with a status StatusRecoverableError
// and a timestamp set to time.Now().
func NewRecoverableErrorEvent(err error) *Event {
return NewEvent(StatusRecoverableError, WithError(err))
}
// NewPermanentErrorEvent wraps an error requiring human intervention to fix
// passed as argument as a Event with a status StatusPermanentError
// and a timestamp set to time.Now().
func NewPermanentErrorEvent(err error) *Event {
return NewEvent(StatusPermanentError, WithError(err))
}
// NewFatalErrorEvent wraps the fatal runtime error passed as argument as a Event
// with a status StatusFatalError and a timestamp set to time.Now().
func NewFatalErrorEvent(err error) *Event {
return NewEvent(StatusFatalError, WithError(err))
}
// StatusIsError returns true for error statuses (e.g. StatusRecoverableError,
// StatusPermanentError, or StatusFatalError)
func StatusIsError(status Status) bool {
return status == StatusRecoverableError ||
status == StatusPermanentError ||
status == StatusFatalError
}
// ReportStatus is a helper function that handles checking if the component.Host has implemented Reporter.
// If it has, the Event is reported. Otherwise, nothing happens.
func ReportStatus(host component.Host, e *Event) {
statusReporter, ok := host.(Reporter)
if ok {
statusReporter.Report(e)
}
}
================================================
FILE: component/componentstatus/status_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package componentstatus
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestNewStatusEvent(t *testing.T) {
statuses := []Status{
StatusStarting,
StatusOK,
StatusRecoverableError,
StatusPermanentError,
StatusFatalError,
StatusStopping,
StatusStopped,
}
for _, status := range statuses {
t.Run(fmt.Sprintf("%s without error", status), func(t *testing.T) {
ev := NewEvent(status)
require.Equal(t, status, ev.Status())
require.NoError(t, ev.Err())
require.False(t, ev.Timestamp().IsZero())
require.Equal(t, pcommon.NewMap(), ev.Attributes())
})
t.Run(fmt.Sprintf("%s without error and attributes", status), func(t *testing.T) {
eventAttrs := pcommon.NewMap()
require.NoError(t, eventAttrs.FromRaw(map[string]any{"test": "a"}))
ev := NewEvent(status, WithAttributes(eventAttrs))
require.Equal(t, status, ev.Status())
require.NoError(t, ev.Err())
require.False(t, ev.Timestamp().IsZero())
require.Equal(t, eventAttrs, ev.Attributes())
})
}
}
func TestStatusEventsWithError(t *testing.T) {
statusConstructorMap := map[Status]func(error) *Event{
StatusRecoverableError: NewRecoverableErrorEvent,
StatusPermanentError: NewPermanentErrorEvent,
StatusFatalError: NewFatalErrorEvent,
}
for status, newEvent := range statusConstructorMap {
t.Run(fmt.Sprintf("error status constructor for: %s", status), func(t *testing.T) {
ev := newEvent(assert.AnError)
require.Equal(t, status, ev.Status())
require.Equal(t, assert.AnError, ev.Err())
require.False(t, ev.Timestamp().IsZero())
})
}
}
func TestStatusIsError(t *testing.T) {
for _, tc := range []struct {
status Status
isError bool
}{
{
status: StatusStarting,
isError: false,
},
{
status: StatusOK,
isError: false,
},
{
status: StatusRecoverableError,
isError: true,
},
{
status: StatusPermanentError,
isError: true,
},
{
status: StatusFatalError,
isError: true,
},
{
status: StatusStopping,
isError: false,
},
{
status: StatusStopped,
isError: false,
},
} {
name := fmt.Sprintf("StatusIsError(%s) is %t", tc.status, tc.isError)
t.Run(name, func(t *testing.T) {
assert.Equal(t, tc.isError, StatusIsError(tc.status))
})
}
}
func Test_ReportStatus(t *testing.T) {
t.Run("Reporter implemented", func(t *testing.T) {
r := &reporter{}
ReportStatus(r, NewEvent(StatusOK))
require.True(t, r.reportStatusCalled)
})
t.Run("Reporter not implemented", func(t *testing.T) {
h := &host{}
ReportStatus(h, NewEvent(StatusOK))
require.False(t, h.reportStatusCalled)
})
}
var (
_ = component.Host(nil)
_ = Reporter(nil)
)
type reporter struct {
reportStatusCalled bool
}
func (r *reporter) GetExtensions() map[component.ID]component.Component {
return nil
}
func (r *reporter) Report(_ *Event) {
r.reportStatusCalled = true
}
var _ = component.Host(nil)
type host struct {
reportStatusCalled bool
}
func (h *host) GetExtensions() map[component.ID]component.Component {
return nil
}
================================================
FILE: component/componenttest/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: component/componenttest/configtest.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package componenttest // import "go.opentelemetry.io/collector/component/componenttest"
import (
"fmt"
"reflect"
"regexp"
"strings"
"go.uber.org/multierr"
)
// The regular expression for valid config field tag.
var configFieldTagRegExp = regexp.MustCompile("^[a-z0-9][a-z0-9_]*$")
// CheckConfigStruct 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 CheckConfigStruct(config any) error {
t := reflect.TypeOf(config)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Kind() != reflect.Struct {
return fmt.Errorf("config must be a struct or a pointer to one, the passed object is a %s", t.Kind())
}
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:
errs = multierr.Append(errs, validateConfigDataType(t.Elem()))
case reflect.Struct:
// Reflect on the pointed data and check each of its fields.
nf := t.NumField()
for i := range nf {
f := t.Field(i)
errs = multierr.Append(errs, checkStructFieldTags(f))
}
default:
// The config object can carry other types but they are not used when
// reading the configuration via koanf so ignore them. Basically ignore:
// reflect.Uintptr, reflect.Chan, reflect.Func, reflect.Interface, and
// reflect.UnsafePointer.
}
if errs != nil {
return fmt.Errorf("type %q from package %q has invalid config settings: %w", t.Name(), t.PkgPath(), errs)
}
return nil
}
// checkStructFieldTags inspects the tags of a struct field.
func checkStructFieldTags(f reflect.StructField) error {
tagValue, ok := f.Tag.Lookup("mapstructure")
if !ok {
// 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
}
if tagValue == "" {
return fmt.Errorf("mapstructure tag on field %q is empty", f.Name)
}
tagParts := strings.Split(tagValue, ",")
if tagParts[0] != "" {
if tagParts[0] == "-" {
// Nothing to do, as mapstructure decode skips this field.
return nil
}
}
for _, tag := range tagParts[1:] {
switch tag {
case "squash":
if (f.Type.Kind() != reflect.Struct) && (f.Type.Kind() != reflect.Ptr || f.Type.Elem().Kind() != reflect.Struct) {
return fmt.Errorf(
"attempt to squash non-struct type on field %q", f.Name)
}
case "remain":
if f.Type.Kind() != reflect.Map && f.Type.Kind() != reflect.Interface {
return fmt.Errorf(`attempt to use "remain" on non-map or interface type 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 fieldTag != "" && !configFieldTagRegExp.MatchString(fieldTag) {
return fmt.Errorf(
"field %q has config tag %q which doesn't satisfy %q",
f.Name,
fieldTag,
configFieldTagRegExp.String())
}
}
return nil
}
================================================
FILE: component/componenttest/configtest_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package componenttest
import (
"io"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCheckConfigStructPointerAndValue(t *testing.T) {
config := struct {
SomeFiled string `mapstructure:"test"`
}{}
assert.NoError(t, CheckConfigStruct(config))
assert.NoError(t, CheckConfigStruct(&config))
}
func TestCheckConfigStruct(t *testing.T) {
type BadConfigTag struct {
BadTagField int `mapstructure:"test-dash"`
}
tests := []struct {
name string
config any
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: "remain_mapstructure_tag",
config: struct {
AdditionalProperties map[string]any `mapstructure:",remain"`
}{},
},
{
name: "remain_with_interface_type",
config: struct {
AdditionalProperties any `mapstructure:",remain"`
}{},
},
{
name: "omitempty_mapstructure_tag",
config: struct {
MyPublicString string `mapstructure:",omitempty"`
}{},
},
{
name: "named_omitempty_mapstructure_tag",
config: struct {
MyPublicString string `mapstructure:"my_public_string,omitempty"`
}{},
},
{
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: "remain_on_non_map",
config: struct {
AdditionalProperties string `mapstructure:",remain"`
}{},
wantErrMsgSubStr: `attempt to use "remain" on non-map or interface type field "AdditionalProperties"`,
},
{
name: "bad_custom_field_name",
config: struct {
AdditionalProperties any `mapstructure:"Additional_Properties"`
}{},
wantErrMsgSubStr: `field "AdditionalProperties" has config tag "Additional_Properties" which doesn't satisfy "^[a-z0-9][a-z0-9_]*$"`,
},
{
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: "public_field_must_have_nonempty_tag",
config: struct {
PublicFieldWithoutMapstructureTag string `mapstructure:""`
}{},
wantErrMsgSubStr: "mapstructure tag on field \"PublicFieldWithoutMapstructureTag\" is empty",
},
{
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 := CheckConfigStruct(tt.config)
if tt.wantErrMsgSubStr == "" {
assert.NoError(t, err)
} else {
require.ErrorContains(t, err, tt.wantErrMsgSubStr)
}
})
}
}
================================================
FILE: component/componenttest/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package componenttest define types and functions used to help test packages
// implementing the component package interfaces.
package componenttest // import "go.opentelemetry.io/collector/component/componenttest"
================================================
FILE: component/componenttest/go.mod
================================================
module go.opentelemetry.io/collector/component/componenttest
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/otel/metric v1.42.0
go.opentelemetry.io/otel/sdk v1.42.0
go.opentelemetry.io/otel/sdk/metric v1.42.0
go.opentelemetry.io/otel/trace v1.42.0
go.uber.org/goleak v1.3.0
go.uber.org/multierr v1.11.0
go.uber.org/zap v1.27.1
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
golang.org/x/sys v0.41.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/component => ../
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
================================================
FILE: component/componenttest/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: component/componenttest/metadata.yaml
================================================
type: component/componenttest
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: component/componenttest/nop_host.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package componenttest // import "go.opentelemetry.io/collector/component/componenttest"
import (
"go.opentelemetry.io/collector/component"
)
var _ component.Host = (*nopHost)(nil)
// nopHost mocks a [component.Host] for testing purposes.
type nopHost struct{}
// NewNopHost returns a [component.Host] that returns empty values
// from method calls. This host is intended to be used in tests
// where a bare-minimum host is desired.
func NewNopHost() component.Host {
return &nopHost{}
}
// GetExtensions returns an empty extensions map.
func (nh *nopHost) GetExtensions() map[component.ID]component.Component {
return map[component.ID]component.Component{}
}
================================================
FILE: component/componenttest/nop_host_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package componenttest
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewNopHost(t *testing.T) {
nh := NewNopHost()
require.NotNil(t, nh)
require.IsType(t, &nopHost{}, nh)
extensions := nh.GetExtensions()
assert.NotNil(t, extensions)
assert.Empty(t, extensions)
}
================================================
FILE: component/componenttest/nop_telemetry.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package componenttest // import "go.opentelemetry.io/collector/component/componenttest"
import (
noopmetric "go.opentelemetry.io/otel/metric/noop"
nooptrace "go.opentelemetry.io/otel/trace/noop"
"go.uber.org/zap"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// NewNopTelemetrySettings returns a new nop telemetry settings for Create* functions.
func NewNopTelemetrySettings() component.TelemetrySettings {
return component.TelemetrySettings{
Logger: zap.NewNop(),
TracerProvider: nooptrace.NewTracerProvider(),
MeterProvider: noopmetric.NewMeterProvider(),
Resource: pcommon.NewResource(),
}
}
================================================
FILE: component/componenttest/nop_telemetry_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package componenttest
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewNopTelemetrySettings(t *testing.T) {
nts := NewNopTelemetrySettings()
assert.NotNil(t, nts.Logger)
assert.NotNil(t, nts.TracerProvider)
assert.NotPanics(t, func() {
nts.TracerProvider.Tracer("test")
})
assert.NotNil(t, nts.MeterProvider)
assert.NotPanics(t, func() {
nts.MeterProvider.Meter("test")
})
assert.Equal(t, 0, nts.Resource.Attributes().Len())
}
================================================
FILE: component/componenttest/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package componenttest
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: component/componenttest/telemetry.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package componenttest // import "go.opentelemetry.io/collector/component/componenttest"
import (
"context"
"errors"
"fmt"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/sdk/trace/tracetest"
"go.opentelemetry.io/collector/component"
)
type TelemetryOption interface {
apply(*telemetryOption)
}
type telemetryOption struct {
metricOpts []sdkmetric.Option
traceOpts []sdktrace.TracerProviderOption
}
type telemetryOptionFunc func(*telemetryOption)
func (f telemetryOptionFunc) apply(o *telemetryOption) { f(o) }
func WithMetricOptions(opts ...sdkmetric.Option) TelemetryOption {
return telemetryOptionFunc(func(to *telemetryOption) {
to.metricOpts = append(to.metricOpts, opts...)
})
}
func WithTraceOptions(opts ...sdktrace.TracerProviderOption) TelemetryOption {
return telemetryOptionFunc(func(to *telemetryOption) {
to.traceOpts = append(to.traceOpts, opts...)
})
}
type Telemetry struct {
Reader *sdkmetric.ManualReader
SpanRecorder *tracetest.SpanRecorder
meterProvider *sdkmetric.MeterProvider
traceProvider *sdktrace.TracerProvider
}
func NewTelemetry(opts ...TelemetryOption) *Telemetry {
reader := sdkmetric.NewManualReader()
spanRecorder := new(tracetest.SpanRecorder)
tOpts := telemetryOption{
metricOpts: []sdkmetric.Option{sdkmetric.WithReader(reader)},
traceOpts: []sdktrace.TracerProviderOption{sdktrace.WithSpanProcessor(spanRecorder)},
}
for _, opt := range opts {
opt.apply(&tOpts)
}
return &Telemetry{
Reader: reader,
SpanRecorder: spanRecorder,
meterProvider: sdkmetric.NewMeterProvider(tOpts.metricOpts...),
traceProvider: sdktrace.NewTracerProvider(tOpts.traceOpts...),
}
}
func (tt *Telemetry) NewTelemetrySettings() component.TelemetrySettings {
set := NewNopTelemetrySettings()
set.MeterProvider = tt.meterProvider
set.TracerProvider = tt.traceProvider
return set
}
func (tt *Telemetry) GetMetric(name string) (metricdata.Metrics, error) {
var rm metricdata.ResourceMetrics
if err := tt.Reader.Collect(context.Background(), &rm); err != nil {
return metricdata.Metrics{}, err
}
for _, sm := range rm.ScopeMetrics {
for _, m := range sm.Metrics {
if m.Name == name {
return m, nil
}
}
}
return metricdata.Metrics{}, fmt.Errorf("metric '%s' not found", name)
}
func (tt *Telemetry) Shutdown(ctx context.Context) error {
return errors.Join(
tt.meterProvider.Shutdown(ctx),
tt.traceProvider.Shutdown(ctx),
)
}
================================================
FILE: component/componenttest/telemetry_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package componenttest
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
func TestNewTelemetry(t *testing.T) {
tel := NewTelemetry()
assert.NotNil(t, tel.Reader)
assert.NotNil(t, tel.SpanRecorder)
set := tel.NewTelemetrySettings()
assert.IsType(t, &sdktrace.TracerProvider{}, set.TracerProvider)
assert.IsType(t, &sdkmetric.MeterProvider{}, set.MeterProvider)
require.NoError(t, tel.Shutdown(context.Background()))
}
================================================
FILE: component/config.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package component // import "go.opentelemetry.io/collector/component"
// Config defines the configuration for a component.Component.
//
// Implementations and/or any sub-configs (other types embedded or included in the Config implementation)
// MUST implement xconfmap.Validator if any validation is required for that part of the configuration
// (e.g. check if a required field is present).
//
// A valid implementation MUST pass the check componenttest.CheckConfigStruct (return nil error).
type Config any
================================================
FILE: component/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package component outlines the components used in the collector
// and provides a foundation for the component’s creation and
// termination process. A component can be either a receiver, exporter,
// processor, an extension, or a connector.
package component // import "go.opentelemetry.io/collector/component"
================================================
FILE: component/go.mod
================================================
module go.opentelemetry.io/collector/component
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/otel/metric v1.42.0
go.opentelemetry.io/otel/trace v1.42.0
go.uber.org/goleak v1.3.0
go.uber.org/zap v1.27.1
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/pdata => ../pdata
retract (
v0.76.0 // Depends on retracted pdata v1.0.0-rc10 module, use v0.76.1
v0.69.0 // Release failed, use v0.69.1
)
replace go.opentelemetry.io/collector/featuregate => ../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../internal/testutil
================================================
FILE: component/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: component/host.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package component // import "go.opentelemetry.io/collector/component"
// Host represents the entity that is hosting a Component. It is used to allow communication
// between the Component and its host (normally the service.Collector is the host).
//
// Components may require `component.Host` to implement additional interfaces to properly function.
// The component is expected to cast the `component.Host` to the interface it needs and return
// an error if the type assertion fails.
type Host interface {
// GetExtensions returns the map of extensions. Only enabled and created extensions will be returned.
// Typically, it 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 are no performance implications due to iteration.
//
// GetExtensions can be called by the component anytime after Component.Start() begins and
// until Component.Shutdown() ends.
//
// The returned map should only be nil if the host does not support extensions at all.
GetExtensions() map[ID]Component
}
================================================
FILE: component/identifiable.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package component // import "go.opentelemetry.io/collector/component"
import (
"encoding"
"errors"
"fmt"
"regexp"
"strings"
)
// typeAndNameSeparator is the separator that is used between type and name in type/name composite keys.
const typeAndNameSeparator = "/"
var (
// typeRegexp is used to validate the type of component.
// A type must start with an ASCII alphabetic character and
// can only contain ASCII alphanumeric characters and '_'.
// This must be kept in sync with the regex in cmd/mdatagen/validate.go.
typeRegexp = regexp.MustCompile(`^[a-zA-Z][0-9a-zA-Z_]{0,62}$`)
// nameRegexp is used to validate the name of a component. A name can consist of
// 1 to 1024 Unicode characters excluding whitespace, control characters, and
// symbols.
nameRegexp = regexp.MustCompile(`^[^\pZ\pC\pS]+$`)
)
var _ fmt.Stringer = Type{}
// Type is the component type as it is used in the config.
type Type struct {
name string
}
// String returns the string representation of the type.
func (t Type) String() string {
return t.name
}
// MarshalText marshals returns the Type name.
func (t Type) MarshalText() ([]byte, error) {
return []byte(t.name), nil
}
// NewType creates a type. It returns an error if the type is invalid.
// A type must
// - have at least one character,
// - start with an ASCII alphabetic character and
// - can only contain ASCII alphanumeric characters and '_'.
func NewType(ty string) (Type, error) {
if ty == "" {
return Type{}, errors.New("id must not be empty")
}
if !typeRegexp.MatchString(ty) {
return Type{}, fmt.Errorf("invalid character(s) in type %q", ty)
}
return Type{name: ty}, nil
}
// MustNewType creates a type. It panics if the type is invalid.
// A type must
// - have at least one character,
// - start with an ASCII alphabetic character and
// - can only contain ASCII alphanumeric characters and '_'.
func MustNewType(strType string) Type {
ty, err := NewType(strType)
if err != nil {
panic(err)
}
return ty
}
var (
_ fmt.Stringer = ID{}
_ encoding.TextMarshaler = ID{}
_ encoding.TextUnmarshaler = (*ID)(nil)
)
// ID represents the identity for a component. It combines two values:
// * type - the Type of the component.
// * name - the name of that component.
// The component ID (combination type + name) is unique for a given component.Kind.
type ID struct {
typeVal Type `mapstructure:"-"`
nameVal string `mapstructure:"-"`
}
// NewID returns a new ID with the given Type and empty name.
func NewID(typeVal Type) ID {
return ID{typeVal: typeVal}
}
// MustNewID builds a Type and returns a new ID with the given Type and empty name.
// This is equivalent to NewID(MustNewType(typeVal)).
// See MustNewType to check the valid values of typeVal.
func MustNewID(typeVal string) ID {
return NewID(MustNewType(typeVal))
}
// NewIDWithName returns a new ID with the given Type and name.
func NewIDWithName(typeVal Type, nameVal string) ID {
return ID{typeVal: typeVal, nameVal: nameVal}
}
// MustNewIDWithName builds a Type and returns a new ID with the given Type and name.
// This is equivalent to NewIDWithName(MustNewType(typeVal), nameVal).
// See MustNewType to check the valid values of typeVal.
func MustNewIDWithName(typeVal, nameVal string) ID {
return NewIDWithName(MustNewType(typeVal), nameVal)
}
// Type returns the type of the component.
func (id ID) Type() Type {
return id.typeVal
}
// Name returns the custom name of the component.
func (id ID) Name() string {
return id.nameVal
}
// MarshalText implements the encoding.TextMarshaler interface.
// This marshals the type and name as one string in the config.
func (id ID) MarshalText() ([]byte, error) {
return []byte(id.String()), nil
}
// UnmarshalText implements the encoding.TextUnmarshaler interface.
func (id *ID) UnmarshalText(text []byte) error {
idStr := string(text)
typeStr, nameStr, hasName := strings.Cut(idStr, typeAndNameSeparator)
typeStr = strings.TrimSpace(typeStr)
if typeStr == "" {
if hasName {
return fmt.Errorf("in %q id: the part before %s should not be empty", idStr, typeAndNameSeparator)
}
return errors.New("id must not be empty")
}
if hasName {
// "name" part is present.
nameStr = strings.TrimSpace(nameStr)
if nameStr == "" {
return fmt.Errorf("in %q id: the part after %s should not be empty", idStr, typeAndNameSeparator)
}
if err := validateName(nameStr); err != nil {
return fmt.Errorf("in %q id: %w", nameStr, err)
}
}
var err error
if id.typeVal, err = NewType(typeStr); err != nil {
return fmt.Errorf("in %q id: %w", idStr, err)
}
id.nameVal = nameStr
return nil
}
// String returns the ID string representation as "type[/name]" format.
func (id ID) String() string {
if id.nameVal == "" {
return id.typeVal.String()
}
return id.typeVal.String() + typeAndNameSeparator + id.nameVal
}
func validateName(nameStr string) error {
if len(nameStr) > 1024 {
return fmt.Errorf("name %q is longer than 1024 characters (%d characters)", nameStr, len(nameStr))
}
if !nameRegexp.MatchString(nameStr) {
return fmt.Errorf("invalid character(s) in name %q", nameStr)
}
return nil
}
================================================
FILE: component/identifiable_example_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package component_test
import (
"fmt"
"go.opentelemetry.io/collector/component"
)
func ExampleNewType() {
t, err := component.NewType("exampleexporter")
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(t.String())
// Output:
// exampleexporter
}
func ExampleMustNewType() {
t := component.MustNewType("examplereceiver")
fmt.Println(t.String())
// Output:
// examplereceiver
}
func ExampleNewID() {
t := component.MustNewType("exampleprocessor")
id := component.NewID(t)
fmt.Println(id.String())
// Output:
// exampleprocessor
}
func ExampleMustNewID() {
id := component.MustNewID("examplereceiver")
fmt.Println(id.String())
// Output:
// examplereceiver
}
func ExampleNewIDWithName() {
t := component.MustNewType("exampleexporter")
id := component.NewIDWithName(t, "customname")
fmt.Println(id.String())
// Output:
// exampleexporter/customname
}
func ExampleMustNewIDWithName() {
id := component.MustNewIDWithName("exampleprocessor", "customproc")
fmt.Println(id.String())
// Output:
// exampleprocessor/customproc
}
================================================
FILE: component/identifiable_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package component
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestMarshalText(t *testing.T) {
id := NewIDWithName(MustNewType("test"), "name")
got, err := id.MarshalText()
require.NoError(t, err)
assert.Equal(t, id.String(), string(got))
}
func TestUnmarshalText(t *testing.T) {
validType := MustNewType("valid_type")
testCases := []struct {
name string
expectedErr bool
expectedID ID
}{
{
name: "valid_type",
expectedID: ID{typeVal: validType, nameVal: ""},
},
{
name: "valid_type/valid_name",
expectedID: ID{typeVal: validType, nameVal: "valid_name"},
},
{
name: " valid_type / valid_name ",
expectedID: ID{typeVal: validType, nameVal: "valid_name"},
},
{
name: "valid_type/中文好",
expectedID: ID{typeVal: validType, nameVal: "中文好"},
},
{
name: "valid_type/name-with-dashes",
expectedID: ID{typeVal: validType, nameVal: "name-with-dashes"},
},
// issue 10816
{
name: "valid_type/Linux-Messages-File_01J49HCH3SWFXRVASWFZFRT3J2__processor0__logs",
expectedID: ID{typeVal: validType, nameVal: "Linux-Messages-File_01J49HCH3SWFXRVASWFZFRT3J2__processor0__logs"},
},
{
name: "valid_type/1",
expectedID: ID{typeVal: validType, nameVal: "1"},
},
{
name: "/valid_name",
expectedErr: true,
},
{
name: " /valid_name",
expectedErr: true,
},
{
name: "valid_type/",
expectedErr: true,
},
{
name: "valid_type/ ",
expectedErr: true,
},
{
name: " ",
expectedErr: true,
},
{
name: "valid_type/invalid name",
expectedErr: true,
},
{
name: "valid_type/" + strings.Repeat("a", 1025),
expectedErr: true,
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
id := ID{}
err := id.UnmarshalText([]byte(tt.name))
if tt.expectedErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tt.expectedID, id)
assert.Equal(t, tt.expectedID.Type(), id.Type())
assert.Equal(t, tt.expectedID.Name(), id.Name())
assert.Equal(t, tt.expectedID.String(), id.String())
})
}
}
func TestNewType(t *testing.T) {
tests := []struct {
name string
shouldErr bool
}{
{name: "active_directory_ds"},
{name: "aerospike"},
{name: "alertmanager"},
{name: "alibabacloud_logservice"},
{name: "apache"},
{name: "apachespark"},
{name: "asapclient"},
{name: "attributes"},
{name: "awscloudwatch"},
{name: "awscloudwatchlogs"},
{name: "awscloudwatchmetrics"},
{name: "awscontainerinsightreceiver"},
{name: "awsecscontainermetrics"},
{name: "awsemf"},
{name: "awsfirehose"},
{name: "awskinesis"},
{name: "awsproxy"},
{name: "awss3"},
{name: "awsxray"},
{name: "azureblob"},
{name: "azuredataexplorer"},
{name: "azureeventhub"},
{name: "azuremonitor"},
{name: "basicauth"},
{name: "batch"},
{name: "bearertokenauth"},
{name: "bigip"},
{name: "carbon"},
{name: "cassandra"},
{name: "chrony"},
{name: "clickhouse"},
{name: "cloudflare"},
{name: "cloudfoundry"},
{name: "collectd"},
{name: "configschema"},
{name: "coralogix"},
{name: "couchdb"},
{name: "count"},
{name: "cumulativetodelta"},
{name: "datadog"},
{name: "dataset"},
{name: "db_storage"},
{name: "debug"},
{name: "deltatorate"},
{name: "demo"},
{name: "docker_observer"},
{name: "docker_stats"},
{name: "dynatrace"},
{name: "ecs_observer"},
{name: "ecs_task_observer"},
{name: "elasticsearch"},
{name: "exceptions"},
{name: "experimental_metricsgeneration"},
{name: "expvar"},
{name: "f5cloud"},
{name: "failover"},
{name: "file"},
{name: "filelog"},
{name: "filestats"},
{name: "file_storage"},
{name: "filter"},
{name: "flinkmetrics"},
{name: "fluentforward"},
{name: "forward"},
{name: "githubgen"},
{name: "gitprovider"},
{name: "golden"},
{name: "googlecloud"},
{name: "googlecloudpubsub"},
{name: "googlecloudspanner"},
{name: "googlemanagedprometheus"},
{name: "groupbyattrs"},
{name: "groupbytrace"},
{name: "haproxy"},
{name: "headers_setter"},
{name: "health_check"},
{name: "honeycombmarker"},
{name: "hostmetrics"},
{name: "host_observer"},
{name: "httpcheck"},
{name: "http_forwarder"},
{name: "iis"},
{name: "influxdb"},
{name: "instana"},
{name: "interval"},
{name: "jaeger"},
{name: "jaeger_encoding"},
{name: "jaegerremotesampling"},
{name: "jmx"},
{name: "journald"},
{name: "json_log_encoding"},
{name: "k8sattributes"},
{name: "k8s_cluster"},
{name: "k8s_events"},
{name: "k8sobjects"},
{name: "k8s_observer"},
{name: "kafka"},
{name: "kafkametrics"},
{name: "kinetica"},
{name: "kubeletstats"},
{name: "loadbalancing"},
{name: "logicmonitor"},
{name: "logstransform"},
{name: "logzio"},
{name: "loki"},
{name: "mdatagen"},
{name: "memcached"},
{name: "memory_limiter"},
{name: "metricstransform"},
{name: "mezmo"},
{name: "mongodb"},
{name: "mongodbatlas"},
{name: "mysql"},
{name: "namedpipe"},
{name: "nginx"},
{name: "nsxt"},
{name: "oauth2client"},
{name: "oidc"},
{name: "opamp"},
{name: "opampsupervisor"},
{name: "opencensus"},
{name: "opensearch"},
{name: "oracledb"},
{name: "osquery"},
{name: "otelarrow"},
{name: "otelcontribcol"},
{name: "oteltestbedcol"},
{name: "otlp"},
{name: "otlp_encoding"},
{name: "otlp_http"},
{name: "otlphttp"},
{name: "otlpjsonfile"},
{name: "ottl"},
{name: "podman_stats"},
{name: "postgresql"},
{name: "pprof"},
{name: "probabilistic_sampler"},
{name: "prometheus"},
{name: "prometheusremotewrite"},
{name: "prometheus_simple"},
{name: "pulsar"},
{name: "purefa"},
{name: "purefb"},
{name: "rabbitmq"},
{name: "receiver_creator"},
{name: "redaction"},
{name: "redis"},
{name: "remotetap"},
{name: "resource"},
{name: "resourcedetection"},
{name: "riak"},
{name: "routing"},
{name: "saphana"},
{name: "sapm"},
{name: "schema"},
{name: "sentry"},
{name: "servicegraph"},
{name: "signalfx"},
{name: "sigv4auth"},
{name: "skywalking"},
{name: "snmp"},
{name: "snowflake"},
{name: "solace"},
{name: "solarwindsapmsettings"},
{name: "span"},
{name: "spanmetrics"},
{name: "splunkenterprise"},
{name: "splunk_hec"},
{name: "sqlquery"},
{name: "sqlserver"},
{name: "sshcheck"},
{name: "statsd"},
{name: "sumologic"},
{name: "syslog"},
{name: "tail_sampling"},
{name: "tcplog"},
{name: "telemetrygen"},
{name: "tencentcloud_logservice"},
{name: "text_encoding"},
{name: "transform"},
{name: "udplog"},
{name: "vcenter"},
{name: "wavefront"},
{name: "webhookevent"},
{name: "windowseventlog"},
{name: "windowsperfcounters"},
{name: "zipkin"},
{name: "zipkin_encoding"},
{name: "zookeeper"},
{name: "zpages"},
{name: strings.Repeat("a", 63)},
{name: "", shouldErr: true},
{name: "contains spaces", shouldErr: true},
{name: "contains-dashes", shouldErr: true},
{name: "0startswithnumber", shouldErr: true},
{name: "contains/slash", shouldErr: true},
{name: "contains:colon", shouldErr: true},
{name: "contains#hash", shouldErr: true},
{name: strings.Repeat("a", 64), shouldErr: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ty, err := NewType(tt.name)
if tt.shouldErr {
assert.Error(t, err)
} else {
require.NoError(t, err)
assert.Equal(t, tt.name, ty.String())
}
})
}
}
================================================
FILE: component/metadata.yaml
================================================
type: component
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: component/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package component
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: component/telemetry.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package component // import "go.opentelemetry.io/collector/component"
import (
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// TelemetrySettings provides components with APIs to report telemetry.
type TelemetrySettings 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
// TracerProvider that the factory can pass to other instrumented third-party libraries.
//
// The service may wrap this provider for attribute injection. The wrapper may implement an
// additional `Unwrap() trace.TracerProvider` method to grant access to the underlying SDK.
TracerProvider trace.TracerProvider
// MeterProvider that the factory can pass to other instrumented third-party libraries.
MeterProvider metric.MeterProvider
// Resource contains the resource attributes for the collector's telemetry.
Resource pcommon.Resource
// prevent unkeyed literal initialization
_ struct{}
}
================================================
FILE: config/configauth/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: config/configauth/README.md
================================================
# Authentication configuration
This module defines necessary interfaces to implement server and client type authenticators:
- Server type authenticators perform authentication for incoming HTTP/gRPC requests and are typically used in receivers.
- Client type authenticators perform client-side authentication for outgoing HTTP/gRPC requests and are typically used in exporters.
The currently known authenticators are listed below.
## Server Authenticators
- [Basic Auth Extension](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/extension/basicauthextension)
- [Bearer Token Extension](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/extension/bearertokenauthextension)
- [OIDC Extension](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/extension/oidcauthextension)
## Client Authenticators
- [ASAP Client Authentication Extension](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/extension/asapauthextension)
- [Basic Auth Extension](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/extension/basicauthextension)
- [Bearer Token Extension](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/extension/bearertokenauthextension)
- [OAuth2 Client Extension](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/extension/oauth2clientauthextension)
- [Sigv4 Extension](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/extension/sigv4authextension)
Example:
```yaml
extensions:
oidc:
# see the blog post on securing the otelcol for information
# on how to setup an OIDC server and how to generate the TLS certs
# required for this example
# https://medium.com/opentelemetry/securing-your-opentelemetry-collector-1a4f9fa5bd6f
issuer_url: http://localhost:8080/auth/realms/opentelemetry
audience: account
oauth2client:
client_id: someclientid
client_secret: someclientsecret
token_url: https://example.com/oauth2/default/v1/token
scopes: ["api.metrics"]
# tls settings for the token client
tls:
insecure: true
ca_file: /var/lib/mycert.pem
cert_file: certfile
key_file: keyfile
# timeout for the token client
timeout: 2s
receivers:
otlp/with_auth:
protocols:
grpc:
endpoint: localhost:4318
tls:
cert_file: /tmp/certs/cert.pem
key_file: /tmp/certs/cert-key.pem
auth:
## oidc is the extension name to use as the authenticator for this receiver
authenticator: oidc
otlp_http/withauth:
endpoint: http://localhost:9000
auth:
authenticator: oauth2client
```
## Creating an authenticator
New authenticators can be added by creating a new extension that also implements the appropriate interface (`configauth.ServerAuthenticator` or `configauth.ClientAuthenticator`).
Generic authenticators that may be used by a good number of users might be accepted as part of the contrib distribution. If you have an interest in contributing an authenticator, open an issue with your proposal. For other cases, you'll need to include your custom authenticator as part of your custom OpenTelemetry Collector, perhaps being built using the [OpenTelemetry Collector Builder](https://github.com/open-telemetry/opentelemetry-collector/tree/main/cmd/builder).
================================================
FILE: config/configauth/config.schema.yaml
================================================
$defs:
config:
description: Config defines the auth settings for the receiver.
type: object
properties:
authenticator:
description: AuthenticatorID specifies the name of the extension to use in order to authenticate the incoming data point.
type: string
x-customType: go.opentelemetry.io/collector/component.ID
================================================
FILE: config/configauth/configauth.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package configauth implements the configuration settings to
// ensure authentication on incoming requests, and allows
// exporters to add authentication on outgoing requests.
package configauth // import "go.opentelemetry.io/collector/config/configauth"
import (
"context"
"errors"
"fmt"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/extension/extensionauth"
)
var (
errAuthenticatorNotFound = errors.New("authenticator not found")
errNotHTTPClient = errors.New("requested authenticator is not a HTTP client authenticator")
errNotGRPCClient = errors.New("requested authenticator is not a gRPC client authenticator")
errNotServer = errors.New("requested authenticator is not a server authenticator")
)
// Config defines the auth settings for the receiver.
type Config struct {
// AuthenticatorID specifies the name of the extension to use in order to authenticate the incoming data point.
AuthenticatorID component.ID `mapstructure:"authenticator,omitempty"`
// prevent unkeyed literal initialization
_ struct{}
}
// GetServerAuthenticator attempts to select the appropriate extensionauth.Server from the list of extensions,
// based on the requested extension name. If an authenticator is not found, an error is returned.
func (a Config) GetServerAuthenticator(_ context.Context, extensions map[component.ID]component.Component) (extensionauth.Server, error) {
if ext, found := extensions[a.AuthenticatorID]; found {
if server, ok := ext.(extensionauth.Server); ok {
return server, nil
}
return nil, errNotServer
}
return nil, fmt.Errorf("failed to resolve authenticator %q: %w", a.AuthenticatorID, errAuthenticatorNotFound)
}
// GetHTTPClientAuthenticator attempts to select the appropriate extensionauth.Client from the list of extensions,
// based on the component id of the extension. If an authenticator is not found, an error is returned.
// This should be only used by HTTP clients.
func (a Config) GetHTTPClientAuthenticator(_ context.Context, extensions map[component.ID]component.Component) (extensionauth.HTTPClient, error) {
if ext, found := extensions[a.AuthenticatorID]; found {
if client, ok := ext.(extensionauth.HTTPClient); ok {
return client, nil
}
return nil, errNotHTTPClient
}
return nil, fmt.Errorf("failed to resolve authenticator %q: %w", a.AuthenticatorID, errAuthenticatorNotFound)
}
// GetGRPCClientAuthenticator attempts to select the appropriate extensionauth.Client from the list of extensions,
// based on the component id of the extension. If an authenticator is not found, an error is returned.
// This should be only used by gRPC clients.
func (a Config) GetGRPCClientAuthenticator(_ context.Context, extensions map[component.ID]component.Component) (extensionauth.GRPCClient, error) {
if ext, found := extensions[a.AuthenticatorID]; found {
if client, ok := ext.(extensionauth.GRPCClient); ok {
return client, nil
}
return nil, errNotGRPCClient
}
return nil, fmt.Errorf("failed to resolve authenticator %q: %w", a.AuthenticatorID, errAuthenticatorNotFound)
}
================================================
FILE: config/configauth/configauth_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configauth
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/extension"
"go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest"
)
var mockID = component.MustNewID("mock")
func TestGetServer(t *testing.T) {
testCases := []struct {
name string
authenticator extension.Extension
expected error
}{
{
name: "obtain server authenticator",
authenticator: extensionauthtest.NewNopServer(),
expected: nil,
},
{
name: "not a server authenticator",
authenticator: extensionauthtest.NewNopClient(),
expected: errNotServer,
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
// prepare
cfg := &Config{
AuthenticatorID: mockID,
}
ext := map[component.ID]component.Component{
mockID: tt.authenticator,
}
authenticator, err := cfg.GetServerAuthenticator(context.Background(), ext)
// verify
if tt.expected != nil {
require.ErrorIs(t, err, tt.expected)
assert.Nil(t, authenticator)
} else {
require.NoError(t, err)
assert.NotNil(t, authenticator)
}
})
}
}
func TestGetServerFails(t *testing.T) {
cfg := &Config{
AuthenticatorID: component.MustNewID("does_not_exist"),
}
authenticator, err := cfg.GetServerAuthenticator(context.Background(), map[component.ID]component.Component{})
require.ErrorIs(t, err, errAuthenticatorNotFound)
assert.Nil(t, authenticator)
}
func TestGetHTTPClient(t *testing.T) {
testCases := []struct {
name string
authenticator extension.Extension
expected error
}{
{
name: "obtain client authenticator",
authenticator: extensionauthtest.NewNopClient(),
expected: nil,
},
{
name: "not a client authenticator",
authenticator: extensionauthtest.NewNopServer(),
expected: errNotHTTPClient,
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
// prepare
cfg := &Config{
AuthenticatorID: mockID,
}
ext := map[component.ID]component.Component{
mockID: tt.authenticator,
}
authenticator, err := cfg.GetHTTPClientAuthenticator(context.Background(), ext)
// verify
if tt.expected != nil {
require.ErrorIs(t, err, tt.expected)
assert.Nil(t, authenticator)
} else {
require.NoError(t, err)
assert.NotNil(t, authenticator)
}
})
}
}
func TestGetGRPCClientFails(t *testing.T) {
cfg := &Config{
AuthenticatorID: component.MustNewID("does_not_exist"),
}
authenticator, err := cfg.GetGRPCClientAuthenticator(context.Background(), map[component.ID]component.Component{})
require.ErrorIs(t, err, errAuthenticatorNotFound)
assert.Nil(t, authenticator)
}
================================================
FILE: config/configauth/go.mod
================================================
module go.opentelemetry.io/collector/config/configauth
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/extension v1.54.0
go.opentelemetry.io/collector/extension/extensionauth v1.54.0
go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest v0.148.0
go.uber.org/goleak v1.3.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
go.opentelemetry.io/collector/pdata v1.54.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/extension => ../../extension
replace go.opentelemetry.io/collector/extension/extensionauth => ../../extension/extensionauth
replace go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest => ../../extension/extensionauth/extensionauthtest
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
================================================
FILE: config/configauth/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: config/configauth/metadata.yaml
================================================
type: config/configauth
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: config/configauth/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configauth
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: config/configcompression/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: config/configcompression/compressiontype.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configcompression // import "go.opentelemetry.io/collector/config/configcompression"
import (
"compress/zlib"
"fmt"
)
// Type represents a compression method
type Type string
type Level int
type CompressionParams struct {
Level Level `mapstructure:"level"`
// prevent unkeyed literal initialization
_ struct{}
}
const (
TypeGzip Type = "gzip"
TypeZlib Type = "zlib"
TypeDeflate Type = "deflate"
TypeSnappy Type = "snappy"
TypeSnappyFramed Type = "x-snappy-framed"
TypeZstd Type = "zstd"
TypeLz4 Type = "lz4"
typeNone Type = "none"
typeEmpty Type = ""
DefaultCompressionLevel = zlib.DefaultCompression
)
// IsCompressed returns false if CompressionType is nil, none, or empty.
// Otherwise, returns true.
func (ct *Type) IsCompressed() bool {
return *ct != typeEmpty && *ct != typeNone
}
func (ct *Type) UnmarshalText(in []byte) error {
typ := Type(in)
if typ == TypeGzip ||
typ == TypeZlib ||
typ == TypeDeflate ||
typ == TypeSnappy ||
typ == TypeSnappyFramed ||
typ == TypeZstd ||
typ == TypeLz4 ||
typ == typeNone ||
typ == typeEmpty {
*ct = typ
return nil
}
return fmt.Errorf("unsupported compression type %q", typ)
}
func (ct *Type) ValidateParams(p CompressionParams) error {
switch *ct {
case TypeGzip, TypeZlib, TypeDeflate:
if p.Level == zlib.DefaultCompression ||
p.Level == zlib.HuffmanOnly ||
p.Level == zlib.NoCompression ||
(p.Level >= zlib.BestSpeed && p.Level <= zlib.BestCompression) {
return nil
}
case TypeZstd:
// Supports arbitrary levels: zstd will map any given
// level to the nearest internally supported level.
return nil
}
if p.Level != 0 {
return fmt.Errorf("unsupported parameters {Level:%+v} for compression type %q", p.Level, *ct)
}
return nil
}
================================================
FILE: config/configcompression/compressiontype_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configcompression
import (
"compress/zlib"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestUnmarshalText(t *testing.T) {
tests := []struct {
name string
compressionName []byte
shouldError bool
isCompressed bool
}{
{
name: "ValidGzip",
compressionName: []byte("gzip"),
shouldError: false,
isCompressed: true,
},
{
name: "ValidZlib",
compressionName: []byte("zlib"),
shouldError: false,
isCompressed: true,
},
{
name: "ValidDeflate",
compressionName: []byte("deflate"),
shouldError: false,
isCompressed: true,
},
{
name: "ValidSnappy",
compressionName: []byte("snappy"),
shouldError: false,
isCompressed: true,
},
{
name: "ValidSnappyFramed",
compressionName: []byte("x-snappy-framed"),
shouldError: false,
isCompressed: true,
},
{
name: "ValidZstd",
compressionName: []byte("zstd"),
shouldError: false,
isCompressed: true,
},
{
name: "ValidEmpty",
compressionName: []byte(""),
shouldError: false,
},
{
name: "ValidNone",
compressionName: []byte("none"),
shouldError: false,
},
{
name: "ValidLz4",
compressionName: []byte("lz4"),
isCompressed: true,
shouldError: false,
},
{
name: "Invalid",
compressionName: []byte("ggip"),
shouldError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
temp := typeNone
err := temp.UnmarshalText(tt.compressionName)
if tt.shouldError {
assert.Error(t, err)
return
}
require.NoError(t, err)
ct := Type(tt.compressionName)
assert.Equal(t, temp, ct)
assert.Equal(t, tt.isCompressed, ct.IsCompressed())
})
}
}
func TestValidateParams(t *testing.T) {
tests := []struct {
name string
compressionName []byte
compressionLevel Level
shouldError bool
}{
{
name: "ValidGzip",
compressionName: []byte("gzip"),
compressionLevel: zlib.DefaultCompression,
shouldError: false,
},
{
name: "InvalidGzip",
compressionName: []byte("gzip"),
compressionLevel: 99,
shouldError: true,
},
{
name: "ValidZlib",
compressionName: []byte("zlib"),
compressionLevel: zlib.DefaultCompression,
shouldError: false,
},
{
name: "ValidDeflate",
compressionName: []byte("deflate"),
compressionLevel: zlib.DefaultCompression,
shouldError: false,
},
{
name: "ValidSnappy",
compressionName: []byte("snappy"),
shouldError: false,
},
{
name: "InvalidSnappy",
compressionName: []byte("snappy"),
compressionLevel: 1,
shouldError: true,
},
{
name: "ValidSnappyFramed",
compressionName: []byte("x-snappy-framed"),
shouldError: false,
},
{
name: "InvalidSnappyFramed",
compressionName: []byte("x-snappy-framed"),
compressionLevel: 1,
shouldError: true,
},
{
name: "ValidZstd",
compressionName: []byte("zstd"),
compressionLevel: zlib.DefaultCompression,
shouldError: false,
},
{
name: "ValidEmpty",
compressionName: []byte(""),
shouldError: false,
},
{
name: "ValidNone",
compressionName: []byte("none"),
shouldError: false,
},
{
name: "ValidLz4",
compressionName: []byte("lz4"),
shouldError: false,
},
{
name: "InvalidLz4",
compressionName: []byte("lz4"),
compressionLevel: 1,
shouldError: true,
},
{
name: "Invalid",
compressionName: []byte("ggip"),
compressionLevel: zlib.DefaultCompression,
shouldError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
compressionParams := CompressionParams{Level: tt.compressionLevel}
temp := Type(tt.compressionName)
err := temp.ValidateParams(compressionParams)
if tt.shouldError {
assert.Error(t, err)
return
}
require.NoError(t, err)
})
}
}
================================================
FILE: config/configcompression/config.schema.yaml
================================================
$defs:
compression_params:
type: object
properties:
level:
$ref: level
level:
type: integer
type:
description: Type represents a compression method
type: string
================================================
FILE: config/configcompression/go.mod
================================================
module go.opentelemetry.io/collector/config/configcompression
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.uber.org/goleak v1.3.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
================================================
FILE: config/configcompression/go.sum
================================================
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
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/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: config/configcompression/metadata.yaml
================================================
type: config/configcompression
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: config/configcompression/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configcompression
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: config/configgrpc/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: config/configgrpc/README.md
================================================
# 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/main/exporter/README.md)
leverage client configuration.
Note that client configuration supports TLS configuration, the
configuration parameters are also defined under `tls` 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): Default before v0.103.0 is `pick_first`, default for v0.103.0 is `round_robin`. See [issue](https://github.com/open-telemetry/opentelemetry-collector/issues/10298). To restore the previous behavior, set `balancer_name` to `pick_first`.
- `compression`: Compression type to use among `gzip`, `snappy`, `zstd`, and `none`.
- `endpoint`: Valid value syntax available [here](https://github.com/grpc/grpc/blob/master/doc/naming.md)
- [`tls`](../configtls/README.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)
- [`auth`](../configauth/README.md)
- [`middlewares`](../configmiddleware/README.md)
Please note that [`per_rpc_auth`](https://pkg.go.dev/google.golang.org/grpc#PerRPCCredentials) which allows the credentials to send for every RPC is now moved to become an [extension](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/extension/bearertokenauthextension). Note that this feature isn't about sending the headers only during the initial connection as an `authorization` header under the `headers` would do: this is sent for every RPC performed during an established connection.
Example:
```yaml
exporters:
otlp_grpc:
endpoint: otelcol2:55690
auth:
authenticator: some-authenticator-extension
tls:
ca_file: ca.pem
cert_file: cert.pem
key_file: key.pem
headers:
test1: "value1"
"test 2": "value 2"
```
### Compression Comparison
[configgrpc_benchmark_test.go](./configgrpc_benchmark_test.go) contains benchmarks comparing the supported compression algorithms. It performs compression using `gzip`, `zstd`, and `snappy` compression on small, medium, and large sized log, trace, and metric payloads. Each test case outputs the uncompressed payload size, the compressed payload size, and the average nanoseconds spent on compression.
The following table summarizes the results, including some additional columns computed from the raw data. The benchmarks were performed on an AWS m5.large EC2 instance with an Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz.
| Request | Compressor | Raw Bytes | Compressed bytes | Compression ratio | Ns / op | Mb compressed / second | Mb saved / second |
|-------------------|------------|-----------|------------------|-------------------|---------|------------------------|-------------------|
| lg_log_request | gzip | 5150 | 262 | 19.66 | 49231 | 104.61 | 99.29 |
| lg_metric_request | gzip | 6800 | 201 | 33.83 | 51816 | 131.23 | 127.35 |
| lg_trace_request | gzip | 9200 | 270 | 34.07 | 65174 | 141.16 | 137.02 |
| md_log_request | gzip | 363 | 268 | 1.35 | 37609 | 9.65 | 2.53 |
| md_metric_request | gzip | 320 | 145 | 2.21 | 30141 | 10.62 | 5.81 |
| md_trace_request | gzip | 451 | 288 | 1.57 | 38270 | 11.78 | 4.26 |
| sm_log_request | gzip | 166 | 168 | 0.99 | 30511 | 5.44 | -0.07 |
| sm_metric_request | gzip | 185 | 142 | 1.30 | 29055 | 6.37 | 1.48 |
| sm_trace_request | gzip | 233 | 205 | 1.14 | 33466 | 6.96 | 0.84 |
| lg_log_request | snappy | 5150 | 475 | 10.84 | 1915 | 2,689.30 | 2,441.25 |
| lg_metric_request | snappy | 6800 | 466 | 14.59 | 2266 | 3,000.88 | 2,795.23 |
| lg_trace_request | snappy | 9200 | 644 | 14.29 | 3281 | 2,804.02 | 2,607.74 |
| md_log_request | snappy | 363 | 300 | 1.21 | 770.0 | 471.43 | 81.82 |
| md_metric_request | snappy | 320 | 162 | 1.98 | 588.6 | 543.66 | 268.43 |
| md_trace_request | snappy | 451 | 330 | 1.37 | 907.7 | 496.86 | 133.30 |
| sm_log_request | snappy | 166 | 184 | 0.90 | 551.8 | 300.83 | -32.62 |
| sm_metric_request | snappy | 185 | 154 | 1.20 | 526.3 | 351.51 | 58.90 |
| sm_trace_request | snappy | 233 | 251 | 0.93 | 682.1 | 341.59 | -26.39 |
| lg_log_request | zstd | 5150 | 223 | 23.09 | 17998 | 286.14 | 273.75 |
| lg_metric_request | zstd | 6800 | 144 | 47.22 | 14289 | 475.89 | 465.81 |
| lg_trace_request | zstd | 9200 | 208 | 44.23 | 17160 | 536.13 | 524.01 |
| md_log_request | zstd | 363 | 261 | 1.39 | 11216 | 32.36 | 9.09 |
| md_metric_request | zstd | 320 | 145 | 2.21 | 9318 | 34.34 | 18.78 |
| md_trace_request | zstd | 451 | 301 | 1.50 | 12583 | 35.84 | 11.92 |
| sm_log_request | zstd | 166 | 165 | 1.01 | 12482 | 13.30 | 0.08 |
| sm_metric_request | zstd | 185 | 139 | 1.33 | 8824 | 20.97 | 5.21 |
| sm_trace_request | zstd | 233 | 203 | 1.15 | 10134 | 22.99 | 2.96 |
Compression ratios will vary in practice as they are highly dependent on the data's information entropy. Compression rates are dependent on the speed of the CPU, and the size of payloads being compressed: smaller payloads compress at slower rates relative to larger payloads, which are able to amortize fixed computation costs over more bytes.
`gzip` is the only required compression algorithm required for [OTLP servers](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/otlp.md#protocol-details), and is a natural first choice. It is not as fast as `snappy`, but achieves better compression ratios and has reasonable performance. If your collector is CPU bound and your OTLP server supports it, you may benefit from using `snappy` compression. If your collector is CPU bound and has a very fast network link, you may benefit from disabling compression, which is the default.
## Server Configuration
[Receivers](https://github.com/open-telemetry/opentelemetry-collector/blob/main/receiver/README.md)
leverage server configuration.
Note that transport configuration can also be configured. The default transport
type is TCP. 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`](../configtls/README.md)
- [`write_buffer_size`](https://godoc.org/google.golang.org/grpc#WriteBufferSize)
- [`auth`](../configauth/README.md)
- [`middlewares`](../configmiddleware/README.md)
================================================
FILE: config/configgrpc/client_middleware_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configgrpc
import (
"context"
"errors"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"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/configmiddleware"
"go.opentelemetry.io/collector/config/confignet"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/config/configtls"
"go.opentelemetry.io/collector/extension"
"go.opentelemetry.io/collector/extension/extensionmiddleware"
"go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest"
)
// testlientMiddleware is a mock implementation of a middleware extension
type testClientMiddleware struct {
extension.Extension
extensionmiddleware.GetGRPCClientOptionsFunc
}
func newTestMiddlewareConfig(name string) configmiddleware.Config {
return configmiddleware.Config{
ID: component.MustNewID(name),
}
}
func newTestClientMiddleware(name string) extension.Extension {
return &testClientMiddleware{
Extension: extensionmiddlewaretest.NewNop(),
GetGRPCClientOptionsFunc: func(_ context.Context) ([]grpc.DialOption, error) {
return []grpc.DialOption{
grpc.WithChainUnaryInterceptor(
func(
ctx context.Context,
method string,
req, reply any,
cc *grpc.ClientConn,
invoker grpc.UnaryInvoker,
opts ...grpc.CallOption,
) error {
// Get existing metadata or create new metadata
md, ok := metadata.FromOutgoingContext(ctx)
if !ok {
md = metadata.New(nil)
} else {
// Clone the metadata to avoid modifying the real metadata map
md = md.Copy()
}
// Check if there's already a middleware sequence header
sequence := ""
if values := md.Get("middleware-sequence"); len(values) > 0 {
sequence = values[0]
}
// Append this middleware's ID to the sequence
if sequence == "" {
sequence = name
} else {
sequence = fmt.Sprintf("%s,%s", sequence, name)
}
// Set the updated sequence
md.Set("middleware-sequence", sequence)
// Create a new context with the updated metadata
newCtx := metadata.NewOutgoingContext(ctx, md)
// Continue the call with our updated context
return invoker(newCtx, method, req, reply, cc, opts...)
}),
}, nil
},
}
}
// TestClientMiddlewareOrdering verifies that client middleware
// interceptors are called in the right order.
func TestClientMiddlewareOrdering(t *testing.T) {
// Create a middleware tracking header that will be modified by our middleware interceptors
const middlewareTrackingHeader = "middleware-sequence"
// Create middleware extensions that will modify the metadata to track their execution order
mockMiddleware1 := newTestClientMiddleware("middleware-1")
mockMiddleware2 := newTestClientMiddleware("middleware-2")
mockExt := map[component.ID]component.Component{
component.MustNewID("middleware1"): mockMiddleware1,
component.MustNewID("middleware2"): mockMiddleware2,
}
// Start a gRPC server that will record the incoming metadata
server := &grpcTraceServer{}
srv, addr := server.startTestServer(t, configoptional.Some(ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:0",
Transport: confignet.TransportTypeTCP,
},
}))
defer srv.Stop()
// Create client config with middleware extensions
clientConfig := ClientConfig{
Endpoint: addr,
TLS: configtls.ClientConfig{
Insecure: true,
},
Middlewares: []configmiddleware.Config{
newTestMiddlewareConfig("middleware1"),
newTestMiddlewareConfig("middleware2"),
},
}
// Send a request using the client with middleware
resp, err := sendTestRequestWithExtensions(t, clientConfig, mockExt)
require.NoError(t, err)
assert.NotNil(t, resp)
// Verify that the middleware order was respected as recorded in the metadata
ictx, ok := metadata.FromIncomingContext(server.recordedContext)
require.True(t, ok, "middleware tracking header not found in metadata")
md := ictx[middlewareTrackingHeader]
require.Len(t, md, 1, "expected exactly one middleware tracking header value")
// The sequence should be "middleware-1,middleware-2" as that's the order they were registered
expectedSequence := "middleware-1,middleware-2"
assert.Equal(t, expectedSequence, md[0])
}
// TestClientMiddlewareToClientErrors tests failure cases for the ToClient method
// specifically related to middleware resolution and API calls.
func TestClientMiddlewareToClientErrors(t *testing.T) {
tests := []struct {
name string
extensions map[component.ID]component.Component
config ClientConfig
errText string
}{
{
name: "extension_not_found",
extensions: map[component.ID]component.Component{},
config: ClientConfig{
Endpoint: "localhost:1234",
TLS: configtls.ClientConfig{
Insecure: true,
},
Middlewares: []configmiddleware.Config{
{
ID: component.MustNewID("nonexistent"),
},
},
},
errText: "failed to resolve middleware \"nonexistent\": middleware not found",
},
{
name: "get_client_options_fails",
extensions: map[component.ID]component.Component{
component.MustNewID("errormw"): extensionmiddlewaretest.NewErr(errors.New("get options failed")),
},
config: ClientConfig{
Endpoint: "localhost:1234",
TLS: configtls.ClientConfig{
Insecure: true,
},
Middlewares: []configmiddleware.Config{
{
ID: component.MustNewID("errormw"),
},
},
},
errText: "get options failed",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
// Test creating the client with middleware errors
_, err := tc.config.ToClientConn(context.Background(), tc.extensions, componenttest.NewNopTelemetrySettings())
require.Error(t, err)
assert.Contains(t, err.Error(), tc.errText)
})
}
}
================================================
FILE: config/configgrpc/config.schema.yaml
================================================
$defs:
client_config:
description: ClientConfig defines common settings for a gRPC client configuration.
type: object
properties:
auth:
description: Auth configuration for outgoing RPCs.
x-optional: true
$ref: go.opentelemetry.io/collector/config/configauth.config
authority:
description: WithAuthority parameter configures client to rewrite ":authority" header (godoc.org/google.golang.org/grpc#WithAuthority)
type: string
balancer_name:
description: 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
type: string
compression:
description: The compression key for supported compression types within collector.
$ref: go.opentelemetry.io/collector/config/configcompression.type
endpoint:
description: 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.
type: string
headers:
description: The headers associated with gRPC requests.
$ref: go.opentelemetry.io/collector/config/configopaque.map_list
keepalive:
description: The keepalive parameters for gRPC client. See grpc.WithKeepaliveParams. (https://godoc.org/google.golang.org/grpc#WithKeepaliveParams).
x-optional: true
$ref: keepalive_client_config
middlewares:
description: Middlewares for the gRPC client.
type: array
items:
$ref: go.opentelemetry.io/collector/config/configmiddleware.config
read_buffer_size:
description: ReadBufferSize for gRPC client. See grpc.WithReadBufferSize. (https://godoc.org/google.golang.org/grpc#WithReadBufferSize).
type: integer
tls:
description: TLS struct exposes TLS client configuration.
$ref: go.opentelemetry.io/collector/config/configtls.client_config
wait_for_ready:
description: WaitForReady parameter configures client to wait for ready state before sending data. (https://github.com/grpc/grpc/blob/master/doc/wait-for-ready.md)
type: boolean
write_buffer_size:
description: WriteBufferSize for gRPC gRPC. See grpc.WithWriteBufferSize. (https://godoc.org/google.golang.org/grpc#WithWriteBufferSize).
type: integer
keepalive_client_config:
description: '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: object
properties:
permit_without_stream:
type: boolean
time:
type: string
x-customType: time.Duration
format: duration
timeout:
type: string
x-customType: time.Duration
format: duration
keepalive_enforcement_policy:
description: 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: object
properties:
min_time:
type: string
x-customType: time.Duration
format: duration
permit_without_stream:
type: boolean
keepalive_server_config:
description: KeepaliveServerConfig is the configuration for keepalive.
type: object
properties:
enforcement_policy:
x-optional: true
$ref: keepalive_enforcement_policy
server_parameters:
x-optional: true
$ref: keepalive_server_parameters
keepalive_server_parameters:
description: 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: object
properties:
max_connection_age:
type: string
x-customType: time.Duration
format: duration
max_connection_age_grace:
type: string
x-customType: time.Duration
format: duration
max_connection_idle:
type: string
x-customType: time.Duration
format: duration
time:
type: string
x-customType: time.Duration
format: duration
timeout:
type: string
x-customType: time.Duration
format: duration
server_config:
description: ServerConfig defines common settings for a gRPC server configuration.
type: object
properties:
auth:
description: Auth for this receiver
x-optional: true
$ref: go.opentelemetry.io/collector/config/configauth.config
include_metadata:
description: Include propagates the incoming connection's metadata to downstream consumers.
type: boolean
keepalive:
description: Keepalive anchor for all the settings related to keepalive.
x-optional: true
$ref: keepalive_server_config
max_concurrent_streams:
description: MaxConcurrentStreams sets the limit on the number of concurrent streams to each ServerTransport. It has effect only for streaming RPCs.
type: integer
x-customType: uint32
max_recv_msg_size_mib:
description: MaxRecvMsgSizeMiB sets the maximum size (in MiB) of messages accepted by the server.
type: integer
middlewares:
description: Middlewares for the gRPC server.
type: array
items:
$ref: go.opentelemetry.io/collector/config/configmiddleware.config
read_buffer_size:
description: ReadBufferSize for gRPC server. See grpc.ReadBufferSize. (https://godoc.org/google.golang.org/grpc#ReadBufferSize).
type: integer
tls:
description: Configures the protocol to use TLS. The default value is nil, which will cause the protocol to not use TLS.
x-optional: true
$ref: go.opentelemetry.io/collector/config/configtls.server_config
write_buffer_size:
description: WriteBufferSize for gRPC server. See grpc.WriteBufferSize. (https://godoc.org/google.golang.org/grpc#WriteBufferSize).
type: integer
allOf:
- $ref: go.opentelemetry.io/collector/config/confignet.addr_config
================================================
FILE: config/configgrpc/configgrpc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configgrpc // import "go.opentelemetry.io/collector/config/configgrpc"
import (
"context"
"crypto/tls"
"errors"
"fmt"
"math"
"net"
"regexp"
"strconv"
"strings"
"time"
"github.com/mostynb/go-grpc-compression/nonclobbering/snappy"
"github.com/mostynb/go-grpc-compression/nonclobbering/zstd"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"go.opentelemetry.io/otel"
"google.golang.org/grpc"
"google.golang.org/grpc/balancer"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/encoding/gzip"
"google.golang.org/grpc/keepalive"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer"
"google.golang.org/grpc/status"
"go.opentelemetry.io/collector/client"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configauth"
"go.opentelemetry.io/collector/config/configcompression"
"go.opentelemetry.io/collector/config/configmiddleware"
"go.opentelemetry.io/collector/config/confignet"
"go.opentelemetry.io/collector/config/configopaque"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/config/configtls"
"go.opentelemetry.io/collector/extension/extensionauth"
)
var errMetadataNotFound = errors.New("no request metadata found")
// 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"`
Timeout time.Duration `mapstructure:"timeout"`
PermitWithoutStream bool `mapstructure:"permit_without_stream,omitempty"`
// prevent unkeyed literal initialization
_ struct{}
}
// NewDefaultKeepaliveClientConfig returns a new instance of KeepaliveClientConfig with default values.
func NewDefaultKeepaliveClientConfig() KeepaliveClientConfig {
return KeepaliveClientConfig{
Time: time.Second * 10,
Timeout: time.Second * 10,
}
}
// BalancerName returns a string with default load balancer value
func BalancerName() string {
return "round_robin"
}
// ClientConfig defines common settings for a gRPC client configuration.
type ClientConfig 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,omitempty"`
// The compression key for supported compression types within collector.
Compression configcompression.Type `mapstructure:"compression,omitempty"`
// TLS struct exposes TLS client configuration.
TLS configtls.ClientConfig `mapstructure:"tls,omitempty"`
// The keepalive parameters for gRPC client. See grpc.WithKeepaliveParams.
// (https://godoc.org/google.golang.org/grpc#WithKeepaliveParams).
Keepalive configoptional.Optional[KeepaliveClientConfig] `mapstructure:"keepalive,omitempty"`
// ReadBufferSize for gRPC client. See grpc.WithReadBufferSize.
// (https://godoc.org/google.golang.org/grpc#WithReadBufferSize).
ReadBufferSize int `mapstructure:"read_buffer_size,omitempty"`
// WriteBufferSize for gRPC gRPC. See grpc.WithWriteBufferSize.
// (https://godoc.org/google.golang.org/grpc#WithWriteBufferSize).
WriteBufferSize int `mapstructure:"write_buffer_size,omitempty"`
// 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,omitempty"`
// The headers associated with gRPC requests.
Headers configopaque.MapList `mapstructure:"headers,omitempty"`
// 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"`
// WithAuthority parameter configures client to rewrite ":authority" header
// (godoc.org/google.golang.org/grpc#WithAuthority)
Authority string `mapstructure:"authority,omitempty"`
// Auth configuration for outgoing RPCs.
Auth configoptional.Optional[configauth.Config] `mapstructure:"auth,omitempty"`
// Middlewares for the gRPC client.
Middlewares []configmiddleware.Config `mapstructure:"middlewares,omitempty"`
}
// NewDefaultClientConfig returns a new instance of ClientConfig with default values.
func NewDefaultClientConfig() ClientConfig {
return ClientConfig{
TLS: configtls.NewDefaultClientConfig(),
Keepalive: configoptional.Some(NewDefaultKeepaliveClientConfig()),
BalancerName: BalancerName(),
}
}
// KeepaliveServerConfig is the configuration for keepalive.
type KeepaliveServerConfig struct {
ServerParameters configoptional.Optional[KeepaliveServerParameters] `mapstructure:"server_parameters,omitempty"`
EnforcementPolicy configoptional.Optional[KeepaliveEnforcementPolicy] `mapstructure:"enforcement_policy,omitempty"`
// prevent unkeyed literal initialization
_ struct{}
}
// NewDefaultKeepaliveServerConfig returns a new instance of KeepaliveServerConfig with default values.
func NewDefaultKeepaliveServerConfig() KeepaliveServerConfig {
return KeepaliveServerConfig{
ServerParameters: configoptional.Some(NewDefaultKeepaliveServerParameters()),
EnforcementPolicy: configoptional.Some(NewDefaultKeepaliveEnforcementPolicy()),
}
}
// 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"`
// prevent unkeyed literal initialization
_ struct{}
}
// NewDefaultKeepaliveServerParameters creates and returns a new instance of KeepaliveServerParameters with default settings.
func NewDefaultKeepaliveServerParameters() KeepaliveServerParameters {
return KeepaliveServerParameters{}
}
// 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"`
// prevent unkeyed literal initialization
_ struct{}
}
// NewDefaultKeepaliveEnforcementPolicy creates and returns a new instance of KeepaliveEnforcementPolicy with default settings.
func NewDefaultKeepaliveEnforcementPolicy() KeepaliveEnforcementPolicy {
return KeepaliveEnforcementPolicy{}
}
// ServerConfig defines common settings for a gRPC server configuration.
type ServerConfig struct {
// Server net.Addr config. For transport only "tcp" and "unix" are valid options.
NetAddr confignet.AddrConfig `mapstructure:",squash"`
// Configures the protocol to use TLS.
// The default value is nil, which will cause the protocol to not use TLS.
TLS configoptional.Optional[configtls.ServerConfig] `mapstructure:"tls,omitempty"`
// MaxRecvMsgSizeMiB sets the maximum size (in MiB) of messages accepted by the server.
MaxRecvMsgSizeMiB int `mapstructure:"max_recv_msg_size_mib,omitempty"`
// 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,omitempty,omitempty"`
// ReadBufferSize for gRPC server. See grpc.ReadBufferSize.
// (https://godoc.org/google.golang.org/grpc#ReadBufferSize).
ReadBufferSize int `mapstructure:"read_buffer_size,omitempty"`
// WriteBufferSize for gRPC server. See grpc.WriteBufferSize.
// (https://godoc.org/google.golang.org/grpc#WriteBufferSize).
WriteBufferSize int `mapstructure:"write_buffer_size,omitempty"`
// Keepalive anchor for all the settings related to keepalive.
Keepalive configoptional.Optional[KeepaliveServerConfig] `mapstructure:"keepalive,omitempty"`
// Auth for this receiver
Auth configoptional.Optional[configauth.Config] `mapstructure:"auth,omitempty"`
// Include propagates the incoming connection's metadata to downstream consumers.
IncludeMetadata bool `mapstructure:"include_metadata,omitempty"`
// Middlewares for the gRPC server.
Middlewares []configmiddleware.Config `mapstructure:"middlewares,omitempty"`
// prevent unkeyed literal initialization
_ struct{}
}
// NewDefaultServerConfig returns a new instance of ServerConfig with default values.
func NewDefaultServerConfig() ServerConfig {
netAddr := confignet.NewDefaultAddrConfig()
// We typically want to create a TCP server and listen over a network.
netAddr.Transport = confignet.TransportTypeTCP
return ServerConfig{
Keepalive: configoptional.Some(NewDefaultKeepaliveServerConfig()),
NetAddr: netAddr,
}
}
func (cc *ClientConfig) Validate() error {
if after, ok := strings.CutPrefix(cc.Endpoint, "unix://"); ok {
if after == "" {
return errors.New("unix socket path cannot be empty")
}
return nil
}
if endpoint := cc.sanitizedEndpoint(); endpoint != "" {
// Validate that the port is in the address
_, port, err := net.SplitHostPort(endpoint)
if err != nil {
return err
}
if _, err := strconv.Atoi(port); err != nil {
return fmt.Errorf(`invalid port "%v"`, port)
}
}
if cc.BalancerName != "" {
if balancer.Get(cc.BalancerName) == nil {
return fmt.Errorf("invalid balancer_name: %s", cc.BalancerName)
}
}
return nil
}
// sanitizedEndpoint strips the prefix of either http:// or https:// from configgrpc.ClientConfig.Endpoint.
func (cc *ClientConfig) sanitizedEndpoint() string {
switch {
case cc.isSchemeHTTP():
return strings.TrimPrefix(cc.Endpoint, "http://")
case cc.isSchemeHTTPS():
return strings.TrimPrefix(cc.Endpoint, "https://")
case strings.HasPrefix(cc.Endpoint, "dns://"):
r := regexp.MustCompile(`^dns:///?`)
return r.ReplaceAllString(cc.Endpoint, "")
default:
return cc.Endpoint
}
}
func (cc *ClientConfig) isSchemeHTTP() bool {
return strings.HasPrefix(cc.Endpoint, "http://")
}
func (cc *ClientConfig) isSchemeHTTPS() bool {
return strings.HasPrefix(cc.Endpoint, "https://")
}
// ToClientConnOption is a sealed interface wrapping options for [ClientConfig.ToClientConn].
type ToClientConnOption interface {
isToClientConnOption()
}
type grpcDialOptionWrapper struct {
opt grpc.DialOption
}
// WithGrpcDialOption wraps a [grpc.DialOption] into a [ToClientConnOption].
func WithGrpcDialOption(opt grpc.DialOption) ToClientConnOption {
return grpcDialOptionWrapper{opt: opt}
}
func (grpcDialOptionWrapper) isToClientConnOption() {}
// ToClientConn creates a client connection to the given target. By default, it's
// a non-blocking dial (the function won't wait for connections to be
// established, and connecting happens in the background). To make it a blocking
// dial, use the WithGrpcDialOption(grpc.WithBlock()) option.
//
// To allow the configuration to reference middleware or authentication extensions,
// the `extensions` argument should be the output of `host.GetExtensions()`.
// It may also be `nil` in tests where no such extension is expected to be used.
func (cc *ClientConfig) ToClientConn(
ctx context.Context,
extensions map[component.ID]component.Component,
settings component.TelemetrySettings,
extraOpts ...ToClientConnOption,
) (*grpc.ClientConn, error) {
grpcOpts, err := cc.getGrpcDialOptions(ctx, extensions, settings, extraOpts)
if err != nil {
return nil, err
}
conn, err := grpc.NewClient(cc.sanitizedEndpoint(), grpcOpts...)
if err != nil {
return nil, err
}
// Initiate connection to match the previous behavior of DialContext
// This ensures the connection is established eagerly rather than lazily
conn.Connect()
return conn, nil
}
func (cc *ClientConfig) addHeadersIfAbsent(ctx context.Context) context.Context {
kv := make([]string, 0, 2*len(cc.Headers))
existingMd, _ := metadata.FromOutgoingContext(ctx)
for k, v := range cc.Headers.Iter {
if len(existingMd.Get(k)) == 0 {
kv = append(kv, k, string(v))
}
}
return metadata.AppendToOutgoingContext(ctx, kv...)
}
func (cc *ClientConfig) getGrpcDialOptions(
ctx context.Context,
extensions map[component.ID]component.Component,
settings component.TelemetrySettings,
extraOpts []ToClientConnOption,
) ([]grpc.DialOption, error) {
var opts []grpc.DialOption
if cc.Compression.IsCompressed() {
cp, err := getGRPCCompressionName(cc.Compression)
if err != nil {
return nil, err
}
opts = append(opts, grpc.WithDefaultCallOptions(grpc.UseCompressor(cp)))
}
tlsCfg, err := cc.TLS.LoadTLSConfig(ctx)
if err != nil {
return nil, err
}
cred := insecure.NewCredentials()
if tlsCfg != nil {
cred = credentials.NewTLS(tlsCfg)
} else if cc.isSchemeHTTPS() {
cred = credentials.NewTLS(&tls.Config{})
}
opts = append(opts, grpc.WithTransportCredentials(cred))
if cc.ReadBufferSize > 0 {
opts = append(opts, grpc.WithReadBufferSize(cc.ReadBufferSize))
}
if cc.WriteBufferSize > 0 {
opts = append(opts, grpc.WithWriteBufferSize(cc.WriteBufferSize))
}
if cc.Keepalive.HasValue() {
keepaliveConfig := cc.Keepalive.Get()
keepAliveOption := grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time: keepaliveConfig.Time,
Timeout: keepaliveConfig.Timeout,
PermitWithoutStream: keepaliveConfig.PermitWithoutStream,
})
opts = append(opts, keepAliveOption)
}
if cc.Auth.HasValue() {
if extensions == nil {
return nil, errors.New("authentication was configured but this component or its host does not support extensions")
}
grpcAuthenticator, cerr := cc.Auth.Get().GetGRPCClientAuthenticator(ctx, extensions)
if cerr != nil {
return nil, cerr
}
perRPCCredentials, perr := grpcAuthenticator.PerRPCCredentials()
if perr != nil {
return nil, err
}
opts = append(opts, grpc.WithPerRPCCredentials(perRPCCredentials))
}
if cc.BalancerName != "" {
opts = append(opts, grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"loadBalancingPolicy":%q}`, cc.BalancerName)))
}
if cc.Authority != "" {
opts = append(opts, grpc.WithAuthority(cc.Authority))
}
otelOpts := []otelgrpc.Option{
otelgrpc.WithTracerProvider(settings.TracerProvider),
otelgrpc.WithPropagators(otel.GetTextMapPropagator()),
otelgrpc.WithMeterProvider(settings.MeterProvider),
}
// Enable OpenTelemetry observability plugin.
opts = append(opts, grpc.WithStatsHandler(otelgrpc.NewClientHandler(otelOpts...)))
if len(cc.Headers) > 0 {
opts = append(opts,
grpc.WithUnaryInterceptor(func(ctx context.Context, method string, req, reply any, gcc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
return invoker(cc.addHeadersIfAbsent(ctx), method, req, reply, gcc, opts...)
}),
grpc.WithStreamInterceptor(func(ctx context.Context, desc *grpc.StreamDesc, gcc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
return streamer(cc.addHeadersIfAbsent(ctx), desc, gcc, method, opts...)
}),
)
}
// Apply middleware options. Note: OpenTelemetry could be registered as an extension.
if len(cc.Middlewares) > 0 && extensions == nil {
return nil, errors.New("middlewares were configured but this component or its host does not support extensions")
}
for _, middleware := range cc.Middlewares {
middlewareOptions, err := middleware.GetGRPCClientOptions(ctx, extensions)
if err != nil {
return nil, fmt.Errorf("failed to get gRPC client options from middleware: %w", err)
}
opts = append(opts, middlewareOptions...)
}
for _, opt := range extraOpts {
if wrapper, ok := opt.(grpcDialOptionWrapper); ok {
opts = append(opts, wrapper.opt)
}
}
return opts, nil
}
func (sc *ServerConfig) Validate() error {
if sc.MaxRecvMsgSizeMiB*1024*1024 < 0 {
return fmt.Errorf("invalid max_recv_msg_size_mib value, must be between 1 and %d: %d", math.MaxInt/1024/1024, sc.MaxRecvMsgSizeMiB)
}
if sc.ReadBufferSize < 0 {
return fmt.Errorf("invalid read_buffer_size value: %d", sc.ReadBufferSize)
}
if sc.WriteBufferSize < 0 {
return fmt.Errorf("invalid write_buffer_size value: %d", sc.WriteBufferSize)
}
return nil
}
// ToServerOption is a sealed interface wrapping options for [ServerConfig.ToServer].
type ToServerOption interface {
isToServerOption()
}
type grpcServerOptionWrapper struct {
opt grpc.ServerOption
}
// WithGrpcServerOption wraps a [grpc.ServerOption] into a [ToServerOption].
func WithGrpcServerOption(opt grpc.ServerOption) ToServerOption {
return grpcServerOptionWrapper{opt: opt}
}
func (grpcServerOptionWrapper) isToServerOption() {}
// ToServer returns a [grpc.Server] for the configuration.
//
// To allow the configuration to reference middleware or authentication extensions,
// the `extensions` argument should be the output of `host.GetExtensions()`.
// It may also be `nil` in tests where no such extension is expected to be used.
func (sc *ServerConfig) ToServer(
ctx context.Context,
extensions map[component.ID]component.Component,
settings component.TelemetrySettings,
extraOpts ...ToServerOption,
) (*grpc.Server, error) {
grpcOpts, err := sc.getGrpcServerOptions(ctx, extensions, settings, extraOpts)
if err != nil {
return nil, err
}
return grpc.NewServer(grpcOpts...), nil
}
func (sc *ServerConfig) getGrpcServerOptions(
ctx context.Context,
extensions map[component.ID]component.Component,
settings component.TelemetrySettings,
extraOpts []ToServerOption,
) ([]grpc.ServerOption, error) {
var opts []grpc.ServerOption
if sc.TLS.HasValue() {
tlsCfg, err := sc.TLS.Get().LoadTLSConfig(ctx)
if err != nil {
return nil, err
}
opts = append(opts, grpc.Creds(credentials.NewTLS(tlsCfg)))
}
if sc.MaxRecvMsgSizeMiB > 0 && sc.MaxRecvMsgSizeMiB*1024*1024 > 0 {
opts = append(opts, grpc.MaxRecvMsgSize(sc.MaxRecvMsgSizeMiB*1024*1024))
}
if sc.MaxConcurrentStreams > 0 {
opts = append(opts, grpc.MaxConcurrentStreams(sc.MaxConcurrentStreams))
}
if sc.ReadBufferSize > 0 {
opts = append(opts, grpc.ReadBufferSize(sc.ReadBufferSize))
}
if sc.WriteBufferSize > 0 {
opts = append(opts, grpc.WriteBufferSize(sc.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 sc.Keepalive.HasValue() {
keepaliveConfig := sc.Keepalive.Get()
if keepaliveConfig.ServerParameters.HasValue() {
svrParams := keepaliveConfig.ServerParameters.Get()
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 keepaliveConfig.EnforcementPolicy.HasValue() {
enfPol := keepaliveConfig.EnforcementPolicy.Get()
opts = append(opts, grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
MinTime: enfPol.MinTime,
PermitWithoutStream: enfPol.PermitWithoutStream,
}))
}
}
var uInterceptors []grpc.UnaryServerInterceptor
var sInterceptors []grpc.StreamServerInterceptor
// Add client info first, before auth.
uInterceptors = append(uInterceptors, enhanceWithClientInformation(sc.IncludeMetadata))
sInterceptors = append(sInterceptors, enhanceStreamWithClientInformation(sc.IncludeMetadata)) //nolint:contextcheck // context already handled
if sc.Auth.HasValue() {
authenticator, err := sc.Auth.Get().GetServerAuthenticator(ctx, extensions)
if err != nil {
return nil, err
}
uInterceptors = append(uInterceptors, authUnaryServerInterceptor(authenticator))
sInterceptors = append(sInterceptors, authStreamServerInterceptor(authenticator)) //nolint:contextcheck // context already handled
}
otelOpts := []otelgrpc.Option{
otelgrpc.WithTracerProvider(settings.TracerProvider),
otelgrpc.WithPropagators(otel.GetTextMapPropagator()),
otelgrpc.WithMeterProvider(settings.MeterProvider),
}
// Enable OpenTelemetry observability plugin.
opts = append(opts, grpc.StatsHandler(otelgrpc.NewServerHandler(otelOpts...)), grpc.ChainUnaryInterceptor(uInterceptors...), grpc.ChainStreamInterceptor(sInterceptors...))
// Apply middleware options. Note: OpenTelemetry could be registered as an extension.
for _, middleware := range sc.Middlewares {
middlewareOptions, err := middleware.GetGRPCServerOptions(ctx, extensions)
if err != nil {
return nil, fmt.Errorf("failed to get gRPC server options from middleware: %w", err)
}
opts = append(opts, middlewareOptions...)
}
for _, opt := range extraOpts {
if wrapper, ok := opt.(grpcServerOptionWrapper); ok {
opts = append(opts, wrapper.opt)
}
}
return opts, nil
}
// getGRPCCompressionName returns compression name registered in grpc.
func getGRPCCompressionName(compressionType configcompression.Type) (string, error) {
switch compressionType {
case configcompression.TypeGzip:
return gzip.Name, nil
case configcompression.TypeSnappy:
return snappy.Name, nil
case configcompression.TypeZstd:
return zstd.Name, nil
default:
return "", fmt.Errorf("unsupported compression type %q", compressionType)
}
}
// enhanceWithClientInformation intercepts the incoming RPC, replacing the incoming context with one that includes
// a client.Info, potentially with the peer's address.
func enhanceWithClientInformation(includeMetadata bool) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
return handler(contextWithClient(ctx, includeMetadata), req)
}
}
func enhanceStreamWithClientInformation(includeMetadata bool) grpc.StreamServerInterceptor {
return func(srv any, ss grpc.ServerStream, _ *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
return handler(srv, wrapServerStream(contextWithClient(ss.Context(), includeMetadata), ss))
}
}
// contextWithClient attempts to add the peer address to the client.Info from the context. When no
// client.Info exists in the context, one is created.
func contextWithClient(ctx context.Context, includeMetadata bool) context.Context {
cl := client.FromContext(ctx)
if p, ok := peer.FromContext(ctx); ok {
cl.Addr = p.Addr
}
if includeMetadata {
if md, ok := metadata.FromIncomingContext(ctx); ok {
copiedMD := md.Copy()
if len(md[client.MetadataHostName]) == 0 && len(md[":authority"]) > 0 {
copiedMD[client.MetadataHostName] = md[":authority"]
}
cl.Metadata = client.NewMetadata(copiedMD)
}
}
return client.NewContext(ctx, cl)
}
func authUnaryServerInterceptor(server extensionauth.Server) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
headers, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, errMetadataNotFound
}
ctx, err := server.Authenticate(ctx, headers)
if err != nil {
if s, ok := status.FromError(err); ok {
return nil, s.Err()
}
return nil, status.Error(codes.Unauthenticated, err.Error())
}
return handler(ctx, req)
}
}
func authStreamServerInterceptor(server extensionauth.Server) grpc.StreamServerInterceptor {
return func(srv any, stream grpc.ServerStream, _ *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
ctx := stream.Context()
headers, ok := metadata.FromIncomingContext(ctx)
if !ok {
return errMetadataNotFound
}
ctx, err := server.Authenticate(ctx, headers)
if err != nil {
if s, ok := status.FromError(err); ok {
return s.Err()
}
return status.Error(codes.Unauthenticated, err.Error())
}
return handler(srv, wrapServerStream(ctx, stream))
}
}
================================================
FILE: config/configgrpc/configgrpc_benchmark_test.go
================================================
// Copyright The OpenTelemetry Authors
// Copyright 2014 gRPC authors.
// SPDX-License-Identifier: Apache-2.0
package configgrpc
import (
"bytes"
"fmt"
"testing"
"github.com/mostynb/go-grpc-compression/nonclobbering/snappy"
"github.com/mostynb/go-grpc-compression/nonclobbering/zstd"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/encoding"
"google.golang.org/grpc/encoding/gzip"
"google.golang.org/grpc/status"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/pdata/testdata"
)
func BenchmarkCompressors(b *testing.B) {
payloads := setupTestPayloads()
compressors := make([]encoding.Compressor, 0)
compressors = append(compressors,
encoding.GetCompressor(gzip.Name),
encoding.GetCompressor(zstd.Name),
encoding.GetCompressor(snappy.Name))
for _, payload := range payloads {
for _, compressor := range compressors {
fmt.Println(payload.name)
messageBytes, err := payload.marshaler.marshal(payload.message)
require.NoError(b, err, "marshal(_) returned an error")
compressedBytes, err := compress(compressor, messageBytes)
require.NoError(b, err, "Compressor.Compress(_) returned an error")
name := fmt.Sprintf("%v/raw_bytes_%v/compressed_bytes_%v/compressor_%v", payload.name, len(messageBytes), len(compressedBytes), compressor.Name())
b.Run(name, func(b *testing.B) {
b.ResetTimer()
for b.Loop() {
require.NoError(b, err, "marshal(_) returned an error")
_, err := compress(compressor, messageBytes)
require.NoError(b, err, "compress(_) returned an error")
}
})
}
}
}
func compress(compressor encoding.Compressor, in []byte) ([]byte, error) {
if compressor == nil {
return nil, nil
}
wrapErr := func(err error) error {
return status.Errorf(codes.Internal, "error while compressing: %v", err.Error())
}
cbuf := &bytes.Buffer{}
z, err := compressor.Compress(cbuf)
if err != nil {
return nil, wrapErr(err)
}
if _, err := z.Write(in); err != nil {
return nil, wrapErr(err)
}
if err := z.Close(); err != nil {
return nil, wrapErr(err)
}
return cbuf.Bytes(), nil
}
type testPayload struct {
name string
message any
marshaler marshaler
}
type marshaler interface {
marshal(any) ([]byte, error)
}
type logMarshaler struct {
plog.Marshaler
}
func (m *logMarshaler) marshal(e any) ([]byte, error) {
return m.MarshalLogs(e.(plog.Logs))
}
type traceMarshaler struct {
ptrace.Marshaler
}
func (m *traceMarshaler) marshal(e any) ([]byte, error) {
return m.MarshalTraces(e.(ptrace.Traces))
}
type metricsMarshaler struct {
pmetric.Marshaler
}
func (m *metricsMarshaler) marshal(e any) ([]byte, error) {
return m.MarshalMetrics(e.(pmetric.Metrics))
}
func setupTestPayloads() []testPayload {
payloads := make([]testPayload, 0)
// log payloads
logMarshaler := &logMarshaler{Marshaler: &plog.ProtoMarshaler{}}
payloads = append(payloads,
testPayload{
name: "sm_log_request",
message: testdata.GenerateLogs(1),
marshaler: logMarshaler,
},
testPayload{
name: "md_log_request",
message: testdata.GenerateLogs(2),
marshaler: logMarshaler,
},
testPayload{
name: "lg_log_request",
message: testdata.GenerateLogs(50),
marshaler: logMarshaler,
})
// trace payloads
tracesMarshaler := &traceMarshaler{Marshaler: &ptrace.ProtoMarshaler{}}
payloads = append(payloads,
testPayload{
name: "sm_trace_request",
message: testdata.GenerateTraces(1),
marshaler: tracesMarshaler,
},
testPayload{
name: "md_trace_request",
message: testdata.GenerateTraces(2),
marshaler: tracesMarshaler,
},
testPayload{
name: "lg_trace_request",
message: testdata.GenerateTraces(50),
marshaler: tracesMarshaler,
})
// metric payloads
metricsMarshaler := &metricsMarshaler{Marshaler: &pmetric.ProtoMarshaler{}}
payloads = append(payloads,
testPayload{
name: "sm_metric_request",
message: testdata.GenerateMetrics(1),
marshaler: metricsMarshaler,
},
testPayload{
name: "md_metric_request",
message: testdata.GenerateMetrics(2),
marshaler: metricsMarshaler,
},
testPayload{
name: "lg_metric_request",
message: testdata.GenerateMetrics(50),
marshaler: metricsMarshaler,
})
return payloads
}
================================================
FILE: config/configgrpc/configgrpc_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configgrpc
import (
"context"
"errors"
"net"
"os"
"path/filepath"
"runtime"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer"
"google.golang.org/grpc/status"
"go.opentelemetry.io/collector/client"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/config/configauth"
"go.opentelemetry.io/collector/config/configcompression"
"go.opentelemetry.io/collector/config/confignet"
"go.opentelemetry.io/collector/config/configopaque"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/config/configtls"
"go.opentelemetry.io/collector/extension"
"go.opentelemetry.io/collector/extension/extensionauth"
"go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest"
"go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp"
)
var (
_ extension.Extension = (*mockAuthServer)(nil)
_ extensionauth.Server = (*mockAuthServer)(nil)
)
type mockAuthServer struct {
component.StartFunc
component.ShutdownFunc
extensionauth.ServerAuthenticateFunc
}
func newMockAuthServer(auth func(ctx context.Context, sources map[string][]string) (context.Context, error)) extensionauth.Server {
return &mockAuthServer{ServerAuthenticateFunc: auth}
}
func TestNewDefaultKeepaliveClientConfig(t *testing.T) {
expectedKeepaliveClientConfig := KeepaliveClientConfig{
Time: time.Second * 10,
Timeout: time.Second * 10,
}
keepaliveClientConfig := NewDefaultKeepaliveClientConfig()
assert.Equal(t, expectedKeepaliveClientConfig, keepaliveClientConfig)
}
func TestNewDefaultClientConfig(t *testing.T) {
keepalive := NewDefaultKeepaliveClientConfig()
expected := ClientConfig{
TLS: configtls.NewDefaultClientConfig(),
Keepalive: configoptional.Some(keepalive),
BalancerName: BalancerName(),
}
result := NewDefaultClientConfig()
assert.Equal(t, expected, result)
}
func TestNewDefaultKeepaliveServerParameters(t *testing.T) {
expectedParams := KeepaliveServerParameters{}
params := NewDefaultKeepaliveServerParameters()
assert.Equal(t, expectedParams, params)
}
func TestNewDefaultKeepaliveEnforcementPolicy(t *testing.T) {
expectedPolicy := KeepaliveEnforcementPolicy{}
policy := NewDefaultKeepaliveEnforcementPolicy()
assert.Equal(t, expectedPolicy, policy)
}
func TestNewDefaultKeepaliveServerConfig(t *testing.T) {
expected := KeepaliveServerConfig{
ServerParameters: configoptional.Some(NewDefaultKeepaliveServerParameters()),
EnforcementPolicy: configoptional.Some(NewDefaultKeepaliveEnforcementPolicy()),
}
result := NewDefaultKeepaliveServerConfig()
assert.Equal(t, expected, result)
}
func TestNewDefaultServerConfig(t *testing.T) {
expected := ServerConfig{
Keepalive: configoptional.Some(NewDefaultKeepaliveServerConfig()),
NetAddr: confignet.AddrConfig{
Transport: confignet.TransportTypeTCP,
},
}
result := NewDefaultServerConfig()
assert.Equal(t, expected, result)
}
var (
testAuthID = component.MustNewID("testauth")
mockID = component.MustNewID("mock")
doesntExistID = component.MustNewID("doesntexist")
)
func TestDefaultGrpcClientSettings(t *testing.T) {
cc := &ClientConfig{
TLS: configtls.ClientConfig{
Insecure: true,
},
}
opts, err := cc.getGrpcDialOptions(context.Background(), nil, componenttest.NewNopTelemetrySettings(), []ToClientConnOption{})
require.NoError(t, err)
/* Expecting 2 DialOptions:
* - WithTransportCredentials (TLS)
* - WithStatsHandler (always, for self-telemetry)
*/
assert.Len(t, opts, 2)
}
func TestGrpcClientExtraOption(t *testing.T) {
cc := &ClientConfig{
TLS: configtls.ClientConfig{
Insecure: true,
},
}
extraOpt := grpc.WithUserAgent("test-agent")
opts, err := cc.getGrpcDialOptions(
context.Background(),
nil,
componenttest.NewNopTelemetrySettings(),
[]ToClientConnOption{WithGrpcDialOption(extraOpt)},
)
require.NoError(t, err)
/* Expecting 3 DialOptions:
* - WithTransportCredentials (TLS)
* - WithStatsHandler (always, for self-telemetry)
* - extraOpt
*/
assert.Len(t, opts, 3)
assert.Equal(t, opts[2], extraOpt)
}
func TestAllGrpcClientSettings(t *testing.T) {
tests := []struct {
settings ClientConfig
name string
extensions map[component.ID]component.Component
}{
{
name: "test all with gzip compression",
settings: ClientConfig{
Headers: configopaque.MapList{
{Name: "test", Value: "test"},
},
Endpoint: "localhost:1234",
Compression: configcompression.TypeGzip,
TLS: configtls.ClientConfig{
Insecure: false,
},
Keepalive: configoptional.Some(KeepaliveClientConfig{
Time: time.Second,
Timeout: time.Second,
PermitWithoutStream: true,
}),
ReadBufferSize: 1024,
WriteBufferSize: 1024,
WaitForReady: true,
BalancerName: "round_robin",
Authority: "pseudo-authority",
Auth: configoptional.Some(configauth.Config{AuthenticatorID: testAuthID}),
},
extensions: map[component.ID]component.Component{
testAuthID: extensionauthtest.NewNopClient(),
},
},
{
name: "test all with snappy compression",
settings: ClientConfig{
Headers: configopaque.MapList{
{Name: "test", Value: "test"},
},
Endpoint: "localhost:1234",
Compression: configcompression.TypeSnappy,
TLS: configtls.ClientConfig{
Insecure: false,
},
Keepalive: configoptional.Some(KeepaliveClientConfig{
Time: time.Second,
Timeout: time.Second,
PermitWithoutStream: true,
}),
ReadBufferSize: 1024,
WriteBufferSize: 1024,
WaitForReady: true,
BalancerName: "round_robin",
Authority: "pseudo-authority",
Auth: configoptional.Some(configauth.Config{AuthenticatorID: testAuthID}),
},
extensions: map[component.ID]component.Component{
testAuthID: extensionauthtest.NewNopClient(),
},
},
{
name: "test all with zstd compression",
settings: ClientConfig{
Headers: configopaque.MapList{
{Name: "test", Value: "test"},
},
Endpoint: "localhost:1234",
Compression: configcompression.TypeZstd,
TLS: configtls.ClientConfig{
Insecure: false,
},
Keepalive: configoptional.Some(KeepaliveClientConfig{
Time: time.Second,
Timeout: time.Second,
PermitWithoutStream: true,
}),
ReadBufferSize: 1024,
WriteBufferSize: 1024,
WaitForReady: true,
BalancerName: "round_robin",
Authority: "pseudo-authority",
Auth: configoptional.Some(configauth.Config{AuthenticatorID: testAuthID}),
},
extensions: map[component.ID]component.Component{
testAuthID: extensionauthtest.NewNopClient(),
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
opts, err := test.settings.getGrpcDialOptions(context.Background(), test.extensions, componenttest.NewNopTelemetrySettings(), []ToClientConnOption{})
require.NoError(t, err)
/* Expecting 11 DialOptions:
* - WithDefaultCallOptions (Compression)
* - WithTransportCredentials (TLS)
* - WithDefaultServiceConfig (BalancerName)
* - WithAuthority (Authority)
* - WithStatsHandler (always, for self-telemetry)
* - WithReadBufferSize (ReadBufferSize)
* - WithWriteBufferSize (WriteBufferSize)
* - WithKeepaliveParams (Keepalive)
* - WithPerRPCCredentials (Auth)
* - WithUnaryInterceptor/WithStreamInterceptor (Headers)
*/
assert.Len(t, opts, 11)
})
}
}
func TestSanitizeEndpoint(t *testing.T) {
cfg := NewDefaultClientConfig()
cfg.Endpoint = "dns://authority/backend.example.com:4317"
assert.Equal(t, "authority/backend.example.com:4317", cfg.sanitizedEndpoint())
cfg.Endpoint = "dns:///backend.example.com:4317"
assert.Equal(t, "backend.example.com:4317", cfg.sanitizedEndpoint())
cfg.Endpoint = "dns:////backend.example.com:4317"
assert.Equal(t, "/backend.example.com:4317", cfg.sanitizedEndpoint())
}
func TestValidateEndpoint(t *testing.T) {
cfg := NewDefaultClientConfig()
cfg.Endpoint = "dns://authority/backend.example.com:4317"
assert.NoError(t, cfg.Validate())
cfg.Endpoint = "unix:///my/unix/socket.sock"
assert.NoError(t, cfg.Validate())
}
func TestHeaders(t *testing.T) {
traceServer := &grpcTraceServer{}
server, addr := traceServer.startTestServer(t, configoptional.Some(ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:0",
Transport: confignet.TransportTypeTCP,
},
}))
defer server.Stop()
// Create client and send request to server with headers
resp, errResp := sendTestRequest(t, ClientConfig{
Endpoint: addr,
TLS: configtls.ClientConfig{
Insecure: true,
},
Headers: configopaque.MapList{
{Name: "testheader", Value: "testvalue"},
},
})
require.NoError(t, errResp)
assert.NotNil(t, resp)
// Check received headers
md, ok := metadata.FromIncomingContext(traceServer.recordedContext)
require.True(t, ok)
assert.Equal(t, []string{"testvalue"}, md.Get("testheader"))
}
func TestDefaultGrpcServerSettings(t *testing.T) {
gss := &ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "0.0.0.0:1234",
},
}
opts, err := gss.getGrpcServerOptions(context.Background(), nil, componenttest.NewNopTelemetrySettings(), []ToServerOption{})
require.NoError(t, err)
assert.Len(t, opts, 3)
}
func TestGrpcServerExtraOption(t *testing.T) {
gss := &ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "0.0.0.0:1234",
},
}
extraOpt := grpc.ConnectionTimeout(1_000_000_000)
opts, err := gss.getGrpcServerOptions(
context.Background(),
nil,
componenttest.NewNopTelemetrySettings(),
[]ToServerOption{WithGrpcServerOption(extraOpt)},
)
require.NoError(t, err)
assert.Len(t, opts, 4)
assert.Equal(t, opts[3], extraOpt)
}
func TestGrpcServerValidate(t *testing.T) {
tests := []struct {
gss *ServerConfig
err string
}{
{
gss: &ServerConfig{
MaxRecvMsgSizeMiB: -1,
NetAddr: confignet.AddrConfig{
Endpoint: "0.0.0.0:1234",
},
},
err: "invalid max_recv_msg_size_mib value",
},
{
gss: &ServerConfig{
MaxRecvMsgSizeMiB: 9223372036854775807,
NetAddr: confignet.AddrConfig{
Endpoint: "0.0.0.0:1234",
},
},
err: "invalid max_recv_msg_size_mib value",
},
{
gss: &ServerConfig{
ReadBufferSize: -1,
NetAddr: confignet.AddrConfig{
Endpoint: "0.0.0.0:1234",
},
},
err: "invalid read_buffer_size value",
},
{
gss: &ServerConfig{
WriteBufferSize: -1,
NetAddr: confignet.AddrConfig{
Endpoint: "0.0.0.0:1234",
},
},
err: "invalid write_buffer_size value",
},
}
for _, tt := range tests {
t.Run(tt.err, func(t *testing.T) {
err := tt.gss.Validate()
require.Error(t, err)
assert.ErrorContains(t, err, tt.err)
})
}
}
func TestAllGrpcServerSettingsExceptAuth(t *testing.T) {
gss := &ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:1234",
Transport: confignet.TransportTypeTCP,
},
TLS: configoptional.Some(configtls.ServerConfig{
Config: configtls.Config{},
ClientCAFile: "",
}),
MaxRecvMsgSizeMiB: 1,
MaxConcurrentStreams: 1024,
ReadBufferSize: 1024,
WriteBufferSize: 1024,
Keepalive: configoptional.Some(KeepaliveServerConfig{
ServerParameters: configoptional.Some(KeepaliveServerParameters{
MaxConnectionIdle: time.Second,
MaxConnectionAge: time.Second,
MaxConnectionAgeGrace: time.Second,
Time: time.Second,
Timeout: time.Second,
}),
EnforcementPolicy: configoptional.Some(KeepaliveEnforcementPolicy{
MinTime: time.Second,
PermitWithoutStream: true,
}),
}),
}
opts, err := gss.getGrpcServerOptions(context.Background(), nil, componenttest.NewNopTelemetrySettings(), []ToServerOption{})
require.NoError(t, err)
assert.Len(t, opts, 10)
}
func TestGrpcServerAuthSettings(t *testing.T) {
gss := &ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "0.0.0.0:1234",
},
}
gss.Auth = configoptional.Some(configauth.Config{
AuthenticatorID: mockID,
})
extensions := map[component.ID]component.Component{
mockID: extensionauthtest.NewNopServer(),
}
srv, err := gss.ToServer(context.Background(), extensions, componenttest.NewNopTelemetrySettings())
require.NoError(t, err)
assert.NotNil(t, srv)
}
func TestGrpcClientConfigInvalidBalancer(t *testing.T) {
settings := ClientConfig{
Headers: configopaque.MapList{
{Name: "test", Value: "test"},
},
Endpoint: "localhost:1234",
Compression: "gzip",
TLS: configtls.ClientConfig{
Insecure: false,
},
Keepalive: configoptional.Some(KeepaliveClientConfig{
Time: time.Second,
Timeout: time.Second,
PermitWithoutStream: true,
}),
ReadBufferSize: 1024,
WriteBufferSize: 1024,
WaitForReady: true,
BalancerName: "test",
}
assert.ErrorContains(t, settings.Validate(), "invalid balancer_name: test")
}
func TestGRPCClientSettingsError(t *testing.T) {
tests := []struct {
settings ClientConfig
err string
extensions map[component.ID]component.Component
}{
{
err: "failed to load TLS config: failed to load CA CertPool File: failed to load cert /doesnt/exist:",
settings: ClientConfig{
Headers: nil,
Endpoint: "localhost:1234",
Compression: "",
TLS: configtls.ClientConfig{
Config: configtls.Config{
CAFile: "/doesnt/exist",
},
Insecure: false,
ServerName: "",
},
},
},
{
err: "failed to load TLS config: failed to load TLS cert and key: for auth via TLS, provide both certificate and key, or neither",
settings: ClientConfig{
Headers: nil,
Endpoint: "localhost:1234",
Compression: "",
TLS: configtls.ClientConfig{
Config: configtls.Config{
CertFile: "/doesnt/exist",
},
Insecure: false,
ServerName: "",
},
},
},
{
err: "failed to resolve authenticator \"doesntexist\": authenticator not found",
settings: ClientConfig{
Endpoint: "localhost:1234",
Auth: configoptional.Some(configauth.Config{AuthenticatorID: doesntExistID}),
},
extensions: map[component.ID]component.Component{},
},
{
err: "authentication was configured but this component or its host does not support extensions",
settings: ClientConfig{
Endpoint: "localhost:1234",
Auth: configoptional.Some(configauth.Config{AuthenticatorID: doesntExistID}),
},
},
{
err: "unsupported compression type \"zlib\"",
settings: ClientConfig{
Endpoint: "localhost:1234",
TLS: configtls.ClientConfig{
Insecure: true,
},
Compression: "zlib",
},
},
{
err: "unsupported compression type \"deflate\"",
settings: ClientConfig{
Endpoint: "localhost:1234",
TLS: configtls.ClientConfig{
Insecure: true,
},
Compression: "deflate",
},
},
{
err: "unsupported compression type \"bad\"",
settings: ClientConfig{
Endpoint: "localhost:1234",
TLS: configtls.ClientConfig{
Insecure: true,
},
Compression: "bad",
},
},
}
for _, test := range tests {
t.Run(test.err, func(t *testing.T) {
require.NoError(t, test.settings.Validate())
_, err := test.settings.ToClientConn(context.Background(), test.extensions, componenttest.NewNopTelemetrySettings())
require.Error(t, err)
assert.ErrorContains(t, err, test.err)
})
}
}
func TestUseSecure(t *testing.T) {
cc := &ClientConfig{
Headers: nil,
Endpoint: "",
Compression: "",
TLS: configtls.ClientConfig{},
}
dialOpts, err := cc.getGrpcDialOptions(context.Background(), nil, componenttest.NewNopTelemetrySettings(), []ToClientConnOption{})
require.NoError(t, err)
assert.Len(t, dialOpts, 2)
}
func TestGRPCServerSettingsError(t *testing.T) {
tests := []struct {
settings ServerConfig
err string
}{
{
err: "failed to load TLS config: failed to load CA CertPool File: failed to load cert /doesnt/exist:",
settings: ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "127.0.0.1:1234",
Transport: confignet.TransportTypeTCP,
},
TLS: configoptional.Some(configtls.ServerConfig{
Config: configtls.Config{
CAFile: "/doesnt/exist",
},
}),
},
},
{
err: "failed to load TLS config: failed to load TLS cert and key: for auth via TLS, provide both certificate and key, or neither",
settings: ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "127.0.0.1:1234",
Transport: confignet.TransportTypeTCP,
},
TLS: configoptional.Some(configtls.ServerConfig{
Config: configtls.Config{
CertFile: "/doesnt/exist",
},
}),
},
},
{
err: "failed to load client CA CertPool: failed to load CA /doesnt/exist:",
settings: ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "127.0.0.1:1234",
Transport: confignet.TransportTypeTCP,
},
TLS: configoptional.Some(configtls.ServerConfig{
ClientCAFile: "/doesnt/exist",
}),
},
},
}
for _, test := range tests {
t.Run(test.err, func(t *testing.T) {
_, err := test.settings.ToServer(context.Background(), nil, componenttest.NewNopTelemetrySettings())
assert.ErrorContains(t, err, test.err)
})
}
}
func TestGRPCServerSettings_ToListener_Error(t *testing.T) {
settings := ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "127.0.0.1:1234567",
Transport: confignet.TransportTypeTCP,
},
}
_, err := settings.NetAddr.Listen(context.Background())
assert.Error(t, err)
}
func TestHTTPReception(t *testing.T) {
tests := []struct {
name string
tlsServerCreds configoptional.Optional[configtls.ServerConfig]
tlsClientCreds configoptional.Optional[configtls.ClientConfig]
hasError bool
}{
{
name: "noTLS",
tlsServerCreds: configoptional.None[configtls.ServerConfig](),
tlsClientCreds: configoptional.Some(configtls.ClientConfig{
Insecure: true,
}),
},
{
name: "TLS",
tlsServerCreds: configoptional.Some(configtls.ServerConfig{
Config: configtls.Config{
CAFile: filepath.Join("testdata", "ca.crt"),
CertFile: filepath.Join("testdata", "server.crt"),
KeyFile: filepath.Join("testdata", "server.key"),
},
}),
tlsClientCreds: configoptional.Some(configtls.ClientConfig{
Config: configtls.Config{
CAFile: filepath.Join("testdata", "ca.crt"),
},
ServerName: "localhost",
}),
},
{
name: "NoServerCertificates",
tlsServerCreds: configoptional.Some(configtls.ServerConfig{
Config: configtls.Config{
CAFile: filepath.Join("testdata", "ca.crt"),
},
}),
tlsClientCreds: configoptional.Some(configtls.ClientConfig{
Config: configtls.Config{
CAFile: filepath.Join("testdata", "ca.crt"),
},
ServerName: "localhost",
}),
hasError: true,
},
{
name: "mTLS",
tlsServerCreds: configoptional.Some(configtls.ServerConfig{
Config: configtls.Config{
CAFile: filepath.Join("testdata", "ca.crt"),
CertFile: filepath.Join("testdata", "server.crt"),
KeyFile: filepath.Join("testdata", "server.key"),
},
ClientCAFile: filepath.Join("testdata", "ca.crt"),
}),
tlsClientCreds: configoptional.Some(configtls.ClientConfig{
Config: configtls.Config{
CAFile: filepath.Join("testdata", "ca.crt"),
CertFile: filepath.Join("testdata", "client.crt"),
KeyFile: filepath.Join("testdata", "client.key"),
},
ServerName: "localhost",
}),
},
{
name: "NoClientCertificate",
tlsServerCreds: configoptional.Some(configtls.ServerConfig{
Config: configtls.Config{
CAFile: filepath.Join("testdata", "ca.crt"),
CertFile: filepath.Join("testdata", "server.crt"),
KeyFile: filepath.Join("testdata", "server.key"),
},
ClientCAFile: filepath.Join("testdata", "ca.crt"),
}),
tlsClientCreds: configoptional.Some(configtls.ClientConfig{
Config: configtls.Config{
CAFile: filepath.Join("testdata", "ca.crt"),
},
ServerName: "localhost",
}),
hasError: true,
},
{
name: "WrongClientCA",
tlsServerCreds: configoptional.Some(configtls.ServerConfig{
Config: configtls.Config{
CAFile: filepath.Join("testdata", "ca.crt"),
CertFile: filepath.Join("testdata", "server.crt"),
KeyFile: filepath.Join("testdata", "server.key"),
},
ClientCAFile: filepath.Join("testdata", "server.crt"),
}),
tlsClientCreds: configoptional.Some(configtls.ClientConfig{
Config: configtls.Config{
CAFile: filepath.Join("testdata", "ca.crt"),
CertFile: filepath.Join("testdata", "client.crt"),
KeyFile: filepath.Join("testdata", "client.key"),
},
ServerName: "localhost",
}),
hasError: true,
},
}
// prepare
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
s, addr := (&grpcTraceServer{}).startTestServer(t, configoptional.Some(ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:0",
Transport: confignet.TransportTypeTCP,
},
TLS: test.tlsServerCreds,
}))
defer s.Stop()
resp, errResp := sendTestRequest(t, ClientConfig{
Endpoint: addr,
TLS: *test.tlsClientCreds.Get(),
})
if test.hasError {
require.Error(t, errResp)
} else {
require.NoError(t, errResp)
assert.NotNil(t, resp)
}
})
}
}
func TestReceiveOnUnixDomainSocket(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("skipping test on windows")
}
socketName := tempSocketName(t)
srv, addr := (&grpcTraceServer{}).startTestServer(t, configoptional.Some(ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: socketName,
Transport: confignet.TransportTypeUnix,
},
}))
defer srv.Stop()
resp, errResp := sendTestRequest(t, ClientConfig{
Endpoint: "unix://" + addr,
TLS: configtls.ClientConfig{
Insecure: true,
},
})
require.NoError(t, errResp)
assert.NotNil(t, resp)
}
func TestContextWithClient(t *testing.T) {
testCases := []struct {
desc string
input context.Context
doMetadata bool
expected client.Info
}{
{
desc: "no peer information, empty client",
input: context.Background(),
expected: client.Info{},
},
{
desc: "existing client with IP, no peer information",
input: client.NewContext(context.Background(), client.Info{
Addr: &net.IPAddr{
IP: net.IPv4(1, 2, 3, 4),
},
}),
expected: client.Info{
Addr: &net.IPAddr{
IP: net.IPv4(1, 2, 3, 4),
},
},
},
{
desc: "empty client, with peer information",
input: peer.NewContext(context.Background(), &peer.Peer{
Addr: &net.IPAddr{
IP: net.IPv4(1, 2, 3, 4),
},
}),
expected: client.Info{
Addr: &net.IPAddr{
IP: net.IPv4(1, 2, 3, 4),
},
},
},
{
desc: "existing client, existing IP gets overridden with peer information",
input: peer.NewContext(client.NewContext(context.Background(), client.Info{
Addr: &net.IPAddr{
IP: net.IPv4(1, 2, 3, 4),
},
}), &peer.Peer{
Addr: &net.IPAddr{
IP: net.IPv4(1, 2, 3, 5),
},
}),
expected: client.Info{
Addr: &net.IPAddr{
IP: net.IPv4(1, 2, 3, 5),
},
},
},
{
desc: "existing client with metadata",
input: client.NewContext(context.Background(), client.Info{
Metadata: client.NewMetadata(map[string][]string{"test-metadata-key": {"test-value"}}),
}),
doMetadata: true,
expected: client.Info{
Metadata: client.NewMetadata(map[string][]string{"test-metadata-key": {"test-value"}}),
},
},
{
desc: "existing client with metadata in context",
input: metadata.NewIncomingContext(
client.NewContext(context.Background(), client.Info{}),
metadata.Pairs("test-metadata-key", "test-value"),
),
doMetadata: true,
expected: client.Info{
Metadata: client.NewMetadata(map[string][]string{"test-metadata-key": {"test-value"}}),
},
},
{
desc: "existing client with metadata in context, no metadata processing",
input: metadata.NewIncomingContext(
client.NewContext(context.Background(), client.Info{}),
metadata.Pairs("test-metadata-key", "test-value"),
),
expected: client.Info{},
},
{
desc: "existing client with Host and metadata",
input: metadata.NewIncomingContext(
client.NewContext(context.Background(), client.Info{}),
metadata.Pairs("test-metadata-key", "test-value", ":authority", "localhost:55443"),
),
doMetadata: true,
expected: client.Info{
Metadata: client.NewMetadata(map[string][]string{"test-metadata-key": {"test-value"}, ":authority": {"localhost:55443"}, "Host": {"localhost:55443"}}),
},
},
}
for _, tt := range testCases {
t.Run(tt.desc, func(t *testing.T) {
cl := client.FromContext(contextWithClient(tt.input, tt.doMetadata))
assert.Equal(t, tt.expected, cl)
})
}
}
func TestStreamInterceptorEnhancesClient(t *testing.T) {
// prepare
inCtx := peer.NewContext(context.Background(), &peer.Peer{
Addr: &net.IPAddr{IP: net.IPv4(1, 1, 1, 1)},
})
stream := &mockedStream{
ctx: inCtx,
}
var handlerCalled bool
handler := func(_ any, stream grpc.ServerStream) error {
handlerCalled = true
cl := client.FromContext(stream.Context())
assert.Equal(t, "1.1.1.1", cl.Addr.String())
return nil
}
// test
err := enhanceStreamWithClientInformation(false)(nil, stream, nil, handler)
// verify
require.NoError(t, err)
assert.True(t, handlerCalled, "the handler should have been called")
}
type mockedStream struct {
ctx context.Context
grpc.ServerStream
}
func (ms *mockedStream) Context() context.Context {
return ms.ctx
}
func TestClientInfoInterceptors(t *testing.T) {
testCases := []struct {
name string
tester func(ptraceotlp.ExportResponse, error)
}{
{
// we only have unary services, we don't have any clients we could use
// to test with streaming services
name: "unary",
tester: func(resp ptraceotlp.ExportResponse, errResp error) {
require.NoError(t, errResp)
require.NotNil(t, resp)
},
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
mock := &grpcTraceServer{}
var addr string
// prepare the server
{
var srv *grpc.Server
srv, addr = mock.startTestServer(t, configoptional.Some(ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:0",
Transport: confignet.TransportTypeTCP,
},
}))
defer srv.Stop()
}
// prepare the client and execute a RPC
{
resp, errResp := sendTestRequest(t, ClientConfig{
Endpoint: addr,
TLS: configtls.ClientConfig{
Insecure: true,
},
})
// test
tt.tester(resp, errResp)
}
// verify
cl := client.FromContext(mock.recordedContext)
// the client address is something like 127.0.0.1:41086
assert.Contains(t, cl.Addr.String(), "127.0.0.1")
})
}
}
func TestClientInfoInterceptorBeforeAuth(t *testing.T) {
mock := &grpcTraceServer{}
var addr string
var authCalled bool
type serverAuthExtension struct {
component.StartFunc
component.ShutdownFunc
extensionauth.Server
}
// prepare the server
{
var srv *grpc.Server
srv, addr = mock.startTestServerWithExtensions(t, configoptional.Some(ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:0",
Transport: confignet.TransportTypeTCP,
},
Auth: configoptional.Some(configauth.Config{
AuthenticatorID: mockID,
}),
}), map[component.ID]component.Component{
mockID: serverAuthExtension{
Server: newMockAuthServer(
func(ctx context.Context, _ map[string][]string) (context.Context, error) {
// verify that client info is populated before auth
authCalled = true
cl := client.FromContext(ctx)
assert.NotNil(t, cl.Addr)
return ctx, nil
},
),
},
})
defer srv.Stop()
}
// prepare the client and execute a RPC
{
_, errResp := sendTestRequest(t, ClientConfig{
Endpoint: addr,
TLS: configtls.ClientConfig{
Insecure: true,
},
})
require.NoError(t, errResp)
}
assert.True(t, authCalled)
}
func TestDefaultUnaryInterceptorAuthSucceeded(t *testing.T) {
// prepare
handlerCalled := false
authCalled := false
expectedAuthData := new(struct{ client.AuthData })
authFunc := func(ctx context.Context, _ map[string][]string) (context.Context, error) {
authCalled = true
cl := client.FromContext(ctx)
cl.Auth = expectedAuthData
ctx = client.NewContext(ctx, cl)
return ctx, nil
}
handler := func(ctx context.Context, _ any) (any, error) {
handlerCalled = true
cl := client.FromContext(ctx)
assert.Equal(t, expectedAuthData, cl.Auth)
return nil, nil
}
ctx := metadata.NewIncomingContext(context.Background(), metadata.Pairs("authorization", "some-auth-data"))
interceptor := authUnaryServerInterceptor(newMockAuthServer(authFunc))
// test
res, err := interceptor(ctx, nil, &grpc.UnaryServerInfo{}, handler)
// verify
assert.Nil(t, res)
require.NoError(t, err)
assert.True(t, authCalled)
assert.True(t, handlerCalled)
}
func TestDefaultUnaryInterceptorAuthFailure(t *testing.T) {
// prepare
authCalled := false
expectedErr := errors.New("not authenticated")
authFunc := func(context.Context, map[string][]string) (context.Context, error) {
authCalled = true
return context.Background(), expectedErr
}
handler := func(context.Context, any) (any, 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"))
interceptor := authUnaryServerInterceptor(newMockAuthServer(authFunc))
// test
res, err := interceptor(ctx, nil, &grpc.UnaryServerInfo{}, handler)
// verify
assert.Nil(t, res)
require.ErrorContains(t, err, expectedErr.Error())
assert.Equal(t, codes.Unauthenticated, status.Code(err))
assert.True(t, authCalled)
}
func TestDefaultUnaryInterceptorAuthFailureWithStatusErr(t *testing.T) {
// prepare
authCalled := false
expectedStatusErr := status.New(codes.Unavailable, "unavailable")
authFunc := func(context.Context, map[string][]string) (context.Context, error) {
authCalled = true
return context.Background(), expectedStatusErr.Err()
}
handler := func(context.Context, any) (any, 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"))
interceptor := authUnaryServerInterceptor(newMockAuthServer(authFunc))
// test
res, err := interceptor(ctx, nil, &grpc.UnaryServerInfo{}, handler)
// verify
assert.Nil(t, res)
require.ErrorContains(t, err, expectedStatusErr.Err().Error())
assert.Equal(t, codes.Unavailable, status.Code(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(context.Context, any) (any, error) {
assert.FailNow(t, "the handler should not have been called!")
return nil, nil
}
interceptor := authUnaryServerInterceptor(newMockAuthServer(authFunc))
// test
res, err := interceptor(context.Background(), nil, &grpc.UnaryServerInfo{}, handler)
// verify
assert.Nil(t, res)
assert.Equal(t, errMetadataNotFound, err)
}
func TestDefaultStreamInterceptorAuthSucceeded(t *testing.T) {
// prepare
handlerCalled := false
authCalled := false
expectedAuthData := new(struct{ client.AuthData })
authFunc := func(ctx context.Context, _ map[string][]string) (context.Context, error) {
authCalled = true
cl := client.FromContext(ctx)
cl.Auth = expectedAuthData
ctx = client.NewContext(ctx, cl)
return ctx, nil
}
handler := func(_ any, stream grpc.ServerStream) error {
// ensure that the client information is propagated down to the underlying stream
cl := client.FromContext(stream.Context())
assert.Equal(t, expectedAuthData, cl.Auth)
handlerCalled = true
return nil
}
streamServer := &mockServerStream{
ctx: metadata.NewIncomingContext(context.Background(), metadata.Pairs("authorization", "some-auth-data")),
}
interceptor := authStreamServerInterceptor(newMockAuthServer(authFunc))
// test
err := interceptor(nil, streamServer, &grpc.StreamServerInfo{}, handler)
// verify
require.NoError(t, err)
assert.True(t, authCalled)
assert.True(t, handlerCalled)
}
func TestDefaultStreamInterceptorAuthFailureWithStatusErr(t *testing.T) {
// prepare
authCalled := false
expectedStatusErr := status.New(codes.Unavailable, "unavailable")
authFunc := func(context.Context, map[string][]string) (context.Context, error) {
authCalled = true
return context.Background(), expectedStatusErr.Err()
}
handler := func(any, grpc.ServerStream) error {
assert.FailNow(t, "the handler should not have been called on auth failure!")
return nil
}
streamServer := &mockServerStream{
ctx: metadata.NewIncomingContext(context.Background(), metadata.Pairs("authorization", "some-auth-data")),
}
interceptor := authStreamServerInterceptor(newMockAuthServer(authFunc))
// test
err := interceptor(nil, streamServer, &grpc.StreamServerInfo{}, handler)
// verify
require.ErrorContains(t, err, expectedStatusErr.Err().Error()) // unfortunately, grpc errors don't wrap the original ones
assert.Equal(t, codes.Unavailable, status.Code(err))
assert.True(t, authCalled)
}
func TestDefaultStreamInterceptorAuthFailure(t *testing.T) {
// prepare
authCalled := false
expectedErr := errors.New("not authenticated")
authFunc := func(context.Context, map[string][]string) (context.Context, error) {
authCalled = true
return context.Background(), expectedErr
}
handler := func(any, grpc.ServerStream) error {
assert.FailNow(t, "the handler should not have been called on auth failure!")
return nil
}
streamServer := &mockServerStream{
ctx: metadata.NewIncomingContext(context.Background(), metadata.Pairs("authorization", "some-auth-data")),
}
interceptor := authStreamServerInterceptor(newMockAuthServer(authFunc))
// test
err := interceptor(nil, streamServer, &grpc.StreamServerInfo{}, handler)
// verify
require.ErrorContains(t, err, expectedErr.Error()) // unfortunately, grpc errors don't wrap the original ones
assert.Equal(t, codes.Unauthenticated, status.Code(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(any, grpc.ServerStream) error {
assert.FailNow(t, "the handler should not have been called!")
return nil
}
streamServer := &mockServerStream{
ctx: context.Background(),
}
interceptor := authStreamServerInterceptor(newMockAuthServer(authFunc))
// test
err := interceptor(nil, streamServer, &grpc.StreamServerInfo{}, handler)
// verify
assert.Equal(t, errMetadataNotFound, err)
}
type mockServerStream struct {
grpc.ServerStream
ctx context.Context
}
func (m *mockServerStream) Context() context.Context {
return m.ctx
}
type grpcTraceServer struct {
ptraceotlp.UnimplementedGRPCServer
recordedContext context.Context
}
func (gts *grpcTraceServer) Export(ctx context.Context, _ ptraceotlp.ExportRequest) (ptraceotlp.ExportResponse, error) {
gts.recordedContext = ctx
return ptraceotlp.NewExportResponse(), nil
}
func (gts *grpcTraceServer) startTestServer(t *testing.T, gss configoptional.Optional[ServerConfig]) (*grpc.Server, string) {
return gts.startTestServerWithExtensions(t, gss, nil)
}
func (gts *grpcTraceServer) startTestServerWithExtensions(t *testing.T, gss configoptional.Optional[ServerConfig], extensions map[component.ID]component.Component, opts ...ToServerOption) (*grpc.Server, string) {
listener, err := gss.Get().NetAddr.Listen(context.Background())
require.NoError(t, err)
server, err := gss.Get().ToServer(context.Background(), extensions, componenttest.NewNopTelemetrySettings(), opts...)
require.NoError(t, err)
ptraceotlp.RegisterGRPCServer(server, gts)
go func() {
_ = server.Serve(listener)
}()
return server, listener.Addr().String()
}
func (gts *grpcTraceServer) startTestServerWithExtensionsError(_ *testing.T, gss ServerConfig, extensions map[component.ID]component.Component, opts ...ToServerOption) (*grpc.Server, error) {
listener, err := gss.NetAddr.Listen(context.Background())
if err != nil {
return nil, err
}
defer listener.Close()
server, err := gss.ToServer(context.Background(), extensions, componenttest.NewNopTelemetrySettings(), opts...)
if err != nil {
return nil, err
}
ptraceotlp.RegisterGRPCServer(server, gts)
return server, nil
}
// sendTestRequest issues a ptraceotlp export request and captures metadata.
func sendTestRequest(t *testing.T, cc ClientConfig) (ptraceotlp.ExportResponse, error) {
return sendTestRequestWithExtensions(t, cc, nil)
}
// sendTestRequestWithExtensions is similar to sendTestRequest but allows specifying the host
func sendTestRequestWithExtensions(t *testing.T, cc ClientConfig, extensions map[component.ID]component.Component) (ptraceotlp.ExportResponse, error) {
grpcClientConn, errClient := cc.ToClientConn(context.Background(), extensions, componenttest.NewNopTelemetrySettings())
require.NoError(t, errClient)
defer func() { assert.NoError(t, grpcClientConn.Close()) }()
c := ptraceotlp.NewGRPCClient(grpcClientConn)
ctx, cancelFunc := context.WithTimeout(context.Background(), 2*time.Second)
resp, errResp := c.Export(ctx, ptraceotlp.NewExportRequest(), grpc.WaitForReady(true))
cancelFunc()
return resp, errResp
}
// tempSocketName provides a temporary Unix socket name for testing.
func tempSocketName(t *testing.T) string {
// The socket path length limit on macOS is 104 characters. Using `os.TempDir` to produce a shorter file path (#12639)
tmpfile, err := os.CreateTemp(os.TempDir(), "sock")
require.NoError(t, err)
require.NoError(t, tmpfile.Close())
socket := tmpfile.Name()
require.NoError(t, os.Remove(socket))
return socket
}
================================================
FILE: config/configgrpc/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package configgrpc defines the configuration settings to create
// a gRPC client and server.
//
// The configuration structs in this package may be shared across signals, but
// assume each struct is used for a single protocol and component.
package configgrpc // import "go.opentelemetry.io/collector/config/configgrpc"
================================================
FILE: config/configgrpc/go.mod
================================================
module go.opentelemetry.io/collector/config/configgrpc
go 1.25.0
require (
github.com/mostynb/go-grpc-compression v1.2.3
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/client v1.54.0
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.opentelemetry.io/collector/config/configauth v1.54.0
go.opentelemetry.io/collector/config/configcompression v1.54.0
go.opentelemetry.io/collector/config/configmiddleware v1.54.0
go.opentelemetry.io/collector/config/confignet v1.54.0
go.opentelemetry.io/collector/config/configopaque v1.54.0
go.opentelemetry.io/collector/config/configoptional v1.54.0
go.opentelemetry.io/collector/config/configtls v1.54.0
go.opentelemetry.io/collector/extension v1.54.0
go.opentelemetry.io/collector/extension/extensionauth v1.54.0
go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest v0.148.0
go.opentelemetry.io/collector/extension/extensionmiddleware v0.148.0
go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest v0.148.0
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/collector/pdata/testdata v0.148.0
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0
go.opentelemetry.io/otel v1.42.0
go.uber.org/goleak v1.3.0
google.golang.org/grpc v1.79.3
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-tpm v0.9.8 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/confmap v1.54.0 // indirect
go.opentelemetry.io/collector/confmap/xconfmap v0.148.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
go.opentelemetry.io/collector/pdata/pprofile v0.148.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/client => ../../client
replace go.opentelemetry.io/collector/config/configauth => ../configauth
replace go.opentelemetry.io/collector/config/configcompression => ../configcompression
replace go.opentelemetry.io/collector/config/confignet => ../confignet
replace go.opentelemetry.io/collector/config/configopaque => ../configopaque
replace go.opentelemetry.io/collector/config/configoptional => ../configoptional
replace go.opentelemetry.io/collector/config/configtls => ../configtls
replace go.opentelemetry.io/collector/extension => ../../extension
replace go.opentelemetry.io/collector/extension/extensionauth => ../../extension/extensionauth
replace go.opentelemetry.io/collector/config/configmiddleware => ../configmiddleware
replace go.opentelemetry.io/collector/extension/extensionmiddleware => ../../extension/extensionmiddleware
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/pdata/testdata => ../../pdata/testdata
replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest
replace go.opentelemetry.io/collector/consumer => ../../consumer
replace go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest => ../../extension/extensionauth/extensionauthtest
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest => ../../extension/extensionmiddleware/extensionmiddlewaretest
replace go.opentelemetry.io/collector/confmap => ../../confmap
replace go.opentelemetry.io/collector/confmap/xconfmap => ../../confmap/xconfmap
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
================================================
FILE: config/configgrpc/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f h1:RJ+BDPLSHQO7cSjKBqjPJSbi1qfk9WcsjQDtZiw3dZw=
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f/go.mod h1:VHbbch/X4roIY22jL1s3qRbZhCiRIgUAF/PdSUcx2io=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/go-tpm-tools v0.4.7 h1:J3ycC8umYxM9A4eF73EofRZu4BxY0jjQnUnkhIBbvws=
github.com/google/go-tpm-tools v0.4.7/go.mod h1:gSyXTZHe3fgbzb6WEGd90QucmsnT1SRdlye82gH8QjQ=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mostynb/go-grpc-compression v1.2.3 h1:42/BKWMy0KEJGSdWvzqIyOZ95YcR9mLPqKctH7Uo//I=
github.com/mostynb/go-grpc-compression v1.2.3/go.mod h1:AghIxF3P57umzqM9yz795+y1Vjs47Km/Y2FE6ouQ7Lg=
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: config/configgrpc/gzip.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configgrpc // import "go.opentelemetry.io/collector/config/configgrpc"
import (
// Import the gzip package which auto-registers the gzip gRPC compressor.
_ "google.golang.org/grpc/encoding/gzip"
)
================================================
FILE: config/configgrpc/metadata.yaml
================================================
type: config/configgrpc
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: config/configgrpc/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configgrpc
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: config/configgrpc/server_middleware_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configgrpc
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configmiddleware"
"go.opentelemetry.io/collector/config/confignet"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/config/configtls"
"go.opentelemetry.io/collector/extension"
"go.opentelemetry.io/collector/extension/extensionmiddleware"
"go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest"
)
// contextKey is a private type for keys defined in this test.
type contextKey int
// Key for the slice of middleware names in the context.
const middlewareCallsKey contextKey = 0
// getMiddlewareCalls retrieves the middleware calls from context or returns an empty slice.
func getMiddlewareCalls(ctx context.Context) []string {
calls, ok := ctx.Value(middlewareCallsKey).([]string)
if !ok {
return []string{}
}
return calls
}
// testServerMiddleware is a test implementation of configmiddleware.Middleware
type testServerMiddleware struct {
extension.Extension
extensionmiddleware.GetGRPCServerOptionsFunc
}
func newTestServerMiddleware(name string) extension.Extension {
return &testServerMiddleware{
Extension: extensionmiddlewaretest.NewNop(),
GetGRPCServerOptionsFunc: func(_ context.Context) ([]grpc.ServerOption, error) {
return []grpc.ServerOption{grpc.ChainUnaryInterceptor(
func(
ctx context.Context,
req any, _ *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (any, error) {
ctx = context.WithValue(ctx, middlewareCallsKey, append(getMiddlewareCalls(ctx), name))
return handler(ctx, req)
})}, nil
},
}
}
func TestGrpcServerUnaryInterceptor(t *testing.T) {
// Register two test extensions
extensions := map[component.ID]component.Component{
component.MustNewID("test1"): newTestServerMiddleware("test1"),
component.MustNewID("test2"): newTestServerMiddleware("test2"),
}
// Setup the server with both middleware options
server := &grpcTraceServer{}
var addr string
// Create the server with middleware interceptors
{
var srv *grpc.Server
srv, addr = server.startTestServerWithExtensions(t, configoptional.Some(ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:0",
Transport: confignet.TransportTypeTCP,
},
Middlewares: []configmiddleware.Config{
newTestMiddlewareConfig("test1"),
newTestMiddlewareConfig("test2"),
},
}), extensions)
defer srv.Stop()
}
// Send a request to trigger the interceptors
resp, errResp := sendTestRequest(t, ClientConfig{
Endpoint: addr,
TLS: configtls.ClientConfig{
Insecure: true,
},
})
require.NoError(t, errResp)
require.NotNil(t, resp)
// Verify interceptors were called in the correct order
assert.Equal(t, []string{"test1", "test2"}, getMiddlewareCalls(server.recordedContext))
}
// TestServerMiddlewareToServerErrors tests failure cases for the ToServer method
// specifically related to middleware resolution and API calls.
func TestServerMiddlewareToServerErrors(t *testing.T) {
tests := []struct {
name string
extensions map[component.ID]component.Component
config ServerConfig
errText string
}{
{
name: "extension_not_found",
extensions: map[component.ID]component.Component{},
config: ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:0",
Transport: confignet.TransportTypeTCP,
},
Middlewares: []configmiddleware.Config{
{
ID: component.MustNewID("nonexistent"),
},
},
},
errText: "failed to resolve middleware \"nonexistent\": middleware not found",
},
{
name: "get_server_options_fails",
extensions: map[component.ID]component.Component{
component.MustNewID("errormw"): extensionmiddlewaretest.NewErr(errors.New("get server options failed")),
},
config: ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:0",
Transport: confignet.TransportTypeTCP,
},
Middlewares: []configmiddleware.Config{
{
ID: component.MustNewID("errormw"),
},
},
},
errText: "get server options failed",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
// Test creating the server with middleware errors
server := &grpcTraceServer{}
srv, err := server.startTestServerWithExtensionsError(t, tc.config, tc.extensions)
if srv != nil {
srv.Stop()
}
require.Error(t, err)
assert.Contains(t, err.Error(), tc.errText)
})
}
}
================================================
FILE: config/configgrpc/testdata/ca.crt
================================================
-----BEGIN CERTIFICATE-----
MIIDNjCCAh4CCQC0I5IQT7eziDANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJB
VTESMBAGA1UECAwJQXVzdHJhbGlhMQ8wDQYDVQQHDAZTeWRuZXkxEjAQBgNVBAoM
CU15T3JnTmFtZTEVMBMGA1UEAwwMTXlDb21tb25OYW1lMB4XDTIyMDgwMzA0MTky
MVoXDTMyMDczMTA0MTkyMVowXTELMAkGA1UEBhMCQVUxEjAQBgNVBAgMCUF1c3Ry
YWxpYTEPMA0GA1UEBwwGU3lkbmV5MRIwEAYDVQQKDAlNeU9yZ05hbWUxFTATBgNV
BAMMDE15Q29tbW9uTmFtZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AMhGP0dy3zvkdx9zI+/XVjPOWlER0OUp7Sgzidc3nLOk42+bH4ofIVNtOFVqlNKi
O1bImu238VdBhd6R5IZZ1ZdIMcCeDgSJYu2X9wA3m4PKz8IdXo5ly2OHghhmCvqG
WxgqDj5wPXiczQwuf1EcDMtRWbXJ6Z/XH1U68R/kRdNLkiZ2LwtjoQpis5XYckLL
CrdF+AL6GeDIe0Mh9QGs26Vux+2kvaOGNUWRPE6Wt4GkqyKqmzYfR9HbflJ4xHT2
I+jE1lg+jMBeom7z8Z90RE4GGcHjO+Vens/88r5EAjTnFj1Kb5gL2deSHY1m/++R
Z/kRyg+zQJyw4fAzlAA4+VkCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAM3gRdTKX
eGwGYVmmKqA2vTxeigQYLHml7OSopcWj2wJfxfp49HXPRuvgpQn9iubxO3Zmhd83
2X1E+T0A8oy5CfxgpAhHb3lY0jm3TjKXm6m+dSODwL3uND8tX+SqR8sRTFxPvPuo
pmvhdTZoRI3EzIiHLTgCuSU25JNP/vrVoKk0JvCkDYTU/WcVfj0v95DTMoWR4JGz
mtBwrgD0EM2XRw5ZMc7sMPli1gqmCbCQUrDZ+rPB78WDCBILBd8Cz75qYTUp98BY
akJyBckdJHAdyEQYDKa9HpmpexOO7IhSXCTEN1DEBgpZgEi/lBDRG/b0OzenUUgt
LUABtWt3pNQ9HA==
-----END CERTIFICATE-----
================================================
FILE: config/configgrpc/testdata/client.crt
================================================
-----BEGIN CERTIFICATE-----
MIIDVTCCAj2gAwIBAgIJANt5fkUlfxyiMA0GCSqGSIb3DQEBCwUAMF0xCzAJBgNV
BAYTAkFVMRIwEAYDVQQIDAlBdXN0cmFsaWExDzANBgNVBAcMBlN5ZG5leTESMBAG
A1UECgwJTXlPcmdOYW1lMRUwEwYDVQQDDAxNeUNvbW1vbk5hbWUwHhcNMjIwODAz
MDQxOTIxWhcNMzIwNzMxMDQxOTIxWjBdMQswCQYDVQQGEwJBVTESMBAGA1UECAwJ
QXVzdHJhbGlhMQ8wDQYDVQQHDAZTeWRuZXkxEjAQBgNVBAoMCU15T3JnTmFtZTEV
MBMGA1UEAwwMTXlDb21tb25OYW1lMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAm/gURxkdWTDS0TyL2j920SfOtOZIo7DjubWLbZtNLrNCZNBsV+8c/ko/
wleWmUJQRHeiZkNFs8TK6d8Grks6ta9oNO4CiCCO1kz4QidA827cL5+WaKWEVn8Y
Z8aiEMjDOnpYnb/ycsXpERN/P22jHpFD3DKSwLXoXQvasbSJsZro+AIaPAurFB7W
rMagCptwzGQDzryqVKEmXo+eN4XRxsoE8yroHsGbQ8GCZ+neftgV3Jhi1qcXZ//A
3ApY5lg06n1A03fYBlXE5L9tYKpIRNl2kq45mJ8DX6Tdp4Z1Y15+keIIyQpx4LRf
rtdbMQNJhBFOwpAajTmaKXxeICFRHQIDAQABoxgwFjAUBgNVHREEDTALgglsb2Nh
bGhvc3QwDQYJKoZIhvcNAQELBQADggEBAKWrbMxms658R/wYwLxzWPrZVKFswOJX
TpSkXGkyRnrhhZi3I8EhLZhlpZ9k8dplcvseVAUdX9hJu0BaDWBiW/VlPVUkWpWR
QZzrssAKhmSYMgl3OiayU30vL9bxYsAX9KeOJfnJ4kWoBpnguToED7wrC1lbzrVK
Vj1AiI3hBdKUdPNO0hyb8yfxbP3MOottMkk89DIebtOhqj2KEU7sKrhW9a5P5D7d
0A+0kf/IunUZ4IYFfha6qy0gRMyayfm9ttrPAY6q3faqtWR7nY87/T/7wHr1LQ1/
Q622p7v3j3y75lGN50kFnSd77ykag/8avEKxOTFoGOQc5VCRYJnJwb4=
-----END CERTIFICATE-----
================================================
FILE: config/configgrpc/testdata/client.key
================================================
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAm/gURxkdWTDS0TyL2j920SfOtOZIo7DjubWLbZtNLrNCZNBs
V+8c/ko/wleWmUJQRHeiZkNFs8TK6d8Grks6ta9oNO4CiCCO1kz4QidA827cL5+W
aKWEVn8YZ8aiEMjDOnpYnb/ycsXpERN/P22jHpFD3DKSwLXoXQvasbSJsZro+AIa
PAurFB7WrMagCptwzGQDzryqVKEmXo+eN4XRxsoE8yroHsGbQ8GCZ+neftgV3Jhi
1qcXZ//A3ApY5lg06n1A03fYBlXE5L9tYKpIRNl2kq45mJ8DX6Tdp4Z1Y15+keII
yQpx4LRfrtdbMQNJhBFOwpAajTmaKXxeICFRHQIDAQABAoIBAQCWxrT7omi/vzYd
9dUQ8Acx3LS0JmaUb71F2x3loJt1iO+nO+FxBIPXw/ltK3U3xWaJOcnx6Biq15R9
kBAKUEl6OA6aFHi4FhlfS9s3QHFGo6YSF8m0ckXDxGvYbqpfZWVt07Z1EYkUsQRF
cL6zl454T1/1r6I0z+XIhVwuLGRsHt2+GCwSrLMnF9aTUJvPFy5G7YlxmL5q1BFu
F70AK9FLZcYqa5nP1F1HbIQB/zsQ8admpKIy5tjaZiLgctXv2GTzzXDEwEnaJMrq
SPr1dGDhdGs5iYRMOMT5Pp9dIG2+ZSSMHFAn4IRoB/cPJbNEUkgwQOPmDYETqSg5
tSjfIUw1AoGBAMjE6PlT1/orlHW4QvKmV73YtKPVfi1Coo/F8G45qFaHDkc6bI9W
ySrnvqWcPs++xOZMoGtLuESw/LEluFo8vMX8aQYrVSz4Pb7AvuYbBRE0EVVui7YB
3B1O0c7QTabmfQYeATYD7qSShLccSpUE+FQa6NdrJOxddJLM0Z9K73q7AoGBAMbg
I2+NYB6XME7tyStOS4pkA4y7brG/M8BCaY34nfOJT4Qh6pqZRnDJ4ReopGoXEqWg
hwFRsBNhsji4GGejRBPnYcfJTSuMXSPromgoH1tR0OQbMJB0pCTavbI9j+endlv4
/P+KV3ZMYOLhL/gaaTG+o6Wh2ehnE/8/rmqGpoIHAoGAHXVG+c5jkkFytxMiP5hI
p4J0ftWEff+Y+p+Ad6veF1QZtDnOU/nX6oO2ZXZXgQPswB3eK+AgWXPen994/USM
LkCq6EzTYpXJ+YMuf3TXeX66TF68ASiks2gtQLsvqZ2IGq2sX9CT43HcJ0Hvb44b
IbwRDgqakFPmFuQWndjQ6qECgYA9bOlFATOY/zWKi2NBHvOyEOYPx6yO9fF0Bo83
rHyMxfJra1Zc3c6l85S0jAAMTIgT5BsOyz5JHjm/zwyqpgDW7PaEkKZnNvllqNgG
t63HtOOCMOu1EnHIeE9zCBS0hkLGcYcjHoWZIkoiiU8ZoH6xQKKm+/CkGYJRqkei
22f+bQKBgGHq2/ZzgxfblD3blKWp8mh5Kw7c/2VwJRvLEMlgzrnRgF7QNhEcH3Jm
aD/pqzAkqHnLVVQ5ogMKrrLl11jQp4kX74+Ps2Yul7UgzXFYy020mQSpJF/FMjrl
PEqwfCiOT2nLyE30x9VClUOGXy1CxH52Yn/g81ENq3jKTptwh+fI
-----END RSA PRIVATE KEY-----
================================================
FILE: config/configgrpc/testdata/server.crt
================================================
-----BEGIN CERTIFICATE-----
MIIDVTCCAj2gAwIBAgIJANt5fkUlfxyhMA0GCSqGSIb3DQEBCwUAMF0xCzAJBgNV
BAYTAkFVMRIwEAYDVQQIDAlBdXN0cmFsaWExDzANBgNVBAcMBlN5ZG5leTESMBAG
A1UECgwJTXlPcmdOYW1lMRUwEwYDVQQDDAxNeUNvbW1vbk5hbWUwHhcNMjIwODAz
MDQxOTIxWhcNMzIwNzMxMDQxOTIxWjBdMQswCQYDVQQGEwJBVTESMBAGA1UECAwJ
QXVzdHJhbGlhMQ8wDQYDVQQHDAZTeWRuZXkxEjAQBgNVBAoMCU15T3JnTmFtZTEV
MBMGA1UEAwwMTXlDb21tb25OYW1lMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAv1Pm2elKl/IpJlX5NqQjRTlA1rHws8F7v1IuaXB2qfk1MsDCt37OvlbR
4ARrY6zdUIrEQ/wrhQsZ2M5/yaj0rfeCgd/SDUKMAqDvXQXBY2AaLubTAIEMs4rF
R5Zq/pcBNz1zu8kRvRgvVuOpTCPR1kRvKFWAp689lXZhUU/BrQQXhdA993xVMRM9
u4fZuJLxNGGR/EhqTec4Z65jAZiUfO7ID94PtaxTrzR/Kjr2CiceR5hwdY40Bcme
D3IAd0J6nN1zIihe+Nqg/ImOG7YS+efQIEWJ8eHOoCK5knFBXRy6WwxeCyAPXyIb
DTrqTy67eTDYc0XZ24F/5Q3GSvfMDwIDAQABoxgwFjAUBgNVHREEDTALgglsb2Nh
bGhvc3QwDQYJKoZIhvcNAQELBQADggEBAKeFHP5rQasRS/XGbPkobfbFyTdGnLay
0Vr6+Rs5+4siKlAIhuUP9A/De61CEkFj8NFi2bmXYv8q3qP/z0lrjw7btrvD7Qc7
lth73k3U2sUVZoqbYQZz0GHCWfZm8yXjP63SKI+81LHbS40ArO0R44BLc9TbbRiR
/LwO/x2+cxs28KdsEkU6jQ6Ly5jyoxw1ysoIeRfIk+FnQD4w29TyGgtX/G15/NN0
ytByIZ8wdbUciunQc3nPXoPc41N+hyi2GZaXMuJ4VlsNmgY+wPmp4y3pl4l0bgCb
1FR8Vvtsi8jLH8J15oAMWdmHQKcoJDE49llx+bQGpNekp6mlfX1DIPI=
-----END CERTIFICATE-----
================================================
FILE: config/configgrpc/testdata/server.key
================================================
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAv1Pm2elKl/IpJlX5NqQjRTlA1rHws8F7v1IuaXB2qfk1MsDC
t37OvlbR4ARrY6zdUIrEQ/wrhQsZ2M5/yaj0rfeCgd/SDUKMAqDvXQXBY2AaLubT
AIEMs4rFR5Zq/pcBNz1zu8kRvRgvVuOpTCPR1kRvKFWAp689lXZhUU/BrQQXhdA9
93xVMRM9u4fZuJLxNGGR/EhqTec4Z65jAZiUfO7ID94PtaxTrzR/Kjr2CiceR5hw
dY40BcmeD3IAd0J6nN1zIihe+Nqg/ImOG7YS+efQIEWJ8eHOoCK5knFBXRy6Wwxe
CyAPXyIbDTrqTy67eTDYc0XZ24F/5Q3GSvfMDwIDAQABAoIBAQC/BuxlAhKiJvyC
9DABKFy2zvU35y3mq/X8Dfec+tbf2pwM8nz3bLrLPDAMNR1rxbqqogJXxr1E9tJ1
r6fTFshFsewx8+DrsFfOgBS9kfOGXvuFfJ2L0U13LcTPNxXY37gtCUQ2aAk3/Z+2
Z1QvW0w1XNqHMOdlhQg95JZB8xnyvXs1niLT/I9d7KbPBmOWkB5Jp7+JaebmWqNS
alxnNqYnhXcrNSAbuR4bgz0l4I+Jprms26C6sakmgCfeMjfWbd2k3tp06vKXmT6q
qKa0855axP9wuSbKbscTDW5RFYTYnu/CSYJ4nZtzSS8a559iG3m61EgPOoVTnTX6
0t0I+kwRAoGBAN403NO4FfHG8k2bFpbATQkmC9UwjMbl5RIEL0fFhNVsuM5jTwHc
0wlFm9tMN8xqg66OFCimC/mUNPWX8nrb/MwrAw6/50rbyqOBFnmFKIfVf4ftpLzt
BLhEg7a/FPgdDgldQD+C6XbMyBA1AF3nbpTnbnj5WVQTl672s9teegSzAoGBANxs
1y6Nfh2DyyU16p61376AAP3WfHvuBgJAC0xGCqoTrbyzl4/r06BTMl51PbWJLDjm
FryTtgM7a8XO1jwfWJno71dnT7Bsy+wYnmJ5+9XHwgO9oZfSFUk+ELrEImI/4NZX
dJLkc0SuCG/wa3Wa76+sFNlzAzBBs83RE2j432E1AoGAF+x5GhJnynAxBkn8VJ6/
rIx8GafwgDmgQCBTNtb9Rj0+aHoot3qe/hCQhzvdhhSxuMlzQi0efPCIAyko4jFt
Nk4rNhtTO6wOVSxAzzSW+Ij0Ah6D7hNWvsAhrjtEdrIqILf5gt0FZdUGdTg/odyY
+08vhbbS90pkumG1W5kAaiECgYEAqjk3eBD26u4jjIn1tTk5H9GUcnMYUVCAvW4e
C3ovtCZcTlTW3+M73B1D0aRy0mWrjAlMV7cuoZJa6TiRQ37lmn5Dj1kONm3ekWZ1
shEIBZEtaFwila88lwJiQwlCkGNKS9zf/qyDw+8uPtwI8JqFLUIUG9VxCexDYddr
SO6g+10CgYEAwUs2BRJb6Od+8XtH32+8DDOnpfWJARY0CwogN2k+D1dbAB8Wkda1
BMADasAcjDFRX6xvyyDlqxcDIoCI1JvpS82I/PTNHeT8pEr5Caln7OHD/BtnPwmI
YR0bvKkoN0jdQdjifpMVXEbJS1VfFLdQYQ8iQMwZfkFmzIkYpvqWVtw=
-----END RSA PRIVATE KEY-----
================================================
FILE: config/configgrpc/wrappedstream.go
================================================
// Copyright The OpenTelemetry Authors
// Copyright 2016 Michal Witkowski. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package configgrpc // import "go.opentelemetry.io/collector/config/configgrpc"
import (
"context"
"google.golang.org/grpc"
)
// this functionality was originally copied from grpc-ecosystem/go-grpc-middleware project
// wrappedServerStream is a thin wrapper around grpc.ServerStream that allows modifying context.
type wrappedServerStream struct {
grpc.ServerStream
// wrappedContext is the wrapper's own Context. You can assign it.
wrappedCtx context.Context
}
// Context returns the wrapper's wrappedContext, overwriting the nested grpc.ServerStream.Context()
func (w *wrappedServerStream) Context() context.Context {
return w.wrappedCtx
}
// wrapServerStream returns a ServerStream with the new context.
func wrapServerStream(wrappedCtx context.Context, stream grpc.ServerStream) *wrappedServerStream {
if existing, ok := stream.(*wrappedServerStream); ok {
existing.wrappedCtx = wrappedCtx
return existing
}
return &wrappedServerStream{ServerStream: stream, wrappedCtx: wrappedCtx}
}
================================================
FILE: config/configgrpc/wrappedstream_test.go
================================================
// Copyright The OpenTelemetry Authors
// Copyright 2016 Michal Witkowski. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package configgrpc // import "go.opentelemetry.io/collector/internal/middleware"
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"google.golang.org/grpc"
)
type ctxKey struct{}
var (
oneCtxKey = ctxKey{}
otherCtxKey = ctxKey{}
)
func TestWrapServerStream(t *testing.T) {
ctx := context.WithValue(t.Context(), oneCtxKey, 1)
fake := &fakeServerStream{ctx: ctx}
assert.NotNil(t, fake.Context().Value(oneCtxKey), "values from fake must propagate to wrapper")
wrapped := wrapServerStream(context.WithValue(fake.Context(), otherCtxKey, 2), fake)
assert.NotNil(t, wrapped.Context().Value(oneCtxKey), "values from wrapper must be set")
assert.NotNil(t, wrapped.Context().Value(otherCtxKey), "values from wrapper must be set")
}
func TestDoubleWrapping(t *testing.T) {
fake := &fakeServerStream{ctx: context.Background()}
wrapped := wrapServerStream(fake.Context(), fake)
assert.Same(t, wrapped, wrapServerStream(wrapped.Context(), wrapped)) // should be noop
assert.Equal(t, fake, wrapped.ServerStream)
}
type fakeServerStream struct {
grpc.ServerStream
ctx context.Context
}
func (f *fakeServerStream) Context() context.Context {
return f.ctx
}
================================================
FILE: config/confighttp/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: config/confighttp/README.md
================================================
# 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/main/exporter/README.md)
leverage client configuration.
Note that client configuration supports TLS configuration, the
configuration parameters are also defined under `tls` like server
configuration. For more information, see [configtls
README](../configtls/README.md).
- `endpoint`: address:port
- `proxy_url`: Proxy URL to use for HTTP requests
- [`tls`](../configtls/README.md)
- [`headers`](https://pkg.go.dev/net/http#Request): name/value pairs added to the HTTP request headers
- certain headers such as Content-Length and Connection are automatically written when needed and values in Header may be ignored.
- `Host` header is automatically derived from `endpoint` value. However, this automatic assignment can be overridden by explicitly setting the Host field in the headers field.
- if `Host` header is provided then it overrides `Host` field in [Request](https://pkg.go.dev/net/http#Request) which results as an override of `Host` header value.
- [`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)
- `compression`: Compression type to use among `gzip`, `zstd`, `snappy`, `zlib`, `deflate`, and `lz4`.
- look at the documentation for the server-side of the communication.
- `none` will be treated as uncompressed, and any other inputs will cause an error.
- `compression_params` : Configure advanced compression options
- `level`: Configure compression level for `compression` type
- The following are valid combinations of `compression` and `level`
- `gzip`
- BestSpeed: `1`
- BestCompression: `9`
- DefaultCompression: `-1`
- `zlib`
- BestSpeed: `1`
- BestCompression: `9`
- DefaultCompression: `-1`
- `deflate`
- BestSpeed: `1`
- BestCompression: `9`
- DefaultCompression: `-1`
- `zstd`
- SpeedFastest: `1`
- SpeedDefault: `3`
- SpeedBetterCompression: `6`
- SpeedBestCompression: `11`
- `snappy`
No compression levels supported yet
- `x-snappy-framed` (When feature gate `confighttp.framedSnappy` is enabled)
No compression levels supported yet
- [`max_idle_conns`](https://golang.org/pkg/net/http/#Transport)
- [`max_idle_conns_per_host`](https://golang.org/pkg/net/http/#Transport)
- [`max_conns_per_host`](https://golang.org/pkg/net/http/#Transport)
- [`idle_conn_timeout`](https://golang.org/pkg/net/http/#Transport)
- [`auth`](../configauth/README.md)
- [`disable_keep_alives`](https://golang.org/pkg/net/http/#Transport)
- [`force_attempt_http2`](https://golang.org/pkg/net/http/#Transport)
- [`http2_read_idle_timeout`](https://pkg.go.dev/golang.org/x/net/http2#Transport)
- [`http2_ping_timeout`](https://pkg.go.dev/golang.org/x/net/http2#Transport)
- [`cookies`](https://pkg.go.dev/net/http#CookieJar)
- [`enabled`] if enabled, the client will store cookies from server responses and reuse them in subsequent requests.
- [`middlewares`](../configmiddleware/README.md)
Example:
```yaml
exporter:
otlp_http:
endpoint: otelcol2:55690
auth:
authenticator: some-authenticator-extension
tls:
ca_file: ca.pem
cert_file: cert.pem
key_file: key.pem
headers:
test1: "value1"
"test 2": "value 2"
compression: gzip
compression_params:
level: 1
cookies:
enabled: true
```
## Server Configuration
[Receivers](https://github.com/open-telemetry/opentelemetry-collector/blob/main/receiver/README.md)
leverage server configuration.
- [`cors`](https://github.com/rs/cors#parameters): Configure [CORS][cors],
allowing the receiver to accept traces from web browsers, even if the receiver
is hosted at a different [origin][origin]. If left blank or set to `null`, CORS
will not be enabled.
- `allowed_origins`: A list of [origins][origin] allowed to send requests to
the receiver. An origin may contain a wildcard (`*`) to replace 0 or more
characters (e.g., `https://*.example.com`). **Do not use** a plain wildcard
`["*"]`, as our CORS response includes `Access-Control-Allow-Credentials: true`, which makes browsers to **disallow a plain wildcard** (this is a security standard). To allow any origin, you can specify at least the protocol, for example `["https://*", "http://*"]`. If no origins are listed, CORS will not be enabled.
- `allowed_headers`: Allow CORS requests to include headers outside the
[default safelist][cors-headers]. By default, safelist headers and
`X-Requested-With` will be allowed. To allow any request header, set to
`["*"]`.
- `max_age`: Sets the value of the [`Access-Control-Max-Age`][cors-cache]
header, allowing clients to cache the response to CORS preflight requests. If
not set, browsers use a default of 5 seconds.
- `endpoint`: Valid value syntax available [here](https://github.com/grpc/grpc/blob/master/doc/naming.md)
- `transport`: The transport protocol to use. Defaults to `tcp`. See the [confignet README](../confignet/README.md) for more information.
- `max_request_body_size`: configures the maximum allowed body size in bytes for a single request. Default: `20971520` (20MiB)
- `include_metadata`: propagates the client metadata from the incoming requests to the downstream consumers. Default: `false`
- `response_headers`: Additional headers attached to each HTTP response sent to the client. Header values are opaque since they may be sensitive
- `compression_algorithms`: configures the list of compression algorithms the server can accept. Default: ["", "gzip", "zstd", "zlib", "snappy", "deflate", "lz4"]
- `x-snappy-framed` can be used if feature gate `confighttp.snappyFramed` is enabled.
- `read_timeout`: maximum duration for reading the entire request, including the body. A zero or negative value means there will be no timeout. Default: `0` (no timeout)
- `read_header_timeout`: amount of time allowed to read request headers. If zero, the value of `read_timeout` is used. If both are zero, there is no timeout. Default: `1m`
- `write_timeout`: maximum duration before timing out writes of the response. A zero or negative value means there will be no timeout. Default: `30s`
- `idle_timeout`: maximum amount of time to wait for the next request when keep-alives are enabled. If zero, the value of `read_timeout` is used. If both are zero, there is no timeout. Default: `1m`
- `keep_alives_enabled`: controls whether HTTP keep-alives are enabled. Default: `true`
- [`tls`](../configtls/README.md)
- [`auth`](../configauth/README.md)
- `request_params`: a list of query parameter names to add to the auth context, along with the HTTP headers
- [`middlewares`](../configmiddleware/README.md)
You can enable [`attribute processor`][attribute-processor] to append any http header to span's attribute using custom key. You also need to enable the "include_metadata"
Example:
```yaml
receivers:
otlp:
protocols:
http:
include_metadata: true
auth:
request_params:
- token
authenticator: some-authenticator-extension
cors:
allowed_origins:
- https://foo.bar.com
- https://*.test.com
allowed_headers:
- Example-Header
max_age: 7200
endpoint: 0.0.0.0:55690
compression_algorithms: ["", "gzip"]
processors:
attributes:
actions:
- key: http.client_ip
from_context: metadata.x-forwarded-for
action: upsert
```
[cors]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
[cors-headers]: https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_request_header
[cors-cache]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age
[origin]: https://developer.mozilla.org/en-US/docs/Glossary/Origin
[attribute-processor]: https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/processor/attributesprocessor/README.md
================================================
FILE: config/confighttp/client.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package confighttp // import "go.opentelemetry.io/collector/config/confighttp"
import (
"context"
"errors"
"fmt"
"net/http"
"net/http/cookiejar"
"net/url"
"time"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel"
"golang.org/x/net/http2"
"golang.org/x/net/publicsuffix"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configauth"
"go.opentelemetry.io/collector/config/configcompression"
"go.opentelemetry.io/collector/config/configmiddleware"
"go.opentelemetry.io/collector/config/configopaque"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/config/configtls"
)
const (
headerContentEncoding = "Content-Encoding"
)
// ClientConfig defines settings for creating an HTTP client.
type ClientConfig struct {
// The target URL to send data to (e.g.: http://some.url:9411/v1/traces).
Endpoint string `mapstructure:"endpoint,omitempty"`
// ProxyURL setting for the collector
ProxyURL string `mapstructure:"proxy_url,omitempty"`
// TLS struct exposes TLS client configuration.
TLS configtls.ClientConfig `mapstructure:"tls,omitempty"`
// ReadBufferSize for HTTP client. See http.Transport.ReadBufferSize.
// Default is 0.
ReadBufferSize int `mapstructure:"read_buffer_size,omitempty"`
// WriteBufferSize for HTTP client. See http.Transport.WriteBufferSize.
// Default is 0.
WriteBufferSize int `mapstructure:"write_buffer_size,omitempty"`
// Timeout parameter configures `http.Client.Timeout`.
// Default is 0 (unlimited).
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.
// Header values are opaque since they may be sensitive.
Headers configopaque.MapList `mapstructure:"headers,omitempty"`
// Auth configuration for outgoing HTTP calls.
Auth configoptional.Optional[configauth.Config] `mapstructure:"auth,omitempty"`
// The compression key for supported compression types within collector.
Compression configcompression.Type `mapstructure:"compression,omitempty"`
// Advanced configuration options for the Compression
CompressionParams configcompression.CompressionParams `mapstructure:"compression_params,omitempty"`
// MaxIdleConns is used to set a limit to the maximum idle HTTP connections the client can keep open.
// By default, it is set to 100. Zero means no limit.
MaxIdleConns int `mapstructure:"max_idle_conns"`
// MaxIdleConnsPerHost is used to set a limit to the maximum idle HTTP connections the host can keep open.
// If zero, [net/http.DefaultMaxIdleConnsPerHost] is used.
MaxIdleConnsPerHost int `mapstructure:"max_idle_conns_per_host,omitempty"`
// MaxConnsPerHost limits the total number of connections per host, including connections in the dialing,
// active, and idle states. Default is 0 (unlimited).
MaxConnsPerHost int `mapstructure:"max_conns_per_host,omitempty"`
// IdleConnTimeout is the maximum amount of time a connection will remain open before closing itself.
// By default, it is set to 90 seconds.
IdleConnTimeout time.Duration `mapstructure:"idle_conn_timeout"`
// DisableKeepAlives, if true, disables HTTP keep-alives and will only use the connection to the server
// for a single HTTP request.
//
// WARNING: enabling this option can result in significant overhead establishing a new HTTP(S)
// connection for every request. Before enabling this option please consider whether changes
// to idle connection settings can achieve your goal.
DisableKeepAlives bool `mapstructure:"disable_keep_alives,omitempty"`
// This is needed in case you run into
// https://github.com/golang/go/issues/59690
// https://github.com/golang/go/issues/36026
// HTTP2ReadIdleTimeout if the connection has been idle for the configured value send a ping frame for health check
// 0s means no health check will be performed.
HTTP2ReadIdleTimeout time.Duration `mapstructure:"http2_read_idle_timeout,omitempty"`
// HTTP2PingTimeout if there's no response to the ping within the configured value, the connection will be closed.
// If not set or set to 0, it defaults to 15s.
HTTP2PingTimeout time.Duration `mapstructure:"http2_ping_timeout,omitempty"`
// Cookies configures the cookie management of the HTTP client.
Cookies configoptional.Optional[CookiesConfig] `mapstructure:"cookies,omitempty"`
// Enabling ForceAttemptHTTP2 forces the HTTP transport to use the HTTP/2 protocol.
// By default, this is set to true.
// NOTE: HTTP/2 does not support settings such as MaxConnsPerHost, MaxIdleConnsPerHost and MaxIdleConns.
ForceAttemptHTTP2 bool `mapstructure:"force_attempt_http2,omitempty"`
// Middlewares are used to add custom functionality to the HTTP client.
// Middleware handlers are called in the order they appear in this list,
// with the first middleware becoming the outermost handler.
Middlewares []configmiddleware.Config `mapstructure:"middlewares,omitempty"`
}
// CookiesConfig defines the configuration of the HTTP client regarding cookies served by the server.
type CookiesConfig struct {
_ struct{}
}
// NewDefaultClientConfig returns ClientConfig type object with
// the default values of 'MaxIdleConns' and 'IdleConnTimeout', as well as [http.DefaultTransport] values.
// Other config options are not added as they are initialized with 'zero value' by GoLang as default.
// We encourage to use this function to create an object of ClientConfig.
func NewDefaultClientConfig() ClientConfig {
// The default values are taken from the values of 'DefaultTransport' of 'http' package.
defaultTransport := http.DefaultTransport.(*http.Transport)
return ClientConfig{
MaxIdleConns: defaultTransport.MaxIdleConns,
IdleConnTimeout: defaultTransport.IdleConnTimeout,
ForceAttemptHTTP2: true,
}
}
func (cc *ClientConfig) Validate() error {
if cc.Compression.IsCompressed() {
if err := cc.Compression.ValidateParams(cc.CompressionParams); err != nil {
return err
}
}
return nil
}
// ToClientOption is an option to change the behavior of the HTTP client
// returned by ClientConfig.ToClient().
// There are currently no available options.
type ToClientOption interface {
sealed()
}
// ToClient creates an HTTP client.
//
// To allow the configuration to reference middleware or authentication extensions,
// the `extensions` argument should be the output of `host.GetExtensions()`.
// It may also be `nil` in tests where no such extension is expected to be used.
func (cc *ClientConfig) ToClient(ctx context.Context, extensions map[component.ID]component.Component, settings component.TelemetrySettings, _ ...ToClientOption) (*http.Client, error) {
tlsCfg, err := cc.TLS.LoadTLSConfig(ctx)
if err != nil {
return nil, err
}
transport := http.DefaultTransport.(*http.Transport).Clone()
if tlsCfg != nil {
transport.TLSClientConfig = tlsCfg
}
if cc.ReadBufferSize > 0 {
transport.ReadBufferSize = cc.ReadBufferSize
}
if cc.WriteBufferSize > 0 {
transport.WriteBufferSize = cc.WriteBufferSize
}
transport.MaxIdleConns = cc.MaxIdleConns
transport.MaxIdleConnsPerHost = cc.MaxIdleConnsPerHost
transport.MaxConnsPerHost = cc.MaxConnsPerHost
transport.IdleConnTimeout = cc.IdleConnTimeout
transport.ForceAttemptHTTP2 = cc.ForceAttemptHTTP2
// Setting the Proxy URL
if cc.ProxyURL != "" {
proxyURL, parseErr := url.ParseRequestURI(cc.ProxyURL)
if parseErr != nil {
return nil, parseErr
}
transport.Proxy = http.ProxyURL(proxyURL)
}
transport.DisableKeepAlives = cc.DisableKeepAlives
if cc.HTTP2ReadIdleTimeout > 0 {
transport2, transportErr := http2.ConfigureTransports(transport)
if transportErr != nil {
return nil, fmt.Errorf("failed to configure http2 transport: %w", transportErr)
}
transport2.ReadIdleTimeout = cc.HTTP2ReadIdleTimeout
transport2.PingTimeout = cc.HTTP2PingTimeout
}
clientTransport := http.RoundTripper(transport)
// Apply middlewares in reverse order so they execute in
// forward order. The first middleware runs after authentication.
if len(cc.Middlewares) > 0 && extensions == nil {
return nil, errors.New("middlewares were configured but this component or its host does not support extensions")
}
for i := len(cc.Middlewares) - 1; i >= 0; i-- {
getClient, rerr := cc.Middlewares[i].GetHTTPClientRoundTripper(ctx, extensions)
// If we failed to get the middleware
if rerr != nil {
return nil, rerr
}
clientTransport, rerr = getClient(ctx, clientTransport)
// If we failed to construct a wrapper
if rerr != nil {
return nil, rerr
}
}
// The Auth RoundTripper should always be the innermost to ensure that
// request signing-based auth mechanisms operate after compression
// and header middleware modifies the request
if cc.Auth.HasValue() {
if extensions == nil {
return nil, errors.New("authentication was configured but this component or its host does not support extensions")
}
auth := cc.Auth.Get()
httpCustomAuthRoundTripper, aerr := auth.GetHTTPClientAuthenticator(ctx, extensions)
if aerr != nil {
return nil, aerr
}
clientTransport, err = httpCustomAuthRoundTripper.RoundTripper(clientTransport)
if err != nil {
return nil, err
}
}
if len(cc.Headers) > 0 {
clientTransport = &headerRoundTripper{
transport: clientTransport,
headers: cc.Headers,
}
}
// Compress the body using specified compression methods if non-empty string is provided.
// Supporting gzip, zlib, deflate, snappy, and zstd; none is treated as uncompressed.
if cc.Compression.IsCompressed() {
// If the compression level is not set, use the default level.
if cc.CompressionParams.Level == 0 {
cc.CompressionParams.Level = configcompression.DefaultCompressionLevel
}
clientTransport, err = newCompressRoundTripper(clientTransport, cc.Compression, cc.CompressionParams)
if err != nil {
return nil, err
}
}
otelOpts := []otelhttp.Option{
otelhttp.WithTracerProvider(settings.TracerProvider),
otelhttp.WithPropagators(otel.GetTextMapPropagator()),
otelhttp.WithMeterProvider(settings.MeterProvider),
}
// wrapping http transport with otelhttp transport to enable otel instrumentation
if settings.TracerProvider != nil && settings.MeterProvider != nil {
clientTransport = otelhttp.NewTransport(clientTransport, otelOpts...)
}
var jar http.CookieJar
if cc.Cookies.HasValue() {
jar, err = cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
if err != nil {
return nil, err
}
}
return &http.Client{
Transport: clientTransport,
Timeout: cc.Timeout,
Jar: jar,
}, nil
}
// Custom RoundTripper that adds headers.
type headerRoundTripper struct {
transport http.RoundTripper
headers configopaque.MapList
}
// RoundTrip is a custom RoundTripper that adds headers to the request.
func (interceptor *headerRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
// Set Host header if provided
hostHeader, found := interceptor.headers.Get("Host")
if found && hostHeader != "" {
// `Host` field should be set to override default `Host` header value which is Endpoint
req.Host = string(hostHeader)
}
for k, v := range interceptor.headers.Iter {
req.Header.Set(k, string(v))
}
// Send the request to next transport.
return interceptor.transport.RoundTrip(req)
}
================================================
FILE: config/confighttp/client_middleware_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package confighttp
import (
"context"
"errors"
"io"
"net/http"
"net/http/httptest"
"strings"
"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/configmiddleware"
"go.opentelemetry.io/collector/extension"
"go.opentelemetry.io/collector/extension/extensionmiddleware"
"go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest"
)
// testClientMiddleware is a test middleware that appends a string to the response body
type testClientMiddleware struct {
extension.Extension
extensionmiddleware.GetHTTPRoundTripperFunc
}
func newTestClientMiddleware(name string) component.Component {
return &testClientMiddleware{
Extension: extensionmiddlewaretest.NewNop(),
GetHTTPRoundTripperFunc: func(_ context.Context) (extensionmiddleware.WrapHTTPRoundTripperFunc, error) {
return func(_ context.Context, transport http.RoundTripper) (http.RoundTripper, error) {
return extensionmiddlewaretest.RoundTripperFunc(
func(req *http.Request) (*http.Response, error) {
resp, err := transport.RoundTrip(req)
if err != nil {
return resp, err
}
// Read the original body
body, err := io.ReadAll(resp.Body)
if err != nil {
return resp, err
}
_ = resp.Body.Close()
// Create a new body with the appended text
newBody := string(body) + "\r\noutput by " + name
// Replace the response body
resp.Body = io.NopCloser(strings.NewReader(newBody))
resp.ContentLength = int64(len(newBody))
return resp, nil
}), nil
}, nil
},
}
}
func newTestClientConfig(name string) configmiddleware.Config {
return configmiddleware.Config{
ID: component.MustNewID(name),
}
}
func TestClientMiddlewares(t *testing.T) {
// Create a test server that returns "OK"
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("OK"))
}))
defer server.Close()
// Register two test extensions
extensions := map[component.ID]component.Component{
component.MustNewID("test1"): newTestClientMiddleware("test1"),
component.MustNewID("test2"): newTestClientMiddleware("test2"),
}
// Test with different middleware configurations
testCases := []struct {
name string
middlewares []configmiddleware.Config
expectedOutput string
}{
{
name: "no_middlewares",
middlewares: nil,
expectedOutput: "OK",
},
{
name: "single_middleware",
middlewares: []configmiddleware.Config{
newTestClientConfig("test1"),
},
expectedOutput: "OK\r\noutput by test1",
},
{
name: "multiple_middlewares",
middlewares: []configmiddleware.Config{
newTestClientConfig("test1"),
newTestClientConfig("test2"),
},
expectedOutput: "OK\r\noutput by test2\r\noutput by test1",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Create HTTP client config with the test middlewares
clientConfig := ClientConfig{
Endpoint: server.URL,
Middlewares: tc.middlewares,
}
// Create the client
client, err := clientConfig.ToClient(context.Background(), extensions, componenttest.NewNopTelemetrySettings())
require.NoError(t, err)
// Create a request to the test server
req, err := http.NewRequest(http.MethodGet, server.URL, http.NoBody)
require.NoError(t, err)
// Send the request
resp, err := client.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
// Check the response
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
assert.Equal(t, tc.expectedOutput, string(body))
})
}
}
func TestClientMiddlewareErrors(t *testing.T) {
// Create a test server that returns "OK"
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("OK"))
}))
defer server.Close()
// Test cases for HTTP client middleware errors
httpTests := []struct {
name string
extensions map[component.ID]component.Component
config ClientConfig
errText string
}{
{
name: "extension_not_found",
extensions: map[component.ID]component.Component{},
config: ClientConfig{
Endpoint: server.URL,
Middlewares: []configmiddleware.Config{
{
ID: component.MustNewID("nonexistent"),
},
},
},
errText: "failed to resolve middleware \"nonexistent\": middleware not found",
},
{
name: "get_round_tripper_fails",
extensions: map[component.ID]component.Component{
component.MustNewID("errormw"): extensionmiddlewaretest.NewErr(errors.New("http middleware error")),
},
config: ClientConfig{
Endpoint: server.URL,
Middlewares: []configmiddleware.Config{
{
ID: component.MustNewID("errormw"),
},
},
},
errText: "http middleware error",
},
}
for _, tc := range httpTests {
t.Run(tc.name, func(t *testing.T) {
// Trying to create the client should fail
_, err := tc.config.ToClient(context.Background(), tc.extensions, componenttest.NewNopTelemetrySettings())
require.Error(t, err)
assert.Contains(t, err.Error(), tc.errText)
})
}
}
// Test failures for gRPC client middlewares by creating a mock implementation
// that can fail in similar ways to HTTP clients
func TestGRPCClientMiddlewareErrors(t *testing.T) {
// Test cases for gRPC client middleware errors
grpcTests := []struct {
name string
extensions map[component.ID]component.Component
config ClientConfig
errText string
}{
{
name: "grpc_extension_not_found",
extensions: map[component.ID]component.Component{},
config: ClientConfig{
Endpoint: "localhost:1234",
Middlewares: []configmiddleware.Config{
{
ID: component.MustNewID("nonexistent"),
},
},
},
errText: "failed to resolve middleware \"nonexistent\": middleware not found",
},
{
name: "grpc_get_client_options_fails",
extensions: map[component.ID]component.Component{
component.MustNewID("errormw"): extensionmiddlewaretest.NewErr(errors.New("grpc middleware error")),
},
config: ClientConfig{
Endpoint: "localhost:1234",
Middlewares: []configmiddleware.Config{
{
ID: component.MustNewID("errormw"),
},
},
},
errText: "grpc middleware error",
},
}
for _, tc := range grpcTests {
t.Run(tc.name, func(t *testing.T) {
// For gRPC, we need to use the configgrpc.ClientConfig structure
// We'll test the middleware failure path here using the HTTP client approach,
// as the middleware resolution logic is the same
_, err := tc.config.ToClient(context.Background(), tc.extensions, componenttest.NewNopTelemetrySettings())
require.Error(t, err)
assert.Contains(t, err.Error(), tc.errText)
})
}
}
================================================
FILE: config/confighttp/client_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package confighttp
import (
"context"
"errors"
"net"
"net/http"
"net/http/httptest"
"net/url"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"go.opentelemetry.io/collector/client"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/config/configauth"
"go.opentelemetry.io/collector/config/configcompression"
"go.opentelemetry.io/collector/config/configmiddleware"
"go.opentelemetry.io/collector/config/configopaque"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/config/configtls"
"go.opentelemetry.io/collector/confmap/confmaptest"
"go.opentelemetry.io/collector/confmap/xconfmap"
"go.opentelemetry.io/collector/extension"
"go.opentelemetry.io/collector/extension/extensionauth"
"go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest"
)
var (
testAuthID = component.MustNewID("testauth")
mockID = component.MustNewID("mock")
dummyID = component.MustNewID("dummy")
nonExistingID = component.MustNewID("nonexisting")
// Omit TracerProvider and MeterProvider in TelemetrySettings as otelhttp.Transport cannot be introspected
nilProvidersSettings = component.TelemetrySettings{Logger: zap.NewNop()}
)
func TestAllHTTPClientSettings(t *testing.T) {
extensions := map[component.ID]component.Component{
testAuthID: extensionauthtest.NewNopClient(),
}
maxIdleConns := 50
maxIdleConnsPerHost := 40
maxConnsPerHost := 45
idleConnTimeout := 30 * time.Second
http2PingTimeout := 5 * time.Second
tests := []struct {
name string
settings ClientConfig
shouldError bool
}{
{
name: "all_valid_settings",
settings: ClientConfig{
Endpoint: "localhost:1234",
TLS: configtls.ClientConfig{
Insecure: false,
},
ReadBufferSize: 1024,
WriteBufferSize: 512,
MaxIdleConns: maxIdleConns,
MaxIdleConnsPerHost: maxIdleConnsPerHost,
MaxConnsPerHost: maxConnsPerHost,
IdleConnTimeout: idleConnTimeout,
Compression: "",
DisableKeepAlives: true,
Cookies: configoptional.Some(CookiesConfig{}),
HTTP2ReadIdleTimeout: idleConnTimeout,
HTTP2PingTimeout: http2PingTimeout,
},
shouldError: false,
},
{
name: "all_valid_settings_http2_enabled",
settings: ClientConfig{
Endpoint: "localhost:1234",
TLS: configtls.ClientConfig{
Insecure: false,
},
ReadBufferSize: 1024,
WriteBufferSize: 512,
MaxIdleConns: maxIdleConns,
MaxIdleConnsPerHost: maxIdleConnsPerHost,
MaxConnsPerHost: maxConnsPerHost,
ForceAttemptHTTP2: true,
IdleConnTimeout: idleConnTimeout,
Compression: "",
DisableKeepAlives: true,
Cookies: configoptional.Some(CookiesConfig{}),
HTTP2ReadIdleTimeout: idleConnTimeout,
HTTP2PingTimeout: http2PingTimeout,
},
shouldError: false,
},
{
name: "all_valid_settings_with_none_compression",
settings: ClientConfig{
Endpoint: "localhost:1234",
TLS: configtls.ClientConfig{
Insecure: false,
},
ReadBufferSize: 1024,
WriteBufferSize: 512,
MaxIdleConns: maxIdleConns,
MaxIdleConnsPerHost: maxIdleConnsPerHost,
MaxConnsPerHost: maxConnsPerHost,
IdleConnTimeout: idleConnTimeout,
Compression: "none",
DisableKeepAlives: true,
HTTP2ReadIdleTimeout: idleConnTimeout,
HTTP2PingTimeout: http2PingTimeout,
},
shouldError: false,
},
{
name: "all_valid_settings_with_gzip_compression",
settings: ClientConfig{
Endpoint: "localhost:1234",
TLS: configtls.ClientConfig{
Insecure: false,
},
ReadBufferSize: 1024,
WriteBufferSize: 512,
MaxIdleConns: maxIdleConns,
MaxIdleConnsPerHost: maxIdleConnsPerHost,
MaxConnsPerHost: maxConnsPerHost,
IdleConnTimeout: idleConnTimeout,
Compression: "gzip",
DisableKeepAlives: true,
HTTP2ReadIdleTimeout: idleConnTimeout,
HTTP2PingTimeout: http2PingTimeout,
},
shouldError: false,
},
{
name: "all_valid_settings_http2_health_check",
settings: ClientConfig{
Endpoint: "localhost:1234",
TLS: configtls.ClientConfig{
Insecure: false,
},
ReadBufferSize: 1024,
WriteBufferSize: 512,
MaxIdleConns: maxIdleConns,
MaxIdleConnsPerHost: maxIdleConnsPerHost,
MaxConnsPerHost: maxConnsPerHost,
IdleConnTimeout: idleConnTimeout,
Compression: "gzip",
DisableKeepAlives: true,
HTTP2ReadIdleTimeout: idleConnTimeout,
HTTP2PingTimeout: http2PingTimeout,
},
shouldError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tel := componenttest.NewNopTelemetrySettings()
tel.TracerProvider = nil
client, err := tt.settings.ToClient(context.Background(), extensions, tel)
if tt.shouldError {
assert.Error(t, err)
return
}
require.NoError(t, err)
switch transport := client.Transport.(type) {
case *http.Transport:
assert.Equal(t, 1024, transport.ReadBufferSize)
assert.Equal(t, 512, transport.WriteBufferSize)
assert.Equal(t, 50, transport.MaxIdleConns)
assert.Equal(t, 40, transport.MaxIdleConnsPerHost)
assert.Equal(t, 45, transport.MaxConnsPerHost)
assert.Equal(t, 30*time.Second, transport.IdleConnTimeout)
assert.True(t, transport.DisableKeepAlives)
case *compressRoundTripper:
assert.EqualValues(t, "gzip", transport.compressionType)
}
})
}
}
func TestPartialHTTPClientSettings(t *testing.T) {
extensions := map[component.ID]component.Component{
testAuthID: extensionauthtest.NewNopClient(),
}
tests := []struct {
name string
settings ClientConfig
shouldError bool
}{
{
name: "valid_partial_settings",
settings: ClientConfig{
Endpoint: "localhost:1234",
TLS: configtls.ClientConfig{
Insecure: false,
},
ReadBufferSize: 1024,
WriteBufferSize: 512,
},
shouldError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tel := componenttest.NewNopTelemetrySettings()
tel.TracerProvider = nil
client, err := tt.settings.ToClient(context.Background(), extensions, tel)
require.NoError(t, err)
transport := client.Transport.(*http.Transport)
assert.Equal(t, 1024, transport.ReadBufferSize)
assert.Equal(t, 512, transport.WriteBufferSize)
assert.Equal(t, 0, transport.MaxIdleConns)
assert.Equal(t, 0, transport.MaxIdleConnsPerHost)
assert.Equal(t, 0, transport.MaxConnsPerHost)
assert.EqualValues(t, 0, transport.IdleConnTimeout)
assert.False(t, transport.DisableKeepAlives)
})
}
}
func TestDefaultHTTPClientSettings(t *testing.T) {
httpClientSettings := NewDefaultClientConfig()
assert.Equal(t, 100, httpClientSettings.MaxIdleConns)
assert.Equal(t, 90*time.Second, httpClientSettings.IdleConnTimeout)
}
func TestProxyURL(t *testing.T) {
testCases := []struct {
name string
proxyURL string
expectedURL *url.URL
err bool
}{
{
name: "default config",
expectedURL: nil,
},
{
name: "proxy is set",
proxyURL: "http://proxy.example.com:8080",
expectedURL: &url.URL{Scheme: "http", Host: "proxy.example.com:8080"},
},
{
name: "proxy is invalid",
proxyURL: "://example.com",
err: true,
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
s := NewDefaultClientConfig()
s.ProxyURL = tt.proxyURL
tel := componenttest.NewNopTelemetrySettings()
tel.TracerProvider = nil
client, err := s.ToClient(context.Background(), nil, tel)
if tt.err {
require.Error(t, err)
} else {
require.NoError(t, err)
}
if err == nil {
transport := client.Transport.(*http.Transport)
require.NotNil(t, transport.Proxy)
url, err := transport.Proxy(&http.Request{URL: &url.URL{Scheme: "http", Host: "example.com"}})
require.NoError(t, err)
if tt.expectedURL == nil {
assert.Nil(t, url)
} else {
require.NotNil(t, url)
assert.Equal(t, tt.expectedURL, url)
}
}
})
}
}
func TestHTTPClientSettingsError(t *testing.T) {
extensions := map[component.ID]component.Component{}
tests := []struct {
settings ClientConfig
err string
}{
{
err: "^failed to load TLS config: failed to load CA CertPool File: failed to load cert /doesnt/exist:",
settings: ClientConfig{
Endpoint: "",
TLS: configtls.ClientConfig{
Config: configtls.Config{
CAFile: "/doesnt/exist",
},
Insecure: false,
ServerName: "",
},
},
},
{
err: "^failed to load TLS config: failed to load TLS cert and key: for auth via TLS, provide both certificate and key, or neither",
settings: ClientConfig{
Endpoint: "",
TLS: configtls.ClientConfig{
Config: configtls.Config{
CertFile: "/doesnt/exist",
},
Insecure: false,
ServerName: "",
},
},
},
{
err: "failed to resolve authenticator \"dummy\": authenticator not found",
settings: ClientConfig{
Endpoint: "https://localhost:1234/v1/traces",
Auth: configoptional.Some(configauth.Config{AuthenticatorID: dummyID}),
},
},
}
for _, tt := range tests {
t.Run(tt.err, func(t *testing.T) {
_, err := tt.settings.ToClient(context.Background(), extensions, componenttest.NewNopTelemetrySettings())
assert.Regexp(t, tt.err, err)
})
}
}
var _ http.RoundTripper = &customRoundTripper{}
type customRoundTripper struct{}
func (c *customRoundTripper) RoundTrip(*http.Request) (*http.Response, error) {
return nil, nil
}
var (
_ extensionauth.HTTPClient = (*mockClient)(nil)
_ extension.Extension = (*mockClient)(nil)
)
type mockClient struct {
component.StartFunc
component.ShutdownFunc
}
// RoundTripper implements extensionauth.HTTPClient.
func (m *mockClient) RoundTripper(http.RoundTripper) (http.RoundTripper, error) {
return &customRoundTripper{}, nil
}
func TestHTTPClientSettingWithAuthConfig(t *testing.T) {
tests := []struct {
name string
shouldErr bool
settings ClientConfig
extensions map[component.ID]component.Component
}{
{
name: "no_auth_extension_enabled",
settings: ClientConfig{
Endpoint: "localhost:1234",
Auth: configoptional.None[configauth.Config](),
},
shouldErr: false,
extensions: map[component.ID]component.Component{
mockID: extensionauthtest.NewNopClient(),
},
},
{
name: "with_auth_configuration_and_no_extension",
settings: ClientConfig{
Endpoint: "localhost:1234",
Auth: configoptional.Some(configauth.Config{AuthenticatorID: dummyID}),
},
shouldErr: true,
extensions: map[component.ID]component.Component{
mockID: extensionauthtest.NewNopClient(),
},
},
{
name: "with_auth_configuration_and_no_extension_map",
settings: ClientConfig{
Endpoint: "localhost:1234",
Auth: configoptional.Some(configauth.Config{AuthenticatorID: dummyID}),
},
shouldErr: true,
},
{
name: "with_auth_configuration_has_extension",
settings: ClientConfig{
Endpoint: "localhost:1234",
Auth: configoptional.Some(configauth.Config{AuthenticatorID: mockID}),
},
shouldErr: false,
extensions: map[component.ID]component.Component{
mockID: &mockClient{},
},
},
{
name: "with_auth_configuration_has_extension_and_headers",
settings: ClientConfig{
Endpoint: "localhost:1234",
Auth: configoptional.Some(configauth.Config{AuthenticatorID: mockID}),
Headers: configopaque.MapList{
{Name: "foo", Value: "bar"},
},
},
shouldErr: false,
extensions: map[component.ID]component.Component{
mockID: &mockClient{},
},
},
{
name: "with_auth_configuration_has_extension_and_compression",
settings: ClientConfig{
Endpoint: "localhost:1234",
Auth: configoptional.Some(configauth.Config{AuthenticatorID: mockID}),
Compression: configcompression.TypeGzip,
},
shouldErr: false,
extensions: map[component.ID]component.Component{
mockID: &mockClient{},
},
},
{
name: "with_auth_configuration_has_err_extension",
settings: ClientConfig{
Endpoint: "localhost:1234",
Auth: configoptional.Some(configauth.Config{AuthenticatorID: mockID}),
},
shouldErr: true,
extensions: map[component.ID]component.Component{
mockID: extensionauthtest.NewErr(errors.New("error")),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Omit TracerProvider and MeterProvider in TelemetrySettings as otelhttp.Transport cannot be introspected
client, err := tt.settings.ToClient(context.Background(), tt.extensions, nilProvidersSettings)
if tt.shouldErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
assert.NotNil(t, client)
transport := client.Transport
// Compression should wrap Auth, unwrap it
if tt.settings.Compression.IsCompressed() {
ct, ok := transport.(*compressRoundTripper)
assert.True(t, ok)
assert.Equal(t, tt.settings.Compression, ct.compressionType)
transport = ct.rt
}
// Headers should wrap Auth, unwrap it
if tt.settings.Headers != nil {
ht, ok := transport.(*headerRoundTripper)
assert.True(t, ok)
assert.Equal(t, tt.settings.Headers, ht.headers)
transport = ht.transport
}
if tt.settings.Auth.HasValue() {
_, ok := transport.(*customRoundTripper)
assert.True(t, ok)
}
})
}
}
func TestHTTPClientHeaders(t *testing.T) {
tests := []struct {
name string
headers configopaque.MapList
}{
{
name: "with_headers",
headers: configopaque.MapList{
{Name: "header1", Value: "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.Iter {
assert.Equal(t, r.Header.Get(k), string(v))
}
w.WriteHeader(http.StatusOK)
}))
defer server.Close()
serverURL, _ := url.Parse(server.URL)
setting := ClientConfig{
Endpoint: serverURL.String(),
TLS: configtls.ClientConfig{},
ReadBufferSize: 0,
WriteBufferSize: 0,
Timeout: 0,
Headers: tt.headers,
}
client, _ := setting.ToClient(context.Background(), nil, componenttest.NewNopTelemetrySettings())
req, err := http.NewRequest(http.MethodGet, setting.Endpoint, http.NoBody)
require.NoError(t, err)
_, err = client.Do(req)
assert.NoError(t, err)
})
}
}
func TestHTTPClientHostHeader(t *testing.T) {
hostHeader := "th"
tt := struct {
name string
headers configopaque.MapList
}{
name: "with_host_header",
headers: configopaque.MapList{
{Name: "Host", Value: configopaque.String(hostHeader)},
},
}
t.Run(tt.name, func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, hostHeader, r.Host)
w.WriteHeader(http.StatusOK)
}))
defer server.Close()
serverURL, _ := url.Parse(server.URL)
setting := ClientConfig{
Endpoint: serverURL.String(),
TLS: configtls.ClientConfig{},
ReadBufferSize: 0,
WriteBufferSize: 0,
Timeout: 0,
Headers: tt.headers,
}
client, _ := setting.ToClient(context.Background(), nil, componenttest.NewNopTelemetrySettings())
req, err := http.NewRequest(http.MethodGet, setting.Endpoint, http.NoBody)
require.NoError(t, err)
_, err = client.Do(req)
assert.NoError(t, err)
})
}
func TestHTTPTransportOptions(t *testing.T) {
settings := componenttest.NewNopTelemetrySettings()
// Disable OTel instrumentation so the *http.Transport object is directly accessible
settings.MeterProvider = nil
settings.TracerProvider = nil
clientConfig := NewDefaultClientConfig()
clientConfig.MaxIdleConns = 100
clientConfig.IdleConnTimeout = time.Duration(100)
clientConfig.MaxConnsPerHost = 100
clientConfig.MaxIdleConnsPerHost = 100
client, err := clientConfig.ToClient(context.Background(), nil, settings)
require.NoError(t, err)
transport, ok := client.Transport.(*http.Transport)
require.True(t, ok, "client.Transport is not an *http.Transport")
require.Equal(t, 100, transport.MaxIdleConns)
require.Equal(t, time.Duration(100), transport.IdleConnTimeout)
require.Equal(t, 100, transport.MaxConnsPerHost)
require.Equal(t, 100, transport.MaxIdleConnsPerHost)
clientConfig = NewDefaultClientConfig()
clientConfig.MaxIdleConns = 0
clientConfig.IdleConnTimeout = 0
clientConfig.MaxConnsPerHost = 0
clientConfig.IdleConnTimeout = time.Duration(0)
client, err = clientConfig.ToClient(context.Background(), nil, settings)
require.NoError(t, err)
transport, ok = client.Transport.(*http.Transport)
require.True(t, ok, "client.Transport is not an *http.Transport")
require.Equal(t, 0, transport.MaxIdleConns)
require.Equal(t, time.Duration(0), transport.IdleConnTimeout)
require.Equal(t, 0, transport.MaxConnsPerHost)
require.Equal(t, 0, transport.MaxIdleConnsPerHost)
}
func TestContextWithClient(t *testing.T) {
testCases := []struct {
name string
input *http.Request
doMetadata bool
expected client.Info
}{
{
name: "request without client IP or headers",
input: &http.Request{},
expected: client.Info{},
},
{
name: "request with client IP",
input: &http.Request{
RemoteAddr: "1.2.3.4:55443",
},
expected: client.Info{
Addr: &net.IPAddr{
IP: net.IPv4(1, 2, 3, 4),
},
},
},
{
name: "request with client headers, no metadata processing",
input: &http.Request{
Header: map[string][]string{"x-tt-header": {"tt-value"}},
},
doMetadata: false,
expected: client.Info{},
},
{
name: "request with client headers",
input: &http.Request{
Header: map[string][]string{"x-tt-header": {"tt-value"}},
},
doMetadata: true,
expected: client.Info{
Metadata: client.NewMetadata(map[string][]string{"x-tt-header": {"tt-value"}}),
},
},
{
name: "request with Host and client headers",
input: &http.Request{
Header: map[string][]string{"x-tt-header": {"tt-value"}},
Host: "localhost:55443",
},
doMetadata: true,
expected: client.Info{
Metadata: client.NewMetadata(map[string][]string{"x-tt-header": {"tt-value"}, "Host": {"localhost:55443"}}),
},
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
ctx := contextWithClient(tt.input, tt.doMetadata)
assert.Equal(t, tt.expected, client.FromContext(ctx))
})
}
}
// TestUnmarshalYAMLWithMiddlewares tests that the "middlewares" field is correctly
// parsed from YAML configurations (fixing the bug where "middleware" was used instead)
func TestClientUnmarshalYAMLWithMiddlewares(t *testing.T) {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "middlewares.yaml"))
require.NoError(t, err)
// Test client configuration
var clientConfig ClientConfig
clientSub, err := cm.Sub("client")
require.NoError(t, err)
require.NoError(t, clientSub.Unmarshal(&clientConfig))
// Validate the client configuration using reflection-based validation
require.NoError(t, xconfmap.Validate(&clientConfig), "Client configuration should be valid")
assert.Equal(t, "http://localhost:4318/v1/traces", clientConfig.Endpoint)
require.Len(t, clientConfig.Middlewares, 2)
assert.Equal(t, component.MustNewID("fancy_middleware"), clientConfig.Middlewares[0].ID)
assert.Equal(t, component.MustNewID("careful_middleware"), clientConfig.Middlewares[1].ID)
}
// TestUnmarshalYAMLComprehensiveConfig tests the complete configuration example
// to ensure all fields including middlewares are parsed correctly
func TestClientUnmarshalYAMLComprehensiveConfig(t *testing.T) {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
require.NoError(t, err)
// Test client configuration
var clientConfig ClientConfig
clientSub, err := cm.Sub("client")
require.NoError(t, err)
require.NoError(t, clientSub.Unmarshal(&clientConfig))
// Validate the client configuration using reflection-based validation
require.NoError(t, xconfmap.Validate(&clientConfig), "Client configuration should be valid")
// Verify basic fields
assert.Equal(t, "http://example.com:4318/v1/traces", clientConfig.Endpoint)
assert.Equal(t, "http://proxy.example.com:8080", clientConfig.ProxyURL)
assert.Equal(t, 30*time.Second, clientConfig.Timeout)
assert.Equal(t, 4096, clientConfig.ReadBufferSize)
assert.Equal(t, 4096, clientConfig.WriteBufferSize)
assert.Equal(t, configcompression.TypeGzip, clientConfig.Compression)
// Verify TLS configuration
assert.False(t, clientConfig.TLS.Insecure)
assert.Equal(t, "/path/to/client.crt", clientConfig.TLS.CertFile)
assert.Equal(t, "/path/to/client.key", clientConfig.TLS.KeyFile)
assert.Equal(t, "/path/to/ca.crt", clientConfig.TLS.CAFile)
assert.Equal(t, "example.com", clientConfig.TLS.ServerName)
// Verify headers
expectedHeaders := configopaque.MapList{
{Name: "User-Agent", Value: "OpenTelemetry-Collector/1.0"},
{Name: "X-Custom-Header", Value: "custom-value"},
}
assert.Equal(t, expectedHeaders, clientConfig.Headers)
// Verify middlewares
require.Len(t, clientConfig.Middlewares, 2)
assert.Equal(t, component.MustNewID("middleware1"), clientConfig.Middlewares[0].ID)
assert.Equal(t, component.MustNewID("middleware2"), clientConfig.Middlewares[1].ID)
}
// TestMiddlewaresFieldCompatibility tests that the new "middlewares" field name
// is used instead of the old "middleware" name, ensuring the bug is fixed
func TestClientMiddlewaresFieldCompatibility(t *testing.T) {
// Test that we can create a config with middlewares using the new field name
clientConfig := ClientConfig{
Endpoint: "http://localhost:4318",
Middlewares: []configmiddleware.Config{
{ID: component.MustNewID("test_middleware")},
},
}
assert.Equal(t, "http://localhost:4318", clientConfig.Endpoint)
assert.Len(t, clientConfig.Middlewares, 1)
assert.Equal(t, component.MustNewID("test_middleware"), clientConfig.Middlewares[0].ID)
}
================================================
FILE: config/confighttp/clientinfohandler.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package confighttp // import "go.opentelemetry.io/collector/config/confighttp"
import (
"context"
"net"
"net/http"
"go.opentelemetry.io/collector/client"
)
// clientInfoHandler is an http.Handler that enhances the incoming request context with client.Info.
type clientInfoHandler struct {
next http.Handler
// include client metadata or not
includeMetadata bool
}
// ServeHTTP intercepts incoming HTTP requests, replacing the request's context with one that contains
// a client.Info containing the client's IP address.
func (h *clientInfoHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
req = req.WithContext(contextWithClient(req, h.includeMetadata)) //nolint:contextcheck //context already handled through contextWithClient
h.next.ServeHTTP(w, req)
}
// contextWithClient attempts to add the client IP address to the client.Info from the context. When no
// client.Info exists in the context, one is created.
func contextWithClient(req *http.Request, includeMetadata bool) context.Context {
cl := client.FromContext(req.Context())
ip := parseIP(req.RemoteAddr)
if ip != nil {
cl.Addr = ip
}
if includeMetadata {
md := req.Header.Clone()
if md.Get(client.MetadataHostName) == "" && req.Host != "" {
md.Add(client.MetadataHostName, req.Host)
}
cl.Metadata = client.NewMetadata(md)
}
ctx := client.NewContext(req.Context(), cl)
return ctx
}
// parseIP parses the given string for an IP address. The input string might contain the port,
// but must not contain a protocol or path. Suitable for getting the IP part of a client connection.
func parseIP(source string) *net.IPAddr {
ipstr, _, err := net.SplitHostPort(source)
if err == nil {
source = ipstr
}
ip := net.ParseIP(source)
if ip != nil {
return &net.IPAddr{
IP: ip,
}
}
return nil
}
================================================
FILE: config/confighttp/clientinfohandler_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package confighttp // import "go.opentelemetry.io/collector/config/confighttp"
import (
"net"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
)
var _ http.Handler = (*clientInfoHandler)(nil)
func TestParseIP(t *testing.T) {
testCases := []struct {
name string
input string
expected *net.IPAddr
}{
{
name: "addr",
input: "1.2.3.4",
expected: &net.IPAddr{
IP: net.IPv4(1, 2, 3, 4),
},
},
{
name: "addr:port",
input: "1.2.3.4:33455",
expected: &net.IPAddr{
IP: net.IPv4(1, 2, 3, 4),
},
},
{
name: "protocol://addr:port",
input: "http://1.2.3.4:33455",
expected: nil,
},
{
name: "addr/path",
input: "1.2.3.4/orders",
expected: nil,
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, parseIP(tt.input))
})
}
}
================================================
FILE: config/confighttp/compress_readcloser.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package confighttp // import "go.opentelemetry.io/collector/config/confighttp"
import "io"
// compressReadCloser couples the original compressed reader
// and the compression reader to ensure that the original body
// is correctly closed to ensure resources are freed.
type compressReadCloser struct {
io.Reader
orig io.ReadCloser
}
var (
_ io.Reader = (*compressReadCloser)(nil)
_ io.Closer = (*compressReadCloser)(nil)
)
func (crc *compressReadCloser) Close() error {
return crc.orig.Close()
}
================================================
FILE: config/confighttp/compress_readcloser_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package confighttp
import (
"bytes"
"errors"
"io"
"testing"
"testing/iotest"
"github.com/stretchr/testify/require"
)
type errorReadCloser struct {
io.Reader
err error
}
func (erc errorReadCloser) Close() error {
return erc.err
}
func TestCompressReadCloser(t *testing.T) {
t.Parallel()
for _, tc := range []struct {
name string
wrapper func(r io.Reader) io.ReadCloser
content []byte
errVal string
}{
{
name: "non mutating wrapper",
wrapper: func(r io.Reader) io.ReadCloser {
return errorReadCloser{
Reader: r,
err: nil,
}
},
content: []byte("hello world"),
errVal: "",
},
{
name: "failed reader",
wrapper: func(r io.Reader) io.ReadCloser {
return errorReadCloser{
Reader: r,
err: errors.New("failed to close reader"),
}
},
errVal: "failed to close reader",
},
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
orig := bytes.NewBuffer([]byte("hello world"))
crc := &compressReadCloser{
Reader: orig,
orig: tc.wrapper(orig),
}
require.NoError(t, iotest.TestReader(crc, orig.Bytes()), "Must be able to read original content")
err := crc.Close()
if tc.errVal != "" {
require.EqualError(t, err, tc.errVal, "Must match the expected error message")
} else {
require.NoError(t, err, "Must not error when closing reader")
}
})
}
}
================================================
FILE: config/confighttp/compression.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// This file contains helper functions regarding compression/decompression for confighttp.
package confighttp // import "go.opentelemetry.io/collector/config/confighttp"
import (
"bufio"
"bytes"
"compress/gzip"
"compress/zlib"
"errors"
"fmt"
"io"
"maps"
"net/http"
"sync"
"github.com/golang/snappy"
"github.com/klauspost/compress/zstd"
"github.com/pierrec/lz4/v4"
"go.opentelemetry.io/collector/config/configcompression"
"go.opentelemetry.io/collector/config/confighttp/internal/metadata"
)
func defaultCompressionAlgorithms() []string {
if metadata.ConfighttpFramedSnappyFeatureGate.IsEnabled() {
return []string{"", "gzip", "zstd", "zlib", "snappy", "deflate", "lz4", "x-snappy-framed"}
}
return []string{"", "gzip", "zstd", "zlib", "snappy", "deflate", "lz4"}
}
type compressRoundTripper struct {
rt http.RoundTripper
compressionType configcompression.Type
compressionParams configcompression.CompressionParams
compressor *compressor
}
var zstdReaderPool sync.Pool
type pooledZstdReadCloser struct {
inner *zstd.Decoder
}
func (pzrc *pooledZstdReadCloser) Read(dst []byte) (int, error) {
if pzrc.inner == nil {
return 0, zstd.ErrDecoderClosed
}
return pzrc.inner.Read(dst)
}
func (pzrc *pooledZstdReadCloser) Close() error {
if pzrc.inner != nil {
err := pzrc.inner.Reset(nil)
if err != nil {
return err
}
zstdReaderPool.Put(pzrc.inner)
pzrc.inner = nil
}
return nil
}
var availableDecoders = map[string]func(body io.ReadCloser) (io.ReadCloser, error){
"": func(io.ReadCloser) (io.ReadCloser, error) {
// Not a compressed payload. Nothing to do.
return nil, nil
},
"gzip": func(body io.ReadCloser) (io.ReadCloser, error) {
gr, err := gzip.NewReader(body)
if err != nil {
return nil, err
}
return gr, nil
},
"zstd": func(body io.ReadCloser) (io.ReadCloser, error) {
v := zstdReaderPool.Get()
var zr *zstd.Decoder
var err error
if v == nil {
// NOTE(tigrannajaryan):
// Concurrency 1 disables async decoding. We don't need async decoding, it is pointless
// for our use-case (a server accepting decoding http requests).
// Disabling async improves performance (I benchmarked it previously when working
// on https://github.com/open-telemetry/opentelemetry-collector-contrib/pull/23257).
zr, err = zstd.NewReader(body, zstd.WithDecoderConcurrency(1))
} else {
zr = v.(*zstd.Decoder)
err = zr.Reset(body)
}
if err != nil {
return nil, err
}
return &pooledZstdReadCloser{inner: zr}, nil
},
"zlib": func(body io.ReadCloser) (io.ReadCloser, error) {
zr, err := zlib.NewReader(body)
if err != nil {
return nil, err
}
return zr, nil
},
"snappy": snappyHandler,
//nolint:unparam // Ignoring the linter request to remove error return since it needs to match the method signature
"lz4": func(body io.ReadCloser) (io.ReadCloser, error) {
return &compressReadCloser{
Reader: lz4.NewReader(body),
orig: body,
}, nil
},
//nolint:unparam // Ignoring the linter request to remove error return since it needs to match the method signature
"x-snappy-framed": func(body io.ReadCloser) (io.ReadCloser, error) {
return &compressReadCloser{
Reader: snappy.NewReader(body),
orig: body,
}, nil
},
}
// snappyFramingHeader is always the first 10 bytes of a snappy framed stream.
var snappyFramingHeader = []byte{
0xff, 0x06, 0x00, 0x00,
0x73, 0x4e, 0x61, 0x50, 0x70, 0x59, // "sNaPpY"
}
// snappyHandler returns an io.ReadCloser that auto-detects the snappy format.
// This is necessary because the collector previously used "content-encoding: snappy"
// but decompressed and compressed the payloads using the snappy framing format.
// However, "content-encoding: snappy" is uses the block format, and "x-snappy-framed"
// is the framing format. This handler is a (hopefully temporary) hack to
// make this work in a backwards-compatible way.
//
// See https://github.com/google/snappy/blob/6af9287fbdb913f0794d0148c6aa43b58e63c8e3/framing_format.txt#L27-L36
// for more details on the framing format.
func snappyHandler(body io.ReadCloser) (io.ReadCloser, error) {
br := bufio.NewReader(body)
peekBytes, err := br.Peek(len(snappyFramingHeader))
if err != nil && !errors.Is(err, io.EOF) {
return nil, err
}
isFramed := len(peekBytes) >= len(snappyFramingHeader) && bytes.Equal(peekBytes[:len(snappyFramingHeader)], snappyFramingHeader)
if isFramed {
return &compressReadCloser{
Reader: snappy.NewReader(br),
orig: body,
}, nil
}
compressed, err := io.ReadAll(br)
if err != nil {
return nil, err
}
decoded, err := snappy.Decode(nil, compressed)
if err != nil {
return nil, err
}
return io.NopCloser(bytes.NewReader(decoded)), nil
}
func newCompressionParams(level configcompression.Level) configcompression.CompressionParams {
return configcompression.CompressionParams{
Level: level,
}
}
func newCompressRoundTripper(rt http.RoundTripper, compressionType configcompression.Type, compressionParams configcompression.CompressionParams) (*compressRoundTripper, error) {
encoder, err := newCompressor(compressionType, compressionParams)
if err != nil {
return nil, err
}
return &compressRoundTripper{
rt: rt,
compressionType: compressionType,
compressionParams: compressionParams,
compressor: encoder,
}, nil
}
func (r *compressRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
if req.Header.Get(headerContentEncoding) != "" {
// If the header already specifies a content encoding then skip compression
// since we don't want to compress it again. This is a safeguard that normally
// should not happen since CompressRoundTripper is not intended to be used
// with http clients which already do their own compression.
return r.rt.RoundTrip(req)
}
// Compress the body.
buf := bytes.NewBuffer([]byte{})
if err := r.compressor.compress(buf, req.Body); err != nil {
return nil, err
}
// Create a new request since the docs say that we cannot modify the "req"
// (see https://golang.org/pkg/net/http/#RoundTripper).
cReq, err := http.NewRequestWithContext(req.Context(), req.Method, req.URL.String(), buf)
if err != nil {
return nil, err
}
// Clone the headers and add the encoding header.
cReq.Header = req.Header.Clone()
cReq.Header.Add(headerContentEncoding, string(r.compressionType))
return r.rt.RoundTrip(cReq)
}
type decompressor struct {
errHandler func(w http.ResponseWriter, r *http.Request, errorMsg string, statusCode int)
base http.Handler
decoders map[string]func(body io.ReadCloser) (io.ReadCloser, error)
maxRequestBodySize int64
}
// httpContentDecompressor 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.
func httpContentDecompressor(h http.Handler, maxRequestBodySize int64, eh func(w http.ResponseWriter, r *http.Request, errorMsg string, statusCode int), enableDecoders []string, decoders map[string]func(body io.ReadCloser) (io.ReadCloser, error)) http.Handler {
errHandler := defaultErrorHandler
if eh != nil {
errHandler = eh
}
enabled := map[string]func(body io.ReadCloser) (io.ReadCloser, error){}
for _, dec := range enableDecoders {
if dec == "x-frame-snappy" && !metadata.ConfighttpFramedSnappyFeatureGate.IsEnabled() {
continue
}
enabled[dec] = availableDecoders[dec]
if dec == "deflate" {
enabled["deflate"] = availableDecoders["zlib"]
}
}
d := &decompressor{
maxRequestBodySize: maxRequestBodySize,
errHandler: errHandler,
base: h,
decoders: enabled,
}
maps.Copy(d.decoders, decoders)
return d
}
func (d *decompressor) ServeHTTP(w http.ResponseWriter, r *http.Request) {
newBody, err := d.newBodyReader(r)
if err != nil {
d.errHandler(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 = http.MaxBytesReader(w, newBody, d.maxRequestBodySize)
}
d.base.ServeHTTP(w, r)
}
func (d *decompressor) newBodyReader(r *http.Request) (io.ReadCloser, error) {
if len(d.decoders) == 0 {
return nil, nil // Signal: don't replace r.Body
}
encoding := r.Header.Get(headerContentEncoding)
decoder, ok := d.decoders[encoding]
if !ok {
return nil, fmt.Errorf("unsupported %s: %s", headerContentEncoding, encoding)
}
return decoder(r.Body)
}
// defaultErrorHandler writes the error message in plain text.
func defaultErrorHandler(w http.ResponseWriter, _ *http.Request, errMsg string, statusCode int) {
http.Error(w, errMsg, statusCode)
}
================================================
FILE: config/confighttp/compression_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package confighttp
import (
"bytes"
"compress/gzip"
"compress/zlib"
"context"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"testing/iotest"
"github.com/golang/snappy"
"github.com/klauspost/compress/zstd"
"github.com/pierrec/lz4/v4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/config/configcompression"
"go.opentelemetry.io/collector/config/confighttp/internal/metadata"
"go.opentelemetry.io/collector/config/confignet"
"go.opentelemetry.io/collector/featuregate"
)
func TestHTTPClientCompression(t *testing.T) {
testBody := []byte("uncompressed_text")
compressedGzipBody := compressGzip(t, testBody)
compressedZlibBody := compressZlib(t, testBody)
compressedDeflateBody := compressZlib(t, testBody)
compressedSnappyFramedBody := compressSnappyFramed(t, testBody)
compressedSnappyBody := compressSnappy(t, testBody)
compressedZstdBody := compressZstd(t, testBody)
compressedLz4Body := compressLz4(t, testBody)
const invalidGzipLevel configcompression.Level = 100
tests := []struct {
name string
encoding configcompression.Type
level configcompression.Level
framedSnappyEnabled bool
reqBody []byte
shouldError bool
}{
{
name: "ValidEmpty",
encoding: "",
reqBody: testBody,
shouldError: false,
},
{
name: "ValidNone",
encoding: "none",
reqBody: testBody,
shouldError: false,
},
{
name: "ValidGzip",
encoding: configcompression.TypeGzip,
level: gzip.DefaultCompression,
reqBody: compressedGzipBody.Bytes(),
shouldError: false,
},
{
name: "ValidGzip-DefaultLevel",
encoding: configcompression.TypeGzip,
reqBody: compressedGzipBody.Bytes(),
shouldError: false,
},
{
name: "InvalidGzip",
encoding: configcompression.TypeGzip,
level: invalidGzipLevel,
reqBody: compressedGzipBody.Bytes(),
shouldError: true,
},
{
name: "InvalidCompression",
encoding: configcompression.Type("invalid"),
level: invalidGzipLevel,
reqBody: compressedGzipBody.Bytes(),
shouldError: true,
},
{
name: "ValidZlib",
encoding: configcompression.TypeZlib,
level: gzip.DefaultCompression,
reqBody: compressedZlibBody.Bytes(),
shouldError: false,
},
{
name: "ValidDeflate",
encoding: configcompression.TypeDeflate,
level: gzip.DefaultCompression,
reqBody: compressedDeflateBody.Bytes(),
shouldError: false,
},
{
name: "ValidSnappy",
encoding: configcompression.TypeSnappy,
framedSnappyEnabled: true,
reqBody: compressedSnappyBody.Bytes(),
shouldError: false,
},
{
name: "InvalidSnappy",
encoding: configcompression.TypeSnappy,
level: gzip.DefaultCompression,
reqBody: compressedSnappyBody.Bytes(),
shouldError: true,
},
{
name: "ValidSnappyFramed",
encoding: configcompression.TypeSnappyFramed,
framedSnappyEnabled: true,
reqBody: compressedSnappyFramedBody.Bytes(),
shouldError: false,
},
{
name: "InvalidSnappyFramed",
encoding: configcompression.TypeSnappyFramed,
level: gzip.DefaultCompression,
reqBody: compressedSnappyFramedBody.Bytes(),
shouldError: true,
},
{
name: "ValidZstd",
encoding: configcompression.TypeZstd,
level: 99,
reqBody: compressedZstdBody.Bytes(),
shouldError: false,
},
{
name: "ValidLz4",
encoding: configcompression.TypeLz4,
reqBody: compressedLz4Body.Bytes(),
shouldError: false,
},
{
name: "InvalidLz4",
encoding: configcompression.TypeLz4,
level: gzip.DefaultCompression,
reqBody: compressedLz4Body.Bytes(),
shouldError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ConfighttpFramedSnappyFeatureGate.ID(), tt.framedSnappyEnabled))
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
assert.NoError(t, err, "failed to read request body: %v", err)
assert.Equal(t, tt.reqBody, body)
w.WriteHeader(http.StatusOK)
}))
t.Cleanup(srv.Close)
reqBody := bytes.NewBuffer(testBody)
req, err := http.NewRequest(http.MethodGet, srv.URL, reqBody)
require.NoError(t, err, "failed to create request to test handler")
clientSettings := ClientConfig{
Endpoint: srv.URL,
Compression: tt.encoding,
CompressionParams: newCompressionParams(tt.level),
}
err = clientSettings.Validate()
if tt.shouldError {
require.Error(t, err)
message := fmt.Sprintf("unsupported parameters {Level:%+v} for compression type %q", tt.level, tt.encoding)
assert.Equal(t, message, err.Error())
return
}
require.NoError(t, err)
client, err := clientSettings.ToClient(context.Background(), nil, componenttest.NewNopTelemetrySettings())
require.NoError(t, err)
res, err := client.Do(req)
if tt.shouldError {
assert.Error(t, err)
return
}
require.NoError(t, err)
_, err = io.ReadAll(res.Body)
require.NoError(t, err)
require.NoError(t, res.Body.Close(), "failed to close request body: %v", err)
})
}
}
func TestHTTPCustomDecompression(t *testing.T) {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte(err.Error()))
return
}
assert.NoError(t, err, "failed to read request body: %v", err)
assert.Equal(t, "decompressed body", string(body))
w.WriteHeader(http.StatusOK)
})
decoders := map[string]func(io.ReadCloser) (io.ReadCloser, error){
"custom-encoding": func(io.ReadCloser) (io.ReadCloser, error) { //nolint:unparam
return io.NopCloser(strings.NewReader("decompressed body")), nil
},
}
srv := httptest.NewServer(httpContentDecompressor(handler, defaultMaxRequestBodySize, defaultErrorHandler, defaultCompressionAlgorithms(), decoders))
t.Cleanup(srv.Close)
req, err := http.NewRequest(http.MethodGet, srv.URL, bytes.NewBuffer([]byte("123decompressed body")))
require.NoError(t, err, "failed to create request to test handler")
req.Header.Set("Content-Encoding", "custom-encoding")
client := srv.Client()
res, err := client.Do(req)
require.NoError(t, err)
assert.Equal(t, http.StatusOK, res.StatusCode, "test handler returned unexpected status code ")
_, err = io.ReadAll(res.Body)
require.NoError(t, res.Body.Close(), "failed to close request body: %v", err)
}
func TestHTTPContentDecompressionHandler(t *testing.T) {
testBody := []byte("uncompressed_text")
noDecoders := map[string]func(io.ReadCloser) (io.ReadCloser, error){}
tests := []struct {
name string
encoding string
reqBody *bytes.Buffer
respCode int
respBody string
framedSnappyEnabled bool
}{
{
name: "NoCompression",
encoding: "",
reqBody: bytes.NewBuffer(testBody),
respCode: http.StatusOK,
},
{
name: "ValidDeflate",
encoding: "deflate",
reqBody: compressZlib(t, testBody),
respCode: http.StatusOK,
},
{
name: "ValidGzip",
encoding: "gzip",
reqBody: compressGzip(t, testBody),
respCode: http.StatusOK,
},
{
name: "ValidZlib",
encoding: "zlib",
reqBody: compressZlib(t, testBody),
respCode: http.StatusOK,
},
{
name: "ValidZstd",
encoding: "zstd",
reqBody: compressZstd(t, testBody),
respCode: http.StatusOK,
},
{
name: "ValidSnappyFramed",
encoding: "x-snappy-framed",
framedSnappyEnabled: true,
reqBody: compressSnappyFramed(t, testBody),
respCode: http.StatusOK,
},
{
name: "ValidSnappy",
encoding: "snappy",
reqBody: compressSnappy(t, testBody),
respCode: http.StatusOK,
},
{
// Should work even without the framed snappy feature gate enabled,
// since during decompression we're peeking the compression header
// and identifying which snappy encoding was used.
name: "ValidSnappyFramedAsSnappy",
encoding: "snappy",
reqBody: compressSnappyFramed(t, testBody),
respCode: http.StatusOK,
},
{
name: "ValidLz4",
encoding: "lz4",
reqBody: compressLz4(t, testBody),
respCode: http.StatusOK,
},
{
name: "InvalidDeflate",
encoding: "deflate",
reqBody: bytes.NewBuffer(testBody),
respCode: http.StatusBadRequest,
respBody: "zlib: invalid header\n",
},
{
name: "InvalidGzip",
encoding: "gzip",
reqBody: bytes.NewBuffer(testBody),
respCode: http.StatusBadRequest,
respBody: "gzip: invalid header\n",
},
{
name: "InvalidZlib",
encoding: "zlib",
reqBody: bytes.NewBuffer(testBody),
respCode: http.StatusBadRequest,
respBody: "zlib: invalid header\n",
},
{
name: "InvalidZstd",
encoding: "zstd",
reqBody: bytes.NewBuffer(testBody),
respCode: http.StatusBadRequest,
respBody: "invalid input: magic number mismatch",
},
{
name: "InvalidSnappyFramed",
encoding: "x-snappy-framed",
framedSnappyEnabled: true,
reqBody: bytes.NewBuffer(testBody),
respCode: http.StatusBadRequest,
respBody: "snappy: corrupt input",
},
{
name: "InvalidSnappy",
encoding: "snappy",
reqBody: bytes.NewBuffer(testBody),
respCode: http.StatusBadRequest,
respBody: "snappy: corrupt input\n",
},
{
name: "UnsupportedCompression",
encoding: "nosuchcompression",
reqBody: bytes.NewBuffer(testBody),
respCode: http.StatusBadRequest,
respBody: "unsupported Content-Encoding: nosuchcompression\n",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ConfighttpFramedSnappyFeatureGate.ID(), tt.framedSnappyEnabled))
srv := httptest.NewServer(httpContentDecompressor(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte(err.Error()))
return
}
assert.NoError(t, err, "failed to read request body: %v", err)
assert.EqualValues(t, testBody, string(body))
w.WriteHeader(http.StatusOK)
}), defaultMaxRequestBodySize, defaultErrorHandler, defaultCompressionAlgorithms(), noDecoders))
t.Cleanup(srv.Close)
req, err := http.NewRequest(http.MethodGet, srv.URL, tt.reqBody)
require.NoError(t, err, "failed to create request to test handler")
req.Header.Set("Content-Encoding", tt.encoding)
client := srv.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 := io.ReadAll(res.Body)
require.NoError(t, res.Body.Close(), "failed to close request body: %v", err)
assert.Equal(t, tt.respBody, string(body))
}
})
}
}
// TestEmptyCompressionAlgorithmsAllowsUncompressed verifies that when CompressionAlgorithms
// is set to an empty array, requests without Content-Encoding header are accepted.
func TestEmptyCompressionAlgorithmsAllowsUncompressed(t *testing.T) {
testBody := []byte(`{"message": "test data"}`)
tests := []struct {
name string
compressionAlgorithms []string
contentEncoding string // empty string means no header
expectedStatus int
expectedError string
compressionBypassed bool // If true, don't compress the body (simulates bypass behavior)
}{
// Case 1: Empty array should bypass decompression
{
name: "EmptyArray_NoContentEncoding_Accepted",
compressionAlgorithms: []string{},
contentEncoding: "",
expectedStatus: http.StatusOK,
},
{
name: "EmptyArray_Gzip_PassedThrough",
compressionAlgorithms: []string{},
contentEncoding: "gzip",
expectedStatus: http.StatusOK,
compressionBypassed: true, // Empty array bypasses decompression
},
{
name: "EmptyArray_RandomEncoding_Accepted",
compressionAlgorithms: []string{},
contentEncoding: "randomstuff",
expectedStatus: http.StatusOK,
},
// Case 2: Explicit list with only compressed formats should reject uncompressed
{
name: "OnlyZstd_NoContentEncoding_Rejected",
compressionAlgorithms: []string{"zstd"},
contentEncoding: "",
expectedStatus: http.StatusBadRequest,
expectedError: "unsupported Content-Encoding",
},
{
name: "OnlyZstd_Zstd_Accepted",
compressionAlgorithms: []string{"zstd"},
contentEncoding: "zstd",
expectedStatus: http.StatusOK,
},
{
name: "OnlyZstd_GzipContentEncoding_Rejected",
compressionAlgorithms: []string{"zstd"},
contentEncoding: "gzip",
expectedStatus: http.StatusBadRequest,
expectedError: "unsupported Content-Encoding",
},
// Case 3: Explicit list including empty string should accept uncompressed
{
name: "WithEmptyString_NoContentEncoding_Accepted",
compressionAlgorithms: []string{"", "gzip", "zstd"},
contentEncoding: "",
expectedStatus: http.StatusOK,
},
{
name: "WithEmptyString_Gzip_Accepted",
compressionAlgorithms: []string{"", "gzip"},
contentEncoding: "gzip",
expectedStatus: http.StatusOK,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create handler that echoes back the request body
// If there's an error reading, it returns 500 which the test will catch
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "failed to read body", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
_, _ = w.Write(body)
})
// Create ServerConfig with the specified CompressionAlgorithms
serverConfig := &ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:0",
Transport: confignet.TransportTypeTCP,
},
CompressionAlgorithms: tt.compressionAlgorithms,
}
srv, err := serverConfig.ToServer(
context.Background(),
nil,
componenttest.NewNopTelemetrySettings(),
handler,
)
require.NoError(t, err)
// Create test server
testSrv := httptest.NewServer(srv.Handler)
defer testSrv.Close()
// Compress the body if needed for the test
requestBody := testBody
if !tt.compressionBypassed && tt.expectedStatus == http.StatusOK {
switch tt.contentEncoding {
case "gzip":
requestBody = compressGzip(t, testBody).Bytes()
case "zstd":
requestBody = compressZstd(t, testBody).Bytes()
}
}
// Create request
req, err := http.NewRequest(http.MethodPost, testSrv.URL, bytes.NewReader(requestBody))
require.NoError(t, err)
req.Header.Set("Content-Type", "application/json")
// Set Content-Encoding header if specified
if tt.contentEncoding != "" {
req.Header.Set("Content-Encoding", tt.contentEncoding)
}
// Send request
client := &http.Client{}
resp, err := client.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
// Verify response
assert.Equal(t, tt.expectedStatus, resp.StatusCode, "Unexpected status code")
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
if tt.expectedError != "" {
assert.Contains(t, string(body), tt.expectedError, "Expected error message not found")
} else {
// For successful requests, body should be echoed back
assert.Equal(t, testBody, body, "Response body should match request body")
}
})
}
}
func TestHTTPContentCompressionRequestWithNilBody(t *testing.T) {
compressedGzipBody := compressGzip(t, []byte{})
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
body, err := io.ReadAll(r.Body)
assert.NoError(t, err, "failed to read request body: %v", err)
assert.Equal(t, compressedGzipBody.Bytes(), body)
}))
defer srv.Close()
req, err := http.NewRequest(http.MethodGet, srv.URL, http.NoBody)
require.NoError(t, err, "failed to create request to test handler")
client := srv.Client()
compressionParams := newCompressionParams(gzip.DefaultCompression)
client.Transport, err = newCompressRoundTripper(http.DefaultTransport, configcompression.TypeGzip, compressionParams)
require.NoError(t, err)
res, err := client.Do(req)
require.NoError(t, err)
_, err = io.ReadAll(res.Body)
require.NoError(t, err)
require.NoError(t, res.Body.Close(), "failed to close request body: %v", err)
}
func TestHTTPContentCompressionCopyError(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
}))
t.Cleanup(srv.Close)
req, err := http.NewRequest(http.MethodGet, srv.URL, iotest.ErrReader(errors.New("read failed")))
require.NoError(t, err)
client := srv.Client()
compressionParams := newCompressionParams(gzip.DefaultCompression)
client.Transport, err = newCompressRoundTripper(http.DefaultTransport, configcompression.TypeGzip, compressionParams)
require.NoError(t, err)
_, err = client.Do(req)
require.Error(t, err)
}
type closeFailBody struct {
*bytes.Buffer
}
func (*closeFailBody) Close() error {
return errors.New("close failed")
}
func TestHTTPContentCompressionRequestBodyCloseError(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
}))
t.Cleanup(srv.Close)
req, err := http.NewRequest(http.MethodGet, srv.URL, &closeFailBody{Buffer: bytes.NewBuffer([]byte("blank"))})
require.NoError(t, err)
client := srv.Client()
compressionParams := newCompressionParams(gzip.DefaultCompression)
client.Transport, err = newCompressRoundTripper(http.DefaultTransport, configcompression.TypeGzip, compressionParams)
require.NoError(t, err)
_, err = client.Do(req)
require.Error(t, err)
}
func TestOverrideCompressionList(t *testing.T) {
// prepare
configuredDecoders := []string{"none", "zlib"}
srv := httptest.NewServer(httpContentDecompressor(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
}), defaultMaxRequestBodySize, defaultErrorHandler, configuredDecoders, nil))
t.Cleanup(srv.Close)
req, err := http.NewRequest(http.MethodGet, srv.URL, compressSnappyFramed(t, []byte("123decompressed body")))
require.NoError(t, err, "failed to create request to test handler")
req.Header.Set("Content-Encoding", "snappy")
client := srv.Client()
// test
res, err := client.Do(req)
require.NoError(t, err)
// verify
assert.Equal(t, http.StatusBadRequest, res.StatusCode, "test handler returned unexpected status code ")
_, err = io.ReadAll(res.Body)
require.NoError(t, res.Body.Close(), "failed to close request body: %v", err)
}
func TestDecompressorAvoidDecompressionBomb(t *testing.T) {
t.Parallel()
for _, tc := range []struct {
name string
encoding string
compress func(tb testing.TB, payload []byte) *bytes.Buffer
framedSnappyEnabled bool
}{
// None encoding is ignored since it does not
// enforce the max body size if content encoding header is not set
{
name: "gzip",
encoding: "gzip",
compress: compressGzip,
},
{
name: "zstd",
encoding: "zstd",
compress: compressZstd,
},
{
name: "zlib",
encoding: "zlib",
compress: compressZlib,
},
{
name: "x-snappy-framed",
encoding: "x-snappy-framed",
compress: compressSnappyFramed,
},
{
name: "x-snappy-not-framed",
encoding: "x-snappy-framed",
compress: compressSnappyFramed,
framedSnappyEnabled: false,
},
{
name: "snappy",
encoding: "snappy",
compress: compressSnappy,
},
{
name: "lz4",
encoding: "lz4",
compress: compressLz4,
},
} {
t.Run(tc.name, func(t *testing.T) {
// t.Parallel() // TODO: Re-enable parallel tests once feature gate is removed. We can't parallelize since registry is shared.
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ConfighttpFramedSnappyFeatureGate.ID(), tc.framedSnappyEnabled))
h := httpContentDecompressor(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
n, err := io.Copy(io.Discard, r.Body)
assert.Equal(t, int64(1024), n, "Must have only read the limited value of bytes")
assert.EqualError(t, err, "http: request body too large")
w.WriteHeader(http.StatusBadRequest)
}),
1024,
defaultErrorHandler,
defaultCompressionAlgorithms(),
availableDecoders,
)
payload := tc.compress(t, make([]byte, 2*1024)) // 2KB uncompressed payload
assert.NotEmpty(t, payload.Bytes(), "Must have data available")
req := httptest.NewRequest(http.MethodPost, "/", payload)
req.Header.Set("Content-Encoding", tc.encoding)
resp := httptest.NewRecorder()
h.ServeHTTP(resp, req)
assert.Equal(t, http.StatusBadRequest, resp.Code, "Must match the expected code")
assert.Empty(t, resp.Body.String(), "Must match the returned string")
})
}
}
func TestPooledZstdReadCloserReadAfterClose(t *testing.T) {
h := httpContentDecompressor(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
buf := make([]byte, 1024)
_, err := r.Body.Read(buf)
assert.NoError(t, err)
err = r.Body.Close()
assert.NoError(t, err)
_, err = r.Body.Read(buf)
assert.ErrorIs(t, err, zstd.ErrDecoderClosed)
w.WriteHeader(http.StatusBadRequest)
}),
defaultMaxRequestBodySize,
defaultErrorHandler,
defaultCompressionAlgorithms(),
availableDecoders,
)
payload := compressZstd(t, make([]byte, 2*1024)) // 2KB uncompressed payload
assert.NotEmpty(t, payload.Bytes(), "Must have data available")
req := httptest.NewRequest(http.MethodPost, "/", payload)
req.Header.Set("Content-Encoding", "zstd")
resp := httptest.NewRecorder()
h.ServeHTTP(resp, req)
assert.Equal(t, http.StatusBadRequest, resp.Code, "Must match the expected code")
assert.Empty(t, resp.Body.String(), "Must match the returned string")
}
func compressGzip(tb testing.TB, body []byte) *bytes.Buffer {
var buf bytes.Buffer
gw, _ := gzip.NewWriterLevel(&buf, gzip.DefaultCompression)
_, err := gw.Write(body)
require.NoError(tb, err)
require.NoError(tb, gw.Close())
return &buf
}
func compressZlib(tb testing.TB, body []byte) *bytes.Buffer {
var buf bytes.Buffer
zw, _ := zlib.NewWriterLevel(&buf, zlib.DefaultCompression)
_, err := zw.Write(body)
require.NoError(tb, err)
require.NoError(tb, zw.Close())
return &buf
}
func compressSnappyFramed(tb testing.TB, body []byte) *bytes.Buffer {
var buf bytes.Buffer
sw := snappy.NewBufferedWriter(&buf)
_, err := sw.Write(body)
require.NoError(tb, err)
require.NoError(tb, sw.Close())
return &buf
}
func compressSnappy(tb testing.TB, body []byte) *bytes.Buffer {
var buf bytes.Buffer
compressed := snappy.Encode(nil, body)
_, err := buf.Write(compressed)
require.NoError(tb, err)
return &buf
}
func compressZstd(tb testing.TB, body []byte) *bytes.Buffer {
var buf bytes.Buffer
compression := zstd.SpeedFastest
encoderLevel := zstd.WithEncoderLevel(compression)
zw, _ := zstd.NewWriter(&buf, encoderLevel)
_, err := zw.Write(body)
require.NoError(tb, err)
require.NoError(tb, zw.Close())
return &buf
}
func compressLz4(tb testing.TB, body []byte) *bytes.Buffer {
var buf bytes.Buffer
lz := lz4.NewWriter(&buf)
_, err := lz.Write(body)
require.NoError(tb, err)
require.NoError(tb, lz.Close())
return &buf
}
================================================
FILE: config/confighttp/compressor.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package confighttp // import "go.opentelemetry.io/collector/config/confighttp"
import (
"bytes"
"compress/gzip"
"compress/zlib"
"errors"
"io"
"sync"
"github.com/golang/snappy"
"github.com/klauspost/compress/zstd"
"github.com/pierrec/lz4/v4"
"go.opentelemetry.io/collector/config/configcompression"
"go.opentelemetry.io/collector/config/confighttp/internal/metadata"
)
type writeCloserReset interface {
io.WriteCloser
Reset(w io.Writer)
}
type compressor struct {
pool sync.Pool
}
type compressorMap map[compressionMapKey]*compressor
type compressionMapKey struct {
compressionType configcompression.Type
compressionParams configcompression.CompressionParams
}
var (
compressorPools = make(compressorMap)
compressorPoolsMu sync.Mutex
)
// writerFactory defines writer field in CompressRoundTripper.
// The validity of input is already checked when NewCompressRoundTripper was called in confighttp,
func newCompressor(compressionType configcompression.Type, compressionParams configcompression.CompressionParams) (*compressor, error) {
compressorPoolsMu.Lock()
defer compressorPoolsMu.Unlock()
mapKey := compressionMapKey{compressionType, compressionParams}
c, ok := compressorPools[mapKey]
if ok {
return c, nil
}
f, err := newWriteCloserResetFunc(compressionType, compressionParams)
if err != nil {
return nil, err
}
c = &compressor{pool: sync.Pool{New: func() any { return f() }}}
compressorPools[mapKey] = c
return c, nil
}
func newWriteCloserResetFunc(compressionType configcompression.Type, compressionParams configcompression.CompressionParams) (func() writeCloserReset, error) {
switch compressionType {
case configcompression.TypeGzip:
return func() writeCloserReset {
w, _ := gzip.NewWriterLevel(nil, int(compressionParams.Level))
return w
}, nil
case configcompression.TypeSnappyFramed:
if !metadata.ConfighttpFramedSnappyFeatureGate.IsEnabled() {
return nil, errors.New("x-snappy-framed is not enabled")
}
return func() writeCloserReset {
return snappy.NewBufferedWriter(nil)
}, nil
case configcompression.TypeSnappy:
if !metadata.ConfighttpFramedSnappyFeatureGate.IsEnabled() {
// If framed snappy feature gate is not enabled, we keep the current behavior
// where the 'Content-Encoding: snappy' is compressed as the framed snappy format.
return func() writeCloserReset {
return snappy.NewBufferedWriter(nil)
}, nil
}
return func() writeCloserReset {
// If framed snappy feature gate is enabled, we use the correct behavior
// where the 'Content-Encoding: snappy' is compressed as the block snappy format.
return &rawSnappyWriter{}
}, nil
case configcompression.TypeZstd:
level := zstd.WithEncoderLevel(zstd.EncoderLevelFromZstd(int(compressionParams.Level)))
return func() writeCloserReset {
zw, _ := zstd.NewWriter(nil, zstd.WithEncoderConcurrency(1), level)
return zw
}, nil
case configcompression.TypeZlib, configcompression.TypeDeflate:
return func() writeCloserReset {
w, _ := zlib.NewWriterLevel(nil, int(compressionParams.Level))
return w
}, nil
case configcompression.TypeLz4:
return func() writeCloserReset {
lz := lz4.NewWriter(nil)
_ = lz.Apply(lz4.ConcurrencyOption(1))
return lz
}, nil
}
return nil, errors.New("unsupported compression type")
}
func (p *compressor) compress(buf *bytes.Buffer, body io.ReadCloser) error {
writer := p.pool.Get().(writeCloserReset)
defer p.pool.Put(writer)
writer.Reset(buf)
if body != nil {
_, copyErr := io.Copy(writer, body)
closeErr := body.Close()
if copyErr != nil {
return copyErr
}
if closeErr != nil {
return closeErr
}
}
return writer.Close()
}
// rawSnappyWriter buffers all writes and, on Close,
// compresses the data as a raw snappy block (non-framed)
// and writes the compressed bytes to the underlying writer.
type rawSnappyWriter struct {
buffer bytes.Buffer
w io.Writer
closed bool
}
// Write buffers the data.
func (w *rawSnappyWriter) Write(p []byte) (int, error) {
return w.buffer.Write(p)
}
// Close compresses the buffered data in one shot using snappy.Encode,
// writes the compressed block to the underlying writer, and marks the writer as closed.
func (w *rawSnappyWriter) Close() error {
if w.closed {
return nil
}
w.closed = true
// Compress the buffered uncompressed bytes.
compressed := snappy.Encode(nil, w.buffer.Bytes())
_, err := w.w.Write(compressed)
return err
}
// Reset sets a new underlying writer, resets the buffer and the closed flag.
func (w *rawSnappyWriter) Reset(newWriter io.Writer) {
w.buffer.Reset()
w.w = newWriter
w.closed = false
}
================================================
FILE: config/confighttp/compressor_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// This file contains helper functions regarding compression/decompression for confighttp.
package confighttp // import "go.opentelemetry.io/collector/config/confighttp"
import (
"bytes"
"fmt"
"io"
"strings"
"testing"
"github.com/klauspost/compress/zstd"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/config/configcompression"
"go.opentelemetry.io/collector/internal/testutil"
)
func BenchmarkCompression(b *testing.B) {
testutil.SkipGCHeavyBench(b)
benchmarks := []struct {
codec configcompression.Type
name string
function func(*testing.B, configcompression.Type, *bytes.Buffer, []byte)
}{
{
codec: configcompression.TypeZstd,
name: "zstdWithConcurrency",
function: benchmarkCompression,
},
{
codec: configcompression.TypeZstd,
name: "zstdNoConcurrency",
function: benchmarkCompressionNoConcurrency,
},
}
payload := make([]byte, 10<<22)
buffer := bytes.Buffer{}
buffer.Grow(len(payload))
ts := &bytes.Buffer{}
defer func() {
fmt.Printf("input => %.2f MB\n", float64(len(payload))/(1024*1024))
fmt.Println(ts)
}()
for i := range benchmarks {
benchmark := &benchmarks[i]
b.Run(benchmark.name, func(b *testing.B) {
benchmark.function(b, benchmark.codec, &buffer, payload)
})
}
}
func benchmarkCompression(b *testing.B, _ configcompression.Type, buf *bytes.Buffer, payload []byte) {
// Concurrency Enabled
stringReader := strings.NewReader(string(payload))
stringReadCloser := io.NopCloser(stringReader)
var enc io.Writer
b.ResetTimer()
b.ReportAllocs()
b.SetBytes(int64(len(payload)))
for b.Loop() {
enc, _ = zstd.NewWriter(nil, zstd.WithEncoderConcurrency(5))
enc.(writeCloserReset).Reset(buf)
_, copyErr := io.Copy(enc, stringReadCloser)
require.NoError(b, copyErr)
}
}
func benchmarkCompressionNoConcurrency(b *testing.B, _ configcompression.Type, buf *bytes.Buffer, payload []byte) {
stringReader := strings.NewReader(string(payload))
stringReadCloser := io.NopCloser(stringReader)
var enc io.Writer
b.ResetTimer()
b.ReportAllocs()
b.SetBytes(int64(len(payload)))
for b.Loop() {
enc, _ = zstd.NewWriter(nil, zstd.WithEncoderConcurrency(1))
enc.(writeCloserReset).Reset(buf)
_, copyErr := io.Copy(enc, stringReadCloser)
require.NoError(b, copyErr)
}
}
================================================
FILE: config/confighttp/config.schema.yaml
================================================
$defs:
auth_config:
type: object
properties:
request_params:
description: RequestParameters is a list of parameters that should be extracted from the request and added to the context. When a parameter is found in both the query string and the header, the value from the query string will be used.
type: array
items:
type: string
allOf:
- $ref: /config/configauth.config
cookies_config:
description: CookiesConfig defines the configuration of the HTTP client regarding cookies served by the server.
client_config:
description: ClientConfig defines settings for creating an HTTP client.
type: object
properties:
auth:
description: Auth configuration for outgoing HTTP calls.
x-optional: true
$ref: /config/configauth.config
compression:
description: The compression key for supported compression types within collector.
$ref: /config/configcompression.type
compression_params:
description: Advanced configuration options for the Compression
$ref: /config/configcompression.compression_params
cookies:
description: Cookies configures the cookie management of the HTTP client.
x-optional: true
$ref: cookies_config
disable_keep_alives:
description: 'DisableKeepAlives, if true, disables HTTP keep-alives and will only use the connection to the server for a single HTTP request. WARNING: enabling this option can result in significant overhead establishing a new HTTP(S) connection for every request. Before enabling this option please consider whether changes to idle connection settings can achieve your goal.'
type: boolean
endpoint:
description: 'The target URL to send data to (e.g.: http://some.url:9411/v1/traces).'
type: string
force_attempt_http2:
description: 'Enabling ForceAttemptHTTP2 forces the HTTP transport to use the HTTP/2 protocol. By default, this is set to true. NOTE: HTTP/2 does not support settings such as MaxConnsPerHost, MaxIdleConnsPerHost and MaxIdleConns.'
type: boolean
headers:
description: Additional headers attached to each HTTP request sent by the client. Existing header values are overwritten if collision happens. Header values are opaque since they may be sensitive.
$ref: /config/configopaque.map_list
http2_ping_timeout:
description: HTTP2PingTimeout if there's no response to the ping within the configured value, the connection will be closed. If not set or set to 0, it defaults to 15s.
type: string
x-customType: time.Duration
format: duration
http2_read_idle_timeout:
description: This is needed in case you run into https://github.com/golang/go/issues/59690 https://github.com/golang/go/issues/36026 HTTP2ReadIdleTimeout if the connection has been idle for the configured value send a ping frame for health check 0s means no health check will be performed.
type: string
x-customType: time.Duration
format: duration
idle_conn_timeout:
description: IdleConnTimeout is the maximum amount of time a connection will remain open before closing itself. By default, it is set to 90 seconds.
type: string
x-customType: time.Duration
format: duration
max_conns_per_host:
description: MaxConnsPerHost limits the total number of connections per host, including connections in the dialing, active, and idle states. Default is 0 (unlimited).
type: integer
max_idle_conns:
description: MaxIdleConns is used to set a limit to the maximum idle HTTP connections the client can keep open. By default, it is set to 100. Zero means no limit.
type: integer
max_idle_conns_per_host:
description: MaxIdleConnsPerHost is used to set a limit to the maximum idle HTTP connections the host can keep open. If zero, [net/http.DefaultMaxIdleConnsPerHost] is used.
type: integer
middlewares:
description: Middlewares are used to add custom functionality to the HTTP client. Middleware handlers are called in the order they appear in this list, with the first middleware becoming the outermost handler.
type: array
items:
$ref: /config/configmiddleware.config
proxy_url:
description: ProxyURL setting for the collector
type: string
read_buffer_size:
description: ReadBufferSize for HTTP client. See http.Transport.ReadBufferSize. Default is 0.
type: integer
timeout:
description: Timeout parameter configures `http.Client.Timeout`. Default is 0 (unlimited).
type: string
x-customType: time.Duration
format: duration
tls:
description: TLS struct exposes TLS client configuration.
$ref: /config/configtls.client_config
write_buffer_size:
description: WriteBufferSize for HTTP client. See http.Transport.WriteBufferSize. Default is 0.
type: integer
cors_config:
description: CORSConfig configures a receiver for HTTP cross-origin resource sharing (CORS). See the underlying https://github.com/rs/cors package for details.
type: object
properties:
allowed_headers:
description: AllowedHeaders sets what headers will be allowed in CORS requests. The Accept, Accept-Language, Content-Type, and Content-Language headers are implicitly allowed. If no headers are listed, X-Requested-With will also be accepted by default. Include "*" to allow any request header.
type: array
items:
type: string
allowed_origins:
description: AllowedOrigins sets the allowed values of the Origin header for HTTP/JSON requests to an OTLP receiver. An origin may contain a wildcard (*) to replace 0 or more characters (e.g., "http://*.domain.com", or "*" to allow any origin).
type: array
items:
type: string
max_age:
description: MaxAge sets the value of the Access-Control-Max-Age response header. Set it to the number of seconds that browsers should cache a CORS preflight response for.
type: integer
server_config:
description: ServerConfig defines settings for creating an HTTP server.
type: object
properties:
auth:
description: Auth for this receiver
x-optional: true
$ref: auth_config
compression_algorithms:
description: 'CompressionAlgorithms configures the list of compression algorithms the server can accept. Default: ["", "gzip", "zstd", "zlib", "snappy", "deflate"]'
type: array
items:
type: string
cors:
description: CORS configures the server for HTTP cross-origin resource sharing (CORS).
x-optional: true
$ref: cors_config
endpoint:
description: Endpoint configures the listening address for the server.
type: string
idle_timeout:
description: IdleTimeout is the maximum amount of time to wait for the next request when keep-alives are enabled. If IdleTimeout is zero, the value of ReadTimeout is used. If both are zero, there is no timeout.
type: string
x-customType: time.Duration
format: duration
include_metadata:
description: IncludeMetadata propagates the client metadata from the incoming requests to the downstream consumers
type: boolean
keep_alives_enabled:
description: KeepAlivesEnabled controls whether HTTP keep-alives are enabled. By default, keep-alives are always enabled. Only very resource-constrained environments should disable them.
type: boolean
max_request_body_size:
description: 'MaxRequestBodySize sets the maximum request body size in bytes. Default: 20MiB.'
type: integer
x-customType: int64
middlewares:
description: Middlewares are used to add custom functionality to the HTTP server. Middleware handlers are called in the order they appear in this list, with the first middleware becoming the outermost handler.
type: array
items:
$ref: /config/configmiddleware.config
read_header_timeout:
description: ReadHeaderTimeout is the amount of time allowed to read request headers. The connection's read deadline is reset after reading the headers and the Handler can decide what is considered too slow for the body. If ReadHeaderTimeout is zero, the value of ReadTimeout is used. If both are zero, there is no timeout.
type: string
x-customType: time.Duration
format: duration
read_timeout:
description: ReadTimeout is the maximum duration for reading the entire request, including the body. A zero or negative value means there will be no timeout. Because ReadTimeout does not let Handlers make per-request decisions on each request body's acceptable deadline or upload rate, most users will prefer to use ReadHeaderTimeout. It is valid to use them both.
type: string
x-customType: time.Duration
format: duration
response_headers:
description: Additional headers attached to each HTTP response sent to the client. Header values are opaque since they may be sensitive.
$ref: /config/configopaque.map_list
tls:
description: TLS struct exposes TLS client configuration.
x-optional: true
$ref: /config/configtls.server_config
write_timeout:
description: WriteTimeout is the maximum duration before timing out writes of the response. It is reset whenever a new request's header is read. Like ReadTimeout, it does not let Handlers make decisions on a per-request basis. A zero or negative value means there will be no timeout.
type: string
x-customType: time.Duration
format: duration
================================================
FILE: config/confighttp/confighttp_example_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package confighttp
import (
"context"
"net/http"
"go.opentelemetry.io/collector/component/componenttest"
)
func ExampleServerConfig() {
settings := NewDefaultServerConfig()
settings.NetAddr.Endpoint = "localhost:443"
// Typically obtained as an argument of Component.Start()
host := componenttest.NewNopHost()
s, err := settings.ToServer(
context.Background(),
host.GetExtensions(),
componenttest.NewNopTelemetrySettings(),
http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}))
if err != nil {
panic(err)
}
l, err := settings.ToListener(context.Background())
if err != nil {
panic(err)
}
if err = s.Serve(l); err != nil {
panic(err)
}
}
================================================
FILE: config/confighttp/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package confighttp defines the configuration settings
// for creating an HTTP client and server.
//
// The configuration structs in this package may be shared across signals, but
// assume each struct is used for a single protocol and component.
package confighttp // import "go.opentelemetry.io/collector/config/confighttp"
//go:generate mdatagen metadata.yaml
================================================
FILE: config/confighttp/documentation.md
================================================
[comment]: <> (Code generated by mdatagen. DO NOT EDIT.)
# confighttp
## Feature Gates
This component has the following feature gates:
| Feature Gate | Stage | Description | From Version | To Version | Reference |
| ------------ | ----- | ----------- | ------------ | ---------- | --------- |
| `confighttp.framedSnappy` | beta | Content encoding 'snappy' will compress/decompress block snappy format while 'x-snappy-framed' will compress/decompress framed snappy format. | v0.125.0 | N/A | [Link](https://github.com/open-telemetry/opentelemetry-collector/issues/10584) |
For more information about feature gates, see the [Feature Gates](https://github.com/open-telemetry/opentelemetry-collector/blob/main/featuregate/README.md) documentation.
================================================
FILE: config/confighttp/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package confighttp
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: config/confighttp/go.mod
================================================
module go.opentelemetry.io/collector/config/confighttp
go 1.25.0
require (
github.com/golang/snappy v1.0.0
github.com/klauspost/compress v1.18.4
github.com/pierrec/lz4/v4 v4.1.26
github.com/rs/cors v1.11.1
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/client v1.54.0
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.opentelemetry.io/collector/config/configauth v1.54.0
go.opentelemetry.io/collector/config/configcompression v1.54.0
go.opentelemetry.io/collector/config/configmiddleware v1.54.0
go.opentelemetry.io/collector/config/confignet v1.54.0
go.opentelemetry.io/collector/config/configopaque v1.54.0
go.opentelemetry.io/collector/config/configoptional v1.54.0
go.opentelemetry.io/collector/config/configtls v1.54.0
go.opentelemetry.io/collector/extension v1.54.0
go.opentelemetry.io/collector/extension/extensionauth v1.54.0
go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest v0.148.0
go.opentelemetry.io/collector/extension/extensionmiddleware v0.148.0
go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest v0.148.0
go.opentelemetry.io/collector/featuregate v1.54.0
go.opentelemetry.io/collector/internal/testutil v0.148.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0
go.opentelemetry.io/otel v1.42.0
go.uber.org/goleak v1.3.0
go.uber.org/zap v1.27.1
golang.org/x/net v0.51.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/google/go-tpm v0.9.8 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/confmap v1.54.0
go.opentelemetry.io/collector/confmap/xconfmap v0.148.0
go.opentelemetry.io/collector/pdata v1.54.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/config/configauth => ../configauth
replace go.opentelemetry.io/collector/config/configcompression => ../configcompression
replace go.opentelemetry.io/collector/config/configmiddleware => ../configmiddleware
replace go.opentelemetry.io/collector/config/confignet => ../confignet
replace go.opentelemetry.io/collector/config/configopaque => ../configopaque
replace go.opentelemetry.io/collector/config/configoptional => ../configoptional
replace go.opentelemetry.io/collector/config/configtls => ../configtls
replace go.opentelemetry.io/collector/extension => ../../extension
replace go.opentelemetry.io/collector/extension/extensionauth => ../../extension/extensionauth
replace go.opentelemetry.io/collector/extension/extensionmiddleware => ../../extension/extensionmiddleware
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest
replace go.opentelemetry.io/collector/consumer => ../../consumer
replace go.opentelemetry.io/collector/client => ../../client
replace go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest => ../../extension/extensionauth/extensionauthtest
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest => ../../extension/extensionmiddleware/extensionmiddlewaretest
replace go.opentelemetry.io/collector/confmap => ../../confmap
replace go.opentelemetry.io/collector/confmap/xconfmap => ../../confmap/xconfmap
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
================================================
FILE: config/confighttp/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f h1:RJ+BDPLSHQO7cSjKBqjPJSbi1qfk9WcsjQDtZiw3dZw=
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f/go.mod h1:VHbbch/X4roIY22jL1s3qRbZhCiRIgUAF/PdSUcx2io=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/go-tpm-tools v0.4.7 h1:J3ycC8umYxM9A4eF73EofRZu4BxY0jjQnUnkhIBbvws=
github.com/google/go-tpm-tools v0.4.7/go.mod h1:gSyXTZHe3fgbzb6WEGd90QucmsnT1SRdlye82gH8QjQ=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY=
github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: config/confighttp/internal/metadata/generated_feature_gates.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/featuregate"
)
var ConfighttpFramedSnappyFeatureGate = featuregate.GlobalRegistry().MustRegister(
"confighttp.framedSnappy",
featuregate.StageBeta,
featuregate.WithRegisterDescription("Content encoding 'snappy' will compress/decompress block snappy format while 'x-snappy-framed' will compress/decompress framed snappy format."),
featuregate.WithRegisterReferenceURL("https://github.com/open-telemetry/opentelemetry-collector/issues/10584"),
featuregate.WithRegisterFromVersion("v0.125.0"),
)
================================================
FILE: config/confighttp/internal/options.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/config/confighttp/internal"
import (
"io"
"net/http"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
// ToServerOptions has options that change the behavior of the HTTP server
// returned by ServerConfig.ToServer().
type ToServerOptions struct {
ErrHandler func(w http.ResponseWriter, r *http.Request, errorMsg string, statusCode int)
Decoders map[string]func(body io.ReadCloser) (io.ReadCloser, error)
OtelhttpOpts []otelhttp.Option
}
func (tso *ToServerOptions) Apply(opts ...ToServerOption) {
for _, o := range opts {
o.apply(tso)
}
}
// ToServerOption is an option to change the behavior of the HTTP server
// returned by ServerConfig.ToServer().
type ToServerOption interface {
apply(*ToServerOptions)
}
// ToServerOptionFunc converts a function into ToServerOption interface.
type ToServerOptionFunc func(*ToServerOptions)
func (of ToServerOptionFunc) apply(e *ToServerOptions) {
of(e)
}
================================================
FILE: config/confighttp/metadata.yaml
================================================
type: confighttp
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
stability:
beta: [metrics, traces, logs]
alpha: [profiles]
feature_gates:
- id: confighttp.framedSnappy
description: "Content encoding 'snappy' will compress/decompress block snappy format while 'x-snappy-framed' will compress/decompress framed snappy format."
stage: beta
from_version: 'v0.125.0'
reference_url: 'https://github.com/open-telemetry/opentelemetry-collector/issues/10584'
================================================
FILE: config/confighttp/server.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package confighttp // import "go.opentelemetry.io/collector/config/confighttp"
import (
"context"
"crypto/tls"
"errors"
"io"
"net"
"net/http"
"strings"
"time"
"github.com/rs/cors"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"golang.org/x/net/http2"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configauth"
"go.opentelemetry.io/collector/config/confighttp/internal"
"go.opentelemetry.io/collector/config/configmiddleware"
"go.opentelemetry.io/collector/config/confignet"
"go.opentelemetry.io/collector/config/configopaque"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/config/configtls"
"go.opentelemetry.io/collector/extension/extensionauth"
)
const defaultMaxRequestBodySize = 20 * 1024 * 1024 // 20MiB
// ServerConfig defines settings for creating an HTTP server.
type ServerConfig struct {
// NetAddr holds configuration for the network listener.
//
// Transport defaults to "tcp" if unspecified, and only
// "tcp", "tcp4", "tcp6", and "unix" are valid options.
NetAddr confignet.AddrConfig `mapstructure:",squash"`
// TLS struct exposes TLS server configuration.
TLS configoptional.Optional[configtls.ServerConfig] `mapstructure:"tls"`
// CORS configures the server for HTTP cross-origin resource sharing (CORS).
CORS configoptional.Optional[CORSConfig] `mapstructure:"cors"`
// Auth for this receiver
Auth configoptional.Optional[AuthConfig] `mapstructure:"auth,omitempty"`
// MaxRequestBodySize sets the maximum request body size in bytes. Default: 20MiB.
MaxRequestBodySize int64 `mapstructure:"max_request_body_size,omitempty"`
// IncludeMetadata propagates the client metadata from the incoming requests to the downstream consumers
IncludeMetadata bool `mapstructure:"include_metadata,omitempty"`
// Additional headers attached to each HTTP response sent to the client.
// Header values are opaque since they may be sensitive.
ResponseHeaders configopaque.MapList `mapstructure:"response_headers,omitempty"`
// CompressionAlgorithms configures the list of compression algorithms the server can accept. Default: ["", "gzip", "zstd", "zlib", "snappy", "deflate"]
CompressionAlgorithms []string `mapstructure:"compression_algorithms,omitempty"`
// ReadTimeout is the maximum duration for reading the entire
// request, including the body. A zero or negative value means
// there will be no timeout.
//
// Because ReadTimeout does not let Handlers make per-request
// decisions on each request body's acceptable deadline or
// upload rate, most users will prefer to use
// ReadHeaderTimeout. It is valid to use them both.
ReadTimeout time.Duration `mapstructure:"read_timeout,omitempty"`
// ReadHeaderTimeout is the amount of time allowed to read
// request headers. The connection's read deadline is reset
// after reading the headers and the Handler can decide what
// is considered too slow for the body. If ReadHeaderTimeout
// is zero, the value of ReadTimeout is used. If both are
// zero, there is no timeout.
ReadHeaderTimeout time.Duration `mapstructure:"read_header_timeout"`
// WriteTimeout is the maximum duration before timing out
// writes of the response. It is reset whenever a new
// request's header is read. Like ReadTimeout, it does not
// let Handlers make decisions on a per-request basis.
// A zero or negative value means there will be no timeout.
WriteTimeout time.Duration `mapstructure:"write_timeout"`
// IdleTimeout is the maximum amount of time to wait for the
// next request when keep-alives are enabled. If IdleTimeout
// is zero, the value of ReadTimeout is used. If both are
// zero, there is no timeout.
IdleTimeout time.Duration `mapstructure:"idle_timeout"`
// Middlewares are used to add custom functionality to the HTTP server.
// Middleware handlers are called in the order they appear in this list,
// with the first middleware becoming the outermost handler.
Middlewares []configmiddleware.Config `mapstructure:"middlewares,omitempty"`
// KeepAlivesEnabled controls whether HTTP keep-alives are enabled.
// By default, keep-alives are always enabled. Only very resource-constrained environments should disable them.
KeepAlivesEnabled bool `mapstructure:"keep_alives_enabled,omitempty"`
}
// NewDefaultServerConfig returns ServerConfig type object with default values.
// We encourage to use this function to create an object of ServerConfig.
func NewDefaultServerConfig() ServerConfig {
netAddr := confignet.NewDefaultAddrConfig()
// We typically want to create a TCP server and listen over a network.
netAddr.Transport = confignet.TransportTypeTCP
return ServerConfig{
NetAddr: netAddr,
WriteTimeout: 30 * time.Second,
ReadHeaderTimeout: 1 * time.Minute,
IdleTimeout: 1 * time.Minute,
KeepAlivesEnabled: true,
}
}
type AuthConfig struct {
// Auth for this receiver.
configauth.Config `mapstructure:",squash"`
// RequestParameters is a list of parameters that should be extracted from the request and added to the context.
// When a parameter is found in both the query string and the header, the value from the query string will be used.
RequestParameters []string `mapstructure:"request_params,omitempty"`
// prevent unkeyed literal initialization
_ struct{}
}
// ToListener creates a net.Listener.
func (sc *ServerConfig) ToListener(ctx context.Context) (net.Listener, error) {
listener, err := sc.NetAddr.Listen(ctx)
if err != nil {
return nil, err
}
if sc.TLS.HasValue() {
var tlsCfg *tls.Config
tlsCfg, err = sc.TLS.Get().LoadTLSConfig(ctx)
if err != nil {
return nil, err
}
tlsCfg.NextProtos = []string{http2.NextProtoTLS, "http/1.1"}
listener = tls.NewListener(listener, tlsCfg)
}
return listener, nil
}
// toServerOptions has options that change the behavior of the HTTP server
// returned by ServerConfig.ToServer().
type toServerOptions = internal.ToServerOptions
// ToServerOption is an option to change the behavior of the HTTP server
// returned by ServerConfig.ToServer().
type ToServerOption = internal.ToServerOption
// WithErrorHandler overrides the HTTP error handler that gets invoked
// when there is a failure inside httpContentDecompressor.
func WithErrorHandler(e func(w http.ResponseWriter, r *http.Request, errorMsg string, statusCode int)) ToServerOption {
return internal.ToServerOptionFunc(func(opts *toServerOptions) {
opts.ErrHandler = e
})
}
// WithDecoder provides support for additional decoders to be configured
// by the caller.
func WithDecoder(key string, dec func(body io.ReadCloser) (io.ReadCloser, error)) ToServerOption {
return internal.ToServerOptionFunc(func(opts *toServerOptions) {
if opts.Decoders == nil {
opts.Decoders = map[string]func(body io.ReadCloser) (io.ReadCloser, error){}
}
opts.Decoders[key] = dec
})
}
// ToServer creates an http.Server from settings object.
//
// To allow the configuration to reference middleware or authentication extensions,
// the `extensions` argument should be the output of `host.GetExtensions()`.
// It may also be `nil` in tests where no such extension is expected to be used.
func (sc *ServerConfig) ToServer(ctx context.Context, extensions map[component.ID]component.Component, settings component.TelemetrySettings, handler http.Handler, opts ...ToServerOption) (*http.Server, error) {
serverOpts := &toServerOptions{}
serverOpts.Apply(opts...)
if sc.MaxRequestBodySize <= 0 {
sc.MaxRequestBodySize = defaultMaxRequestBodySize
}
if sc.CompressionAlgorithms == nil {
sc.CompressionAlgorithms = defaultCompressionAlgorithms()
}
// Apply middlewares in reverse order so they execute in
// forward order. The first middleware runs after
// decompression, below, preceded by Auth, CORS, etc.
if len(sc.Middlewares) > 0 && extensions == nil {
return nil, errors.New("middlewares were configured but this component or its host does not support extensions")
}
for i := len(sc.Middlewares) - 1; i >= 0; i-- {
wrapper, err := sc.Middlewares[i].GetHTTPServerHandler(ctx, extensions)
// If we failed to get the middleware
if err != nil {
return nil, err
}
handler, err = wrapper(ctx, handler)
// If we failed to construct a wrapper
if err != nil {
return nil, err
}
}
handler = httpContentDecompressor(
handler,
sc.MaxRequestBodySize,
serverOpts.ErrHandler,
sc.CompressionAlgorithms,
serverOpts.Decoders,
)
if sc.MaxRequestBodySize > 0 {
handler = maxRequestBodySizeInterceptor(handler, sc.MaxRequestBodySize)
}
if sc.Auth.HasValue() {
if extensions == nil {
return nil, errors.New("authentication was configured but this component or its host does not support extensions")
}
auth := sc.Auth.Get()
server, err := auth.GetServerAuthenticator(ctx, extensions)
if err != nil {
return nil, err
}
handler = authInterceptor(handler, server, auth.RequestParameters, serverOpts)
}
if sc.CORS.HasValue() && len(sc.CORS.Get().AllowedOrigins) > 0 {
corsConfig := sc.CORS.Get()
co := cors.Options{
AllowedOrigins: corsConfig.AllowedOrigins,
AllowCredentials: true,
AllowedHeaders: corsConfig.AllowedHeaders,
MaxAge: corsConfig.MaxAge,
}
handler = cors.New(co).Handler(handler)
}
if sc.CORS.HasValue() && len(sc.CORS.Get().AllowedOrigins) == 0 && len(sc.CORS.Get().AllowedHeaders) > 0 {
settings.Logger.Warn("The CORS configuration specifies allowed headers but no allowed origins, and is therefore ignored.")
}
if sc.ResponseHeaders != nil {
handler = responseHeadersHandler(handler, sc.ResponseHeaders)
}
otelOpts := append(
[]otelhttp.Option{
otelhttp.WithTracerProvider(settings.TracerProvider),
otelhttp.WithPropagators(otel.GetTextMapPropagator()),
otelhttp.WithSpanNameFormatter(func(_ string, r *http.Request) string {
// https://opentelemetry.io/docs/specs/semconv/http/http-spans/#name:
//
// "HTTP span names SHOULD be {method} {target} if there is a (low-cardinality) target available.
// If there is no (low-cardinality) {target} available, HTTP span names SHOULD be {method}.
//
// The {method} MUST be {http.request.method} if the method represents the original method known
// to the instrumentation. In other cases (when {http.request.method} is set to _OTHER),
// {method} MUST be HTTP.
//
// Instrumentation MUST NOT default to using URI path as a {target}."
//
method := standardizeHTTPMethod(r.Method, "HTTP")
if r.Pattern != "" {
return method + " " + r.Pattern
}
return method
}),
otelhttp.WithMeterProvider(settings.MeterProvider),
},
serverOpts.OtelhttpOpts...)
// Enable OpenTelemetry observability plugin.
handler = otelhttp.NewHandler(handler, "", otelOpts...)
// wrap the current handler in an interceptor that will add client.Info to the request's context
handler = &clientInfoHandler{
next: handler,
includeMetadata: sc.IncludeMetadata,
}
errorLog, err := zap.NewStdLogAt(settings.Logger, zapcore.ErrorLevel)
if err != nil {
return nil, err // If an error occurs while creating the logger, return nil and the error
}
server := &http.Server{
Handler: handler,
ReadTimeout: sc.ReadTimeout,
ReadHeaderTimeout: sc.ReadHeaderTimeout,
WriteTimeout: sc.WriteTimeout,
IdleTimeout: sc.IdleTimeout,
ErrorLog: errorLog,
}
// Set keep-alives enabled/disabled
server.SetKeepAlivesEnabled(sc.KeepAlivesEnabled)
return server, err
}
func responseHeadersHandler(handler http.Handler, headers configopaque.MapList) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
h := w.Header()
for k, v := range headers.Iter {
h.Set(k, string(v))
}
handler.ServeHTTP(w, r)
})
}
// CORSConfig configures a receiver for HTTP cross-origin resource sharing (CORS).
// See the underlying https://github.com/rs/cors package for details.
type CORSConfig struct {
// AllowedOrigins sets the allowed values of the Origin header for
// HTTP/JSON requests to an OTLP receiver. An origin may contain a
// wildcard (*) to replace 0 or more characters (e.g.,
// "http://*.domain.com", or "*" to allow any origin).
AllowedOrigins []string `mapstructure:"allowed_origins,omitempty"`
// AllowedHeaders sets what headers will be allowed in CORS requests.
// The Accept, Accept-Language, Content-Type, and Content-Language
// headers are implicitly allowed. If no headers are listed,
// X-Requested-With will also be accepted by default. Include "*" to
// allow any request header.
AllowedHeaders []string `mapstructure:"allowed_headers,omitempty"`
// MaxAge sets the value of the Access-Control-Max-Age response header.
// Set it to the number of seconds that browsers should cache a CORS
// preflight response for.
MaxAge int `mapstructure:"max_age,omitempty"`
// prevent unkeyed literal initialization
_ struct{}
}
// NewDefaultCORSConfig creates a default cross-origin resource sharing (CORS) configuration.
func NewDefaultCORSConfig() CORSConfig {
return CORSConfig{}
}
func authInterceptor(next http.Handler, server extensionauth.Server, requestParams []string, serverOpts *internal.ToServerOptions) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
sources := r.Header
query := r.URL.Query()
for _, param := range requestParams {
if val, ok := query[param]; ok {
sources[param] = val
}
}
ctx, err := server.Authenticate(r.Context(), sources)
if err != nil {
if serverOpts.ErrHandler != nil {
serverOpts.ErrHandler(w, r, err.Error(), http.StatusUnauthorized)
} else {
http.Error(w, err.Error(), http.StatusUnauthorized)
}
return
}
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func maxRequestBodySizeInterceptor(next http.Handler, maxRecvSize int64) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxRecvSize)
next.ServeHTTP(w, r)
})
}
// standardizeHTTPMethod returns an upper case HTTP method if well-known, otherwise unknown.
// Based on https://github.com/open-telemetry/opentelemetry-go-contrib/blob/1530d71edc6d40d0659187d069081b639ef1b394/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv/util.go#L119
func standardizeHTTPMethod(method, unknown string) string {
method = strings.ToUpper(method)
switch method {
case http.MethodConnect, http.MethodDelete, http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodPatch, http.MethodPost, http.MethodPut, http.MethodTrace:
return method
}
return unknown
}
================================================
FILE: config/confighttp/server_middleware_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package confighttp
import (
"context"
"errors"
"io"
"net/http"
"net/http/httptest"
"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/configmiddleware"
"go.opentelemetry.io/collector/config/confignet"
"go.opentelemetry.io/collector/extension"
"go.opentelemetry.io/collector/extension/extensionmiddleware"
"go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest"
)
// testServerMiddleware is a test implementation of configmiddleware.Config
type testServerMiddleware struct {
extension.Extension
extensionmiddleware.GetHTTPHandlerFunc
}
func newTestServerMiddleware(name string) component.Component {
return &testServerMiddleware{
Extension: extensionmiddlewaretest.NewNop(),
GetHTTPHandlerFunc: func(_ context.Context) (extensionmiddleware.WrapHTTPHandlerFunc, error) {
return func(_ context.Context, handler http.Handler) (http.Handler, error) {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Append middleware name to the URL path
r.URL.Path += name + "/"
// Call the next handler in the chain
handler.ServeHTTP(w, r)
// Add middleware name to the response
_, _ = w.Write([]byte("\r\nserved by " + name))
}), nil
}, nil
},
}
}
func newTestServerConfig(name string) configmiddleware.Config {
return configmiddleware.Config{
ID: component.MustNewID(name),
}
}
func TestServerMiddleware(t *testing.T) {
// Register two test extensions
extensions := map[component.ID]component.Component{
component.MustNewID("test1"): newTestServerMiddleware("test1"),
component.MustNewID("test2"): newTestServerMiddleware("test2"),
}
// Test with different middleware configurations
testCases := []struct {
name string
middlewares []configmiddleware.Config
expectedOutput string
}{
{
name: "no_middlewares",
middlewares: nil,
expectedOutput: "OK{/}",
},
{
name: "single_middleware",
middlewares: []configmiddleware.Config{
newTestServerConfig("test1"),
},
expectedOutput: "OK{/test1/}\r\nserved by test1",
},
{
name: "multiple_middlewares",
middlewares: []configmiddleware.Config{
newTestServerConfig("test1"),
newTestServerConfig("test2"),
},
expectedOutput: "OK{/test1/test2/}\r\nserved by test2\r\nserved by test1",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Create server config with the test middlewares
cfg := ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:0",
Transport: confignet.TransportTypeTCP,
},
Middlewares: tc.middlewares,
}
// Create a test handler that responds with the request path
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte("OK{" + r.URL.Path + "}"))
})
// Create the server
srv, err := cfg.ToServer(
context.Background(),
extensions,
componenttest.NewNopTelemetrySettings(),
handler,
)
require.NoError(t, err)
// Create a test request
req := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
// Create a response recorder
rec := httptest.NewRecorder()
// Serve the request
srv.Handler.ServeHTTP(rec, req)
// Get the response
resp := rec.Result()
defer resp.Body.Close()
// Check the response
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
assert.Equal(t, tc.expectedOutput, string(body))
})
}
}
func TestServerMiddlewareErrors(t *testing.T) {
// Create a basic handler for testing
handler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte("OK"))
})
// Test cases for HTTP server middleware errors
httpTests := []struct {
name string
extensions map[component.ID]component.Component
config ServerConfig
errText string
}{
{
name: "extension_not_found",
extensions: map[component.ID]component.Component{},
config: ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:0",
Transport: confignet.TransportTypeTCP,
},
Middlewares: []configmiddleware.Config{
{
ID: component.MustNewID("nonexistent"),
},
},
},
errText: "failed to resolve middleware \"nonexistent\": middleware not found",
},
{
name: "get_http_handler_fails",
extensions: map[component.ID]component.Component{
component.MustNewID("errormw"): extensionmiddlewaretest.NewErr(errors.New("http middleware error")),
},
config: ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:0",
Transport: confignet.TransportTypeTCP,
},
Middlewares: []configmiddleware.Config{
{
ID: component.MustNewID("errormw"),
},
},
},
errText: "http middleware error",
},
}
for _, tc := range httpTests {
t.Run(tc.name, func(t *testing.T) {
// Trying to create the server should fail
_, err := tc.config.ToServer(
context.Background(),
tc.extensions,
componenttest.NewNopTelemetrySettings(),
handler,
)
require.Error(t, err)
assert.Contains(t, err.Error(), tc.errText)
})
}
// Test cases for gRPC server middleware errors
grpcTests := []struct {
name string
extensions map[component.ID]component.Component
config ServerConfig
errText string
}{
{
name: "grpc_extension_not_found",
extensions: map[component.ID]component.Component{},
config: ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:0",
Transport: confignet.TransportTypeTCP,
},
Middlewares: []configmiddleware.Config{
{
ID: component.MustNewID("nonexistent"),
},
},
},
errText: "failed to resolve middleware \"nonexistent\": middleware not found",
},
{
name: "get_grpc_handler_fails",
extensions: map[component.ID]component.Component{
component.MustNewID("errormw"): extensionmiddlewaretest.NewErr(errors.New("grpc middleware error")),
},
config: ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:0",
Transport: confignet.TransportTypeTCP,
},
Middlewares: []configmiddleware.Config{
{
ID: component.MustNewID("errormw"),
},
},
},
errText: "grpc middleware error",
},
}
for _, tc := range grpcTests {
t.Run(tc.name, func(t *testing.T) {
// Trying to create the server should fail
_, err := tc.config.ToServer(
context.Background(),
tc.extensions,
componenttest.NewNopTelemetrySettings(),
handler,
)
require.Error(t, err)
assert.Contains(t, err.Error(), tc.errText)
})
}
}
================================================
FILE: config/confighttp/server_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package confighttp
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/http/httptest"
"path/filepath"
"runtime"
"strconv"
"strings"
"testing"
"time"
"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/configauth"
"go.opentelemetry.io/collector/config/confignet"
"go.opentelemetry.io/collector/config/configopaque"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/config/configtls"
"go.opentelemetry.io/collector/confmap/confmaptest"
"go.opentelemetry.io/collector/confmap/xconfmap"
"go.opentelemetry.io/collector/extension"
"go.opentelemetry.io/collector/extension/extensionauth"
)
var (
_ extension.Extension = (*mockAuthServer)(nil)
_ extensionauth.Server = (*mockAuthServer)(nil)
)
type mockAuthServer struct {
component.StartFunc
component.ShutdownFunc
extensionauth.ServerAuthenticateFunc
}
func newMockAuthServer(auth func(ctx context.Context, sources map[string][]string) (context.Context, error)) extension.Extension {
return &mockAuthServer{ServerAuthenticateFunc: auth}
}
func TestHTTPServerSettingsError(t *testing.T) {
tests := []struct {
settings ServerConfig
err string
}{
{
err: "^failed to load TLS config: failed to load CA CertPool File: failed to load cert /doesnt/exist:",
settings: ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:0",
Transport: confignet.TransportTypeTCP,
},
TLS: configoptional.Some(configtls.ServerConfig{
Config: configtls.Config{
CAFile: "/doesnt/exist",
},
}),
},
},
{
err: "^failed to load TLS config: failed to load TLS cert and key: for auth via TLS, provide both certificate and key, or neither",
settings: ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:0",
Transport: confignet.TransportTypeTCP,
},
TLS: configoptional.Some(configtls.ServerConfig{
Config: configtls.Config{
CertFile: "/doesnt/exist",
},
}),
},
},
{
err: "failed to load client CA CertPool: failed to load CA /doesnt/exist:",
settings: ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:0",
Transport: confignet.TransportTypeTCP,
},
TLS: configoptional.Some(configtls.ServerConfig{
ClientCAFile: "/doesnt/exist",
}),
},
},
}
for _, tt := range tests {
t.Run(tt.err, func(t *testing.T) {
_, err := tt.settings.ToListener(context.Background())
assert.Regexp(t, tt.err, err)
})
}
}
func TestHTTPServerTLS(t *testing.T) {
tests := []struct {
name string
tlsServerCreds configoptional.Optional[configtls.ServerConfig]
tlsClientCreds *configtls.ClientConfig
hasError bool
forceHTTP1 bool
}{
{
name: "noTLS",
tlsServerCreds: configoptional.None[configtls.ServerConfig](),
tlsClientCreds: &configtls.ClientConfig{
Insecure: true,
},
},
{
name: "TLS",
tlsServerCreds: configoptional.Some(configtls.ServerConfig{
Config: configtls.Config{
CAFile: filepath.Join("testdata", "ca.crt"),
CertFile: filepath.Join("testdata", "server.crt"),
KeyFile: filepath.Join("testdata", "server.key"),
},
}),
tlsClientCreds: &configtls.ClientConfig{
Config: configtls.Config{
CAFile: filepath.Join("testdata", "ca.crt"),
},
ServerName: "localhost",
},
},
{
name: "TLS (HTTP/1.1)",
tlsServerCreds: configoptional.Some(configtls.ServerConfig{
Config: configtls.Config{
CAFile: filepath.Join("testdata", "ca.crt"),
CertFile: filepath.Join("testdata", "server.crt"),
KeyFile: filepath.Join("testdata", "server.key"),
},
}),
tlsClientCreds: &configtls.ClientConfig{
Config: configtls.Config{
CAFile: filepath.Join("testdata", "ca.crt"),
},
ServerName: "localhost",
},
forceHTTP1: true,
},
{
name: "NoServerCertificates",
tlsServerCreds: configoptional.Some(configtls.ServerConfig{
Config: configtls.Config{
CAFile: filepath.Join("testdata", "ca.crt"),
},
}),
tlsClientCreds: &configtls.ClientConfig{
Config: configtls.Config{
CAFile: filepath.Join("testdata", "ca.crt"),
},
ServerName: "localhost",
},
hasError: true,
},
{
name: "mTLS",
tlsServerCreds: configoptional.Some(configtls.ServerConfig{
Config: configtls.Config{
CAFile: filepath.Join("testdata", "ca.crt"),
CertFile: filepath.Join("testdata", "server.crt"),
KeyFile: filepath.Join("testdata", "server.key"),
},
ClientCAFile: filepath.Join("testdata", "ca.crt"),
}),
tlsClientCreds: &configtls.ClientConfig{
Config: configtls.Config{
CAFile: filepath.Join("testdata", "ca.crt"),
CertFile: filepath.Join("testdata", "client.crt"),
KeyFile: filepath.Join("testdata", "client.key"),
},
ServerName: "localhost",
},
},
{
name: "NoClientCertificate",
tlsServerCreds: configoptional.Some(configtls.ServerConfig{
Config: configtls.Config{
CAFile: filepath.Join("testdata", "ca.crt"),
CertFile: filepath.Join("testdata", "server.crt"),
KeyFile: filepath.Join("testdata", "server.key"),
},
ClientCAFile: filepath.Join("testdata", "ca.crt"),
}),
tlsClientCreds: &configtls.ClientConfig{
Config: configtls.Config{
CAFile: filepath.Join("testdata", "ca.crt"),
},
ServerName: "localhost",
},
hasError: true,
},
{
name: "WrongClientCA",
tlsServerCreds: configoptional.Some(configtls.ServerConfig{
Config: configtls.Config{
CAFile: filepath.Join("testdata", "ca.crt"),
CertFile: filepath.Join("testdata", "server.crt"),
KeyFile: filepath.Join("testdata", "server.key"),
},
ClientCAFile: filepath.Join("testdata", "server.crt"),
}),
tlsClientCreds: &configtls.ClientConfig{
Config: configtls.Config{
CAFile: filepath.Join("testdata", "ca.crt"),
CertFile: filepath.Join("testdata", "client.crt"),
KeyFile: filepath.Join("testdata", "client.key"),
},
ServerName: "localhost",
},
hasError: true,
},
}
// prepare
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sc := &ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:0",
Transport: confignet.TransportTypeTCP,
},
TLS: tt.tlsServerCreds,
}
ln, err := sc.ToListener(context.Background())
require.NoError(t, err)
startServer(t, sc, ln, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, errWrite := fmt.Fprint(w, "tt")
assert.NoError(t, errWrite)
}))
prefix := "https://"
expectedProto := "HTTP/2.0"
if tt.tlsClientCreds.Insecure {
prefix = "http://"
expectedProto = "HTTP/1.1"
}
cc := &ClientConfig{
Endpoint: prefix + ln.Addr().String(),
TLS: *tt.tlsClientCreds,
ForceAttemptHTTP2: true,
}
client, errClient := cc.ToClient(context.Background(), nil, nilProvidersSettings)
require.NoError(t, errClient)
if tt.forceHTTP1 {
expectedProto = "HTTP/1.1"
client.Transport.(*http.Transport).ForceAttemptHTTP2 = false
}
resp, errResp := client.Get(cc.Endpoint)
if tt.hasError {
require.Error(t, errResp)
} else {
require.NoError(t, errResp)
body, errRead := io.ReadAll(resp.Body)
require.NoError(t, errRead)
assert.Equal(t, "tt", string(body))
assert.Equal(t, expectedProto, resp.Proto)
}
})
}
}
func TestHTTPServerTransport(t *testing.T) {
if runtime.GOOS == "linux" {
t.Run("unix", func(t *testing.T) {
addr := "@" + t.Name() // abstract unix socket
sc := &ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: addr,
Transport: confignet.TransportTypeUnix,
},
}
ln, err := sc.ToListener(context.Background())
require.NoError(t, err)
startServer(t, sc, ln, http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}))
client := http.Client{
Transport: &http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return net.Dial("unix", addr)
},
},
Timeout: 5 * time.Second, // Set a client-level timeout
}
resp, err := client.Get("http://whatever/foo")
require.NoError(t, err)
defer resp.Body.Close()
assert.Equal(t, http.StatusOK, resp.StatusCode)
})
}
}
func TestHTTPCors(t *testing.T) {
tests := []struct {
name string
CORSConfig configoptional.Optional[CORSConfig]
allowedWorks bool
disallowedWorks bool
extraHeaderWorks bool
}{
{
name: "noCORS",
allowedWorks: false,
disallowedWorks: false,
extraHeaderWorks: false,
},
{
name: "emptyCORS",
CORSConfig: configoptional.Some(NewDefaultCORSConfig()),
allowedWorks: false,
disallowedWorks: false,
extraHeaderWorks: false,
},
{
name: "OriginCORS",
CORSConfig: configoptional.Some(CORSConfig{
AllowedOrigins: []string{"allowed-*.com"},
}),
allowedWorks: true,
disallowedWorks: false,
extraHeaderWorks: false,
},
{
name: "CacheableCORS",
CORSConfig: configoptional.Some(CORSConfig{
AllowedOrigins: []string{"allowed-*.com"},
MaxAge: 360,
}),
allowedWorks: true,
disallowedWorks: false,
extraHeaderWorks: false,
},
{
name: "HeaderCORS",
CORSConfig: configoptional.Some(CORSConfig{
AllowedOrigins: []string{"allowed-*.com"},
AllowedHeaders: []string{"ExtraHeader"},
}),
allowedWorks: true,
disallowedWorks: false,
extraHeaderWorks: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sc := &ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:0",
Transport: confignet.TransportTypeTCP,
},
CORS: tt.CORSConfig,
}
ln, err := sc.ToListener(context.Background())
require.NoError(t, err)
startServer(t, sc, ln, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
}))
url := "http://" + ln.Addr().String()
expectedStatus := http.StatusNoContent
if !tt.CORSConfig.HasValue() || len(tt.CORSConfig.Get().AllowedOrigins) == 0 {
expectedStatus = http.StatusOK
}
// Verify allowed domain gets responses that allow CORS.
verifyCorsResp(t, url, "allowed-origin.com", tt.CORSConfig, false, expectedStatus, tt.allowedWorks)
// Verify allowed domain and extra headers gets responses that allow CORS.
verifyCorsResp(t, url, "allowed-origin.com", tt.CORSConfig, true, expectedStatus, tt.extraHeaderWorks)
// Verify disallowed domain gets responses that disallow CORS.
verifyCorsResp(t, url, "disallowed-origin.com", tt.CORSConfig, false, expectedStatus, tt.disallowedWorks)
})
}
}
func TestHTTPCorsInvalidSettings(t *testing.T) {
sc := &ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:0",
Transport: confignet.TransportTypeTCP,
},
CORS: configoptional.Some(CORSConfig{AllowedHeaders: []string{"some-header"}}),
}
// This effectively does not enable CORS but should also not cause an error
s, err := sc.ToServer(
context.Background(),
nil,
componenttest.NewNopTelemetrySettings(),
http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}))
require.NoError(t, err)
require.NotNil(t, s)
require.NoError(t, s.Close())
}
func TestHTTPCorsWithSettings(t *testing.T) {
sc := &ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:0",
Transport: confignet.TransportTypeTCP,
},
CORS: configoptional.Some(CORSConfig{
AllowedOrigins: []string{"*"},
}),
Auth: configoptional.Some(AuthConfig{
Config: configauth.Config{
AuthenticatorID: mockID,
},
}),
}
extensions := map[component.ID]component.Component{
mockID: newMockAuthServer(func(ctx context.Context, _ map[string][]string) (context.Context, error) {
return ctx, errors.New("Settings failed")
}),
}
srv, err := sc.ToServer(context.Background(), extensions, componenttest.NewNopTelemetrySettings(), nil)
require.NoError(t, err)
require.NotNil(t, srv)
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodOptions, "/", http.NoBody)
req.Header.Set("Origin", "http://localhost")
req.Header.Set("Access-Control-Request-Method", http.MethodPost)
srv.Handler.ServeHTTP(rec, req)
assert.Equal(t, http.StatusNoContent, rec.Result().StatusCode)
assert.Equal(t, "*", rec.Header().Get("Access-Control-Allow-Origin"))
}
func TestHTTPServerHeaders(t *testing.T) {
tests := []struct {
name string
headers configopaque.MapList
}{
{
name: "noHeaders",
headers: nil,
},
{
name: "withHeaders",
headers: configopaque.MapList{
{Name: "x-new-header-1", Value: "value1"},
{Name: "x-new-header-2", Value: "value2"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sc := &ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:0",
Transport: confignet.TransportTypeTCP,
},
ResponseHeaders: tt.headers,
}
ln, err := sc.ToListener(context.Background())
require.NoError(t, err)
startServer(t, sc, ln, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
}))
url := "http://" + ln.Addr().String()
// Verify allowed domain gets responses that allow CORS.
verifyHeadersResp(t, url, tt.headers)
})
}
}
func verifyCorsResp(t *testing.T, url, origin string, set configoptional.Optional[CORSConfig], extraHeader bool, wantStatus int, wantAllowed bool) {
req, err := http.NewRequest(http.MethodOptions, url, http.NoBody)
require.NoError(t, err, "Error creating trace OPTIONS request: %v", err)
req.Header.Set("Origin", origin)
if extraHeader {
req.Header.Set("ExtraHeader", "foo")
req.Header.Set("Access-Control-Request-Headers", "extraheader")
}
req.Header.Set("Access-Control-Request-Method", "POST")
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err, "Error sending OPTIONS to http server")
require.NotNil(t, resp.Body)
require.NoError(t, resp.Body.Close(), "Error closing OPTIONS response body")
assert.Equal(t, wantStatus, resp.StatusCode)
gotAllowOrigin := resp.Header.Get("Access-Control-Allow-Origin")
gotAllowMethods := resp.Header.Get("Access-Control-Allow-Methods")
wantAllowOrigin := ""
wantAllowMethods := ""
wantMaxAge := ""
if wantAllowed {
wantAllowOrigin = origin
wantAllowMethods = "POST"
if set.HasValue() && set.Get().MaxAge != 0 {
wantMaxAge = strconv.Itoa(set.Get().MaxAge)
}
}
assert.Equal(t, wantAllowOrigin, gotAllowOrigin)
assert.Equal(t, wantAllowMethods, gotAllowMethods)
assert.Equal(t, wantMaxAge, resp.Header.Get("Access-Control-Max-Age"))
}
func verifyHeadersResp(t *testing.T, url string, expected configopaque.MapList) {
req, err := http.NewRequest(http.MethodGet, url, http.NoBody)
require.NoError(t, err, "Error creating request")
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err, "Error sending request to http server")
require.NotNil(t, resp.Body)
require.NoError(t, resp.Body.Close(), "Error closing response body")
assert.Equal(t, http.StatusOK, resp.StatusCode)
for k, v := range expected.Iter {
assert.Equal(t, string(v), resp.Header.Get(k))
}
}
func TestServerAuth(t *testing.T) {
// prepare
authCalled := false
sc := ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:0",
Transport: confignet.TransportTypeTCP,
},
Auth: configoptional.Some(AuthConfig{
Config: configauth.Config{
AuthenticatorID: mockID,
},
}),
}
extensions := map[component.ID]component.Component{
mockID: newMockAuthServer(func(ctx context.Context, _ map[string][]string) (context.Context, error) {
authCalled = true
return ctx, nil
}),
}
handlerCalled := false
handler := http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
handlerCalled = true
})
srv, err := sc.ToServer(context.Background(), extensions, componenttest.NewNopTelemetrySettings(), handler)
require.NoError(t, err)
// tt
srv.Handler.ServeHTTP(&httptest.ResponseRecorder{}, httptest.NewRequest(http.MethodGet, "/", http.NoBody))
// verify
assert.True(t, handlerCalled)
assert.True(t, authCalled)
}
func TestInvalidServerAuth(t *testing.T) {
sc := ServerConfig{
Auth: configoptional.Some(AuthConfig{
Config: configauth.Config{
AuthenticatorID: nonExistingID,
},
}),
}
srv, err := sc.ToServer(context.Background(), nil, componenttest.NewNopTelemetrySettings(), http.NewServeMux())
require.Error(t, err)
require.Nil(t, srv)
}
func TestFailedServerAuth(t *testing.T) {
// prepare
sc := ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:0",
Transport: confignet.TransportTypeTCP,
},
Auth: configoptional.Some(AuthConfig{
Config: configauth.Config{
AuthenticatorID: mockID,
},
}),
}
extensions := map[component.ID]component.Component{
mockID: newMockAuthServer(func(ctx context.Context, _ map[string][]string) (context.Context, error) {
return ctx, errors.New("invalid authorization")
}),
}
srv, err := sc.ToServer(context.Background(), extensions, componenttest.NewNopTelemetrySettings(), http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}))
require.NoError(t, err)
// tt
response := &httptest.ResponseRecorder{}
srv.Handler.ServeHTTP(response, httptest.NewRequest(http.MethodGet, "/", http.NoBody))
// verify
assert.Equal(t, http.StatusUnauthorized, response.Result().StatusCode)
assert.Equal(t, fmt.Sprintf("%v %s", http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized)), response.Result().Status)
}
func TestFailedServerAuthWithErrorHandler(t *testing.T) {
// prepare
sc := ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:0",
Transport: confignet.TransportTypeTCP,
},
Auth: configoptional.Some(AuthConfig{
Config: configauth.Config{
AuthenticatorID: mockID,
},
}),
}
extensions := map[component.ID]component.Component{
mockID: newMockAuthServer(func(ctx context.Context, _ map[string][]string) (context.Context, error) {
return ctx, errors.New("invalid authorization")
}),
}
eh := func(w http.ResponseWriter, _ *http.Request, err string, statusCode int) {
assert.Equal(t, http.StatusUnauthorized, statusCode)
// custom error handler uses real error string
assert.Equal(t, "invalid authorization", err)
// custom error handler changes returned status code
http.Error(w, err, http.StatusInternalServerError)
}
srv, err := sc.ToServer(context.Background(), extensions, componenttest.NewNopTelemetrySettings(), http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}), WithErrorHandler(eh))
require.NoError(t, err)
// tt
response := &httptest.ResponseRecorder{}
srv.Handler.ServeHTTP(response, httptest.NewRequest(http.MethodGet, "/", http.NoBody))
// verify
assert.Equal(t, http.StatusInternalServerError, response.Result().StatusCode)
assert.Equal(t, fmt.Sprintf("%v %s", http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)), response.Result().Status)
}
func TestServerWithErrorHandler(t *testing.T) {
// prepare
sc := ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:0",
Transport: confignet.TransportTypeTCP,
},
}
eh := func(w http.ResponseWriter, _ *http.Request, _ string, statusCode int) {
assert.Equal(t, http.StatusBadRequest, statusCode)
// custom error handler changes returned status code
http.Error(w, "invalid request", http.StatusInternalServerError)
}
srv, err := sc.ToServer(
context.Background(),
nil,
componenttest.NewNopTelemetrySettings(),
http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}),
WithErrorHandler(eh),
)
require.NoError(t, err)
// tt
response := &httptest.ResponseRecorder{}
req, err := http.NewRequest(http.MethodGet, srv.Addr, http.NoBody)
require.NoError(t, err, "Error creating request: %v", err)
req.Header.Set("Content-Encoding", "something-invalid")
srv.Handler.ServeHTTP(response, req)
// verify
assert.Equal(t, http.StatusInternalServerError, response.Result().StatusCode)
}
func TestServerWithDecoder(t *testing.T) {
// prepare
sc := NewDefaultServerConfig()
sc.NetAddr.Endpoint = "localhost:0"
decoder := func(body io.ReadCloser) (io.ReadCloser, error) {
return body, nil
}
srv, err := sc.ToServer(
context.Background(),
nil,
componenttest.NewNopTelemetrySettings(),
http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}),
WithDecoder("something-else", decoder),
)
require.NoError(t, err)
// tt
response := &httptest.ResponseRecorder{}
req, err := http.NewRequest(http.MethodGet, srv.Addr, bytes.NewBuffer([]byte("something")))
require.NoError(t, err, "Error creating request: %v", err)
req.Header.Set("Content-Encoding", "something-else")
srv.Handler.ServeHTTP(response, req)
// verify
assert.Equal(t, http.StatusOK, response.Result().StatusCode)
}
func TestServerWithDecompression(t *testing.T) {
// prepare
sc := ServerConfig{
MaxRequestBodySize: 1000, // 1 KB
}
body := []byte(strings.Repeat("a", 1000*1000)) // 1 MB
srv, err := sc.ToServer(
context.Background(),
nil,
componenttest.NewNopTelemetrySettings(),
http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
actualBody, err := io.ReadAll(req.Body)
assert.ErrorContains(t, err, "http: request body too large")
assert.Len(t, actualBody, 1000)
if err != nil {
resp.WriteHeader(http.StatusBadRequest)
} else {
resp.WriteHeader(http.StatusOK)
}
}),
)
require.NoError(t, err)
testSrv := httptest.NewServer(srv.Handler)
defer testSrv.Close()
req, err := http.NewRequest(http.MethodGet, testSrv.URL, compressZstd(t, body))
require.NoError(t, err, "Error creating request: %v", err)
req.Header.Set("Content-Encoding", "zstd")
// tt
c := http.Client{}
resp, err := c.Do(req)
require.NoError(t, err, "Error sending request: %v", err)
_, err = io.ReadAll(resp.Body)
require.NoError(t, err, "Error reading response body: %v", err)
// verifications is done mostly within the tt, but this is only a sanity check
// that we got into the tt handler
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
}
func TestDefaultMaxRequestBodySize(t *testing.T) {
tests := []struct {
name string
settings ServerConfig
expected int64
}{
{
name: "default",
settings: ServerConfig{},
expected: defaultMaxRequestBodySize,
},
{
name: "zero",
settings: ServerConfig{MaxRequestBodySize: 0},
expected: defaultMaxRequestBodySize,
},
{
name: "negative",
settings: ServerConfig{MaxRequestBodySize: -1},
expected: defaultMaxRequestBodySize,
},
{
name: "custom",
settings: ServerConfig{MaxRequestBodySize: 100},
expected: 100,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := tt.settings.ToServer(
context.Background(),
nil,
componenttest.NewNopTelemetrySettings(),
http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}),
)
require.NoError(t, err)
assert.Equal(t, tt.expected, tt.settings.MaxRequestBodySize)
})
}
}
func TestAuthWithQueryParams(t *testing.T) {
// prepare
authCalled := false
sc := ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:0",
Transport: confignet.TransportTypeTCP,
},
Auth: configoptional.Some(AuthConfig{
RequestParameters: []string{"auth"},
Config: configauth.Config{
AuthenticatorID: mockID,
},
}),
}
extensions := map[component.ID]component.Component{
mockID: newMockAuthServer(func(ctx context.Context, sources map[string][]string) (context.Context, error) {
require.Len(t, sources, 1)
assert.Equal(t, "1", sources["auth"][0])
authCalled = true
return ctx, nil
}),
}
handlerCalled := false
handler := http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
handlerCalled = true
})
srv, err := sc.ToServer(context.Background(), extensions, componenttest.NewNopTelemetrySettings(), handler)
require.NoError(t, err)
// tt
srv.Handler.ServeHTTP(&httptest.ResponseRecorder{}, httptest.NewRequest(http.MethodGet, "/?auth=1", http.NoBody))
// verify
assert.True(t, handlerCalled)
assert.True(t, authCalled)
}
func BenchmarkHTTPRequest(b *testing.B) {
tests := []struct {
name string
forceHTTP1 bool
clientPerThread bool
}{
{
name: "HTTP/2.0, shared client (like load balancer)",
forceHTTP1: false,
clientPerThread: false,
},
{
name: "HTTP/1.1, shared client (like load balancer)",
forceHTTP1: true,
clientPerThread: false,
},
{
name: "HTTP/2.0, client per thread (like single app)",
forceHTTP1: false,
clientPerThread: true,
},
{
name: "HTTP/1.1, client per thread (like single app)",
forceHTTP1: true,
clientPerThread: true,
},
}
tlsServerCreds := configoptional.Some(configtls.ServerConfig{
Config: configtls.Config{
CAFile: filepath.Join("testdata", "ca.crt"),
CertFile: filepath.Join("testdata", "server.crt"),
KeyFile: filepath.Join("testdata", "server.key"),
},
})
tlsClientCreds := &configtls.ClientConfig{
Config: configtls.Config{
CAFile: filepath.Join("testdata", "ca.crt"),
},
ServerName: "localhost",
}
sc := &ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:0",
Transport: confignet.TransportTypeTCP,
},
TLS: tlsServerCreds,
}
ln, err := sc.ToListener(context.Background())
require.NoError(b, err)
startServer(b, sc, ln, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, errWrite := fmt.Fprint(w, "tt")
assert.NoError(b, errWrite)
}))
for _, bb := range tests {
cc := &ClientConfig{
Endpoint: "https://" + ln.Addr().String(),
TLS: *tlsClientCreds,
}
b.Run(bb.name, func(b *testing.B) {
var c *http.Client
if !bb.clientPerThread {
c, err = cc.ToClient(context.Background(), nil, nilProvidersSettings)
require.NoError(b, err)
}
b.RunParallel(func(pb *testing.PB) {
if c == nil {
c, err = cc.ToClient(context.Background(), nil, nilProvidersSettings)
require.NoError(b, err)
}
if bb.forceHTTP1 {
c.Transport.(*http.Transport).ForceAttemptHTTP2 = false
}
for pb.Next() {
resp, errResp := c.Get(cc.Endpoint)
require.NoError(b, errResp)
body, errRead := io.ReadAll(resp.Body)
_ = resp.Body.Close()
require.NoError(b, errRead)
require.Equal(b, "tt", string(body))
}
c.CloseIdleConnections()
})
// Wait for connections to close before closing server to prevent log spam
<-time.After(10 * time.Millisecond)
})
}
}
func TestDefaultHTTPServerSettings(t *testing.T) {
httpServerSettings := NewDefaultServerConfig()
assert.NotNil(t, httpServerSettings.CORS)
assert.NotNil(t, httpServerSettings.TLS)
assert.Equal(t, 1*time.Minute, httpServerSettings.IdleTimeout)
assert.Equal(t, 30*time.Second, httpServerSettings.WriteTimeout)
assert.Equal(t, time.Duration(0), httpServerSettings.ReadTimeout)
assert.Equal(t, 1*time.Minute, httpServerSettings.ReadHeaderTimeout)
assert.True(t, httpServerSettings.KeepAlivesEnabled) // Default should be true (keep-alives enabled by default)
}
func TestHTTPServerKeepAlives(t *testing.T) {
tests := []struct {
name string
keepAlivesEnabled bool
expectedKeepAlives bool
}{
{
name: "KeepAlives enabled",
keepAlivesEnabled: true,
expectedKeepAlives: true,
},
{
name: "KeepAlives disabled",
keepAlivesEnabled: false,
expectedKeepAlives: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sc := &ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:0",
Transport: confignet.TransportTypeTCP,
},
KeepAlivesEnabled: tt.keepAlivesEnabled,
}
ln, err := sc.ToListener(context.Background())
require.NoError(t, err)
startServer(t, sc, ln, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
}))
// Since http.Server.disableKeepAlives is a private field and difficult to test directly,
// we'll verify the configuration was set by testing the server behavior.
// The main verification is that ToServer() succeeds without error when DisableKeepAlives is set.
resp, err := http.Get("http://" + ln.Addr().String())
require.NoError(t, err)
require.NotNil(t, resp)
_ = resp.Body.Close()
assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, tt.keepAlivesEnabled, sc.KeepAlivesEnabled)
})
}
}
func TestHTTPServerTelemetry_Tracing(t *testing.T) {
// Create a pattern route. The server name the span after the
// pattern rather than the client-specified path.
mux := http.NewServeMux()
mux.HandleFunc("/b/{bucket}/o/{objectname...}", func(http.ResponseWriter, *http.Request) {})
type testcase struct {
handler http.Handler
httpMethod string
expectedSpanName string
}
for name, tc := range map[string]testcase{
"pattern": {
handler: mux,
httpMethod: "GET",
expectedSpanName: "GET /b/{bucket}/o/{objectname...}",
},
"no_pattern": {
handler: http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}),
httpMethod: "GET",
expectedSpanName: "GET",
},
"unknown_method": {
handler: mux,
httpMethod: "FOOBAR",
expectedSpanName: "HTTP /b/{bucket}/o/{objectname...}",
},
"lowercase_method": {
handler: mux,
httpMethod: "get",
expectedSpanName: "GET /b/{bucket}/o/{objectname...}",
},
} {
t.Run(name, func(t *testing.T) {
telemetry := componenttest.NewTelemetry()
config := NewDefaultServerConfig()
config.NetAddr.Endpoint = "localhost:0"
srv, err := config.ToServer(
context.Background(),
nil,
telemetry.NewTelemetrySettings(),
tc.handler,
)
require.NoError(t, err)
done := make(chan struct{})
lis, err := config.ToListener(context.Background())
require.NoError(t, err)
go func() {
defer close(done)
_ = srv.Serve(lis)
}()
defer func() {
assert.NoError(t, srv.Close())
<-done
}()
req, err := http.NewRequest(tc.httpMethod, fmt.Sprintf("http://%s/b/bucket123/o/object456/segment", lis.Addr()), http.NoBody)
require.NoError(t, err)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
resp.Body.Close()
spans := telemetry.SpanRecorder.Ended()
require.Len(t, spans, 1)
assert.Equal(t, tc.expectedSpanName, spans[0].Name())
})
}
}
// TestUnmarshalYAMLWithMiddlewares tests that the "middlewares" field is correctly
// parsed from YAML configurations (fixing the bug where "middleware" was used instead)
func TestServerUnmarshalYAMLWithMiddlewares(t *testing.T) {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "middlewares.yaml"))
require.NoError(t, err)
// Test server configuration
var serverConfig ServerConfig
serverSub, err := cm.Sub("server")
require.NoError(t, err)
require.NoError(t, serverSub.Unmarshal(&serverConfig))
// Validate the server configuration using reflection-based validation
require.NoError(t, xconfmap.Validate(&serverConfig), "Server configuration should be valid")
assert.Equal(t, "0.0.0.0:4318", serverConfig.NetAddr.Endpoint)
require.Len(t, serverConfig.Middlewares, 2)
assert.Equal(t, component.MustNewID("careful_middleware"), serverConfig.Middlewares[0].ID)
assert.Equal(t, component.MustNewID("support_middleware"), serverConfig.Middlewares[1].ID)
}
// TestUnmarshalYAMLComprehensiveConfig tests the complete configuration example
// to ensure all fields including middlewares are parsed correctly
func TestServerUnmarshalYAMLComprehensiveConfig(t *testing.T) {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
require.NoError(t, err)
// Test server configuration
var serverConfig ServerConfig
serverSub, err := cm.Sub("server")
require.NoError(t, err)
require.NoError(t, serverSub.Unmarshal(&serverConfig))
// Validate the server configuration using reflection-based validation
require.NoError(t, xconfmap.Validate(&serverConfig), "Server configuration should be valid")
// Verify basic fields
assert.Equal(t, "0.0.0.0:4318", serverConfig.NetAddr.Endpoint)
assert.Equal(t, 30*time.Second, serverConfig.ReadTimeout)
assert.Equal(t, 10*time.Second, serverConfig.ReadHeaderTimeout)
assert.Equal(t, 30*time.Second, serverConfig.WriteTimeout)
assert.Equal(t, 120*time.Second, serverConfig.IdleTimeout)
assert.True(t, serverConfig.KeepAlivesEnabled) // Should be true as configured in config.yaml
assert.Equal(t, int64(33554432), serverConfig.MaxRequestBodySize)
assert.True(t, serverConfig.IncludeMetadata)
// Verify TLS configuration
assert.Equal(t, "/path/to/server.crt", serverConfig.TLS.Get().CertFile)
assert.Equal(t, "/path/to/server.key", serverConfig.TLS.Get().KeyFile)
assert.Equal(t, "/path/to/ca.crt", serverConfig.TLS.Get().CAFile)
assert.Equal(t, "/path/to/client-ca.crt", serverConfig.TLS.Get().ClientCAFile)
// Verify CORS configuration
expectedOrigins := []string{"https://example.com", "https://*.test.com"}
assert.Equal(t, expectedOrigins, serverConfig.CORS.Get().AllowedOrigins)
corsHeaders := []string{"Content-Type", "Accept"}
assert.Equal(t, corsHeaders, serverConfig.CORS.Get().AllowedHeaders)
assert.Equal(t, 7200, serverConfig.CORS.Get().MaxAge)
// Verify response headers
expectedResponseHeaders := configopaque.MapList{
{Name: "Server", Value: "OpenTelemetry-Collector"},
{Name: "X-Flavor", Value: "apple"},
}
assert.Equal(t, expectedResponseHeaders, serverConfig.ResponseHeaders)
// Verify compression algorithms
expectedAlgorithms := []string{"", "gzip", "zstd", "zlib", "snappy", "deflate"}
assert.Equal(t, expectedAlgorithms, serverConfig.CompressionAlgorithms)
// Verify middlewares
require.Len(t, serverConfig.Middlewares, 3)
assert.Equal(t, component.MustNewID("server_middleware1"), serverConfig.Middlewares[0].ID)
assert.Equal(t, component.MustNewID("server_middleware2"), serverConfig.Middlewares[1].ID)
assert.Equal(t, component.MustNewID("server_middleware3"), serverConfig.Middlewares[2].ID)
}
func startServer(tb testing.TB, sc *ServerConfig, ln net.Listener, h http.Handler) {
s, err := sc.ToServer(tb.Context(), nil, componenttest.NewNopTelemetrySettings(), h)
require.NoError(tb, err)
tb.Cleanup(func() {
require.NoError(tb, s.Close())
})
go func() { _ = s.Serve(ln) }()
}
================================================
FILE: config/confighttp/testdata/ca.crt
================================================
-----BEGIN CERTIFICATE-----
MIIDNjCCAh4CCQCBqDI24JacNTANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJB
VTESMBAGA1UECAwJQXVzdHJhbGlhMQ8wDQYDVQQHDAZTeWRuZXkxEjAQBgNVBAoM
CU15T3JnTmFtZTEVMBMGA1UEAwwMTXlDb21tb25OYW1lMB4XDTIyMDgwMzA0MTAx
N1oXDTMyMDczMTA0MTAxN1owXTELMAkGA1UEBhMCQVUxEjAQBgNVBAgMCUF1c3Ry
YWxpYTEPMA0GA1UEBwwGU3lkbmV5MRIwEAYDVQQKDAlNeU9yZ05hbWUxFTATBgNV
BAMMDE15Q29tbW9uTmFtZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
ANFDGZCSxNdaJQ1q7P5qVAZDKCj+3ClpFWAVT5HYDlOqgIjYRZ45/YGfKsDbK6rj
/+v5xTnTZt8e8kRfPgrZTBtPtefu6tKwxYYr3sSCR3TU4bX4dIM9ZwrFaTrPP1hk
UXR8zlk7F6gcWS+WX7LdupMs5SdZIePhkzpkxYIBatdRMf7w2f5v6M3UnOrCoyz0
YPvvyZKq5zo9uBlVkrUL/QDrOB5yYjith7l8FkLHAirGTaszfF+8pZwzZn4ykVbn
eQQs1g6ujR0DgQh/k6A6XDQfg4JWQqNt3kkiO7NPIHTrl+W/nKdqve864ECAHSM2
NIgGtQqVavPKD6pr15V328cCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEALZuCeu8U
yjJlR+BbDqiZw7/EBpGgTX4mv9CofJc9ErxFGJTYiaLZ1Jf3bwE+0G9ym8UfxKG8
9xCYmoVbEQhgLzYDpYPxkKi5X7RUMw9fKlbRqwy2Ek1mDnSIYPRalhfOXBT5E652
OUeILLRJlAPL3SrALjJM5Jjn4pkwankE53mfU4LpC7xWOjuwkSPRor1XCwoAZMTz
EZsZGUQf5M69ZAy0wWHu4C94rlgD37hybUEzhsr9UiK2v1Gn3a7BSAgtkbD0OIe5
BzCu+UHQO4u841SbXn4q8aO1idaR8UhPAqfVno+L7ZmHRGsgvMk1vlMbroIIYaAz
2LQP6IwYUWRM9Q==
-----END CERTIFICATE-----
================================================
FILE: config/confighttp/testdata/client.crt
================================================
-----BEGIN CERTIFICATE-----
MIIDVTCCAj2gAwIBAgIJANt5fkUlfxyOMA0GCSqGSIb3DQEBCwUAMF0xCzAJBgNV
BAYTAkFVMRIwEAYDVQQIDAlBdXN0cmFsaWExDzANBgNVBAcMBlN5ZG5leTESMBAG
A1UECgwJTXlPcmdOYW1lMRUwEwYDVQQDDAxNeUNvbW1vbk5hbWUwHhcNMjIwODAz
MDQxMDE3WhcNMzIwNzMxMDQxMDE3WjBdMQswCQYDVQQGEwJBVTESMBAGA1UECAwJ
QXVzdHJhbGlhMQ8wDQYDVQQHDAZTeWRuZXkxEjAQBgNVBAoMCU15T3JnTmFtZTEV
MBMGA1UEAwwMTXlDb21tb25OYW1lMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEApmnIUees+b/V5gEgEOwyjPo9xeejzd/DSo02x/syauoU4mhINwa8cuB+
nvGhCUY5w+bZfOnWhY03wjnqM1Ay+sg7yHZgqeHclzQh5d39ojeaMa3x8c3IQsKZ
j/UTyYmkX6Gap8Z8fk1ucM43clM3nUUf3THzeo0X3MTm7FIs2OCxcbCYo8GUo6GV
rn7nhutOrYgyPrgo7bi8xF2sJRRKC/C7MBCeNaJlgj53jB93gF15/ZxPbAQCtI5R
J+3YrKp987RSuXtDeqm1zSCmV6LnT2vVsSNmnDdvWHQF7SqgauqT0Z7gm4/M/xXw
ge6GJaENOV1wIUsfbYj6UCMt6tZpjQIDAQABoxgwFjAUBgNVHREEDTALgglsb2Nh
bGhvc3QwDQYJKoZIhvcNAQELBQADggEBAHwMOQIz++7JPWRb3xHTkt6jIlAw/zVl
3UmdXMqEEscLGcjmDqu0u1qPGlS48Ukb4efAQsp+/2KLQex7jdl3r4p0+3O530AY
MEtlK2oH1uE3KDMyswA0INrGeZpHRP2BI6QJGbxcJN22MuHFmKwPBJlltgVtK0Gt
IhG/cRJnVMcX+iBdsxygW5u7P8MpC8Eoved/F8yKB66uJ4LyZC5KL9RG58Y0JJin
KDvpzSGLMnHgnuJlGOGz/uUYXZ4IX5g95yKiDZ9+GkVo+DRHAUNEUjfQSRujmO10
INTHs4/hd0MI6NaHXwObvmOsa8VRRVWa995O5Y9rZMYg/4mjm1Axcm4=
-----END CERTIFICATE-----
================================================
FILE: config/confighttp/testdata/client.key
================================================
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEApmnIUees+b/V5gEgEOwyjPo9xeejzd/DSo02x/syauoU4mhI
Nwa8cuB+nvGhCUY5w+bZfOnWhY03wjnqM1Ay+sg7yHZgqeHclzQh5d39ojeaMa3x
8c3IQsKZj/UTyYmkX6Gap8Z8fk1ucM43clM3nUUf3THzeo0X3MTm7FIs2OCxcbCY
o8GUo6GVrn7nhutOrYgyPrgo7bi8xF2sJRRKC/C7MBCeNaJlgj53jB93gF15/ZxP
bAQCtI5RJ+3YrKp987RSuXtDeqm1zSCmV6LnT2vVsSNmnDdvWHQF7SqgauqT0Z7g
m4/M/xXwge6GJaENOV1wIUsfbYj6UCMt6tZpjQIDAQABAoIBAEAIGfUyAMPEhchP
jIgWakkGjLhWrhesTtejyH1gcYDj+w828vqBVAeby/zamo0YAWgYrny6+TlAIkFQ
yYXfCQ6n9yDmM8GKT7e6boSlS0+ct28AMEVLWhAeErpqoad9l8rYQsrlu8dZgfJT
1s/dp1uTWnRhIP95xMHE3dn2sJzuEV9HunkgS6re6YyTqNZFLAPVn33wDRVSaxH6
3VJG6T3Y2xs77AKTKz5Mji/gqK3XFAed0rehowgS4A8mEUrZt9gzioRlmV2tshvx
st5KDw7lGEZKbgGB/+cwLJhHgEK7KyJRsR/shtDyXKRlEC19qV6zSupz9BSQNqRN
+vWQ070CgYEA0NTYgdxz8msVMXtLCectwMLfLU99H/frGCvjBv0wLni2XF+eS9gX
D6W4nFbDA+wZfSBMpEqriHvrDpMk7RN3Q9rAKtqeyzDboWiJRRT4DZeRz9o+WEt3
+wDP8W0dS7IRw8Jnrsj9JgmY35fGXZDUWZRnPuvrpolojUnMTKNK/ccCgYEAzAA1
roKRNOn08P3SZ0jCUA0O+Ke5sycV6QPoLk5qQnL5eEjOlvd+6U128DAf1DW5O6/8
C0YsTMOY/sQxXliSt/iygaJVoo7Tt1L60ctNY4KX5EiWs2AgvvTu89ESwbhvvfvw
Xt7PNfa0eBSgC+Lrg5y2Lahs8DijWkTbxo3ebgsCgYEAmyLjzGUnRZnDXsUHE85H
sQGTpid8/rjAT26a82A34O4QG0N1Z0aaqycjpBDYQxusO8Y46XwHPhdAoc0yC2UA
nsntJGjQuoYLQzdTcpyHQiGtUsoAsrst4KvTzriOoOMiS1kqiTAKz60lgkVQOcYT
2pBiut2sbEV8BCokuXI9jZUCgYBbg9yRJNGvQyU21ycEXoeNEc6djeColegmWDJY
U6Unmhx/8Wl8IBs23iF1LqGYuWEXfaM8C4bkCPshjzH2eRWYomCx9vkjq58epoMO
in11Hqi1KDsyzPTjtU1c43Xeoba/K75xUNL0CnB7TgVeT7YHnM29PclhGodtf2Z4
dDxMcQKBgQC46rtyosn/2uR+88DCWSVTnc4cA6tOEB8WP7Z7mk8yOv1CGgyP4187
iDPL+91O4C5sT02MFuwElCmxuO1RQQhVTGzIxPd9RQ7t+l1PorTeodYF/ezrRl47
xuxp4nF50ThlTf1AuhQxpPC1JsXqkH3d582ZLErzrgijMYxk5sYJRA==
-----END RSA PRIVATE KEY-----
================================================
FILE: config/confighttp/testdata/config.yaml
================================================
# Comprehensive HTTP configuration example showing all available options
# with middleware configurations (using the new "middlewares" field)
# HTTP Client Configuration
client:
# The target URL to send data to
endpoint: "http://example.com:4318/v1/traces"
# Proxy URL setting for the collector
proxy_url: "http://proxy.example.com:8080"
# TLS configuration
tls:
insecure: false
cert_file: "/path/to/client.crt"
key_file: "/path/to/client.key"
ca_file: "/path/to/ca.crt"
server_name_override: "example.com"
insecure_skip_verify: false
# HTTP client buffer sizes
read_buffer_size: 4096
write_buffer_size: 4096
# Request timeout
timeout: 30s
# Custom headers
headers:
"User-Agent": "OpenTelemetry-Collector/1.0"
"X-Custom-Header": "custom-value"
# Compression setting
compression: "gzip"
# Disable HTTP/2
disable_keep_alives: false
http2_read_idle_timeout: 10s
http2_ping_timeout: 15s
# Maximum idle connections
max_idle_conns: 100
max_idle_conns_per_host: 10
max_conns_per_host: 50
idle_conn_timeout: 90s
# Authentication configuration
auth:
authenticator: "oauth2client"
# Cookies configuration
cookies:
enabled: true
# Middlewares configuration (note: plural "middlewares")
middlewares:
- id: "middleware1"
- id: "middleware2"
# HTTP Server Configuration
server:
# Network endpoint configuration
endpoint: "0.0.0.0:4318"
transport: tcp
# TLS configuration
tls:
cert_file: "/path/to/server.crt"
key_file: "/path/to/server.key"
ca_file: "/path/to/ca.crt"
client_ca_file: "/path/to/client-ca.crt"
reload_interval: 24h
# CORS configuration
cors:
allowed_origins:
- "https://example.com"
- "https://*.test.com"
allowed_headers:
- "Content-Type"
- "Accept"
max_age: 7200
# Authentication configuration
auth:
authenticator: "basic"
# Server timeouts
read_timeout: 30s
read_header_timeout: 10s
write_timeout: 30s
idle_timeout: 120s
# HTTP keep-alives configuration
keep_alives_enabled: true
# Maximum request size
max_request_body_size: 33554432 # 32MB
# Include metadata in the context
include_metadata: true
# Response headers to add to every response
response_headers:
"Server": "OpenTelemetry-Collector"
"X-Flavor": "apple"
# Compression algorithms supported by the server
compression_algorithms: ["", "gzip", "zstd", "zlib", "snappy", "deflate"]
# Middlewares configuration (note: plural "middlewares")
middlewares:
- id: "server_middleware1"
- id: "server_middleware2"
- id: "server_middleware3"
================================================
FILE: config/confighttp/testdata/middlewares.yaml
================================================
# Simple middleware-focused test configuration
client:
endpoint: "http://localhost:4318/v1/traces"
middlewares:
- id: "fancy_middleware"
- id: "careful_middleware"
server:
endpoint: "0.0.0.0:4318"
transport: tcp
middlewares:
- id: "careful_middleware"
- id: "support_middleware"
================================================
FILE: config/confighttp/testdata/server.crt
================================================
-----BEGIN CERTIFICATE-----
MIIDVTCCAj2gAwIBAgIJANt5fkUlfxyNMA0GCSqGSIb3DQEBCwUAMF0xCzAJBgNV
BAYTAkFVMRIwEAYDVQQIDAlBdXN0cmFsaWExDzANBgNVBAcMBlN5ZG5leTESMBAG
A1UECgwJTXlPcmdOYW1lMRUwEwYDVQQDDAxNeUNvbW1vbk5hbWUwHhcNMjIwODAz
MDQxMDE3WhcNMzIwNzMxMDQxMDE3WjBdMQswCQYDVQQGEwJBVTESMBAGA1UECAwJ
QXVzdHJhbGlhMQ8wDQYDVQQHDAZTeWRuZXkxEjAQBgNVBAoMCU15T3JnTmFtZTEV
MBMGA1UEAwwMTXlDb21tb25OYW1lMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAupEXxNIX+2k0PC/yLfuWSbFr22eLwYSYkPDydFENKYID905hcWqaqr4o
u1Fu8nofbeKrpTgmx7lwXB5VIkdNcnwLyt40nPETGN2m0vBa7Bm6z/KJTW0iHmTz
5fVzt9rggnRTRSovNy43weKoZXHcl/lmGbGy+lqQ69jJTT9yXipkMTIkscYgSHK3
GVxUhrsBc/mZyBLSj3Tpt3Az71N4npTp6XYbgx4MhllbH9xFlw2hLs1DU+vwJhSE
w99UrLhRz8L3ePmShdkySD9QZxCKD5euiumVfPLfzWV2JmLejC3MD3sd5ql+5itv
WrnYPcky3OclXcnZgDo+ZloWLYPWhQIDAQABoxgwFjAUBgNVHREEDTALgglsb2Nh
bGhvc3QwDQYJKoZIhvcNAQELBQADggEBAJGfixdI6T6dxb37fJPNa0Uerq5aZPb9
GLJg3CqkZ4yjfsveZ91lH7fRPRjXld3aMb6978kpoczGYJeAdY8j0BPvA+UfsuYO
bzm6HUgiTjyCnLPQIuAlzuYDsBQhzz6gdRclgnuQ8JO/xTDIxVLJDuueannqEn9x
s/RQ5PeWf0D+zvSu4p8UENKfaPqoI5q7nb93IHL38PMDfB7n4oEDIkG0zfxX/zYI
h5ZhzlWSscfCRE4Hdcb8RcSuzwI2JzidMY/ZXUr20iNuCSbv4zhsoDASk7Ud+yj4
d4JkdOA4NbaT8/aR+Ectc2ItBc2lb0xzEqQnjg3zRQjEzeryHN0hxWU=
-----END CERTIFICATE-----
================================================
FILE: config/confighttp/testdata/server.key
================================================
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAupEXxNIX+2k0PC/yLfuWSbFr22eLwYSYkPDydFENKYID905h
cWqaqr4ou1Fu8nofbeKrpTgmx7lwXB5VIkdNcnwLyt40nPETGN2m0vBa7Bm6z/KJ
TW0iHmTz5fVzt9rggnRTRSovNy43weKoZXHcl/lmGbGy+lqQ69jJTT9yXipkMTIk
scYgSHK3GVxUhrsBc/mZyBLSj3Tpt3Az71N4npTp6XYbgx4MhllbH9xFlw2hLs1D
U+vwJhSEw99UrLhRz8L3ePmShdkySD9QZxCKD5euiumVfPLfzWV2JmLejC3MD3sd
5ql+5itvWrnYPcky3OclXcnZgDo+ZloWLYPWhQIDAQABAoIBAF8u/01vUsT126yJ
WamUHgzi9AAwR+EnYR8xjsFBSNHQf22BE73lgZtzARzwYwZawAY0CxZ0G3TyaxzU
bOLcNese1nVeAMHBTNj23NHpxrmGNwU43EwgTbPsFXNRUwSOKtTjvEghSY2Bivjk
Rr3a5YyztR+OxZ1s71skcy9yG0tmveGRQS2Gn3SrLgrX7PjgUULpd7TNkWlAyTB7
sFvVeHVEv8AcFDvHCBvlNJvMG2WT7kD4vjbRc0V1y89D4wO3kNVumajNjiSUopsU
zFe1vegcSLIgf1i2QEtXLob6mrQJplDA3G8oQX4FF0Oovk/bLwzC6IHNeFOo5eFM
TfGzd9ECgYEA6Ixx+2Bai2zdSz19NVaQEksDVdT/ivorgYGpIllLOwjU3ZCIEs2J
iNNPlIX9uFcwIGcfoHsU66iUWrwprfnbloezq5aPdHuhW2PE+JUEBqzmQDy+ax/i
HI2IaD42+OJY5RdRfS7cvczIFrW9XECCTkhr08CxZTO7W7VCP0OnpPMCgYEAzWGQ
hHgpyj5kfco/KlRrK6bQBkhbqWkbkoZohVL6hJ2UxC/NBFS3fWYQ2LFyFPBdu+lI
RXkDxroCQJfWGNAe4PlbKcWh5Q9Ps/s8wl0DIR/KM2yhzE2GqfDC6pQ3xFpizbcm
jl+AL2U6Y9Et1nvPjZKo6STf5yrEdIuHgaq61KcCgYEA4g/tmf2/53vr4AGlXx2I
LpBHbMADrzmk41+FaMO/M2NRcxXWgdjW03EAEpTy4am4Ojelch9UZgZaOZ5jMiIL
Slke2zYgvI6WfD4Ps8tAv7CCoH2sanzzFOitaxDX5bg7zHCPog7VPZj+Bb2kmDKJ
ucoDMDVI/eV9RBh/jvqY1OsCgYEAx5iYxVSucGFgcis6Zd3y5VJRermZczO12xmK
vH9e/cDTUjKOUTYvuMuXdbBFiXnr7nIRjYrFE72z8KhfJnAkgklzwk3SP3U45VY1
v0J7hxaJAJ8DQzTYuZFFLIptBAM/YGMtMlI3llgPffBNVtOuawzr4OC4RMV4dTcg
svCEb6MCgYEAnRx87p5bAgDVug3pdFI7qRi5Tgxa25c5GsFwhmmr3EgtxKQKLFIH
G6KOWJBpFePVrz3PH71d+J1mB/g8GqzpHBuYTBDD3hyz8iKHN+pmoM4m2/tTqKRY
26XUGOUIxnksgsw2O2R9AASrEm4hhAg5AxmANgqOIZijIcHWF8q9QpE=
-----END RSA PRIVATE KEY-----
================================================
FILE: config/confighttp/xconfighttp/Makefile
================================================
include ../../../Makefile.Common
================================================
FILE: config/confighttp/xconfighttp/go.mod
================================================
module go.opentelemetry.io/collector/config/confighttp/xconfighttp
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.opentelemetry.io/collector/config/confighttp v0.148.0
go.opentelemetry.io/collector/config/confignet v1.54.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0
go.opentelemetry.io/otel/sdk v1.42.0
go.opentelemetry.io/otel/trace v1.42.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/go-tpm v0.9.8 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.18.4 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pierrec/lz4/v4 v4.1.26 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rs/cors v1.11.1 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/client v1.54.0 // indirect
go.opentelemetry.io/collector/component v1.54.0 // indirect
go.opentelemetry.io/collector/config/configauth v1.54.0 // indirect
go.opentelemetry.io/collector/config/configcompression v1.54.0 // indirect
go.opentelemetry.io/collector/config/configmiddleware v1.54.0 // indirect
go.opentelemetry.io/collector/config/configopaque v1.54.0 // indirect
go.opentelemetry.io/collector/config/configoptional v1.54.0 // indirect
go.opentelemetry.io/collector/config/configtls v1.54.0 // indirect
go.opentelemetry.io/collector/confmap v1.54.0 // indirect
go.opentelemetry.io/collector/confmap/xconfmap v0.148.0 // indirect
go.opentelemetry.io/collector/extension/extensionauth v1.54.0 // indirect
go.opentelemetry.io/collector/extension/extensionmiddleware v0.148.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/pdata v1.54.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/config/confighttp => ../../confighttp
replace go.opentelemetry.io/collector/client => ../../../client
replace go.opentelemetry.io/collector/consumer => ../../../consumer
replace go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest => ../../../extension/extensionauth/extensionauthtest
replace go.opentelemetry.io/collector/component/componenttest => ../../../component/componenttest
replace go.opentelemetry.io/collector/config/configauth => ../../configauth
replace go.opentelemetry.io/collector/config/configoptional => ../../configoptional
replace go.opentelemetry.io/collector/pdata => ../../../pdata
replace go.opentelemetry.io/collector/extension/extensionauth => ../../../extension/extensionauth
replace go.opentelemetry.io/collector/config/configopaque => ../../configopaque
replace go.opentelemetry.io/collector/component => ../../../component
replace go.opentelemetry.io/collector/extension => ../../../extension
replace go.opentelemetry.io/collector/config/configtls => ../../configtls
replace go.opentelemetry.io/collector/config/configcompression => ../../configcompression
replace go.opentelemetry.io/collector/featuregate => ../../../featuregate
replace go.opentelemetry.io/collector/extension/extensionmiddleware => ../../../extension/extensionmiddleware
replace go.opentelemetry.io/collector/config/configmiddleware => ../../configmiddleware
replace go.opentelemetry.io/collector/config/confignet => ../../confignet
replace go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest => ../../../extension/extensionmiddleware/extensionmiddlewaretest
replace go.opentelemetry.io/collector/confmap => ../../../confmap
replace go.opentelemetry.io/collector/confmap/xconfmap => ../../../confmap/xconfmap
replace go.opentelemetry.io/collector/internal/testutil => ../../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../../internal/componentalias
================================================
FILE: config/confighttp/xconfighttp/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f h1:RJ+BDPLSHQO7cSjKBqjPJSbi1qfk9WcsjQDtZiw3dZw=
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f/go.mod h1:VHbbch/X4roIY22jL1s3qRbZhCiRIgUAF/PdSUcx2io=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/go-tpm-tools v0.4.7 h1:J3ycC8umYxM9A4eF73EofRZu4BxY0jjQnUnkhIBbvws=
github.com/google/go-tpm-tools v0.4.7/go.mod h1:gSyXTZHe3fgbzb6WEGd90QucmsnT1SRdlye82gH8QjQ=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY=
github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: config/confighttp/xconfighttp/metadata.yaml
================================================
type: config/confighttp/xconfighttp
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: config/confighttp/xconfighttp/options.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xconfighttp // import "go.opentelemetry.io/collector/config/confighttp/xconfighttp"
import (
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/config/confighttp/internal"
)
// WithOtelHTTPOptions allows providing (or overriding) options passed
// to the otelhttp.NewHandler() function.
//
// This is located in the experimental sub-package because the otelhttp library
// has not reached v1.x yet and exposing its types in confighttp public API
// could lead to breaking changes in the future.
// See https://github.com/open-telemetry/opentelemetry-collector/pull/11769
func WithOtelHTTPOptions(httpopts ...otelhttp.Option) confighttp.ToServerOption {
return internal.ToServerOptionFunc(func(opts *internal.ToServerOptions) {
opts.OtelhttpOpts = append(opts.OtelhttpOpts, httpopts...)
})
}
================================================
FILE: config/confighttp/xconfighttp/options_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xconfighttp
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/sdk/trace/tracetest"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/config/confignet"
)
func TestServerWithOtelHTTPOptions(t *testing.T) {
// prepare
sc := confighttp.ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:0",
Transport: confignet.TransportTypeTCP,
},
}
telemetry := componenttest.NewNopTelemetrySettings()
tp, te := tracerProvider(t)
telemetry.TracerProvider = tp
srv, err := sc.ToServer(
context.Background(),
nil,
telemetry,
http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}),
WithOtelHTTPOptions(
otelhttp.WithSpanNameFormatter(func(_ string, r *http.Request) string {
return "example" + r.URL.Path
}),
otelhttp.WithFilter(func(r *http.Request) bool {
return r.URL.Path != "/foobar"
}),
),
)
require.NoError(t, err)
for _, path := range []string{"/path", "/foobar"} {
response := &httptest.ResponseRecorder{}
req, err := http.NewRequest(http.MethodGet, srv.Addr+path, http.NoBody)
require.NoError(t, err)
srv.Handler.ServeHTTP(response, req)
assert.Equal(t, http.StatusOK, response.Result().StatusCode)
}
spans := te.GetSpans().Snapshots()
assert.Len(t, spans, 1, "the request to /foobar should not be traced")
assert.Equal(t, "example/path", spans[0].Name())
}
func tracerProvider(t *testing.T) (trace.TracerProvider, *tracetest.InMemoryExporter) {
exporter := tracetest.NewInMemoryExporter()
tp := sdktrace.NewTracerProvider(
sdktrace.WithSampler(sdktrace.AlwaysSample()),
sdktrace.WithSyncer(exporter),
)
t.Cleanup(func() {
assert.NoError(t, tp.Shutdown(context.Background()))
})
return tp, exporter
}
================================================
FILE: config/configmiddleware/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: config/configmiddleware/README.md
================================================
# OpenTelemetry Collector Middleware Configuration
This package implements a configuration struct for referring to
[middleware extensions](../../extension/extensionmiddleware/README.md).
## Overview
The `configmiddleware` package defines a `Config` type that
allows components to configure middleware extensions, typically as
an ordered list.
This support is built in for push-based receivers configured through
`confighttp` and `configgrpc`, as for example in the OTLP receiver:
```yaml
receivers:
otlp:
protocols:
http:
middlewares:
- id: limitermiddleware
```
## Methods
The package provides four key methods to retrieve appropriate middleware handlers:
1. **GetHTTPClientRoundTripper**: Obtains a function to wrap an HTTP client with a middleware extension via a `http.RoundTripper`.
2. **GetHTTPServerHandler**: Obtains a function to wrap an HTTP server with a middleware extension via a `http.Handler`.
3. **GetGRPCClientOptions**: Obtains a `[]grpc.DialOption` that configure a middleware extension for gRPC clients.
4. **GetGRPCServerOptions**: Obtains a `[]grpc.ServerOption` that configure a middleware extension for gRPC servers.
These functions are typically called during Start() by a component,
passing the `component.Host` extensions.
An error is returned if the named extension cannot be found.
================================================
FILE: config/configmiddleware/config.schema.yaml
================================================
$defs:
config:
description: Middleware defines the extension ID for a middleware component.
type: object
properties:
id:
description: ID specifies the name of the extension to use.
type: string
x-customType: go.opentelemetry.io/collector/component.ID
================================================
FILE: config/configmiddleware/configmiddleware.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package configmiddleware implements a configuration struct to
// name middleware extensions.
package configmiddleware // import "go.opentelemetry.io/collector/config/configmiddleware"
import (
"context"
"errors"
"fmt"
"google.golang.org/grpc"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/extension/extensionmiddleware"
)
var (
errMiddlewareNotFound = errors.New("middleware not found")
errNotHTTPServer = errors.New("requested extension is not an HTTP server middleware")
errNotGRPCServer = errors.New("requested extension is not a gRPC server middleware")
errNotHTTPClient = errors.New("requested extension is not an HTTP client middleware")
errNotGRPCClient = errors.New("requested extension is not a gRPC client middleware")
)
// Middleware defines the extension ID for a middleware component.
type Config struct {
// ID specifies the name of the extension to use.
ID component.ID `mapstructure:"id,omitempty"`
// prevent unkeyed literal initialization
_ struct{}
}
// GetHTTPClientRoundTripper attempts to select the appropriate
// extensionmiddleware.HTTPClient from the map of extensions, and
// returns the HTTP client wrapper function. If a middleware is not
// found, an error is returned. This should only be used by HTTP
// clients.
func (m Config) GetHTTPClientRoundTripper(ctx context.Context, extensions map[component.ID]component.Component) (extensionmiddleware.WrapHTTPRoundTripperFunc, error) {
if ext, found := extensions[m.ID]; found {
if client, ok := ext.(extensionmiddleware.HTTPClient); ok {
return client.GetHTTPRoundTripper(ctx)
}
return nil, errNotHTTPClient
}
return nil, fmt.Errorf("failed to resolve middleware %q: %w", m.ID, errMiddlewareNotFound)
}
// GetHTTPServerHandler attempts to select the appropriate
// extensionmiddleware.HTTPServer from the map of extensions, and
// returns the http.Handler wrapper function. If a middleware is not
// found, an error is returned. This should only be used by HTTP
// servers.
func (m Config) GetHTTPServerHandler(ctx context.Context, extensions map[component.ID]component.Component) (extensionmiddleware.WrapHTTPHandlerFunc, error) {
if ext, found := extensions[m.ID]; found {
if server, ok := ext.(extensionmiddleware.HTTPServer); ok {
return server.GetHTTPHandler(ctx)
}
return nil, errNotHTTPServer
}
return nil, fmt.Errorf("failed to resolve middleware %q: %w", m.ID, errMiddlewareNotFound)
}
// GetGRPCClientOptions attempts to select the appropriate
// extensionmiddleware.GRPCClient from the map of extensions, and
// returns the gRPC dial options. If a middleware is not found, an
// error is returned. This should only be used by gRPC clients.
func (m Config) GetGRPCClientOptions(ctx context.Context, extensions map[component.ID]component.Component) ([]grpc.DialOption, error) {
if ext, found := extensions[m.ID]; found {
if client, ok := ext.(extensionmiddleware.GRPCClient); ok {
return client.GetGRPCClientOptions(ctx)
}
return nil, errNotGRPCClient
}
return nil, fmt.Errorf("failed to resolve middleware %q: %w", m.ID, errMiddlewareNotFound)
}
// GetGRPCServerOptions attempts to select the appropriate
// extensionmiddleware.GRPCServer from the map of extensions, and
// returns the gRPC server options. If a middleware is not found, an
// error is returned. This should only be used by gRPC servers.
func (m Config) GetGRPCServerOptions(ctx context.Context, extensions map[component.ID]component.Component) ([]grpc.ServerOption, error) {
if ext, found := extensions[m.ID]; found {
if server, ok := ext.(extensionmiddleware.GRPCServer); ok {
return server.GetGRPCServerOptions(ctx)
}
return nil, errNotGRPCServer
}
return nil, fmt.Errorf("failed to resolve middleware %q: %w", m.ID, errMiddlewareNotFound)
}
================================================
FILE: config/configmiddleware/configmiddleware_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configmiddleware
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/extension"
"go.opentelemetry.io/collector/extension/extensionmiddleware"
"go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest"
)
var testID = component.MustNewID("test")
type mockWrongType struct {
component.StartFunc
component.ShutdownFunc
}
func TestConfig_GetHTTPServerHandler(t *testing.T) {
ctx := context.Background()
tests := []struct {
name string
middleware Config
extensions map[component.ID]component.Component
wantErr error
}{
{
name: "found_and_valid",
middleware: Config{
ID: testID,
},
extensions: map[component.ID]component.Component{
testID: extensionmiddlewaretest.NewNop(),
},
wantErr: nil,
},
{
name: "middleware_not_found",
middleware: Config{
ID: testID,
},
extensions: map[component.ID]component.Component{},
wantErr: errMiddlewareNotFound,
},
{
name: "middleware_wrong_type",
middleware: Config{
ID: testID,
},
extensions: map[component.ID]component.Component{
testID: mockWrongType{},
},
wantErr: errNotHTTPServer,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
value, err := tt.middleware.GetHTTPServerHandler(ctx, tt.extensions)
if tt.wantErr != nil {
require.ErrorIs(t, err, tt.wantErr)
} else {
require.NoError(t, err)
require.NotNil(t, value)
}
})
}
}
func TestConfig_GetHTTPClientRoundTripper(t *testing.T) {
ctx := context.Background()
tests := []struct {
name string
middleware Config
extensions map[component.ID]component.Component
wantErr error
}{
{
name: "found_and_valid",
middleware: Config{
ID: testID,
},
extensions: map[component.ID]component.Component{
testID: extensionmiddlewaretest.NewNop(),
},
wantErr: nil,
},
{
name: "middleware_not_found",
middleware: Config{
ID: testID,
},
extensions: map[component.ID]component.Component{},
wantErr: errMiddlewareNotFound,
},
{
name: "middleware_wrong_type",
middleware: Config{
ID: testID,
},
extensions: map[component.ID]component.Component{
testID: mockWrongType{},
},
wantErr: errNotHTTPClient,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
value, err := tt.middleware.GetHTTPClientRoundTripper(ctx, tt.extensions)
if tt.wantErr != nil {
require.ErrorIs(t, err, tt.wantErr)
} else {
require.NoError(t, err)
require.NotNil(t, value)
}
})
}
}
func TestConfig_GetGRPCServerOptions(t *testing.T) {
ctx := context.Background()
tests := []struct {
name string
middleware Config
extensions map[component.ID]component.Component
wantErr error
}{
{
name: "found_and_valid",
middleware: Config{
ID: testID,
},
extensions: map[component.ID]component.Component{
testID: struct {
extension.Extension
extensionmiddleware.GetGRPCServerOptionsFunc
}{
Extension: extensionmiddlewaretest.NewNop(),
GetGRPCServerOptionsFunc: func(context.Context) ([]grpc.ServerOption, error) {
return []grpc.ServerOption{
grpc.EmptyServerOption{},
}, nil
},
},
},
wantErr: nil,
},
{
name: "middleware_not_found",
middleware: Config{
ID: testID,
},
extensions: map[component.ID]component.Component{},
wantErr: errMiddlewareNotFound,
},
{
name: "middleware_wrong_type",
middleware: Config{
ID: testID,
},
extensions: map[component.ID]component.Component{
testID: mockWrongType{},
},
wantErr: errNotGRPCServer,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
value, err := tt.middleware.GetGRPCServerOptions(ctx, tt.extensions)
if tt.wantErr != nil {
require.ErrorIs(t, err, tt.wantErr)
} else {
require.NoError(t, err)
require.NotNil(t, value)
}
})
}
}
func TestConfig_GetGRPCClientOptions(t *testing.T) {
ctx := context.Background()
tests := []struct {
name string
middleware Config
extensions map[component.ID]component.Component
wantErr error
}{
{
name: "found_and_valid",
middleware: Config{
ID: testID,
},
extensions: map[component.ID]component.Component{
testID: struct {
extension.Extension
extensionmiddleware.GetGRPCClientOptionsFunc
}{
Extension: extensionmiddlewaretest.NewNop(),
GetGRPCClientOptionsFunc: func(_ context.Context) ([]grpc.DialOption, error) {
return []grpc.DialOption{
grpc.EmptyDialOption{},
}, nil
},
},
},
wantErr: nil,
},
{
name: "middleware_not_found",
middleware: Config{
ID: testID,
},
extensions: map[component.ID]component.Component{},
wantErr: errMiddlewareNotFound,
},
{
name: "middleware_wrong_type",
middleware: Config{
ID: testID,
},
extensions: map[component.ID]component.Component{
testID: mockWrongType{},
},
wantErr: errNotGRPCClient,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
value, err := tt.middleware.GetGRPCClientOptions(ctx, tt.extensions)
if tt.wantErr != nil {
require.ErrorIs(t, err, tt.wantErr)
} else {
require.NoError(t, err)
require.NotNil(t, value)
}
})
}
}
================================================
FILE: config/configmiddleware/go.mod
================================================
module go.opentelemetry.io/collector/config/configmiddleware
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/extension v1.54.0
go.opentelemetry.io/collector/extension/extensionmiddleware v0.148.0
go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest v0.148.0
google.golang.org/grpc v1.79.3
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
go.opentelemetry.io/collector/pdata v1.54.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/extension/extensionmiddleware => ../../extension/extensionmiddleware
replace go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest => ../../extension/extensionmiddleware/extensionmiddlewaretest
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/extension => ../../extension
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
================================================
FILE: config/configmiddleware/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: config/configmiddleware/metadata.yaml
================================================
type: config/configmiddleware
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: config/confignet/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: config/confignet/README.md
================================================
# Network Configuration Settings
[Receivers](https://github.com/open-telemetry/opentelemetry-collector/blob/main/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".
- `dialer`: Dialer configuration
- `timeout`: Dialer timeout is the maximum amount of time a dial will wait for a connect to complete. The default is no timeout.
Note that for TCP receivers only the `endpoint` configuration setting is
required.
================================================
FILE: config/confignet/config.schema.yaml
================================================
$defs:
addr_config:
description: AddrConfig represents a network endpoint address.
type: object
properties:
dialer:
description: DialerConfig contains options for connecting to an address.
$ref: dialer_config
endpoint:
description: 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.
type: string
transport:
description: Transport to use. Allowed 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".
$ref: transport_type
dialer_config:
description: DialerConfig contains options for connecting to an address.
type: object
properties:
timeout:
description: Timeout is the maximum amount of time a dial will wait for a connect to complete. The default is no timeout.
type: string
x-customType: time.Duration
format: duration
tcp_addr_config:
description: TCPAddrConfig represents a TCP endpoint address.
type: object
properties:
dialer:
description: DialerConfig contains options for connecting to an address.
$ref: dialer_config
endpoint:
description: 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.
type: string
transport_type:
description: TransportType represents a type of network transport protocol
type: string
================================================
FILE: config/confignet/confignet.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package confignet // import "go.opentelemetry.io/collector/config/confignet"
import (
"context"
"fmt"
"net"
"time"
)
// TransportType represents a type of network transport protocol
type TransportType string
const (
TransportTypeTCP TransportType = "tcp"
TransportTypeTCP4 TransportType = "tcp4"
TransportTypeTCP6 TransportType = "tcp6"
TransportTypeUDP TransportType = "udp"
TransportTypeUDP4 TransportType = "udp4"
TransportTypeUDP6 TransportType = "udp6"
TransportTypeIP TransportType = "ip"
TransportTypeIP4 TransportType = "ip4"
TransportTypeIP6 TransportType = "ip6"
TransportTypeUnix TransportType = "unix"
TransportTypeUnixgram TransportType = "unixgram"
TransportTypeUnixPacket TransportType = "unixpacket"
transportTypeEmpty TransportType = ""
)
// UnmarshalText unmarshalls text to a TransportType.
// Valid values are "tcp", "tcp4", "tcp6", "udp", "udp4",
// "udp6", "ip", "ip4", "ip6", "unix", "unixgram" and "unixpacket"
func (tt *TransportType) UnmarshalText(in []byte) error {
typ := TransportType(in)
switch typ {
case TransportTypeTCP,
TransportTypeTCP4,
TransportTypeTCP6,
TransportTypeUDP,
TransportTypeUDP4,
TransportTypeUDP6,
TransportTypeIP,
TransportTypeIP4,
TransportTypeIP6,
TransportTypeUnix,
TransportTypeUnixgram,
TransportTypeUnixPacket,
transportTypeEmpty:
*tt = typ
return nil
default:
return fmt.Errorf("unsupported transport type %q", typ)
}
}
// DialerConfig contains options for connecting to an address.
type DialerConfig struct {
// Timeout is the maximum amount of time a dial will wait for
// a connect to complete. The default is no timeout.
Timeout time.Duration `mapstructure:"timeout,omitempty"`
// prevent unkeyed literal initialization
_ struct{}
}
// NewDefaultDialerConfig creates a new DialerConfig with any default values set
func NewDefaultDialerConfig() DialerConfig {
return DialerConfig{}
}
// AddrConfig represents a network endpoint address.
type AddrConfig 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,omitempty"`
// Transport to use. Allowed 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 TransportType `mapstructure:"transport,omitempty"`
// DialerConfig contains options for connecting to an address.
DialerConfig DialerConfig `mapstructure:"dialer,omitempty"`
// prevent unkeyed literal initialization
_ struct{}
}
// NewDefaultAddrConfig creates a new AddrConfig with any default values set
func NewDefaultAddrConfig() AddrConfig {
return AddrConfig{
DialerConfig: NewDefaultDialerConfig(),
}
}
// Dial equivalent with net.Dialer's DialContext for this address.
func (na *AddrConfig) Dial(ctx context.Context) (net.Conn, error) {
d := net.Dialer{Timeout: na.DialerConfig.Timeout}
return d.DialContext(ctx, string(na.Transport), na.Endpoint)
}
// Listen equivalent with net.ListenConfig's Listen for this address.
func (na *AddrConfig) Listen(ctx context.Context) (net.Listener, error) {
lc := net.ListenConfig{}
return lc.Listen(ctx, string(na.Transport), na.Endpoint)
}
func (na *AddrConfig) Validate() error {
switch na.Transport {
case TransportTypeTCP,
TransportTypeTCP4,
TransportTypeTCP6,
TransportTypeUDP,
TransportTypeUDP4,
TransportTypeUDP6,
TransportTypeIP,
TransportTypeIP4,
TransportTypeIP6,
TransportTypeUnix,
TransportTypeUnixgram,
TransportTypeUnixPacket:
return nil
default:
return fmt.Errorf("invalid transport type %q", na.Transport)
}
}
// TCPAddrConfig represents a TCP endpoint address.
type TCPAddrConfig 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,omitempty"`
// DialerConfig contains options for connecting to an address.
DialerConfig DialerConfig `mapstructure:"dialer,omitempty"`
// prevent unkeyed literal initialization
_ struct{}
}
// NewDefaultTCPAddrConfig creates a new TCPAddrConfig with any default values set
func NewDefaultTCPAddrConfig() TCPAddrConfig {
return TCPAddrConfig{
DialerConfig: NewDefaultDialerConfig(),
}
}
// Dial equivalent with net.Dialer's DialContext for this address.
func (na *TCPAddrConfig) Dial(ctx context.Context) (net.Conn, error) {
d := net.Dialer{Timeout: na.DialerConfig.Timeout}
return d.DialContext(ctx, string(TransportTypeTCP), na.Endpoint)
}
// Listen equivalent with net.ListenConfig's Listen for this address.
func (na *TCPAddrConfig) Listen(ctx context.Context) (net.Listener, error) {
lc := net.ListenConfig{}
return lc.Listen(ctx, string(TransportTypeTCP), na.Endpoint)
}
================================================
FILE: config/confignet/confignet_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package confignet
import (
"context"
"errors"
"net"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewDefaultDialerConfig(t *testing.T) {
expectedDialerConfig := DialerConfig{}
dialerConfig := NewDefaultDialerConfig()
require.Equal(t, expectedDialerConfig, dialerConfig)
}
func TestNewDefaultAddrConfig(t *testing.T) {
expectedAddrConfig := AddrConfig{}
addrConfig := NewDefaultAddrConfig()
require.Equal(t, expectedAddrConfig, addrConfig)
}
func TestNewDefaultTCPAddrConfig(t *testing.T) {
expectedTCPAddrConfig := TCPAddrConfig{}
tcpAddrconfig := NewDefaultTCPAddrConfig()
require.Equal(t, expectedTCPAddrConfig, tcpAddrconfig)
}
func TestAddrConfigTimeout(t *testing.T) {
nac := &AddrConfig{
Endpoint: "localhost:0",
Transport: TransportTypeTCP,
DialerConfig: DialerConfig{
Timeout: -1 * time.Second,
},
}
_, err := nac.Dial(context.Background())
require.Error(t, err)
var netErr net.Error
if errors.As(err, &netErr) {
assert.True(t, netErr.Timeout())
} else {
assert.Fail(t, "error should be a net.Error")
}
}
func TestTCPAddrConfigTimeout(t *testing.T) {
nac := &TCPAddrConfig{
Endpoint: "localhost:0",
DialerConfig: DialerConfig{
Timeout: -1 * time.Second,
},
}
_, err := nac.Dial(context.Background())
require.Error(t, err)
var netErr net.Error
if errors.As(err, &netErr) {
assert.True(t, netErr.Timeout())
} else {
assert.Fail(t, "error should be a net.Error")
}
}
func TestAddrConfig(t *testing.T) {
nas := &AddrConfig{
Endpoint: "localhost:0",
Transport: TransportTypeTCP,
}
ln, err := nas.Listen(context.Background())
require.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 := &AddrConfig{
Endpoint: ln.Addr().String(),
Transport: TransportTypeTCP,
}
var conn net.Conn
conn, err = nac.Dial(context.Background())
require.NoError(t, err)
_, err = conn.Write([]byte("test"))
assert.NoError(t, err)
assert.NoError(t, conn.Close())
<-done
assert.NoError(t, ln.Close())
}
func Test_NetAddr_Validate(t *testing.T) {
na := &AddrConfig{
Transport: TransportTypeTCP,
}
require.NoError(t, na.Validate())
na = &AddrConfig{
Transport: transportTypeEmpty,
}
require.Error(t, na.Validate())
na = &AddrConfig{
Transport: "random string",
}
assert.Error(t, na.Validate())
}
func TestTCPAddrConfig(t *testing.T) {
nas := &TCPAddrConfig{
Endpoint: "localhost:0",
}
ln, err := nas.Listen(context.Background())
require.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 := &TCPAddrConfig{
Endpoint: ln.Addr().String(),
}
var conn net.Conn
conn, err = nac.Dial(context.Background())
require.NoError(t, err)
_, err = conn.Write([]byte("test"))
assert.NoError(t, err)
assert.NoError(t, conn.Close())
<-done
assert.NoError(t, ln.Close())
}
func Test_TransportType_UnmarshalText(t *testing.T) {
var tt TransportType
err := tt.UnmarshalText([]byte("tcp"))
require.NoError(t, err)
err = tt.UnmarshalText([]byte("invalid"))
require.Error(t, err)
}
================================================
FILE: config/confignet/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package confignet implements the configuration settings for protocols to
// connect and transport data information.
package confignet // import "go.opentelemetry.io/collector/config/confignet"
================================================
FILE: config/confignet/go.mod
================================================
module go.opentelemetry.io/collector/config/confignet
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.uber.org/goleak v1.3.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
================================================
FILE: config/confignet/go.sum
================================================
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
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/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: config/confignet/metadata.yaml
================================================
type: config/confignet
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: config/confignet/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package confignet
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: config/configopaque/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: config/configopaque/config.schema.yaml
================================================
$defs:
map_list:
description: MapList is a replacement for map[string]configopaque.String with a similar API, which can also be unmarshalled from (and is stored as) a list of name/value pairs. Pairs are assumed to have distinct names. This is checked during config validation.
type: array
items:
$ref: pair
pair:
description: Pair is an element of a MapList, and consists of a name and an opaque value.
type: object
properties:
name:
type: string
value:
$ref: string
string:
description: String alias that is marshaled and printed in an opaque way. To recover the original value, cast it to a string.
type: string
================================================
FILE: config/configopaque/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package configopaque implements a String type alias to mask sensitive information.
// Use configopaque.String on the type of sensitive fields, to mask the
// opaque string as `[REDACTED]`.
//
// This ensures that no sensitive information is leaked in logs or when printing the
// full Collector configurations.
//
// The only way to view the value stored in a configopaque.String is to first convert
// it to a string by casting with the builtin `string` function.
//
// To achieve this, configopaque.String implements standard library interfaces
// like fmt.Stringer, encoding.TextMarshaler and others to ensure that the
// underlying value is masked when printed or serialized.
//
// If new interfaces that would leak opaque values are added to the standard library
// or become widely used in the Go ecosystem, these will eventually be implemented
// by configopaque.String as well. This is not considered a breaking change.
package configopaque // import "go.opentelemetry.io/collector/config/configopaque"
================================================
FILE: config/configopaque/doc_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configopaque_test
import (
"encoding/json"
"fmt"
"go.yaml.in/yaml/v3"
"go.opentelemetry.io/collector/config/configopaque"
)
func Example_opaqueString() {
rawBytes := []byte(`{
"Censored": "sensitive",
"Uncensored": "not sensitive"
}`)
// JSON unmarshaling
var cfg ExampleConfigString
err := json.Unmarshal(rawBytes, &cfg)
if err != nil {
panic(err)
}
// YAML marshaling
bytes, err := yaml.Marshal(cfg)
if err != nil {
panic(err)
}
fmt.Printf("encoded cfg (YAML) is:\n%s\n\n", string(bytes))
// Output: encoded cfg (YAML) is:
// censored: '[REDACTED]'
// uncensored: not sensitive
}
type ExampleConfigString struct {
Censored configopaque.String
Uncensored string
}
func Example_opaqueSlice() {
cfg := &ExampleConfigSlice{
Censored: []configopaque.String{"data", "is", "sensitive"},
Uncensored: []string{"data", "is", "not", "sensitive"},
}
// JSON marshaling
bytes, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
panic(err)
}
fmt.Printf("encoded cfg (JSON) is\n%s\n\n", string(bytes))
// Output: encoded cfg (JSON) is
// {
// "Censored": [
// "[REDACTED]",
// "[REDACTED]",
// "[REDACTED]"
// ],
// "Uncensored": [
// "data",
// "is",
// "not",
// "sensitive"
// ]
// }
}
type ExampleConfigSlice struct {
Censored []configopaque.String
Uncensored []string
}
func Example_opaqueMap() {
cfg := &ExampleConfigMap{
Censored: map[string]configopaque.String{
"token": "sensitivetoken",
},
Uncensored: map[string]string{
"key": "cloud.zone",
"value": "zone-1",
},
}
// yaml marshaling
bytes, err := yaml.Marshal(cfg)
if err != nil {
panic(err)
}
fmt.Printf("encoded cfg (YAML) is:\n%s\n\n", string(bytes))
// Output: encoded cfg (YAML) is:
// censored:
// token: '[REDACTED]'
// uncensored:
// key: cloud.zone
// value: zone-1
}
type ExampleConfigMap struct {
Censored map[string]configopaque.String
Uncensored map[string]string
}
================================================
FILE: config/configopaque/go.mod
================================================
module go.opentelemetry.io/collector/config/configopaque
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/confmap v1.54.0
go.opentelemetry.io/collector/confmap/xconfmap v0.148.0
go.uber.org/goleak v1.3.0
go.yaml.in/yaml/v3 v3.0.4
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/confmap => ../../confmap
replace go.opentelemetry.io/collector/confmap/xconfmap => ../../confmap/xconfmap
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
================================================
FILE: config/configopaque/go.sum
================================================
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/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: config/configopaque/maplist.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configopaque // import "go.opentelemetry.io/collector/config/configopaque"
import (
"cmp"
"fmt"
"iter"
"slices"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/xconfmap"
)
// Pair is an element of a MapList, and consists of a name and an opaque value.
type Pair struct {
Name string `mapstructure:"name"`
Value String `mapstructure:"value"`
// prevent unkeyed literal initialization
_ struct{}
}
// MapList is a replacement for map[string]configopaque.String with a similar API,
// which can also be unmarshalled from (and is stored as) a list of name/value pairs.
//
// Pairs are assumed to have distinct names. This is checked during config validation.
type MapList []Pair
var _ confmap.Unmarshaler = (*MapList)(nil)
// Unmarshal is called by the Collector when unmarshalling from a map.
// When the input config is a slice, this will be skipped,
// and mapstructure's default unmarshalling logic will be used.
func (ml *MapList) Unmarshal(conf *confmap.Conf) error {
var m2 map[string]String
if err := conf.Unmarshal(&m2); err != nil {
return err
}
*ml = make(MapList, 0, len(m2))
for name, value := range m2 {
*ml = append(*ml, Pair{
Name: name,
Value: value,
})
}
slices.SortFunc(*ml, func(p1, p2 Pair) int {
return cmp.Compare(p1.Name, p2.Name)
})
return nil
}
var _ xconfmap.Validator = MapList(nil)
func (ml MapList) Validate() error {
// Check for duplicate keys
counts := make(map[string]int, len(ml))
for _, OpaquePair := range ml {
counts[OpaquePair.Name]++
}
if len(counts) == len(ml) {
return nil
}
var duplicates []string
for name, cnt := range counts {
if cnt > 1 {
duplicates = append(duplicates, name)
}
}
slices.Sort(duplicates)
return fmt.Errorf("duplicate keys in map-style list: %v", duplicates)
}
var _ iter.Seq2[string, String] = MapList(nil).Iter
// Iter is an iterator over key/value pairs for use in for-range loops.
// It is the MapList equivalent of directly ranging over a map.
func (ml MapList) Iter(yield func(name string, value String) bool) {
for _, OpaquePair := range ml {
if !yield(OpaquePair.Name, OpaquePair.Value) {
break
}
}
}
// Get looks up a pair's value based on its name.
// It is the MapList equivalent of `val, ok := m[key]`.
// However, it has linear time complexity.
func (ml MapList) Get(name string) (val String, ok bool) {
for _, OpaquePair := range ml {
if OpaquePair.Name == name {
return OpaquePair.Value, true
}
}
return val, false
}
// Set sets the value corresponding to a given name.
// It is the MapList equivalent of `m[key] = val`.
// However, it has linear time complexity,
// and does not affect shallow copies.
func (ml *MapList) Set(name string, val String) {
if ml == nil {
panic("assignment to entry in nil *MapList")
}
for i, OpaquePair := range *ml {
if OpaquePair.Name == name {
*ml = slices.Clone(*ml)
(*ml)[i].Value = val
return
}
}
*ml = append(make(MapList, 0, len(*ml)+1), *ml...)
*ml = append(*ml, Pair{Name: name, Value: val})
}
================================================
FILE: config/configopaque/maplist_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configopaque_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/config/configopaque"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/xconfmap"
)
const headersList = `
headers:
- name: "a"
value: "b"
- name: "c"
value: "d"
`
const headersMap = `
headers:
"a": "b"
"c": "d"
`
const headersBad1 = `
headers:
"bad": 1
`
const headersBad2 = `
headers: "foo"
`
const headersDupe = `
headers:
- name: "foo"
value: "bar"
- name: "foo"
value: "baz"
`
type testConfig struct {
Headers configopaque.MapList `mapstructure:"headers"`
}
func TestMapListDuality(t *testing.T) {
retrieved1, err := confmap.NewRetrievedFromYAML([]byte(headersList))
require.NoError(t, err)
conf1, err := retrieved1.AsConf()
require.NoError(t, err)
var tc1 testConfig
require.NoError(t, conf1.Unmarshal(&tc1))
assert.NoError(t, xconfmap.Validate(&tc1))
retrieved2, err := confmap.NewRetrievedFromYAML([]byte(headersMap))
require.NoError(t, err)
conf2, err := retrieved2.AsConf()
require.NoError(t, err)
var tc2 testConfig
require.NoError(t, conf2.Unmarshal(&tc2))
assert.NoError(t, xconfmap.Validate(&tc2))
assert.Equal(t, tc1, tc2)
}
func TestMapListUnmarshalError(t *testing.T) {
var tc testConfig
retrieved, err := confmap.NewRetrievedFromYAML([]byte(headersBad1))
require.NoError(t, err)
conf, err := retrieved.AsConf()
require.NoError(t, err)
require.EqualError(t, conf.Unmarshal(&tc),
"decoding failed due to the following error(s):\n\n"+
"'headers' decoding failed due to the following error(s):\n\n"+
"'[bad]' expected type 'configopaque.String', got unconvertible type 'int'")
retrieved, err = confmap.NewRetrievedFromYAML([]byte(headersBad2))
require.NoError(t, err)
conf, err = retrieved.AsConf()
require.NoError(t, err)
// Not sure if there is a way to change the error message to include the map case?
require.EqualError(t, conf.Unmarshal(&tc),
"decoding failed due to the following error(s):\n\n"+
"'headers' source data must be an array or slice, got string")
}
func TestMapListValidate(t *testing.T) {
retrieved, err := confmap.NewRetrievedFromYAML([]byte(headersDupe))
require.NoError(t, err)
conf, err := retrieved.AsConf()
require.NoError(t, err)
var tc testConfig
require.NoError(t, conf.Unmarshal(&tc))
require.EqualError(t, xconfmap.Validate(&tc), `headers: duplicate keys in map-style list: [foo]`)
}
func TestMapListMethods(t *testing.T) {
ml := configopaque.MapList{
{Name: "a", Value: "1"},
{Name: "b", Value: "2"},
{Name: "c", Value: "3"},
}
type pair = struct {
k string
v configopaque.String
}
var kvs []pair
for k, v := range ml.Iter {
kvs = append(kvs, pair{k, v})
if k == "b" {
break
}
}
assert.Equal(t, []pair{{"a", "1"}, {"b", "2"}}, kvs)
v, ok := ml.Get("a")
assert.True(t, ok)
if ok {
assert.Equal(t, configopaque.String("1"), v)
}
v, ok = ml.Get("d")
assert.False(t, ok)
assert.Zero(t, v)
ml2 := ml
assert.Len(t, ml2, 3)
// Set existing key
ml2.Set("c", "4")
assert.Len(t, ml, 3)
assert.Len(t, ml2, 3)
v, _ = ml.Get("c")
assert.Equal(t, configopaque.String("3"), v)
v, _ = ml2.Get("c")
assert.Equal(t, configopaque.String("4"), v)
// Set new key
ml2.Set("d", "5")
assert.Len(t, ml, 3)
assert.Len(t, ml2, 4)
_, ok = ml.Get("d")
assert.False(t, ok)
v, ok = ml2.Get("d")
assert.True(t, ok)
assert.Equal(t, configopaque.String("5"), v)
}
func TestMapListNil(t *testing.T) {
var ml *configopaque.MapList
assert.Panics(t, func() {
ml.Set("a", "0")
})
}
================================================
FILE: config/configopaque/metadata.yaml
================================================
type: config/configopaque
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: config/configopaque/opaque.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configopaque // import "go.opentelemetry.io/collector/config/configopaque"
import (
"fmt"
)
// String alias that is marshaled and printed in an opaque way.
// To recover the original value, cast it to a string.
type String string
const maskedString = "[REDACTED]"
// MarshalText marshals the string as `[REDACTED]`.
func (s String) MarshalText() ([]byte, error) {
return []byte(maskedString), nil
}
// String formats the string as `[REDACTED]`.
// This is used for the %s and %q verbs.
func (s String) String() string {
return maskedString
}
// GoString formats the string as `[REDACTED]`.
// This is used for the %#v verb.
func (s String) GoString() string {
return fmt.Sprintf("%#v", maskedString)
}
// MarshalBinary marshals the string `[REDACTED]` as []byte.
func (s String) MarshalBinary() (text []byte, err error) {
return []byte(maskedString), nil
}
================================================
FILE: config/configopaque/opaque_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configopaque // import "go.opentelemetry.io/collector/config/configopaque"
import (
"encoding"
"encoding/hex"
"encoding/json"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var _ encoding.TextMarshaler = String("")
var _ fmt.Stringer = String("")
var _ fmt.GoStringer = String("")
var _ encoding.BinaryMarshaler = String("")
func TestStringMarshalText(t *testing.T) {
examples := []String{"opaque", "s", "veryveryveryveryveryveryveryveryveryverylong"}
for _, example := range examples {
opaque, err := example.MarshalText()
require.NoError(t, err)
assert.Equal(t, maskedString, string(opaque))
}
}
type TestStruct struct {
Opaque String `json:"opaque" yaml:"opaque"`
Plain string `json:"plain" yaml:"plain"`
}
var example = TestStruct{
Opaque: "opaque",
Plain: "plain",
}
func TestStringJSON(t *testing.T) {
bytes, err := json.Marshal(example)
require.NoError(t, err)
assert.JSONEq(t, `{"opaque":"[REDACTED]","plain":"plain"}`, string(bytes))
}
func TestStringFmt(t *testing.T) {
examples := []String{"opaque", "s", "veryveryveryveryveryveryveryveryveryverylong"}
verbs := []string{"%s", "%q", "%v", "%#v", "%+v", "%x"}
for _, example := range examples {
for _, verb := range verbs {
t.Run(fmt.Sprintf("%s/%s", string(example), verb), func(t *testing.T) {
assert.Equal(t,
fmt.Sprintf(verb, maskedString),
fmt.Sprintf(verb, example),
)
})
}
for _, verb := range verbs {
t.Run(fmt.Sprintf("string(%s)/%s", string(example), verb), func(t *testing.T) {
// converting to a string allows to output as an unredacted string still:
var expected string
switch verb {
case "%s", "%v", "%+v":
expected = string(example)
case "%q", "%#v":
expected = "\"" + string(example) + "\""
case "%x":
expected = hex.EncodeToString([]byte(example))
default:
t.Errorf("unexpected verb %q", verb)
}
assert.Equal(t,
expected,
fmt.Sprintf(verb, string(example)),
)
})
}
}
}
func TestStringMarshalBinary(t *testing.T) {
examples := []String{"opaque", "s", "veryveryveryveryveryveryveryveryveryverylong"}
for _, example := range examples {
opaque, err := example.MarshalBinary()
require.NoError(t, err)
assert.Equal(t, []byte("[REDACTED]"), opaque)
}
}
================================================
FILE: config/configopaque/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configopaque
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: config/configoptional/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: config/configoptional/documentation.md
================================================
[comment]: <> (Code generated by mdatagen. DO NOT EDIT.)
# configoptional
## Feature Gates
This component has the following feature gates:
| Feature Gate | Stage | Description | From Version | To Version | Reference |
| ------------ | ----- | ----------- | ------------ | ---------- | --------- |
| `configoptional.AddEnabledField` | beta | Allows optional fields to be toggled via an 'enabled' field. | v0.138.0 | N/A | [Link](https://github.com/open-telemetry/opentelemetry-collector/issues/14021) |
For more information about feature gates, see the [Feature Gates](https://github.com/open-telemetry/opentelemetry-collector/blob/main/featuregate/README.md) documentation.
================================================
FILE: config/configoptional/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package configoptional
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: config/configoptional/go.mod
================================================
module go.opentelemetry.io/collector/config/configoptional
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/confmap v1.54.0
go.opentelemetry.io/collector/confmap/xconfmap v0.148.0
go.opentelemetry.io/collector/featuregate v1.54.0
go.uber.org/goleak v1.3.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/confmap => ../../confmap
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/confmap/xconfmap => ../../confmap/xconfmap
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
================================================
FILE: config/configoptional/go.sum
================================================
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/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: config/configoptional/internal/metadata/generated_feature_gates.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/featuregate"
)
var ConfigoptionalAddEnabledFieldFeatureGate = featuregate.GlobalRegistry().MustRegister(
"configoptional.AddEnabledField",
featuregate.StageBeta,
featuregate.WithRegisterDescription("Allows optional fields to be toggled via an 'enabled' field."),
featuregate.WithRegisterReferenceURL("https://github.com/open-telemetry/opentelemetry-collector/issues/14021"),
featuregate.WithRegisterFromVersion("v0.138.0"),
)
================================================
FILE: config/configoptional/metadata.yaml
================================================
type: configoptional
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
stability:
beta: [metrics, traces, logs]
alpha: [profiles]
feature_gates:
- id: configoptional.AddEnabledField
description: "Allows optional fields to be toggled via an 'enabled' field."
stage: beta
from_version: 'v0.138.0'
reference_url: 'https://github.com/open-telemetry/opentelemetry-collector/issues/14021'
================================================
FILE: config/configoptional/optional.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configoptional // import "go.opentelemetry.io/collector/config/configoptional"
//go:generate mdatagen metadata.yaml
import (
"errors"
"fmt"
"reflect"
"strings"
"go.opentelemetry.io/collector/config/configoptional/internal/metadata"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/xconfmap"
)
type flavor int
const (
noneFlavor flavor = 0
defaultFlavor flavor = 1
someFlavor flavor = 2
)
// Optional represents a value that may or may not be present.
// It supports two flavors for all types: Some(value) and None.
// It supports a third flavor for struct types: Default(defaultVal).
//
// For struct types, it supports unmarshaling from a configuration source.
// For struct types, it supports an 'enabled' field to explicitly disable a section.
// The zero value of Optional is None.
type Optional[T any] struct {
// value is the value of the Optional.
value T
// flavor indicates the flavor of the Optional.
// The zero value of flavor is noneFlavor.
flavor flavor
}
// deref a reflect.Type to its underlying type.
func deref(t reflect.Type) reflect.Type {
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
return t
}
// assertStructKind checks if T can be dereferenced into a type with struct kind.
//
// We assert this because our unmarshaling logic currently only supports structs.
// This can be removed if we ever support scalar values.
func assertStructKind[T any]() error {
var instance T
t := deref(reflect.TypeOf(instance))
if t.Kind() != reflect.Struct {
return fmt.Errorf("configoptional: %q does not have a struct kind", t)
}
return nil
}
// assertNoEnabledField checks that a struct type
// does not have a field with a mapstructure tag "enabled".
//
// We assert this because we discussed an alternative design where we have an explicit
// "enabled" field in the struct to indicate if the struct is enabled or not.
// See https://github.com/open-telemetry/opentelemetry-collector/pull/13060.
// This can be removed if we ever support such a design (or if we just want to allow
// the "enabled" field in the struct).
func assertNoEnabledField[T any]() error {
var i T
t := deref(reflect.TypeOf(i))
if t.Kind() != reflect.Struct {
// Not a struct, no need to check for "enabled" field.
return nil
}
// Check if the struct has a field with the name "enabled".
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
mapstructureTags := strings.SplitN(field.Tag.Get("mapstructure"), ",", 2)
if len(mapstructureTags) > 0 && mapstructureTags[0] == "enabled" {
return errors.New("configoptional: underlying type cannot have a field with mapstructure tag 'enabled'")
}
}
return nil
}
// Some creates an Optional with a value and no factory.
//
// It panics if T has a field with the mapstructure tag "enabled".
func Some[T any](value T) Optional[T] {
if err := assertNoEnabledField[T](); err != nil {
panic(err)
}
return Optional[T]{value: value, flavor: someFlavor}
}
// Default creates an Optional with a default value for unmarshaling.
//
// It panics if
// - T is not a struct OR
// - T has a field with the mapstructure tag "enabled".
func Default[T any](value T) Optional[T] {
err := errors.Join(assertStructKind[T](), assertNoEnabledField[T]())
if err != nil {
panic(err)
}
return Optional[T]{value: value, flavor: defaultFlavor}
}
// None has no value. It has the same behavior as a nil pointer when unmarshaling.
//
// The zero value of Optional[T] is None[T]. Prefer using this constructor
// for validation.
//
// It panics if T has a field with the mapstructure tag "enabled".
func None[T any]() Optional[T] {
if err := assertNoEnabledField[T](); err != nil {
panic(err)
}
return Optional[T]{}
}
// HasValue checks if the Optional has a value.
func (o Optional[T]) HasValue() bool {
return o.flavor == someFlavor
}
// Get returns the value of the Optional.
// If the value is not present, it returns nil.
func (o *Optional[T]) Get() *T {
if !o.HasValue() {
return nil
}
return &o.value
}
// GetOrInsertDefault makes the Optional into a Some(val) and returns val.
//
// In particular, if it is Default(val) it turns it into Some(val)
// and if it is None[T]() it turns it into Some(zeroVal) where zeroVal is T's zero value.
// This method is useful for programmatic usage of an optional.
//
// It panics if
// - T is not a struct OR
// - T has a field with the mapstructure tag "enabled".
func (o *Optional[T]) GetOrInsertDefault() *T {
err := errors.Join(assertStructKind[T](), assertNoEnabledField[T]())
if err != nil {
panic(err)
}
if o.HasValue() {
return o.Get()
}
empty := confmap.NewFromStringMap(map[string]any{})
if err := empty.Unmarshal(o); err != nil {
// This should never happen, if it happens it is a bug, so this panic is not documented.
panic(fmt.Errorf("failed to unmarshal empty map into %T type: %w. Please report this bug", o.value, err))
}
return o.Get()
}
var _ confmap.Unmarshaler = (*Optional[any])(nil)
// Unmarshal the configuration into the Optional value.
//
// The behavior of this method depends on the state of the Optional:
// - None[T]: does nothing if the configuration is nil, otherwise it unmarshals into the zero value of T.
// - Some[T](val): equivalent to unmarshaling into a field of type T with value val.
// - Default[T](val), equivalent to unmarshaling into a field of type T with base value val,
// using val without overrides from the configuration if the configuration is nil.
//
// (Under the `configoptional.AddEnabledField` feature gate)
// If the configuration contains an 'enabled' field:
// - if enabled is true: the Optional becomes Some after unmarshaling.
// - if enabled is false: the Optional becomes None regardless of other configuration values.
//
// T must be derefenceable to a type with struct kind and not have an 'enabled' field.
// Scalar values are not supported.
func (o *Optional[T]) Unmarshal(conf *confmap.Conf) error {
if err := assertNoEnabledField[T](); err != nil {
return err
}
if o.flavor == noneFlavor && conf.ToStringMap() == nil {
// If the Optional is None and the configuration is nil, we do nothing.
// This replicates the behavior of unmarshaling into a field with a nil pointer.
return nil
}
isEnabled := true
if metadata.ConfigoptionalAddEnabledFieldFeatureGate.IsEnabled() && conf.IsSet("enabled") {
enabled := conf.Get("enabled")
conf.Delete("enabled")
var ok bool
if isEnabled, ok = enabled.(bool); !ok {
return fmt.Errorf("unexpected type %T for 'enabled': got '%v' value expected 'true' or 'false'", enabled, enabled)
}
}
if err := conf.Unmarshal(&o.value, xconfmap.WithForceUnmarshaler()); err != nil {
return err
}
if isEnabled {
o.flavor = someFlavor
} else {
o.flavor = noneFlavor
// override o.value with zero value.
var zero T
o.value = zero
}
return nil
}
var _ confmap.Marshaler = (*Optional[any])(nil)
// Marshal the Optional value into the configuration.
// If the Optional is None or Default, it does not marshal anything.
// If the Optional is Some, it marshals the value into the configuration.
//
// T must be derefenceable to a type with struct kind.
// Scalar values are not supported.
func (o Optional[T]) Marshal(conf *confmap.Conf) error {
if err := assertStructKind[T](); err != nil {
return err
}
if o.flavor == noneFlavor || o.flavor == defaultFlavor {
// Optional is None or Default, do not marshal anything.
return conf.Marshal(map[string]any(nil))
}
if err := conf.Marshal(o.value); err != nil {
return fmt.Errorf("configoptional: failed to marshal Optional value: %w", err)
}
return nil
}
var _ xconfmap.Validator = (*Optional[any])(nil)
// Validate implements [xconfmap.Validator]. This is required because the
// private fields in [xconfmap.Validator] can't be seen by the reflection used
// by [xconfmap.Validate], and therefore we have to continue the validation
// chain manually. This method isn't meant to be called directly, and should
// generally only be called by [xconfmap.Validate].
func (o *Optional[T]) Validate() error {
// When the flavor is None, the user has not passed this value,
// and therefore we should not validate it. The parent struct holding
// the Optional type can determine whether a None value is valid for
// a given config.
//
// If the flavor is still Default, then the user has not passed this
// value and we should also not validate it.
if o.flavor == noneFlavor || o.flavor == defaultFlavor {
return nil
}
// For the some flavor, validate the actual value.
return xconfmap.Validate(o.value)
}
================================================
FILE: config/configoptional/optional_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configoptional
import (
"errors"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/config/configoptional/internal/metadata"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/confmaptest"
"go.opentelemetry.io/collector/confmap/xconfmap"
"go.opentelemetry.io/collector/featuregate"
)
type Config[T any] struct {
Sub1 Optional[T] `mapstructure:"sub"`
}
type Sub struct {
Foo string `mapstructure:"foo"`
}
type WithEnabled struct {
Enabled bool `mapstructure:"enabled"`
}
type WithEnabledOmitEmpty struct {
Enabled bool `mapstructure:"enabled,omitempty"`
}
type OmitEmpty struct {
Foo int `mapstructure:",omitempty"`
}
type NoMapstructure struct {
Foo string
}
var subDefault = Sub{
Foo: "foobar",
}
func ptr[T any](v T) *T {
return &v
}
func TestDefaultPanics(t *testing.T) {
assert.Panics(t, func() {
_ = Default(1)
})
assert.Panics(t, func() {
_ = Default(ptr(1))
})
assert.Panics(t, func() {
_ = Default(WithEnabled{})
})
assert.Panics(t, func() {
_ = Default(WithEnabledOmitEmpty{})
})
assert.Panics(t, func() {
_ = Some(WithEnabled{})
})
assert.Panics(t, func() {
_ = None[WithEnabled]()
})
assert.Panics(t, func() {
opt := None[int]()
_ = opt.GetOrInsertDefault()
})
assert.Panics(t, func() {
var opt Optional[WithEnabled]
_ = opt.GetOrInsertDefault()
})
assert.NotPanics(t, func() {
_ = Default(NoMapstructure{})
})
assert.NotPanics(t, func() {
_ = Default(OmitEmpty{})
})
assert.NotPanics(t, func() {
_ = Default(subDefault)
})
assert.NotPanics(t, func() {
_ = Default(ptr(subDefault))
})
}
func TestEqualityDefault(t *testing.T) {
defaultOne := Default(subDefault)
defaultTwo := Default(subDefault)
assert.Equal(t, defaultOne, defaultTwo)
}
func TestNoneZeroVal(t *testing.T) {
var none Optional[Sub]
require.False(t, none.HasValue())
require.Nil(t, none.Get())
var zeroVal Sub
ret := none.GetOrInsertDefault()
require.True(t, none.HasValue())
assert.Equal(t, &zeroVal, ret)
}
func TestNone(t *testing.T) {
none := None[Sub]()
require.False(t, none.HasValue())
require.Nil(t, none.Get())
var zeroVal Sub
ret := none.GetOrInsertDefault()
require.True(t, none.HasValue())
assert.Equal(t, &zeroVal, ret)
}
func ExampleNone() {
type Person struct {
Name string
Age int
}
opt := None[Person]()
// A None has no value.
fmt.Println(opt.HasValue())
fmt.Println(opt.Get())
// GetOrInsertDefault places the zero value
// and returns it, allowing you to modify it.
opt.GetOrInsertDefault().Name = "John Doe"
fmt.Println(opt.HasValue())
fmt.Println(opt.Get())
// Output:
// false
//
// true
// &{John Doe 0}
}
func TestSome(t *testing.T) {
some := Some(Sub{
Foo: "foobar",
})
require.True(t, some.HasValue())
retGet := some.Get()
assert.Equal(t, "foobar", retGet.Foo)
retGetOrInsertDefault := some.GetOrInsertDefault()
require.True(t, some.HasValue())
assert.Equal(t, retGet, retGetOrInsertDefault)
}
func ExampleSome() {
type Person struct {
Name string
Age int
}
opt := Some(Person{
Name: "John Doe",
Age: 42,
})
// A Some has a value.
fmt.Println(opt.HasValue())
fmt.Println(opt.Get())
// GetOrInsertDefault only returns a reference
// to the inner value without modifying it.
opt.GetOrInsertDefault().Name = "Jane Doe"
fmt.Println(opt.HasValue())
fmt.Println(opt.Get())
// Output:
// true
// &{John Doe 42}
// true
// &{Jane Doe 42}
}
func TestDefault(t *testing.T) {
defaultSub := Default(subDefault)
require.False(t, defaultSub.HasValue())
require.Nil(t, defaultSub.Get())
ret := defaultSub.GetOrInsertDefault()
require.True(t, defaultSub.HasValue())
assert.Equal(t, &subDefault, ret)
}
func ExampleDefault() {
type Person struct {
Name string
Age int
}
opt := Default(Person{
Name: "John Doe",
Age: 42,
})
// A Default has no value.
fmt.Println(opt.HasValue())
fmt.Println(opt.Get())
// GetOrInsertDefault places the default value
// and returns it, allowing you to modify it.
opt.GetOrInsertDefault().Age = 38
fmt.Println(opt.HasValue())
fmt.Println(opt.Get())
// Output:
// false
//
// true
// &{John Doe 38}
}
func TestUnmarshalOptional(t *testing.T) {
tests := []struct {
name string
config map[string]any
defaultCfg Config[Sub]
expectedSub bool
expectedFoo string
}{
{
name: "none_no_config",
defaultCfg: Config[Sub]{
Sub1: None[Sub](),
},
expectedSub: false,
},
{
name: "none_with_config",
config: map[string]any{
"sub": map[string]any{
"foo": "bar",
},
},
defaultCfg: Config[Sub]{
Sub1: None[Sub](),
},
expectedSub: true,
expectedFoo: "bar", // input overrides default
},
{
// nil is treated as an empty map because of the hooks we use.
name: "none_with_config_no_foo",
config: map[string]any{
"sub": nil,
},
defaultCfg: Config[Sub]{
Sub1: None[Sub](),
},
expectedSub: false,
},
{
name: "none_with_config_no_foo_empty_map",
config: map[string]any{
"sub": map[string]any{},
},
defaultCfg: Config[Sub]{
Sub1: None[Sub](),
},
expectedSub: true,
expectedFoo: "",
},
{
name: "default_no_config",
defaultCfg: Config[Sub]{
Sub1: Default(subDefault),
},
expectedSub: false,
},
{
name: "default_with_config",
config: map[string]any{
"sub": map[string]any{
"foo": "bar",
},
},
defaultCfg: Config[Sub]{
Sub1: Default(subDefault),
},
expectedSub: true,
expectedFoo: "bar", // input overrides default
},
{
name: "default_with_config_no_foo",
config: map[string]any{
"sub": nil,
},
defaultCfg: Config[Sub]{
Sub1: Default(subDefault),
},
expectedSub: true,
expectedFoo: "foobar", // default applies
},
{
name: "default_with_config_no_foo_empty_map",
config: map[string]any{
"sub": map[string]any{},
},
defaultCfg: Config[Sub]{
Sub1: Default(subDefault),
},
expectedSub: true,
expectedFoo: "foobar", // default applies
},
{
name: "some_no_config",
defaultCfg: Config[Sub]{
Sub1: Some(Sub{
Foo: "foobar",
}),
},
expectedSub: true,
expectedFoo: "foobar", // value is not modified
},
{
name: "some_with_config",
config: map[string]any{
"sub": map[string]any{
"foo": "bar",
},
},
defaultCfg: Config[Sub]{
Sub1: Some(Sub{
Foo: "foobar",
}),
},
expectedSub: true,
expectedFoo: "bar", // input overrides previous value
},
{
name: "some_with_config_no_foo",
config: map[string]any{
"sub": nil,
},
defaultCfg: Config[Sub]{
Sub1: Some(Sub{
Foo: "foobar",
}),
},
expectedSub: true,
expectedFoo: "foobar", // default applies
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
cfg := test.defaultCfg
conf := confmap.NewFromStringMap(test.config)
require.NoError(t, conf.Unmarshal(&cfg))
require.Equal(t, test.expectedSub, cfg.Sub1.HasValue())
if test.expectedSub {
require.Equal(t, test.expectedFoo, cfg.Sub1.Get().Foo)
}
})
}
}
func TestAddFieldEnabledFeatureGate(t *testing.T) {
tests := []struct {
name string
config map[string]any
defaultCfg Config[Sub]
expectedSub bool
expectedFoo string
}{
{
name: "none_with_enabled_true",
config: map[string]any{
"sub": map[string]any{
"enabled": true,
"foo": "bar",
},
},
defaultCfg: Config[Sub]{
Sub1: None[Sub](),
},
expectedSub: true,
expectedFoo: "bar",
},
{
name: "none_with_enabled_false",
config: map[string]any{
"sub": map[string]any{
"enabled": false,
"foo": "bar",
},
},
defaultCfg: Config[Sub]{
Sub1: None[Sub](),
},
expectedSub: false,
},
{
name: "none_with_enabled_false_no_other_config",
config: map[string]any{
"sub": map[string]any{
"enabled": false,
},
},
defaultCfg: Config[Sub]{
Sub1: None[Sub](),
},
expectedSub: false,
},
{
name: "default_with_enabled_true",
config: map[string]any{
"sub": map[string]any{
"enabled": true,
"foo": "bar",
},
},
defaultCfg: Config[Sub]{
Sub1: Default(subDefault),
},
expectedSub: true,
expectedFoo: "bar",
},
{
name: "default_with_enabled_false",
config: map[string]any{
"sub": map[string]any{
"enabled": false,
"foo": "bar",
},
},
defaultCfg: Config[Sub]{
Sub1: Default(subDefault),
},
expectedSub: false,
},
{
name: "default_with_enabled_false_no_other_config",
config: map[string]any{
"sub": map[string]any{
"enabled": false,
},
},
defaultCfg: Config[Sub]{
Sub1: Default(subDefault),
},
expectedSub: false,
},
{
name: "some_with_enabled_true",
config: map[string]any{
"sub": map[string]any{
"enabled": true,
"foo": "baz",
},
},
defaultCfg: Config[Sub]{
Sub1: Some(Sub{
Foo: "foobar",
}),
},
expectedSub: true,
expectedFoo: "baz",
},
{
name: "some_with_enabled_false",
config: map[string]any{
"sub": map[string]any{
"enabled": false,
"foo": "baz",
},
},
defaultCfg: Config[Sub]{
Sub1: Some(Sub{
Foo: "foobar",
}),
},
expectedSub: false,
},
{
name: "some_with_enabled_false_no_other_config",
config: map[string]any{
"sub": map[string]any{
"enabled": false,
},
},
defaultCfg: Config[Sub]{
Sub1: Some(Sub{
Foo: "foobar",
}),
},
expectedSub: false,
},
}
oldVal := metadata.ConfigoptionalAddEnabledFieldFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ConfigoptionalAddEnabledFieldFeatureGate.ID(), true))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ConfigoptionalAddEnabledFieldFeatureGate.ID(), oldVal))
}()
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
cfg := test.defaultCfg
conf := confmap.NewFromStringMap(test.config)
require.NoError(t, conf.Unmarshal(&cfg))
require.Equal(t, test.expectedSub, cfg.Sub1.HasValue())
if test.expectedSub {
require.Equal(t, test.expectedFoo, cfg.Sub1.Get().Foo)
}
})
}
}
func TestEnabledFalseResetsValue(t *testing.T) {
oldVal := metadata.ConfigoptionalAddEnabledFieldFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ConfigoptionalAddEnabledFieldFeatureGate.ID(), true))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ConfigoptionalAddEnabledFieldFeatureGate.ID(), oldVal))
}()
cfg := Config[Sub]{Sub1: Some(Sub{Foo: "initial"})}
require.True(t, cfg.Sub1.HasValue())
cm := confmap.NewFromStringMap(map[string]any{
"sub": map[string]any{"enabled": false, "foo": "ignored"},
})
require.NoError(t, cm.Unmarshal(&cfg))
require.Equal(t, None[Sub](), cfg.Sub1)
}
func TestUnmarshalErrorEnabledInvalidType(t *testing.T) {
oldVal := metadata.ConfigoptionalAddEnabledFieldFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ConfigoptionalAddEnabledFieldFeatureGate.ID(), true))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ConfigoptionalAddEnabledFieldFeatureGate.ID(), oldVal))
}()
cm := confmap.NewFromStringMap(map[string]any{
"sub": map[string]any{
"enabled": "something",
"foo": "bar",
},
})
cfg := Config[Sub]{
Sub1: None[Sub](),
}
err := cm.Unmarshal(&cfg)
require.ErrorContains(t, err, "unexpected type string for 'enabled': got 'something' value expected 'true' or 'false'")
}
func TestUnmarshalErrorEnabledField(t *testing.T) {
cm := confmap.NewFromStringMap(map[string]any{
"enabled": true,
})
// Use zero value to avoid panic on constructor.
var none Optional[WithEnabled]
require.Error(t, cm.Unmarshal(&none))
}
func TestUnmarshalConfigPointer(t *testing.T) {
cm := confmap.NewFromStringMap(map[string]any{
"sub": map[string]any{
"foo": "bar",
},
})
var cfg Config[*Sub]
err := cm.Unmarshal(&cfg)
require.NoError(t, err)
assert.True(t, cfg.Sub1.HasValue())
assert.Equal(t, "bar", (*cfg.Sub1.Get()).Foo)
}
func TestUnmarshalErr(t *testing.T) {
cm := confmap.NewFromStringMap(map[string]any{
"field": "value",
})
cfg := Config[Sub]{
Sub1: Default(subDefault),
}
assert.False(t, cfg.Sub1.HasValue())
err := cm.Unmarshal(&cfg)
require.Error(t, err)
require.ErrorContains(t, err, "has invalid keys: field")
assert.False(t, cfg.Sub1.HasValue())
}
type MyIntConfig struct {
Val int `mapstructure:"my_int"`
}
type MyConfig struct {
Optional[MyIntConfig] `mapstructure:",squash"`
}
var myIntDefault = MyIntConfig{
Val: 1,
}
func TestSquashedOptional(t *testing.T) {
cm := confmap.NewFromStringMap(map[string]any{
"my_int": 42,
})
cfg := MyConfig{
Default(myIntDefault),
}
err := cm.Unmarshal(&cfg)
require.NoError(t, err)
assert.True(t, cfg.HasValue())
assert.Equal(t, 42, cfg.Get().Val)
}
func confFromYAML(t *testing.T, yaml string) *confmap.Conf {
t.Helper()
cm, err := confmap.NewRetrievedFromYAML([]byte(yaml))
require.NoError(t, err)
conf, err := cm.AsConf()
require.NoError(t, err)
return conf
}
func TestComparePointerUnmarshal(t *testing.T) {
tests := []struct {
yaml string
}{
{yaml: ""},
{yaml: "sub: "},
{yaml: "sub: null"},
{yaml: "sub: {}"},
{yaml: "sub: {foo: bar}"},
}
for _, test := range tests {
t.Run(test.yaml, func(t *testing.T) {
var optCfg Config[Sub]
conf := confFromYAML(t, test.yaml)
optErr := conf.Unmarshal(&optCfg)
require.NoError(t, optErr)
var ptrCfg struct {
Sub1 *Sub `mapstructure:"sub"`
}
ptrErr := conf.Unmarshal(&ptrCfg)
require.NoError(t, ptrErr)
assert.Equal(t, optCfg.Sub1.Get(), ptrCfg.Sub1)
})
}
}
func TestOptionalMarshal(t *testing.T) {
tests := []struct {
name string
value Config[Sub]
expected map[string]any
}{
{
name: "none (zero value)",
value: Config[Sub]{},
expected: map[string]any{"sub": nil},
},
{
name: "none",
value: Config[Sub]{Sub1: None[Sub]()},
expected: map[string]any{"sub": nil},
},
{
name: "default",
value: Config[Sub]{Sub1: Default(subDefault)},
expected: map[string]any{"sub": nil},
},
{
name: "some",
value: Config[Sub]{Sub1: Some(Sub{
Foo: "bar",
})},
expected: map[string]any{
"sub": map[string]any{
"foo": "bar",
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
conf := confmap.New()
require.NoError(t, conf.Marshal(test.value))
assert.Equal(t, test.expected, conf.ToStringMap())
})
}
}
func TestComparePointerMarshal(t *testing.T) {
type Wrap[T any] struct {
// Note: passes without requiring "squash".
Sub1 T `mapstructure:"sub"`
}
type WrapOmitEmpty[T any] struct {
// Note: passes without requiring "squash", except with Default-flavored Optional values.
Sub1 T `mapstructure:"sub,omitempty"`
}
tests := []struct {
pointer *Sub
optional Optional[Sub]
skipOmitEmpty bool
}{
{pointer: nil, optional: None[Sub]()},
{pointer: nil, optional: Default(subDefault), skipOmitEmpty: true}, // does not work with omitempty
{pointer: &Sub{Foo: "bar"}, optional: Some(Sub{Foo: "bar"})},
}
for _, test := range tests {
t.Run(fmt.Sprintf("%v vs %v", test.pointer, test.optional), func(t *testing.T) {
wrapPointer := Wrap[*Sub]{Sub1: test.pointer}
confPointer := confmap.NewFromStringMap(nil)
require.NoError(t, confPointer.Marshal(wrapPointer))
wrapOptional := Wrap[Optional[Sub]]{Sub1: test.optional}
confOptional := confmap.NewFromStringMap(nil)
require.NoError(t, confOptional.Marshal(wrapOptional))
assert.Equal(t, confPointer.ToStringMap(), confOptional.ToStringMap())
})
if test.skipOmitEmpty {
continue
}
t.Run(fmt.Sprintf("%v vs %v (omitempty)", test.pointer, test.optional), func(t *testing.T) {
wrapPointer := WrapOmitEmpty[*Sub]{Sub1: test.pointer}
confPointer := confmap.NewFromStringMap(nil)
require.NoError(t, confPointer.Marshal(wrapPointer))
wrapOptional := WrapOmitEmpty[Optional[Sub]]{Sub1: test.optional}
confOptional := confmap.NewFromStringMap(nil)
require.NoError(t, confOptional.Marshal(wrapOptional))
assert.Equal(t, confPointer.ToStringMap(), confOptional.ToStringMap())
})
}
}
type invalid struct{}
func (invalid) Validate() error {
return errors.New("invalid")
}
var _ xconfmap.Validator = invalid{}
type hasNested struct {
CouldBe Optional[invalid]
}
func TestOptionalValidate(t *testing.T) {
require.NoError(t, xconfmap.Validate(hasNested{
CouldBe: None[invalid](),
}))
require.NoError(t, xconfmap.Validate(hasNested{
CouldBe: Default(invalid{}),
}))
require.Error(t, xconfmap.Validate(hasNested{
CouldBe: Some(invalid{}),
}))
}
type validatedConfig struct {
Default Optional[optionalConfig] `mapstructure:"default"`
Some Optional[someConfig] `mapstructure:"some"`
}
var _ xconfmap.Validator = (*optionalConfig)(nil)
type optionalConfig struct {
StringVal string `mapstructure:"string_val"`
}
func (n optionalConfig) Validate() error {
if n.StringVal == "invalid" {
return errors.New("field `string_val` cannot be set to `invalid`")
}
return nil
}
type someConfig struct {
Nested Optional[optionalConfig] `mapstructure:"nested"`
}
func newDefaultValidatedConfig() validatedConfig {
return validatedConfig{
Default: Default(optionalConfig{StringVal: "valid"}),
}
}
func newInvalidDefaultConfig() validatedConfig {
return validatedConfig{
Default: Default(optionalConfig{StringVal: "invalid"}),
}
}
func TestOptionalFileValidate(t *testing.T) {
cases := []struct {
name string
variant string
cfg func() validatedConfig
err error
}{
{
name: "valid default with just key set and no subfields",
variant: "implicit",
cfg: newDefaultValidatedConfig,
},
{
name: "valid default with keys set in default",
variant: "explicit",
cfg: newDefaultValidatedConfig,
},
{
name: "invalid config",
variant: "invalid",
cfg: newDefaultValidatedConfig,
err: errors.New("default: field `string_val` cannot be set to `invalid`\nsome: nested: field `string_val` cannot be set to `invalid`"),
},
{
name: "invalid default throws an error",
variant: "implicit",
cfg: newInvalidDefaultConfig,
err: errors.New("default: field `string_val` cannot be set to `invalid`"),
},
{
name: "invalid default does not throw an error when key is not set",
variant: "no_default",
cfg: newInvalidDefaultConfig,
},
{
name: "invalid default invalid default does not throw an error when the value is overridden",
variant: "explicit",
cfg: newInvalidDefaultConfig,
},
}
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
conf, err := confmaptest.LoadConf(fmt.Sprintf("testdata/validate_%s.yaml", tt.variant))
require.NoError(t, err)
cfg := tt.cfg()
err = conf.Unmarshal(&cfg)
require.NoError(t, err)
err = xconfmap.Validate(cfg)
if tt.err == nil {
require.NoError(t, err)
} else {
require.EqualError(t, err, tt.err.Error())
}
})
}
}
================================================
FILE: config/configoptional/testdata/validate_explicit.yaml
================================================
default:
string_val: valid
some:
nested:
string_val: valid
================================================
FILE: config/configoptional/testdata/validate_implicit.yaml
================================================
default:
some:
nested:
string_val: value1
================================================
FILE: config/configoptional/testdata/validate_invalid.yaml
================================================
default:
string_val: invalid
some:
nested:
string_val: invalid
================================================
FILE: config/configoptional/testdata/validate_no_default.yaml
================================================
some:
nested:
string_val: value1
================================================
FILE: config/configretry/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: config/configretry/backoff.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configretry // import "go.opentelemetry.io/collector/config/configretry"
import (
"errors"
"time"
"github.com/cenkalti/backoff/v5"
)
// NewDefaultBackOffConfig returns the default settings for RetryConfig.
func NewDefaultBackOffConfig() BackOffConfig {
return BackOffConfig{
Enabled: true,
InitialInterval: 5 * time.Second,
RandomizationFactor: backoff.DefaultRandomizationFactor,
Multiplier: backoff.DefaultMultiplier,
MaxInterval: 30 * time.Second,
MaxElapsedTime: 5 * time.Minute,
}
}
// BackOffConfig defines configuration for retrying batches in case of export failure.
// The current supported strategy is exponential backoff.
type BackOffConfig 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"`
// RandomizationFactor is a random factor used to calculate next backoffs
// Randomized interval = RetryInterval * (1 ± RandomizationFactor)
RandomizationFactor float64 `mapstructure:"randomization_factor"`
// Multiplier is the value multiplied by the backoff interval bounds
Multiplier float64 `mapstructure:"multiplier"`
// 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. If set to 0, the retries are never stopped.
MaxElapsedTime time.Duration `mapstructure:"max_elapsed_time"`
// prevent unkeyed literal initialization
_ struct{}
}
func (bs *BackOffConfig) Validate() error {
if !bs.Enabled {
return nil
}
if bs.InitialInterval < 0 {
return errors.New("'initial_interval' must be non-negative")
}
if bs.RandomizationFactor < 0 || bs.RandomizationFactor > 1 {
return errors.New("'randomization_factor' must be within [0, 1]")
}
if bs.Multiplier < 0 {
return errors.New("'multiplier' must be non-negative")
}
if bs.MaxInterval < 0 {
return errors.New("'max_interval' must be non-negative")
}
if bs.MaxElapsedTime < 0 {
return errors.New("'max_elapsed_time' must be non-negative")
}
if bs.MaxElapsedTime > 0 {
if bs.MaxElapsedTime < bs.InitialInterval {
return errors.New("'max_elapsed_time' must not be less than 'initial_interval'")
}
if bs.MaxElapsedTime < bs.MaxInterval {
return errors.New("'max_elapsed_time' must not be less than 'max_interval'")
}
}
return nil
}
================================================
FILE: config/configretry/backoff_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configretry
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewDefaultBackOffSettings(t *testing.T) {
cfg := NewDefaultBackOffConfig()
require.NoError(t, cfg.Validate())
assert.Equal(t,
BackOffConfig{
Enabled: true,
InitialInterval: 5 * time.Second,
RandomizationFactor: 0.5,
Multiplier: 1.5,
MaxInterval: 30 * time.Second,
MaxElapsedTime: 5 * time.Minute,
}, cfg)
}
func TestInvalidInitialInterval(t *testing.T) {
cfg := NewDefaultBackOffConfig()
require.NoError(t, cfg.Validate())
cfg.InitialInterval = -1
assert.Error(t, cfg.Validate())
}
func TestInvalidRandomizationFactor(t *testing.T) {
cfg := NewDefaultBackOffConfig()
require.NoError(t, cfg.Validate())
cfg.RandomizationFactor = -1
require.Error(t, cfg.Validate())
cfg.RandomizationFactor = 2
assert.Error(t, cfg.Validate())
}
func TestInvalidMultiplier(t *testing.T) {
cfg := NewDefaultBackOffConfig()
require.NoError(t, cfg.Validate())
cfg.Multiplier = -1
assert.Error(t, cfg.Validate())
}
func TestZeroMultiplierIsValid(t *testing.T) {
cfg := NewDefaultBackOffConfig()
assert.NoError(t, cfg.Validate())
cfg.Multiplier = 0
assert.NoError(t, cfg.Validate())
}
func TestInvalidMaxInterval(t *testing.T) {
cfg := NewDefaultBackOffConfig()
require.NoError(t, cfg.Validate())
cfg.MaxInterval = -1
assert.Error(t, cfg.Validate())
}
func TestInvalidMaxElapsedTime(t *testing.T) {
cfg := NewDefaultBackOffConfig()
require.NoError(t, cfg.Validate())
cfg.MaxElapsedTime = -1
require.Error(t, cfg.Validate())
cfg.MaxElapsedTime = 60
// MaxElapsedTime is 60, InitialInterval is 5s, so it should be invalid
require.Error(t, cfg.Validate())
cfg.InitialInterval = 0
// MaxElapsedTime is 60, MaxInterval is 30s, so it should be invalid
require.Error(t, cfg.Validate())
cfg.MaxInterval = 0
assert.NoError(t, cfg.Validate())
cfg.InitialInterval = 50
// MaxElapsedTime is 0, so it should be valid
cfg.MaxElapsedTime = 0
assert.NoError(t, cfg.Validate())
}
func TestDisabledWithInvalidValues(t *testing.T) {
cfg := BackOffConfig{
Enabled: false,
InitialInterval: -1,
RandomizationFactor: -1,
Multiplier: 0,
MaxInterval: -1,
MaxElapsedTime: -1,
}
assert.NoError(t, cfg.Validate())
}
================================================
FILE: config/configretry/config.schema.yaml
================================================
$defs:
back_off_config:
description: BackOffConfig defines configuration for retrying batches in case of export failure. The current supported strategy is exponential backoff.
type: object
properties:
enabled:
description: Enabled indicates whether to not retry sending batches in case of export failure.
type: boolean
initial_interval:
description: InitialInterval the time to wait after the first failure before retrying.
type: string
x-customType: time.Duration
format: duration
max_elapsed_time:
description: 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. If set to 0, the retries are never stopped.
type: string
x-customType: time.Duration
format: duration
max_interval:
description: MaxInterval is the upper bound on backoff interval. Once this value is reached the delay between consecutive retries will always be `MaxInterval`.
type: string
x-customType: time.Duration
format: duration
multiplier:
description: Multiplier is the value multiplied by the backoff interval bounds
type: number
x-customType: float64
randomization_factor:
description: RandomizationFactor is a random factor used to calculate next backoffs Randomized interval = RetryInterval * (1 ± RandomizationFactor)
type: number
x-customType: float64
================================================
FILE: config/configretry/go.mod
================================================
module go.opentelemetry.io/collector/config/configretry
go 1.25.0
require (
github.com/cenkalti/backoff/v5 v5.0.3
github.com/stretchr/testify v1.11.1
go.uber.org/goleak v1.3.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
================================================
FILE: config/configretry/go.sum
================================================
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
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/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: config/configretry/metadata.yaml
================================================
type: config/configretry
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: config/configretry/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configretry
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: config/configtelemetry/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: config/configtelemetry/config.schema.yaml
================================================
$defs:
level:
description: Level is the level of internal telemetry (metrics, logs, traces about the component itself) that every component should generate.
type: integer
x-customType: int32
================================================
FILE: config/configtelemetry/configtelemetry.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configtelemetry // import "go.opentelemetry.io/collector/config/configtelemetry"
import (
"errors"
"fmt"
"strings"
)
const (
// LevelNone indicates that no telemetry should be collected.
LevelNone Level = iota - 1
// LevelBasic indicates that only core Collector telemetry should be collected.
LevelBasic
// LevelNormal indicates that all low-overhead telemetry should be collected.
LevelNormal
// LevelDetailed indicates that all available telemetry should be collected.
LevelDetailed
levelNoneStr = "None"
levelBasicStr = "Basic"
levelNormalStr = "Normal"
levelDetailedStr = "Detailed"
)
// Level is the level of internal telemetry (metrics, logs, traces about the component itself)
// that every component should generate.
type Level int32
func (l Level) String() string {
switch l {
case LevelNone:
return levelNoneStr
case LevelBasic:
return levelBasicStr
case LevelNormal:
return levelNormalStr
case LevelDetailed:
return levelDetailedStr
}
return ""
}
// MarshalText marshals Level to text.
func (l Level) MarshalText() (text []byte, err error) {
return []byte(l.String()), nil
}
// UnmarshalText unmarshalls text to a Level.
func (l *Level) UnmarshalText(text []byte) error {
if l == nil {
return errors.New("cannot unmarshal to a nil *Level")
}
str := strings.ToLower(string(text))
switch str {
case strings.ToLower(levelNoneStr):
*l = LevelNone
return nil
case strings.ToLower(levelBasicStr):
*l = LevelBasic
return nil
case strings.ToLower(levelNormalStr):
*l = LevelNormal
return nil
case strings.ToLower(levelDetailedStr):
*l = LevelDetailed
return nil
}
return fmt.Errorf("unknown metrics level %q", str)
}
================================================
FILE: config/configtelemetry/configtelemetry_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configtelemetry
import (
"encoding"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var (
_ encoding.TextMarshaler = (*Level)(nil)
_ encoding.TextUnmarshaler = (*Level)(nil)
)
func TestUnmarshalText(t *testing.T) {
tests := []struct {
str []string
level Level
err bool
}{
{
str: []string{"", "other_string"},
level: LevelNone,
err: true,
},
{
str: []string{"none", "None", "NONE"},
level: LevelNone,
},
{
str: []string{"basic", "Basic", "BASIC"},
level: LevelBasic,
},
{
str: []string{"normal", "Normal", "NORMAL"},
level: LevelNormal,
},
{
str: []string{"detailed", "Detailed", "DETAILED"},
level: LevelDetailed,
},
}
for _, test := range tests {
for _, str := range test.str {
t.Run(str, func(t *testing.T) {
var lvl Level
err := lvl.UnmarshalText([]byte(str))
if test.err {
assert.Error(t, err)
} else {
require.NoError(t, err)
assert.Equal(t, test.level, lvl)
}
})
}
}
}
func TestUnmarshalTextNilLevel(t *testing.T) {
lvl := (*Level)(nil)
assert.Error(t, lvl.UnmarshalText([]byte(levelNormalStr)))
}
func TestLevelStringMarshal(t *testing.T) {
tests := []struct {
str string
level Level
err bool
}{
{
str: "",
level: Level(-10),
},
{
str: levelNoneStr,
level: LevelNone,
},
{
str: levelBasicStr,
level: LevelBasic,
},
{
str: levelNormalStr,
level: LevelNormal,
},
{
str: levelDetailedStr,
level: LevelDetailed,
},
}
for _, tt := range tests {
t.Run(tt.str, func(t *testing.T) {
assert.Equal(t, tt.str, tt.level.String())
got, err := tt.level.MarshalText()
require.NoError(t, err)
assert.Equal(t, tt.str, string(got))
})
}
}
================================================
FILE: config/configtelemetry/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package configtelemetry defines various telemetry level for configuration.
// It enables every component to have access to telemetry level
// to enable metrics only when necessary.
//
// This document provides guidance on which telemetry level to adopt for Collector metrics.
// When adopting a telemetry level, component authors are expected to rely on this guidance to
// justify their choice of telemetry level.
//
// 1. configtelemetry.None
//
// No telemetry data is recorded.
//
// 2. configtelemetry.Basic
//
// Telemetry associated with this level provides essential coverage of the Collector telemetry.
// It should only be used for telemetry generated by the core Collector API.
// Components outside of the core API MUST NOT record telemetry at this level.
//
// 3. configtelemetry.Normal
//
// Telemetry associated with this level provides intermediate coverage of the Collector telemetry.
// It should be the default for component authors.
//
// Normal-level telemetry should have limited cardinality and data volume, though it is acceptable
// for them to scale linearly with the monitored resources.
// For example, there may be a limit of 5 attribute sets or 5 spans generated per request.
//
// Normal-level telemetry should also have a low computational cost: it should not contain values
// requiring significant additional computation compared to the normal flow of processing.
//
// This is the default level recommended when running the Collector.
//
// 4. configtelemetry.Detailed
//
// Telemetry associated with this level provides complete coverage of the collector telemetry.
//
// The signals associated with this level may exhibit high cardinality, high data volume, or high
// computational cost.
package configtelemetry // import "go.opentelemetry.io/collector/config/configtelemetry"
================================================
FILE: config/configtelemetry/go.mod
================================================
module go.opentelemetry.io/collector/config/configtelemetry
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.uber.org/goleak v1.3.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
================================================
FILE: config/configtelemetry/go.sum
================================================
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
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/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: config/configtelemetry/metadata.yaml
================================================
type: config/configtelemetry
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: config/configtelemetry/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configtelemetry
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: config/configtls/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: config/configtls/README.md
================================================
# 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 HTTPs or gRPC connection. See
[grpc.WithInsecure()](https://godoc.org/google.golang.org/grpc#WithInsecure)
for gRPC.
- `curve_preferences` (default = []): specify your curve preferences that will
be used in an ECDHE handshake, in preference order. Accepted values are:
- X25519
- P521
- P256
- P384
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.
- `cert_pem`: Alternative to `cert_file`. Provide the certificate contents as a string instead of a filepath.
- `key_file`: Path to the TLS key to use for TLS required connections. Should
only be used if `insecure` is set to false.
- `key_pem`: Alternative to `key_file`. Provide the key contents as a string instead of a filepath.
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.
- `ca_pem`: Alternative to `ca_file`. Provide the CA cert contents as a string instead of a filepath.
You can also combine defining a certificate authority with the system certificate authorities.
- `include_system_ca_certs_pool` (default = false): whether to load the system certificate authorities pool
alongside the certificate authority.
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.
Minimum and maximum TLS version can be set:
__IMPORTANT__: TLS 1.0 and 1.1 are deprecated due to known vulnerabilities and should be avoided.
- `min_version` (default = "1.2"): Minimum acceptable TLS version.
- options: ["1.0", "1.1", "1.2", "1.3"]
- `max_version` (default = "" handled by [crypto/tls](https://github.com/golang/go/blob/ed9db1d36ad6ef61095d5941ad9ee6da7ab6d05a/src/crypto/tls/common.go#L700) - currently TLS 1.3): Maximum acceptable TLS version.
- options: ["1.0", "1.1", "1.2", "1.3"]
Explicit cipher suites can be set. If left blank, a safe default list is used. See https://go.dev/src/crypto/tls/cipher_suites.go for a list of supported cipher suites.
- `cipher_suites`: (default = []): List of cipher suites to use.
Example:
```
cipher_suites:
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
```
Additionally certificates may be reloaded by setting the below configuration.
- `reload_interval` (optional) : ReloadInterval specifies the duration after which the certificate will be reloaded.
If not set, it will never be reloaded.
Accepts a [duration string](https://pkg.go.dev/time#ParseDuration),
valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
How TLS/mTLS is configured depends on whether configuring the client or server.
See below for examples.
- `tpm` (optional): Use the trusted platform module to retrieve the TLS key.
## Client Configuration
[Exporters](https://github.com/open-telemetry/opentelemetry-collector/blob/main/exporter/README.md)
leverage client configuration. The TLS configuration parameters are defined
under `tls`, like server configuration.
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_grpc:
endpoint: myserver.local:55690
tls:
insecure: false
ca_file: server.crt
cert_file: client.crt
key_file: client.key
min_version: "1.1"
max_version: "1.2"
otlp/insecure:
endpoint: myserver.local:55690
tls:
insecure: true
otlp/secure_no_verify:
endpoint: myserver.local:55690
tls:
insecure: false
insecure_skip_verify: true
```
## Server Configuration
[Receivers](https://github.com/open-telemetry/opentelemetry-collector/blob/main/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.
- `client_ca_file_reload` (default = false): Reload the ClientCAs file when it is modified.
Example:
```yaml
receivers:
otlp:
protocols:
grpc:
endpoint: mysite.local:55690
tls:
cert_file: server.crt
key_file: server.key
otlp/mtls:
protocols:
grpc:
endpoint: mysite.local:55690
tls:
client_ca_file: client.pem
cert_file: server.crt
key_file: server.key
otlp/notls:
protocols:
grpc:
endpoint: mysite.local:55690
```
## Trusted platform module (TPM) configuration
The [trusted platform module](https://trustedcomputinggroup.org/resource/trusted-platform-module-tpm-summary/) (TPM) configuration can be used for loading TLS key from TPM. Currently only TSS2 format is supported.
- `enabled` (default = false): Enables loading `tls.key_file` from TPM.
- `path` (default = ""): The path to the TPM device or Unix domain socket. For instance `/dev/tpm0` or `/dev/tpmrm0`. This option is not supported on Windows.
- `owner_auth` (default = ""): The owner authorization value. This is used to authenticate the TPM device. If not set, the default owner authorization will be used.
- `auth` (default = ""): The authorization value. This is used to authenticate the TPM device. If not set, the default authorization will be used.
Example:
```yaml
exporters:
otlp_grpc:
endpoint: myserver.local:55690
tls:
ca_file: ca.crt
cert_file: client.crt
key_file: client-tss2.key
tpm:
enabled: true
path: /dev/tpmrm0
```
The `client-tss2.key` private key with TSS2 format will be loaded from the TPM device `/dev/tpmrm0`.
================================================
FILE: config/configtls/clientcasfilereloader.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configtls // import "go.opentelemetry.io/collector/config/configtls"
import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"sync"
"github.com/fsnotify/fsnotify"
)
type clientCAsFileReloader struct {
clientCAsFile string
certPool *x509.CertPool
lastReloadError error
lock sync.RWMutex
loader clientCAsFileLoader
watcher *fsnotify.Watcher
shutdownCH chan bool
}
type clientCAsFileLoader interface {
loadClientCAFile() (*x509.CertPool, error)
}
func newClientCAsReloader(clientCAsFile string, loader clientCAsFileLoader) (*clientCAsFileReloader, error) {
certPool, err := loader.loadClientCAFile()
if err != nil {
return nil, fmt.Errorf("failed to load client CA CertPool: %w", err)
}
reloader := &clientCAsFileReloader{
clientCAsFile: clientCAsFile,
certPool: certPool,
loader: loader,
shutdownCH: nil,
watcher: nil,
}
return reloader, nil
}
func (r *clientCAsFileReloader) getClientConfig(original *tls.Config) (*tls.Config, error) {
r.lock.RLock()
defer r.lock.RUnlock()
return &tls.Config{
RootCAs: original.RootCAs,
GetCertificate: original.GetCertificate,
GetClientCertificate: original.GetClientCertificate,
MinVersion: original.MinVersion,
MaxVersion: original.MaxVersion,
NextProtos: original.NextProtos,
ClientCAs: r.certPool,
ClientAuth: tls.RequireAndVerifyClientCert,
}, nil
}
func (r *clientCAsFileReloader) reload() {
r.lock.Lock()
defer r.lock.Unlock()
certPool, err := r.loader.loadClientCAFile()
if err != nil {
r.lastReloadError = err
} else {
r.certPool = certPool
r.lastReloadError = nil
}
}
func (r *clientCAsFileReloader) getLastError() error {
r.lock.Lock()
defer r.lock.Unlock()
return r.lastReloadError
}
func (r *clientCAsFileReloader) startWatching() error {
if r.shutdownCH != nil {
return errors.New("client CA file watcher already started")
}
watcher, err := fsnotify.NewWatcher()
if err != nil {
return fmt.Errorf("failed to create watcher to reload client CA CertPool: %w", err)
}
r.watcher = watcher
err = watcher.Add(r.clientCAsFile)
if err != nil {
return fmt.Errorf("failed to add client CA file to watcher: %w", err)
}
r.shutdownCH = make(chan bool)
go r.handleWatcherEvents()
return nil
}
func (r *clientCAsFileReloader) handleWatcherEvents() {
defer r.watcher.Close()
for {
select {
case _, ok := <-r.shutdownCH:
_ = ok
return
case event, ok := <-r.watcher.Events:
if !ok {
continue
}
// NOTE: k8s configmaps uses symlinks, we need this workaround.
// original configmap file is removed.
// SEE: https://martensson.io/go-fsnotify-and-kubernetes-configmaps/
if event.Has(fsnotify.Remove) || event.Has(fsnotify.Chmod) {
// remove the watcher since the file is removed
if err := r.watcher.Remove(event.Name); err != nil {
r.lastReloadError = err
}
// add a new watcher pointing to the new symlink/file
if err := r.watcher.Add(r.clientCAsFile); err != nil {
r.lastReloadError = err
}
r.reload()
}
if event.Has(fsnotify.Write) {
r.reload()
}
}
}
}
func (r *clientCAsFileReloader) shutdown() error {
if r.shutdownCH == nil {
return errors.New("client CAs file watcher is not running")
}
r.shutdownCH <- true
close(r.shutdownCH)
r.shutdownCH = nil
return nil
}
================================================
FILE: config/configtls/clientcasfilereloader_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configtls
import (
"crypto/x509"
"fmt"
"os"
"path/filepath"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCannotShutdownIfNotWatching(t *testing.T) {
reloader, _, _ := createReloader(t)
err := reloader.shutdown()
assert.Error(t, err)
}
func TestCannotStartIfAlreadyWatching(t *testing.T) {
reloader, _, _ := createReloader(t)
err := reloader.startWatching()
require.NoError(t, err)
err = reloader.startWatching()
require.Error(t, err)
err = reloader.shutdown()
assert.NoError(t, err)
}
func TestClosingWatcherDoesntBreakReloader(t *testing.T) {
reloader, loader, _ := createReloader(t)
err := reloader.startWatching()
require.NoError(t, err)
assert.Equal(t, 1, loader.reloadNumber())
err = reloader.watcher.Close()
require.NoError(t, err)
err = reloader.shutdown()
assert.NoError(t, err)
}
func TestErrorRecordedIfFileDeleted(t *testing.T) {
reloader, loader, filePath := createReloader(t)
err := reloader.startWatching()
require.NoError(t, err)
assert.Equal(t, 1, loader.reloadNumber())
loader.returnErrorOnSubsequentCalls("test error on reload")
err = os.WriteFile(filePath, []byte("some_data"), 0o600)
require.NoError(t, err)
assert.Eventually(t, func() bool {
return loader.reloadNumber() > 1 && reloader.getLastError() != nil
}, 5*time.Second, 10*time.Millisecond)
lastErr := reloader.getLastError()
require.EqualError(t, lastErr, "test error on reload")
err = reloader.shutdown()
assert.NoError(t, err)
}
func createReloader(t *testing.T) (*clientCAsFileReloader, *testLoader, string) {
tmpClientCAsFilePath := createTempFile(t)
loader := &testLoader{}
reloader, _ := newClientCAsReloader(tmpClientCAsFilePath, loader)
return reloader, loader, tmpClientCAsFilePath
}
func createTempFile(t *testing.T) string {
tmpCa, err := os.CreateTemp(t.TempDir(), "clientCAs.crt")
require.NoError(t, err)
tmpCaPath, err := filepath.Abs(tmpCa.Name())
assert.NoError(t, err)
assert.NoError(t, tmpCa.Close())
return tmpCaPath
}
type testLoader struct {
err atomic.Value
counter atomic.Uint32
}
func (r *testLoader) loadClientCAFile() (*x509.CertPool, error) {
r.counter.Add(1)
v := r.err.Load()
if v == nil {
return nil, nil
}
return nil, v.(error)
}
func (r *testLoader) returnErrorOnSubsequentCalls(msg string) {
r.err.Store(fmt.Errorf("%s", msg))
}
func (r *testLoader) reloadNumber() int {
return int(r.counter.Load())
}
================================================
FILE: config/configtls/config.schema.yaml
================================================
$defs:
client_config:
description: ClientConfig 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: object
properties:
insecure:
description: In gRPC and HTTP when set to true, this is used to disable the client transport security. See https://godoc.org/google.golang.org/grpc#WithInsecure for gRPC. Please refer to https://godoc.org/crypto/tls#Config for more information. (optional, default false)
type: boolean
insecure_skip_verify:
description: InsecureSkipVerify will enable TLS but not verify the certificate.
type: boolean
server_name_override:
description: 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)
type: string
allOf:
- $ref: config
config:
description: 'Config 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 Config.'
type: object
properties:
ca_file:
description: 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)
type: string
ca_pem:
description: In memory PEM encoded cert. (optional)
$ref: /config/configopaque.string
cert_file:
description: Path to the TLS cert to use for TLS required connections. (optional)
type: string
cert_pem:
description: In memory PEM encoded TLS cert to use for TLS required connections. (optional)
$ref: /config/configopaque.string
cipher_suites:
description: CipherSuites is a list of TLS cipher suites that the TLS transport can use. If left blank, a safe default list is used. See https://go.dev/src/crypto/tls/cipher_suites.go for a list of supported cipher suites.
type: array
items:
type: string
curve_preferences:
description: contains the elliptic curves that will be used in an ECDHE handshake, in preference order Defaults to empty list and "crypto/tls" defaults are used, internally.
type: array
items:
type: string
include_system_ca_certs_pool:
description: If true, load system CA certificates pool in addition to the certificates configured in this struct.
type: boolean
key_file:
description: Path to the TLS key to use for TLS required connections. (optional)
type: string
key_pem:
description: In memory PEM encoded TLS key to use for TLS required connections. (optional)
$ref: /config/configopaque.string
max_version:
description: MaxVersion sets the maximum TLS version that is acceptable. If not set, refer to crypto/tls for defaults. (optional)
type: string
min_version:
description: MinVersion sets the minimum TLS version that is acceptable. If not set, TLS 1.2 will be used. (optional)
type: string
reload_interval:
description: ReloadInterval specifies the duration after which the certificate will be reloaded If not set, it will never be reloaded (optional)
type: string
x-customType: time.Duration
format: duration
tpm:
description: Trusted platform module configuration
$ref: tpm_config
server_config:
description: ServerConfig 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: object
properties:
client_ca_file:
description: 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)
type: string
client_ca_file_reload:
description: Reload the ClientCAs file when it is modified (optional, default false)
type: boolean
allOf:
- $ref: config
tpm_config:
description: TPMConfig defines trusted platform module configuration for storing TLS keys.
type: object
properties:
auth:
type: string
enabled:
type: boolean
owner_auth:
type: string
path:
description: The path to the TPM device or Unix domain socket. For instance /dev/tpm0 or /dev/tpmrm0.
type: string
================================================
FILE: config/configtls/configtls.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configtls // import "go.opentelemetry.io/collector/config/configtls"
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"maps"
"os"
"path/filepath"
"slices"
"sync"
"time"
"go.opentelemetry.io/collector/config/configopaque"
)
// We should avoid that users unknowingly use a vulnerable TLS version.
// The defaults should be a safe configuration
const defaultMinTLSVersion = tls.VersionTLS12
// Uses the default MaxVersion from "crypto/tls" which is the maximum supported version
const defaultMaxTLSVersion = 0
var systemCertPool = x509.SystemCertPool
// Config 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 Config.
type Config 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,omitempty"`
// In memory PEM encoded cert. (optional)
CAPem configopaque.String `mapstructure:"ca_pem,omitempty"`
// If true, load system CA certificates pool in addition to the certificates
// configured in this struct.
IncludeSystemCACertsPool bool `mapstructure:"include_system_ca_certs_pool,omitempty"`
// Path to the TLS cert to use for TLS required connections. (optional)
CertFile string `mapstructure:"cert_file,omitempty"`
// In memory PEM encoded TLS cert to use for TLS required connections. (optional)
CertPem configopaque.String `mapstructure:"cert_pem,omitempty"`
// Path to the TLS key to use for TLS required connections. (optional)
KeyFile string `mapstructure:"key_file,omitempty"`
// In memory PEM encoded TLS key to use for TLS required connections. (optional)
KeyPem configopaque.String `mapstructure:"key_pem,omitempty"`
// MinVersion sets the minimum TLS version that is acceptable.
// If not set, TLS 1.2 will be used. (optional)
MinVersion string `mapstructure:"min_version,omitempty"`
// MaxVersion sets the maximum TLS version that is acceptable.
// If not set, refer to crypto/tls for defaults. (optional)
MaxVersion string `mapstructure:"max_version,omitempty"`
// CipherSuites is a list of TLS cipher suites that the TLS transport can use.
// If left blank, a safe default list is used.
// See https://go.dev/src/crypto/tls/cipher_suites.go for a list of supported cipher suites.
CipherSuites []string `mapstructure:"cipher_suites,omitempty"`
// ReloadInterval specifies the duration after which the certificate will be reloaded
// If not set, it will never be reloaded (optional)
ReloadInterval time.Duration `mapstructure:"reload_interval,omitempty"`
// contains the elliptic curves that will be used in
// an ECDHE handshake, in preference order
// Defaults to empty list and "crypto/tls" defaults are used, internally.
CurvePreferences []string `mapstructure:"curve_preferences,omitempty"`
// Trusted platform module configuration
TPMConfig TPMConfig `mapstructure:"tpm,omitempty"`
}
// NewDefaultConfig creates a new Config with any default values set.
func NewDefaultConfig() Config {
return Config{}
}
// ClientConfig 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 ClientConfig struct {
// squash ensures fields are correctly decoded in embedded struct.
Config `mapstructure:",squash"`
// These are config options specific to client connections.
// In gRPC and HTTP when set to true, this is used to disable the client transport security.
// See https://godoc.org/google.golang.org/grpc#WithInsecure for gRPC.
// Please refer to https://godoc.org/crypto/tls#Config for more information.
// (optional, default false)
Insecure bool `mapstructure:"insecure,omitempty"`
// InsecureSkipVerify will enable TLS but not verify the certificate.
InsecureSkipVerify bool `mapstructure:"insecure_skip_verify,omitempty"`
// 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,omitempty"`
// prevent unkeyed literal initialization
_ struct{}
}
// NewDefaultClientConfig creates a new ClientConfig with any default values set.
func NewDefaultClientConfig() ClientConfig {
return ClientConfig{
Config: NewDefaultConfig(),
}
}
// ServerConfig 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 ServerConfig struct {
// squash ensures fields are correctly decoded in embedded struct.
Config `mapstructure:",squash"`
// 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,omitempty"`
// Reload the ClientCAs file when it is modified
// (optional, default false)
ReloadClientCAFile bool `mapstructure:"client_ca_file_reload,omitempty"`
// prevent unkeyed literal initialization
_ struct{}
}
// NewDefaultServerConfig creates a new ServerConfig with any default values set.
func NewDefaultServerConfig() ServerConfig {
return ServerConfig{
Config: NewDefaultConfig(),
}
}
// certReloader is a wrapper object for certificate reloading
// Its GetCertificate method will either return the current certificate or reload from disk
// if the last reload happened more than ReloadInterval ago
type certReloader struct {
nextReload time.Time
cert *tls.Certificate
lock sync.RWMutex
tls Config
}
func (c Config) newCertReloader() (*certReloader, error) {
cert, err := c.loadCertificate()
if err != nil {
return nil, err
}
return &certReloader{
tls: c,
nextReload: time.Now().Add(c.ReloadInterval),
cert: &cert,
}, nil
}
func (r *certReloader) GetCertificate() (*tls.Certificate, error) {
now := time.Now()
// Read locking here before we do the time comparison
// If a reload is in progress this will block and we will skip reloading in the current
// call once we can continue
r.lock.RLock()
if r.tls.ReloadInterval != 0 && r.nextReload.Before(now) && (r.tls.hasCertFile() || r.tls.hasKeyFile()) {
// Need to release the read lock, otherwise we deadlock
r.lock.RUnlock()
r.lock.Lock()
defer r.lock.Unlock()
cert, err := r.tls.loadCertificate()
if err != nil {
return nil, fmt.Errorf("failed to load TLS cert and key: %w", err)
}
r.cert = &cert
r.nextReload = now.Add(r.tls.ReloadInterval)
return r.cert, nil
}
defer r.lock.RUnlock()
return r.cert, nil
}
func (c Config) Validate() error {
if c.hasCAFile() && c.hasCAPem() {
return errors.New("provide either a CA file or the PEM-encoded string, but not both")
}
// Ensure certificate is not set using both file and PEM
if c.hasCertFile() && c.hasCertPem() {
return errors.New("provide either certificate file or PEM, but not both")
}
// Ensure key is not set using both file and PEM
if c.hasKeyFile() && c.hasKeyPem() {
return errors.New("provide either key file or PEM, but not both")
}
// Fail if only one of cert/key is provided (mismatch case)
if c.hasCert() != c.hasKey() {
return errors.New("TLS configuration must include both certificate and key (CertFile/CertPem and KeyFile/KeyPem)")
}
minTLS, err := convertVersion(c.MinVersion, defaultMinTLSVersion)
if err != nil {
return fmt.Errorf("invalid TLS min_version: %w", err)
}
maxTLS, err := convertVersion(c.MaxVersion, defaultMaxTLSVersion)
if err != nil {
return fmt.Errorf("invalid TLS max_version: %w", err)
}
if maxTLS < minTLS && maxTLS != defaultMaxTLSVersion {
return errors.New("invalid TLS configuration: min_version cannot be greater than max_version")
}
return nil
}
func (c ServerConfig) Validate() error {
// For servers, both certificate and key are required:
// - If both are missing, error.
// - If only one is provided (mismatch), error.
if !c.hasCert() && !c.hasKey() {
return errors.New("TLS configuration must include both certificate and key for server connections")
}
return nil
}
// loadTLSConfig loads TLS certificates and returns a tls.Config.
// This will set the RootCAs and Certificates of a tls.Config.
func (c Config) loadTLSConfig() (*tls.Config, error) {
certPool, err := c.loadCACertPool()
if err != nil {
return nil, err
}
var getCertificate func(*tls.ClientHelloInfo) (*tls.Certificate, error)
var getClientCertificate func(*tls.CertificateRequestInfo) (*tls.Certificate, error)
if c.hasCert() || c.hasKey() {
var certReloader *certReloader
certReloader, err = c.newCertReloader()
if err != nil {
return nil, fmt.Errorf("failed to load TLS cert and key: %w", err)
}
getCertificate = func(*tls.ClientHelloInfo) (*tls.Certificate, error) { return certReloader.GetCertificate() }
getClientCertificate = func(*tls.CertificateRequestInfo) (*tls.Certificate, error) { return certReloader.GetCertificate() }
}
minTLS, err := convertVersion(c.MinVersion, defaultMinTLSVersion)
if err != nil {
return nil, fmt.Errorf("invalid TLS min_version: %w", err)
}
maxTLS, err := convertVersion(c.MaxVersion, defaultMaxTLSVersion)
if err != nil {
return nil, fmt.Errorf("invalid TLS max_version: %w", err)
}
cipherSuites, err := convertCipherSuites(c.CipherSuites)
if err != nil {
return nil, err
}
allowedCurves := slices.Collect(maps.Values(tlsCurveTypes))
curvePreferences := make([]tls.CurveID, 0, len(c.CurvePreferences))
for _, curve := range c.CurvePreferences {
curveID, ok := tlsCurveTypes[curve]
if !ok {
return nil, fmt.Errorf("invalid curve type: %s. Expected values are %s", curveID, allowedCurves)
}
curvePreferences = append(curvePreferences, curveID)
}
// If no curve preferences were explicitly specified in the configuration, use
// the ones we allow. This helps in particular with FIPS builds where not all curves
// are allowed.
if len(curvePreferences) == 0 {
curvePreferences = allowedCurves
}
return &tls.Config{
RootCAs: certPool,
GetCertificate: getCertificate,
GetClientCertificate: getClientCertificate,
MinVersion: minTLS,
MaxVersion: maxTLS,
CipherSuites: cipherSuites,
CurvePreferences: curvePreferences,
}, nil
}
func convertCipherSuites(cipherSuites []string) ([]uint16, error) {
var result []uint16
var errs []error
for _, suite := range cipherSuites {
found := false
for _, supported := range tls.CipherSuites() {
if suite == supported.Name {
result = append(result, supported.ID)
found = true
break
}
}
if !found {
errs = append(errs, fmt.Errorf("invalid TLS cipher suite: %q", suite))
}
}
return result, errors.Join(errs...)
}
func (c Config) loadCACertPool() (*x509.CertPool, 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
switch {
case c.hasCAFile() && c.hasCAPem():
return nil, errors.New("failed to load CA CertPool: provide either a CA file or the PEM-encoded string, but not both")
case c.hasCAFile():
// Set up user specified truststore from file
certPool, err = c.loadCertFile(c.CAFile)
if err != nil {
return nil, fmt.Errorf("failed to load CA CertPool File: %w", err)
}
case c.hasCAPem():
// Set up user specified truststore from PEM
certPool, err = c.loadCertPem([]byte(c.CAPem))
if err != nil {
return nil, fmt.Errorf("failed to load CA CertPool PEM: %w", err)
}
}
return certPool, nil
}
func (c Config) loadCertFile(certPath string) (*x509.CertPool, error) {
certPem, err := os.ReadFile(filepath.Clean(certPath))
if err != nil {
return nil, fmt.Errorf("failed to load cert %s: %w", certPath, err)
}
return c.loadCertPem(certPem)
}
func (c Config) loadCertPem(certPem []byte) (*x509.CertPool, error) {
certPool := x509.NewCertPool()
if c.IncludeSystemCACertsPool {
scp, err := systemCertPool()
if err != nil {
return nil, err
}
if scp != nil {
certPool = scp
}
}
if !certPool.AppendCertsFromPEM(certPem) {
return nil, errors.New("failed to parse cert")
}
return certPool, nil
}
func (c Config) loadCertificate() (tls.Certificate, error) {
switch {
case c.hasCert() != c.hasKey():
return tls.Certificate{}, errors.New("for auth via TLS, provide both certificate and key, or neither")
case !c.hasCert() && !c.hasKey():
return tls.Certificate{}, nil
case c.hasCertFile() && c.hasCertPem():
return tls.Certificate{}, errors.New("for auth via TLS, provide either a certificate or the PEM-encoded string, but not both")
case c.hasKeyFile() && c.hasKeyPem():
return tls.Certificate{}, errors.New("for auth via TLS, provide either a key or the PEM-encoded string, but not both")
}
var certPem, keyPem []byte
var err error
if c.hasCertFile() {
certPem, err = os.ReadFile(c.CertFile)
if err != nil {
return tls.Certificate{}, err
}
} else {
certPem = []byte(c.CertPem)
}
if c.hasKeyFile() {
keyPem, err = os.ReadFile(c.KeyFile)
if err != nil {
return tls.Certificate{}, err
}
} else {
keyPem = []byte(c.KeyPem)
}
if c.TPMConfig.Enabled {
certificate, errTPM := c.TPMConfig.tpmCertificate(keyPem, certPem, openTPM(c.TPMConfig.Path))
if errTPM != nil {
return tls.Certificate{}, fmt.Errorf("failed to load private key from TPM: %w", errTPM)
}
return certificate, nil
}
certificate, errKeyPair := tls.X509KeyPair(certPem, keyPem)
if errKeyPair != nil {
return tls.Certificate{}, fmt.Errorf("failed to load TLS cert and key PEMs: %w", errKeyPair)
}
return certificate, err
}
func (c Config) loadCert(caPath string) (*x509.CertPool, error) {
caPEM, err := os.ReadFile(filepath.Clean(caPath))
if err != nil {
return nil, fmt.Errorf("failed to load CA %s: %w", caPath, err)
}
var certPool *x509.CertPool
if c.IncludeSystemCACertsPool {
if certPool, err = systemCertPool(); err != nil {
return nil, err
}
}
if certPool == nil {
certPool = x509.NewCertPool()
}
if !certPool.AppendCertsFromPEM(caPEM) {
return nil, fmt.Errorf("failed to parse CA %s", caPath)
}
return certPool, nil
}
// LoadTLSConfig loads the TLS configuration.
func (c ClientConfig) LoadTLSConfig(_ context.Context) (*tls.Config, error) {
if c.Insecure && !c.hasCA() {
return nil, nil
}
tlsCfg, err := c.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
}
// LoadTLSConfig loads the TLS configuration.
func (c ServerConfig) LoadTLSConfig(_ context.Context) (*tls.Config, error) {
tlsCfg, err := c.loadTLSConfig()
if err != nil {
return nil, fmt.Errorf("failed to load TLS config: %w", err)
}
if c.ClientCAFile != "" {
reloader, err := newClientCAsReloader(c.ClientCAFile, &c)
if err != nil {
return nil, err
}
if c.ReloadClientCAFile {
err = reloader.startWatching()
if err != nil {
return nil, err
}
tlsCfg.GetConfigForClient = func(*tls.ClientHelloInfo) (*tls.Config, error) { return reloader.getClientConfig(tlsCfg) }
}
tlsCfg.ClientCAs = reloader.certPool
tlsCfg.ClientAuth = tls.RequireAndVerifyClientCert
}
return tlsCfg, nil
}
func (c ServerConfig) loadClientCAFile() (*x509.CertPool, error) {
return c.loadCert(c.ClientCAFile)
}
func (c Config) hasCA() bool { return c.hasCAFile() || c.hasCAPem() }
func (c Config) hasCert() bool { return c.hasCertFile() || c.hasCertPem() }
func (c Config) hasKey() bool { return c.hasKeyFile() || c.hasKeyPem() }
func (c Config) hasCAFile() bool { return c.CAFile != "" }
func (c Config) hasCAPem() bool { return len(c.CAPem) != 0 }
func (c Config) hasCertFile() bool { return c.CertFile != "" }
func (c Config) hasCertPem() bool { return len(c.CertPem) != 0 }
func (c Config) hasKeyFile() bool { return c.KeyFile != "" }
func (c Config) hasKeyPem() bool { return len(c.KeyPem) != 0 }
func convertVersion(v string, defaultVersion uint16) (uint16, error) {
// Use a default that is explicitly defined
if v == "" {
return defaultVersion, nil
}
val, ok := tlsVersions[v]
if !ok {
return 0, fmt.Errorf("unsupported TLS version: %q", v)
}
return val, nil
}
var tlsVersions = map[string]uint16{
"1.0": tls.VersionTLS10,
"1.1": tls.VersionTLS11,
"1.2": tls.VersionTLS12,
"1.3": tls.VersionTLS13,
}
================================================
FILE: config/configtls/configtls_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configtls
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/config/configopaque"
"go.opentelemetry.io/collector/confmap/xconfmap"
)
func TestNewDefaultConfig(t *testing.T) {
expectedConfig := Config{}
config := NewDefaultConfig()
require.Equal(t, expectedConfig, config)
}
func TestNewDefaultClientConfig(t *testing.T) {
expectedConfig := ClientConfig{
Config: NewDefaultConfig(),
}
config := NewDefaultClientConfig()
require.Equal(t, expectedConfig, config)
}
func TestNewDefaultServerConfig(t *testing.T) {
expectedConfig := ServerConfig{
Config: NewDefaultConfig(),
}
config := NewDefaultServerConfig()
require.Equal(t, expectedConfig, config)
}
func TestOptionsToConfig(t *testing.T) {
tests := []struct {
name string
options Config
expectError string
}{
{
name: "should load system CA",
options: Config{CAFile: ""},
},
{
name: "should load custom CA",
options: Config{CAFile: filepath.Join("testdata", "ca-1.crt")},
},
{
name: "should load system CA and custom CA",
options: Config{IncludeSystemCACertsPool: true, CAFile: filepath.Join("testdata", "ca-1.crt")},
},
{
name: "should fail with invalid CA file path",
options: Config{CAFile: filepath.Join("testdata", "not/valid")},
expectError: "failed to load CA",
},
{
name: "should fail with invalid CA file content",
options: Config{CAFile: filepath.Join("testdata", "testCA-bad.txt")},
expectError: "failed to parse cert",
},
{
name: "should load valid TLS settings",
options: Config{
CAFile: filepath.Join("testdata", "ca-1.crt"),
CertFile: filepath.Join("testdata", "server-1.crt"),
KeyFile: filepath.Join("testdata", "server-1.key"),
},
},
{
name: "should fail with missing TLS KeyFile",
options: Config{
CAFile: filepath.Join("testdata", "ca-1.crt"),
CertFile: filepath.Join("testdata", "server-1.crt"),
},
expectError: "provide both certificate and key, or neither",
},
{
name: "should fail with invalid TLS KeyFile",
options: Config{
CAFile: filepath.Join("testdata", "ca-1.crt"),
CertFile: filepath.Join("testdata", "server-1.crt"),
KeyFile: filepath.Join("testdata", "not/valid"),
},
expectError: "failed to load TLS cert and key",
},
{
name: "should fail with missing TLS Cert",
options: Config{
CAFile: filepath.Join("testdata", "ca-1.crt"),
KeyFile: filepath.Join("testdata", "server-1.key"),
},
expectError: "provide both certificate and key, or neither",
},
{
name: "should fail with invalid TLS Cert",
options: Config{
CAFile: filepath.Join("testdata", "ca-1.crt"),
CertFile: filepath.Join("testdata", "not/valid"),
KeyFile: filepath.Join("testdata", "server-1.key"),
},
expectError: "failed to load TLS cert and key",
},
{
name: "should fail with invalid TLS CA",
options: Config{
CAFile: filepath.Join("testdata", "not/valid"),
},
expectError: "failed to load CA",
},
{
name: "should fail with invalid CA pool",
options: Config{
CAFile: filepath.Join("testdata", "testCA-bad.txt"),
},
expectError: "failed to parse cert",
},
{
name: "should pass with valid CA pool",
options: Config{
CAFile: filepath.Join("testdata", "ca-1.crt"),
},
},
{
name: "should pass with valid min and max version",
options: Config{
MinVersion: "1.1",
MaxVersion: "1.2",
},
},
{
name: "should pass with invalid min",
options: Config{
MinVersion: "1.7",
},
expectError: "invalid TLS min_",
},
{
name: "should pass with invalid max",
options: Config{
MaxVersion: "1.7",
},
expectError: "invalid TLS max_",
},
{
name: "should load custom CA PEM",
options: Config{CAPem: readFilePanics("testdata/ca-1.crt")},
},
{
name: "should load valid TLS settings with PEMs",
options: Config{
CAPem: readFilePanics("testdata/ca-1.crt"),
CertPem: readFilePanics("testdata/server-1.crt"),
KeyPem: readFilePanics("testdata/server-1.key"),
},
},
{
name: "mix Cert file and Key PEM provided",
options: Config{
CertFile: "testdata/server-1.crt",
KeyPem: readFilePanics("testdata/server-1.key"),
},
},
{
name: "mix Cert PEM and Key File provided",
options: Config{
CertPem: readFilePanics("testdata/server-1.crt"),
KeyFile: "testdata/server-1.key",
},
},
{
name: "should fail with invalid CA PEM",
options: Config{CAPem: readFilePanics("testdata/testCA-bad.txt")},
expectError: "failed to parse cert",
},
{
name: "should fail CA file and PEM both provided",
options: Config{
CAFile: "testdata/ca-1.crt",
CAPem: readFilePanics("testdata/ca-1.crt"),
},
expectError: "provide either a CA file or the PEM-encoded string, but not both",
},
{
name: "should fail Cert file and PEM both provided",
options: Config{
CertFile: "testdata/server-1.crt",
CertPem: readFilePanics("testdata/server-1.crt"),
KeyFile: "testdata/server-1.key",
},
expectError: "provide either a certificate or the PEM-encoded string, but not both",
},
{
name: "should fail Key file and PEM both provided",
options: Config{
CertFile: "testdata/server-1.crt",
KeyFile: "testdata/ca-1.crt",
KeyPem: readFilePanics("testdata/server-1.key"),
},
expectError: "provide either a key or the PEM-encoded string, but not both",
},
{
name: "should fail to load valid TLS settings with bad Cert PEM",
options: Config{
CAPem: readFilePanics("testdata/ca-1.crt"),
CertPem: readFilePanics("testdata/testCA-bad.txt"),
KeyPem: readFilePanics("testdata/server-1.key"),
},
expectError: "failed to load TLS cert and key PEMs",
},
{
name: "should fail to load valid TLS settings with bad Key PEM",
options: Config{
CAPem: readFilePanics("testdata/ca-1.crt"),
CertPem: readFilePanics("testdata/server-1.crt"),
KeyPem: readFilePanics("testdata/testCA-bad.txt"),
},
expectError: "failed to load TLS cert and key PEMs",
},
{
name: "should fail with missing TLS KeyPem",
options: Config{
CAPem: readFilePanics("testdata/ca-1.crt"),
CertPem: readFilePanics("testdata/server-1.crt"),
},
expectError: "provide both certificate and key, or neither",
},
{
name: "should fail with missing TLS Cert PEM",
options: Config{
CAPem: readFilePanics("testdata/ca-1.crt"),
KeyPem: readFilePanics("testdata/server-1.key"),
},
expectError: "provide both certificate and key, or neither",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
cfg, err := test.options.loadTLSConfig()
if test.expectError != "" {
assert.ErrorContains(t, err, test.expectError)
} else {
require.NoError(t, err)
assert.NotNil(t, cfg)
}
})
}
}
func readFilePanics(filePath string) configopaque.String {
fileContents, err := os.ReadFile(filepath.Clean(filePath))
if err != nil {
panic(fmt.Sprintf("failed to read file %s: %v", filePath, err))
}
return configopaque.String(fileContents)
}
func TestLoadTLSClientConfigError(t *testing.T) {
tlsSetting := ClientConfig{
Config: Config{
CertFile: "doesnt/exist",
KeyFile: "doesnt/exist",
},
}
_, err := tlsSetting.LoadTLSConfig(context.Background())
assert.Error(t, err)
}
func TestLoadTLSClientConfig(t *testing.T) {
tlsSetting := ClientConfig{
Insecure: true,
}
tlsCfg, err := tlsSetting.LoadTLSConfig(context.Background())
require.NoError(t, err)
assert.Nil(t, tlsCfg)
tlsSetting = ClientConfig{}
tlsCfg, err = tlsSetting.LoadTLSConfig(context.Background())
require.NoError(t, err)
assert.NotNil(t, tlsCfg)
tlsSetting = ClientConfig{
InsecureSkipVerify: true,
}
tlsCfg, err = tlsSetting.LoadTLSConfig(context.Background())
require.NoError(t, err)
assert.NotNil(t, tlsCfg)
assert.True(t, tlsCfg.InsecureSkipVerify)
}
func TestLoadTLSServerConfigError(t *testing.T) {
tlsSetting := ServerConfig{
Config: Config{
CertFile: "doesnt/exist",
KeyFile: "doesnt/exist",
},
}
_, err := tlsSetting.LoadTLSConfig(context.Background())
require.Error(t, err)
tlsSetting = ServerConfig{
ClientCAFile: "doesnt/exist",
}
_, err = tlsSetting.LoadTLSConfig(context.Background())
assert.Error(t, err)
}
func TestLoadTLSServerConfig(t *testing.T) {
tlsSetting := ServerConfig{}
tlsCfg, err := tlsSetting.LoadTLSConfig(context.Background())
require.NoError(t, err)
assert.NotNil(t, tlsCfg)
}
func TestLoadTLSServerConfigReload(t *testing.T) {
tmpCaPath := createTempClientCaFile(t)
overwriteClientCA(t, tmpCaPath, "ca-1.crt")
tlsSetting := ServerConfig{
ClientCAFile: tmpCaPath,
ReloadClientCAFile: true,
}
tlsCfg, err := tlsSetting.LoadTLSConfig(context.Background())
require.NoError(t, err)
assert.NotNil(t, tlsCfg)
firstClient, err := tlsCfg.GetConfigForClient(nil)
require.NoError(t, err)
overwriteClientCA(t, tmpCaPath, "ca-2.crt")
assert.EventuallyWithT(t, func(t *assert.CollectT) {
secondClient, err := tlsCfg.GetConfigForClient(nil)
require.NoError(t, err)
assert.NotEqual(t, firstClient.ClientCAs, secondClient.ClientCAs)
}, 5*time.Second, 10*time.Millisecond)
}
func TestLoadTLSServerConfigFailingReload(t *testing.T) {
tmpCaPath := createTempClientCaFile(t)
overwriteClientCA(t, tmpCaPath, "ca-1.crt")
tlsSetting := ServerConfig{
ClientCAFile: tmpCaPath,
ReloadClientCAFile: true,
}
tlsCfg, err := tlsSetting.LoadTLSConfig(context.Background())
require.NoError(t, err)
assert.NotNil(t, tlsCfg)
firstClient, err := tlsCfg.GetConfigForClient(nil)
require.NoError(t, err)
overwriteClientCA(t, tmpCaPath, "testCA-bad.txt")
assert.Eventually(t, func() bool {
_, loadError := tlsCfg.GetConfigForClient(nil)
return loadError == nil
}, 5*time.Second, 10*time.Millisecond)
secondClient, err := tlsCfg.GetConfigForClient(nil)
require.NoError(t, err)
assert.Equal(t, firstClient.ClientCAs, secondClient.ClientCAs)
}
func TestLoadTLSServerConfigFailingInitialLoad(t *testing.T) {
tmpCaPath := createTempClientCaFile(t)
overwriteClientCA(t, tmpCaPath, "testCA-bad.txt")
tlsSetting := ServerConfig{
ClientCAFile: tmpCaPath,
ReloadClientCAFile: true,
}
tlsCfg, err := tlsSetting.LoadTLSConfig(context.Background())
require.Error(t, err)
assert.Nil(t, tlsCfg)
}
func TestLoadTLSServerConfigWrongPath(t *testing.T) {
tmpCaPath := createTempClientCaFile(t)
tlsSetting := ServerConfig{
ClientCAFile: tmpCaPath + "wrong-path",
ReloadClientCAFile: true,
}
tlsCfg, err := tlsSetting.LoadTLSConfig(context.Background())
require.Error(t, err)
assert.Nil(t, tlsCfg)
}
func TestLoadTLSServerConfigFailing(t *testing.T) {
tmpCaPath := createTempClientCaFile(t)
overwriteClientCA(t, tmpCaPath, "ca-1.crt")
tlsSetting := ServerConfig{
ClientCAFile: tmpCaPath,
ReloadClientCAFile: true,
}
tlsCfg, err := tlsSetting.LoadTLSConfig(context.Background())
require.NoError(t, err)
assert.NotNil(t, tlsCfg)
firstClient, err := tlsCfg.GetConfigForClient(nil)
require.NoError(t, err)
assert.NotNil(t, firstClient)
err = os.Remove(tmpCaPath)
require.NoError(t, err)
firstClient, err = tlsCfg.GetConfigForClient(nil)
require.NoError(t, err)
assert.NotNil(t, firstClient)
}
func overwriteClientCA(t *testing.T, targetFilePath, testdataFileName string) {
targetFile, err := os.OpenFile(filepath.Clean(targetFilePath), os.O_RDWR, 0o600)
require.NoError(t, err)
testdataFilePath := filepath.Join("testdata", testdataFileName)
testdataFile, err := os.OpenFile(filepath.Clean(testdataFilePath), os.O_RDONLY, 0o200)
require.NoError(t, err)
_, err = io.Copy(targetFile, testdataFile)
assert.NoError(t, err)
assert.NoError(t, targetFile.Close())
assert.NoError(t, testdataFile.Close())
}
func createTempClientCaFile(t *testing.T) string {
tmpCa, err := os.CreateTemp(t.TempDir(), "ca-tmp.crt")
require.NoError(t, err)
tmpCaPath, err := filepath.Abs(tmpCa.Name())
assert.NoError(t, err)
assert.NoError(t, tmpCa.Close())
return tmpCaPath
}
func TestEagerlyLoadCertificate(t *testing.T) {
options := Config{
CertFile: filepath.Join("testdata", "client-1.crt"),
KeyFile: filepath.Join("testdata", "client-1.key"),
}
cfg, err := options.loadTLSConfig()
require.NoError(t, err)
assert.NotNil(t, cfg)
cert, err := cfg.GetCertificate(&tls.ClientHelloInfo{})
require.NoError(t, err)
assert.NotNil(t, cert)
pCert, err := x509.ParseCertificate(cert.Certificate[0])
require.NoError(t, err)
assert.NotNil(t, pCert)
assert.ElementsMatch(t, []string{"example1"}, pCert.DNSNames)
}
func TestCertificateReload(t *testing.T) {
tests := []struct {
name string
reloadInterval time.Duration
wait time.Duration
cert2 string
key2 string
dns1 string
dns2 string
errText string
}{
{
name: "Should reload the certificate after reload-interval",
reloadInterval: 100 * time.Microsecond,
wait: 100 * time.Microsecond,
cert2: "client-2.crt",
key2: "client-2.key",
dns1: "example1",
dns2: "example2",
},
{
name: "Should return same cert if called before reload-interval",
reloadInterval: 100 * time.Millisecond,
wait: 100 * time.Microsecond,
cert2: "client-2.crt",
key2: "client-2.key",
dns1: "example1",
dns2: "example1",
},
{
name: "Should always return same cert if reload-interval is 0",
reloadInterval: 0,
wait: 100 * time.Microsecond,
cert2: "client-2.crt",
key2: "client-2.key",
dns1: "example1",
dns2: "example1",
},
{
name: "Should return an error if reloading fails",
reloadInterval: 100 * time.Microsecond,
wait: 100 * time.Microsecond,
cert2: "testCA-bad.txt",
key2: "client-2.key",
dns1: "example1",
errText: "failed to load TLS cert and key: failed to load TLS cert and key PEMs: tls: failed to find any PEM data in certificate input",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// Copy certs into a temp dir so we can safely modify them
tempDir := t.TempDir()
certFile, err := os.CreateTemp(tempDir, "cert")
require.NoError(t, err)
defer certFile.Close()
keyFile, err := os.CreateTemp(tempDir, "key")
require.NoError(t, err)
defer keyFile.Close()
fdc, err := os.Open(filepath.Join("testdata", "client-1.crt"))
require.NoError(t, err)
_, err = io.Copy(certFile, fdc)
require.NoError(t, err)
require.NoError(t, fdc.Close())
fdk, err := os.Open(filepath.Join("testdata", "client-1.key"))
require.NoError(t, err)
_, err = io.Copy(keyFile, fdk)
assert.NoError(t, err)
assert.NoError(t, fdk.Close())
options := Config{
CertFile: certFile.Name(),
KeyFile: keyFile.Name(),
ReloadInterval: test.reloadInterval,
}
cfg, err := options.loadTLSConfig()
require.NoError(t, err)
assert.NotNil(t, cfg)
// Assert that we loaded the original certificate
cert, err := cfg.GetCertificate(&tls.ClientHelloInfo{})
require.NoError(t, err)
assert.NotNil(t, cert)
pCert, err := x509.ParseCertificate(cert.Certificate[0])
require.NoError(t, err)
assert.NotNil(t, pCert)
assert.Equal(t, test.dns1, pCert.DNSNames[0])
// Change the certificate
assert.NoError(t, certFile.Truncate(0))
assert.NoError(t, keyFile.Truncate(0))
_, err = certFile.Seek(0, 0)
require.NoError(t, err)
_, err = keyFile.Seek(0, 0)
require.NoError(t, err)
fdc2, err := os.Open(filepath.Join("testdata", test.cert2))
require.NoError(t, err)
_, err = io.Copy(certFile, fdc2)
assert.NoError(t, err)
assert.NoError(t, fdc2.Close())
fdk2, err := os.Open(filepath.Join("testdata", test.key2))
require.NoError(t, err)
_, err = io.Copy(keyFile, fdk2)
assert.NoError(t, err)
assert.NoError(t, fdk2.Close())
// Wait ReloadInterval to ensure a reload will happen
time.Sleep(test.wait)
// Assert that we loaded the new certificate
cert, err = cfg.GetCertificate(&tls.ClientHelloInfo{})
if test.errText == "" {
require.NoError(t, err)
assert.NotNil(t, cert)
pCert, err = x509.ParseCertificate(cert.Certificate[0])
require.NoError(t, err)
assert.NotNil(t, pCert)
assert.Equal(t, test.dns2, pCert.DNSNames[0])
} else {
assert.EqualError(t, err, test.errText)
}
})
}
}
func TestMinMaxTLSVersions(t *testing.T) {
tests := []struct {
name string
minVersion string
maxVersion string
outMinVersion uint16
outMaxVersion uint16
errorTxt string
}{
{name: `TLS Config ["", ""] to give [TLS1.2, 0]`, minVersion: "", maxVersion: "", outMinVersion: tls.VersionTLS12, outMaxVersion: 0},
{name: `TLS Config ["", "1.3"] to give [TLS1.2, TLS1.3]`, minVersion: "", maxVersion: "1.3", outMinVersion: tls.VersionTLS12, outMaxVersion: tls.VersionTLS13},
{name: `TLS Config ["1.2", ""] to give [TLS1.2, 0]`, minVersion: "1.2", maxVersion: "", outMinVersion: tls.VersionTLS12, outMaxVersion: 0},
{name: `TLS Config ["1.3", "1.3"] to give [TLS1.3, TLS1.3]`, minVersion: "1.3", maxVersion: "1.3", outMinVersion: tls.VersionTLS13, outMaxVersion: tls.VersionTLS13},
{name: `TLS Config ["1.0", "1.1"] to give [TLS1.0, TLS1.1]`, minVersion: "1.0", maxVersion: "1.1", outMinVersion: tls.VersionTLS10, outMaxVersion: tls.VersionTLS11},
{name: `TLS Config ["asd", ""] to give [Error]`, minVersion: "asd", maxVersion: "", errorTxt: `invalid TLS min_version: unsupported TLS version: "asd"`},
{name: `TLS Config ["", "asd"] to give [Error]`, minVersion: "", maxVersion: "asd", errorTxt: `invalid TLS max_version: unsupported TLS version: "asd"`},
{name: `TLS Config ["0.4", ""] to give [Error]`, minVersion: "0.4", maxVersion: "", errorTxt: `invalid TLS min_version: unsupported TLS version: "0.4"`},
// Allowing this, however, expecting downstream TLS handshake will throw an error
{name: `TLS Config ["1.2", "1.1"] to give [TLS1.2, TLS1.1]`, minVersion: "1.2", maxVersion: "1.1", outMinVersion: tls.VersionTLS12, outMaxVersion: tls.VersionTLS11},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
setting := Config{
MinVersion: test.minVersion,
MaxVersion: test.maxVersion,
}
config, err := setting.loadTLSConfig()
if test.errorTxt == "" {
assert.Equal(t, config.MinVersion, test.outMinVersion)
assert.Equal(t, config.MaxVersion, test.outMaxVersion)
} else {
assert.EqualError(t, err, test.errorTxt)
}
})
}
}
func TestConfigValidate(t *testing.T) {
tests := []struct {
name string
tlsConfig Config
errorTxt string
}{
{name: `TLS Config ["", ""] to be valid`, tlsConfig: Config{MinVersion: "", MaxVersion: ""}},
{name: `TLS Config ["", "1.3"] to be valid`, tlsConfig: Config{MinVersion: "", MaxVersion: "1.3"}},
{name: `TLS Config ["1.2", ""] to be valid`, tlsConfig: Config{MinVersion: "1.2", MaxVersion: ""}},
{name: `TLS Config ["1.3", "1.3"] to be valid`, tlsConfig: Config{MinVersion: "1.3", MaxVersion: "1.3"}},
{name: `TLS Config ["1.0", "1.1"] to be valid`, tlsConfig: Config{MinVersion: "1.0", MaxVersion: "1.1"}},
{name: `TLS Config ["asd", ""] to give [Error]`, tlsConfig: Config{MinVersion: "asd", MaxVersion: ""}, errorTxt: `invalid TLS min_version: unsupported TLS version: "asd"`},
{name: `TLS Config ["", "asd"] to give [Error]`, tlsConfig: Config{MinVersion: "", MaxVersion: "asd"}, errorTxt: `invalid TLS max_version: unsupported TLS version: "asd"`},
{name: `TLS Config ["0.4", ""] to give [Error]`, tlsConfig: Config{MinVersion: "0.4", MaxVersion: ""}, errorTxt: `invalid TLS min_version: unsupported TLS version: "0.4"`},
{name: `TLS Config ["1.2", "1.1"] to give [Error]`, tlsConfig: Config{MinVersion: "1.2", MaxVersion: "1.1"}, errorTxt: `invalid TLS configuration: min_version cannot be greater than max_version`},
{name: `TLS Config with both CA File and PEM`, tlsConfig: Config{CAFile: "test", CAPem: "test"}, errorTxt: `provide either a CA file or the PEM-encoded string, but not both`},
{name: `TLS Config with cert file but no key`, tlsConfig: Config{CertFile: "cert.pem"}, errorTxt: `TLS configuration must include both certificate and key (CertFile/CertPem and KeyFile/KeyPem)`},
{name: `TLS Config with key file but no cert`, tlsConfig: Config{KeyFile: "key.pem"}, errorTxt: `TLS configuration must include both certificate and key (CertFile/CertPem and KeyFile/KeyPem)`},
{name: `TLS Config with cert PEM but no key`, tlsConfig: Config{CertPem: "cert-pem"}, errorTxt: `TLS configuration must include both certificate and key (CertFile/CertPem and KeyFile/KeyPem)`},
{name: `TLS Config with key PEM but no cert`, tlsConfig: Config{KeyPem: "key-pem"}, errorTxt: `TLS configuration must include both certificate and key (CertFile/CertPem and KeyFile/KeyPem)`},
{name: `TLS Config with both cert file and cert PEM`, tlsConfig: Config{CertFile: "cert.pem", CertPem: "cert-pem", KeyFile: "key.pem"}, errorTxt: `provide either certificate file or PEM, but not both`},
{name: `TLS Config with both key file and key PEM`, tlsConfig: Config{CertFile: "cert.pem", KeyFile: "key.pem", KeyPem: "key-pem"}, errorTxt: `provide either key file or PEM, but not both`},
{name: `TLS Config with cert file and key PEM`, tlsConfig: Config{CertFile: "cert.pem", KeyPem: "key-pem"}},
{name: `TLS Config with cert PEM and key file`, tlsConfig: Config{CertPem: "cert-pem", KeyFile: "key.pem"}},
{name: `TLS Config with valid cert and key files`, tlsConfig: Config{CertFile: "cert.pem", KeyFile: "key.pem"}},
{name: `TLS Config with valid cert and key PEM`, tlsConfig: Config{CertPem: "cert-pem", KeyPem: "key-pem"}},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
err := test.tlsConfig.Validate()
if test.errorTxt == "" {
assert.NoError(t, err)
} else {
assert.EqualError(t, err, test.errorTxt)
}
})
}
}
func TestCipherSuites(t *testing.T) {
tests := []struct {
name string
tlsSetting Config
wantErr string
result []uint16
}{
{
name: "no suites set",
tlsSetting: Config{},
result: nil,
},
{
name: "one cipher suite set",
tlsSetting: Config{
CipherSuites: []string{"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"},
},
result: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
},
{
name: "invalid cipher suite set",
tlsSetting: Config{
CipherSuites: []string{"FOO"},
},
wantErr: `invalid TLS cipher suite: "FOO"`,
},
{
name: "multiple invalid cipher suites set",
tlsSetting: Config{
CipherSuites: []string{"FOO", "BAR"},
},
wantErr: `invalid TLS cipher suite: "FOO"
invalid TLS cipher suite: "BAR"`,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
config, err := test.tlsSetting.loadTLSConfig()
if test.wantErr != "" {
assert.EqualError(t, err, test.wantErr)
} else {
require.NoError(t, err)
assert.Equal(t, test.result, config.CipherSuites)
}
})
}
}
func TestSystemCertPool(t *testing.T) {
anError := errors.New("my error")
tests := []struct {
name string
tlsConfig Config
wantErr error
systemCertFn func() (*x509.CertPool, error)
}{
{
name: "not using system cert pool",
tlsConfig: Config{
IncludeSystemCACertsPool: false,
CAFile: filepath.Join("testdata", "ca-1.crt"),
},
wantErr: nil,
systemCertFn: x509.SystemCertPool,
},
{
name: "using system cert pool",
tlsConfig: Config{
IncludeSystemCACertsPool: true,
CAFile: filepath.Join("testdata", "ca-1.crt"),
},
wantErr: nil,
systemCertFn: x509.SystemCertPool,
},
{
name: "error loading system cert pool",
tlsConfig: Config{
IncludeSystemCACertsPool: true,
CAFile: filepath.Join("testdata", "ca-1.crt"),
},
wantErr: anError,
systemCertFn: func() (*x509.CertPool, error) {
return nil, anError
},
},
{
name: "nil system cert pool",
tlsConfig: Config{
IncludeSystemCACertsPool: true,
CAFile: filepath.Join("testdata", "ca-1.crt"),
},
wantErr: nil,
systemCertFn: func() (*x509.CertPool, error) {
return nil, nil
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
oldSystemCertPool := systemCertPool
systemCertPool = test.systemCertFn
defer func() {
systemCertPool = oldSystemCertPool
}()
serverConfig := ServerConfig{
Config: test.tlsConfig,
}
c, err := serverConfig.LoadTLSConfig(context.Background())
if test.wantErr != nil {
require.ErrorContains(t, err, test.wantErr.Error())
} else {
assert.NotNil(t, c.RootCAs)
}
clientConfig := ClientConfig{
Config: test.tlsConfig,
}
c, err = clientConfig.LoadTLSConfig(context.Background())
if test.wantErr != nil {
assert.ErrorContains(t, err, test.wantErr.Error())
} else {
assert.NotNil(t, c.RootCAs)
}
})
}
}
func TestSystemCertPool_loadCert(t *testing.T) {
anError := errors.New("my error")
tests := []struct {
name string
tlsConfig Config
wantErr error
systemCertFn func() (*x509.CertPool, error)
}{
{
name: "not using system cert pool",
tlsConfig: Config{
IncludeSystemCACertsPool: false,
},
wantErr: nil,
systemCertFn: x509.SystemCertPool,
},
{
name: "using system cert pool",
tlsConfig: Config{
IncludeSystemCACertsPool: true,
},
wantErr: nil,
systemCertFn: x509.SystemCertPool,
},
{
name: "error loading system cert pool",
tlsConfig: Config{
IncludeSystemCACertsPool: true,
},
wantErr: anError,
systemCertFn: func() (*x509.CertPool, error) {
return nil, anError
},
},
{
name: "nil system cert pool",
tlsConfig: Config{
IncludeSystemCACertsPool: true,
},
wantErr: nil,
systemCertFn: func() (*x509.CertPool, error) {
return nil, nil
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
oldSystemCertPool := systemCertPool
systemCertPool = test.systemCertFn
defer func() {
systemCertPool = oldSystemCertPool
}()
certPool, err := test.tlsConfig.loadCert(filepath.Join("testdata", "ca-1.crt"))
if test.wantErr != nil {
assert.Equal(t, test.wantErr, err)
} else {
assert.NotNil(t, certPool)
}
})
}
}
func TestCurvePreferences(t *testing.T) {
type testCase struct {
name string
preferences []string
expectedCurveIDs []tls.CurveID
expectedErr string
}
tests := []testCase{
{
name: "P521",
preferences: []string{"P521"},
expectedCurveIDs: []tls.CurveID{tls.CurveP521},
},
{
name: "P-256",
preferences: []string{"P256"},
expectedCurveIDs: []tls.CurveID{tls.CurveP256},
},
{
name: "multiple",
preferences: []string{"P256", "P521"},
expectedCurveIDs: []tls.CurveID{tls.CurveP256, tls.CurveP521},
},
{
name: "invalid-curve",
preferences: []string{"P25223236"},
expectedCurveIDs: []tls.CurveID{},
expectedErr: "invalid curve type",
},
}
// X25519 curves are not supported when GODEBUG=fips140=only is set, so we
// detect if it is and conditionally add test cases for those curves.
if !strings.Contains(os.Getenv("GODEBUG"), "fips140=only") {
tests = append(tests,
testCase{
name: "X25519MLKEM768",
preferences: []string{"X25519MLKEM768"},
expectedCurveIDs: []tls.CurveID{tls.X25519MLKEM768},
},
testCase{
name: "X25519",
preferences: []string{"X25519"},
expectedCurveIDs: []tls.CurveID{tls.X25519},
},
)
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
tlsSetting := ClientConfig{
Config: Config{
CurvePreferences: test.preferences,
},
}
config, err := tlsSetting.LoadTLSConfig(context.Background())
if test.expectedErr == "" {
require.NoError(t, err)
require.ElementsMatchf(t, test.expectedCurveIDs, config.CurvePreferences, "expected %v, got %v", test.expectedCurveIDs, config.CurvePreferences)
} else {
require.ErrorContains(t, err, test.expectedErr)
}
})
}
}
func TestServerConfigValidate(t *testing.T) {
tests := []struct {
name string
serverConfig ServerConfig
errorTxt string
}{
{
name: "server config without certificates",
serverConfig: ServerConfig{},
errorTxt: "TLS configuration must include both certificate and key for server connections",
},
{
name: "server config with cert file but no key",
serverConfig: ServerConfig{
Config: Config{
CertFile: "cert.pem",
},
},
errorTxt: "config: TLS configuration must include both certificate and key (CertFile/CertPem and KeyFile/KeyPem)",
},
{
name: "server config with key file but no cert",
serverConfig: ServerConfig{
Config: Config{
KeyFile: "key.pem",
},
},
errorTxt: "config: TLS configuration must include both certificate and key (CertFile/CertPem and KeyFile/KeyPem)",
},
{
name: "server config with cert PEM but no key",
serverConfig: ServerConfig{
Config: Config{
CertPem: "cert-pem",
},
},
errorTxt: "config: TLS configuration must include both certificate and key (CertFile/CertPem and KeyFile/KeyPem)",
},
{
name: "server config with key PEM but no cert",
serverConfig: ServerConfig{
Config: Config{
KeyPem: "key-pem",
},
},
errorTxt: "config: TLS configuration must include both certificate and key (CertFile/CertPem and KeyFile/KeyPem)",
},
{
name: "server config with both cert file and cert PEM",
serverConfig: ServerConfig{
Config: Config{
CertFile: "cert.pem",
CertPem: "cert-pem",
KeyFile: "key.pem",
},
},
errorTxt: "config: provide either certificate file or PEM, but not both",
},
{
name: "server config with both key file and key PEM",
serverConfig: ServerConfig{
Config: Config{
CertFile: "cert.pem",
KeyFile: "key.pem",
KeyPem: "key-pem",
},
},
errorTxt: "config: provide either key file or PEM, but not both",
},
{
name: "valid server config with cert and key files",
serverConfig: ServerConfig{
Config: Config{
CertFile: "cert.pem",
KeyFile: "key.pem",
},
},
},
{
name: "valid server config with cert and key PEM",
serverConfig: ServerConfig{
Config: Config{
CertPem: "cert-pem",
KeyPem: "key-pem",
},
},
},
{
name: "valid server config with mixed cert file and key PEM",
serverConfig: ServerConfig{
Config: Config{
CertFile: "cert.pem",
KeyPem: "key-pem",
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
err := xconfmap.Validate(test.serverConfig)
if test.errorTxt == "" {
assert.NoError(t, err)
} else {
assert.ErrorContains(t, err, test.errorTxt)
}
})
}
}
func TestClientConfigValidate(t *testing.T) {
tests := []struct {
name string
clientConfig ClientConfig
errorTxt string
}{
{
name: "valid empty client config",
clientConfig: ClientConfig{},
},
{
name: "valid client config with insecure connection",
clientConfig: ClientConfig{
Insecure: true,
},
},
{
name: "valid client config with cert and key files",
clientConfig: ClientConfig{
Config: Config{
CertFile: "cert.pem",
KeyFile: "key.pem",
},
},
},
{
name: "valid client config with mixed cert file and key PEM",
clientConfig: ClientConfig{
Config: Config{
CertFile: "cert.pem",
KeyPem: "key-pem",
},
},
},
{
name: "client config with only cert file",
clientConfig: ClientConfig{
Config: Config{
CertFile: "cert.pem",
},
},
errorTxt: "config: TLS configuration must include both certificate and key (CertFile/CertPem and KeyFile/KeyPem)",
},
{
name: "client config with only key file",
clientConfig: ClientConfig{
Config: Config{
KeyFile: "key.pem",
},
},
errorTxt: "config: TLS configuration must include both certificate and key (CertFile/CertPem and KeyFile/KeyPem)",
},
{
name: "client config with only cert PEM",
clientConfig: ClientConfig{
Config: Config{
CertPem: "cert-pem",
},
},
errorTxt: "config: TLS configuration must include both certificate and key (CertFile/CertPem and KeyFile/KeyPem)",
},
{
name: "client config with only key PEM",
clientConfig: ClientConfig{
Config: Config{
KeyPem: "key-pem",
},
},
errorTxt: "config: TLS configuration must include both certificate and key (CertFile/CertPem and KeyFile/KeyPem)",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
err := xconfmap.Validate(test.clientConfig)
if test.errorTxt == "" {
assert.NoError(t, err)
} else {
assert.ErrorContains(t, err, test.errorTxt)
}
})
}
}
================================================
FILE: config/configtls/curves_fips.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:build requirefips
package configtls // import "go.opentelemetry.io/collector/config/configtls"
import "crypto/tls"
var tlsCurveTypes = map[string]tls.CurveID{
"P256": tls.CurveP256,
"P384": tls.CurveP384,
"P521": tls.CurveP521,
// The following X25519 curves are not available in FIPS mode, so we remove them from the map.
// See also https://cs.opensource.google/go/go/+/refs/tags/go1.24.6:src/crypto/ecdh/x25519.go
//"X25519": tls.X25519,
//"X25519MLKEM768": tls.X25519MLKEM768,
}
================================================
FILE: config/configtls/curves_nofips.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:build !requirefips
package configtls // import "go.opentelemetry.io/collector/config/configtls"
import "crypto/tls"
var tlsCurveTypes = map[string]tls.CurveID{
"P256": tls.CurveP256,
"P384": tls.CurveP384,
"P521": tls.CurveP521,
"X25519": tls.X25519,
"X25519MLKEM768": tls.X25519MLKEM768,
}
================================================
FILE: config/configtls/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package configtls implements the TLS settings to load and
// configure TLS clients and servers.
package configtls // import "go.opentelemetry.io/collector/config/configtls"
================================================
FILE: config/configtls/go.mod
================================================
module go.opentelemetry.io/collector/config/configtls
go 1.25.0
require (
github.com/foxboron/go-tpm-keyfiles v0.0.0-20250903184740-5d135037bd4d
github.com/fsnotify/fsnotify v1.9.0
github.com/google/go-tpm v0.9.8
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/config/configopaque v1.54.0
go.opentelemetry.io/collector/confmap/xconfmap v0.148.0
go.opentelemetry.io/collector/internal/testutil v0.148.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/google/go-tpm-tools v0.4.7 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/collector/confmap v1.54.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/sys v0.41.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/config/configopaque => ../configopaque
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/confmap => ../../confmap
replace go.opentelemetry.io/collector/confmap/xconfmap => ../../confmap/xconfmap
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
================================================
FILE: config/configtls/go.sum
================================================
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/foxboron/go-tpm-keyfiles v0.0.0-20250903184740-5d135037bd4d h1:EdO/NMMuCZfxhdzTZLuKAciQSnI2DV+Ppg8+vAYrnqA=
github.com/foxboron/go-tpm-keyfiles v0.0.0-20250903184740-5d135037bd4d/go.mod h1:uAyTlAUxchYuiFjTHmuIEJ4nGSm7iOPaGcAyA81fJ80=
github.com/foxboron/swtpm_test v0.0.0-20230726224112-46aaafdf7006 h1:50sW4r0PcvlpG4PV8tYh2RVCapszJgaOLRCS2subvV4=
github.com/foxboron/swtpm_test v0.0.0-20230726224112-46aaafdf7006/go.mod h1:eIXCMsMYCaqq9m1KSSxXwQG11krpuNPGP3k0uaWrbas=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/google/go-configfs-tsm v0.3.3-0.20240919001351-b4b5b84fdcbc h1:SG12DWUUM5igxm+//YX5Yq4vhdoRnOG9HkCodkOn+YU=
github.com/google/go-configfs-tsm v0.3.3-0.20240919001351-b4b5b84fdcbc/go.mod h1:EL1GTDFMb5PZQWDviGfZV9n87WeGTR/JUg13RfwkgRo=
github.com/google/go-sev-guest v0.14.0 h1:dCb4F3YrHTtrDX3cYIPTifEDz7XagZmXQioxRBW4wOo=
github.com/google/go-sev-guest v0.14.0/go.mod h1:SK9vW+uyfuzYdVN0m8BShL3OQCtXZe/JPF7ZkpD3760=
github.com/google/go-tdx-guest v0.3.2-0.20241009005452-097ee70d0843 h1:+MoPobRN9HrDhGyn6HnF5NYo4uMBKaiFqAtf/D/OB4A=
github.com/google/go-tdx-guest v0.3.2-0.20241009005452-097ee70d0843/go.mod h1:g/n8sKITIT9xRivBUbizo34DTsUm2nN2uU3A662h09g=
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/go-tpm-tools v0.4.7 h1:J3ycC8umYxM9A4eF73EofRZu4BxY0jjQnUnkhIBbvws=
github.com/google/go-tpm-tools v0.4.7/go.mod h1:gSyXTZHe3fgbzb6WEGd90QucmsnT1SRdlye82gH8QjQ=
github.com/google/logger v1.1.1 h1:+6Z2geNxc9G+4D4oDO9njjjn2d0wN5d7uOo0vOIW1NQ=
github.com/google/logger v1.1.1/go.mod h1:BkeJZ+1FhQ+/d087r4dzojEg1u2ZX+ZqG1jTUrLM+zQ=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: config/configtls/metadata.yaml
================================================
type: config/configtls
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: config/configtls/testdata/ca-1.crt
================================================
-----BEGIN CERTIFICATE-----
MIIDNjCCAh4CCQDkU3rM23H5hzANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJB
VTESMBAGA1UECAwJQXVzdHJhbGlhMQ8wDQYDVQQHDAZTeWRuZXkxEjAQBgNVBAoM
CU15T3JnTmFtZTEVMBMGA1UEAwwMTXlDb21tb25OYW1lMB4XDTIyMDgwMzA0MTgx
OFoXDTMyMDczMTA0MTgxOFowXTELMAkGA1UEBhMCQVUxEjAQBgNVBAgMCUF1c3Ry
YWxpYTEPMA0GA1UEBwwGU3lkbmV5MRIwEAYDVQQKDAlNeU9yZ05hbWUxFTATBgNV
BAMMDE15Q29tbW9uTmFtZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AK836YUxmCDcznt11ReI5fY/DSJzz+Fs7czoE72RMvW+SMH2YhX9XC55xAMPZ+IV
szoG5Fatd/GWBfoACmaM3ZEmYskuRnu4pxqOEpRIsBukOiILBMxa/cwqiDyLiacC
w0B1NhysG28XnxUWrYxd9jFlJ+wAIx7XT+1QM0xGCGr9agSQ/ow6+QMWZ5Qc1n2e
EmaoU861qlF+0LeyZeBNeo+C7jTikIC+CRKVNX5t9MLqSmlxfrXe0qCS99zmPKfg
OhtteZVAKbdPKSoi2ls6EQ1dNB2Mq3GHkd8kGi30FuRCTQLKaXacUdjtQfbKxuGl
RjXlN6mDoUs8mIO861mVFXECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAUrgRTBBO
pwYjZsLNw10FYK19P6FpVm/nbbzTJmqKlxReLRkkTyNm/tB5W1LdRN9RG15h62Ii
JBGxpeCMDElwCwXN2OOwqdXczafLa9AhPnPw/DYuQAd9dS7/XHG/ArQFTL+GLd8T
bdlnED9Z9qMygF13btLQUHzKaOk6dndLsquoTjgjj4SNBe2Isj7z4upZOix2cgJB
9ddZGlv8/zKSgRp9UotGOOxG7HJ1KWhYLU7E0aERqambNv8UFvhmf+biHq3nCeAF
HBeua27MNj4kGCzqHS7sVqZKVU81aFyhV2WmfIUA0Qp+nh9QEW0yrgI+pTnOx6np
JUHGleZ3rKHQZw==
-----END CERTIFICATE-----
================================================
FILE: config/configtls/testdata/ca-2.crt
================================================
-----BEGIN CERTIFICATE-----
MIIDNjCCAh4CCQDOIY+kxxC6dzANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJB
VTESMBAGA1UECAwJQXVzdHJhbGlhMQ8wDQYDVQQHDAZTeWRuZXkxEjAQBgNVBAoM
CU15T3JnTmFtZTEVMBMGA1UEAwwMTXlDb21tb25OYW1lMB4XDTIyMDgwMzA0MTgy
N1oXDTMyMDczMTA0MTgyN1owXTELMAkGA1UEBhMCQVUxEjAQBgNVBAgMCUF1c3Ry
YWxpYTEPMA0GA1UEBwwGU3lkbmV5MRIwEAYDVQQKDAlNeU9yZ05hbWUxFTATBgNV
BAMMDE15Q29tbW9uTmFtZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
ALm3FA/0uN36RPpMnRbV7HF2dY0quEevuHFPjDwBa6bRrn+p1kDezkZ+YGatT7yg
Or+HLU+9WoJKQe738d/gp+BRwTpuTkFh6YeM1t+EclZZfSX/jQwia/7p0CGZ24wD
CR33YifauT+AMS1VLbkTTFQFQ+XwkIqUL8aP3T1XvYSnOvbeNcIYEH6ABXyDwBdL
ytuJ0YnFwYj0lJcAHf1EYQEXNXz6s1E1Y3r4pZfo209PK8aHR/oT1Gc310B35cfA
trYaGsynla/aUjrvm147hj32Wln3EG10qkIOAwkR0iTYNfYdCG4vJKUFOSj1AFei
A0PWnOYP+zDFR1bEz22VfV8CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAluPjsPhg
OZ31iLqnRoxDJ8EuYtY2ft1RV4WUY+o9zY24JRZ/1b1cC6VzTECQQTiOuM+G+U0k
FlTfU3VaY+m+oP8F/2n9X+9M87flX1AgE3EOqae3CNN7IYCu6ipCLaOZQwL4CjFe
hsurX7eyu8LDrX8/T+B60V13gCsXi6E1Dt+9lRNzjIU9YcqwuDqlPU0I0WCjuP2H
flisUQ3Z70gg9bPd7h3EVfdrlceGYQpuiO5aHXhzr8oIoOo3K6L6RXaHAjLd56SM
ZKa+VUCCJcfq6HwnSRUveu2txD1lUHFag2amfJu22V0JgB4Dvm1ZkzU4PM00ZJSK
lSu6E+lI09aLHg==
-----END CERTIFICATE-----
================================================
FILE: config/configtls/testdata/client-1.crt
================================================
-----BEGIN CERTIFICATE-----
MIIDVDCCAjygAwIBAgIJANt5fkUlfxyeMA0GCSqGSIb3DQEBCwUAMF0xCzAJBgNV
BAYTAkFVMRIwEAYDVQQIDAlBdXN0cmFsaWExDzANBgNVBAcMBlN5ZG5leTESMBAG
A1UECgwJTXlPcmdOYW1lMRUwEwYDVQQDDAxNeUNvbW1vbk5hbWUwHhcNMjIwODAz
MDQxODE5WhcNMzIwNzMxMDQxODE5WjBdMQswCQYDVQQGEwJBVTESMBAGA1UECAwJ
QXVzdHJhbGlhMQ8wDQYDVQQHDAZTeWRuZXkxEjAQBgNVBAoMCU15T3JnTmFtZTEV
MBMGA1UEAwwMTXlDb21tb25OYW1lMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAwDgNEcPTkTASpfFa0AwPlUFPWhlm2Av1mh3oNsf3kHOBXQymJ3HkXDq/
7durWduubkP1jsOGqO9rcXD1Q3mmNYqsqRRydi5DbMHcFcSSA6g2QncTJwhRE/q/
/00t6e5BhBLXscK+uJEDzEGu9CJVFkkdbeMccfb26C3os1VHGzcp5c/pCNjj93TM
3iwlQYMoEgCo7iUDxyIQ5tjQBn/QmEPcytut11tAIlGPy+SxQjMCykREPOVuwvNh
hZFscpCkvQPTEvv7KBZFBvYafa820CY3z++IIqQ7YBZdxYpYwBuVamUyPKB+lpsn
aD5G2LQjENdjYcRXys04bWgafalZJQIDAQABoxcwFTATBgNVHREEDDAKgghleGFt
cGxlMTANBgkqhkiG9w0BAQsFAAOCAQEAoN6fyv+0ri3wnYMZaP2+m4NA/gk+I4Lp
eP4OpQHkHbm3wjbWZUYLJZ6IvhPHfCNAXdqCs+mpG35HI6Bg+x1CVFrNeueInKTg
0v+0q1FlvSQhsQJoumX2bk/uSLHMIU3hhYIts0vFC0k04Vf7n9hEq7pOZD/akTaw
haLsQe/SRXSTjkar+Csi4DXyi/qshlkV6FOUz9vogAR0W3l8x7dqzwBHL4gRMddM
ZdSfhVFOMwKqUrucYebYZhdAvYqMtlTph46lk+hd5TarFDFJ2zEjbx9NU5gY1b8V
/Kfm2ZHR0yWKGfg9I4TRGZgufm1HBEMnMq1b15DUZxNTagFtPAP18Q==
-----END CERTIFICATE-----
================================================
FILE: config/configtls/testdata/client-1.key
================================================
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAwDgNEcPTkTASpfFa0AwPlUFPWhlm2Av1mh3oNsf3kHOBXQym
J3HkXDq/7durWduubkP1jsOGqO9rcXD1Q3mmNYqsqRRydi5DbMHcFcSSA6g2QncT
JwhRE/q//00t6e5BhBLXscK+uJEDzEGu9CJVFkkdbeMccfb26C3os1VHGzcp5c/p
CNjj93TM3iwlQYMoEgCo7iUDxyIQ5tjQBn/QmEPcytut11tAIlGPy+SxQjMCykRE
POVuwvNhhZFscpCkvQPTEvv7KBZFBvYafa820CY3z++IIqQ7YBZdxYpYwBuVamUy
PKB+lpsnaD5G2LQjENdjYcRXys04bWgafalZJQIDAQABAoIBAFemN29uWD7QKPC6
SaqslT599W0kQB0r9uY71POF44Fe6hI//lPmPzc/It2XWV80KSnmm0ZqKjFGWzvz
QiNuiTfI8Ep5JGh3WA9zpqPWaq54OaW9HmKiDDaMFJiZ3OHa3s0Wunw4TTdkCNNO
8DQqo5nx5RWChioBbz0YEhAURsRFbGqFavDPvlEPOSanCB+mDOliKqX0XizffRZ3
UBQuWa6VjDxHH93b+oJ2/zR5UOlXKHgcqNWeBofxBiiX8ZF5ylwNGOCEE2Gm+KfZ
KUYxGlDKohSYxVjmcyLPoWGrUX83lDKD2u9VrVdgCJwA+IHEsIg9KARb6jFLzACp
RYSDM9ECgYEA7gm8+h44D27I1yRF/3rbhxggsgRo0j5ag9NVLehp11G0ZsdoklJx
uVhDJbjHG9HVqEMfduljr4GpyeMt2ayEmo/ejcWyl0JBMFXPXRvrubM5qoCVOqUu
WYo/JtvIyEAQQicwo5okiPddhFvcQebSH7NXRpKWROMftnlisgtv/xsCgYEAzrk1
vB2O/DTydcLxkY2m8E5qo1ZvTMPW6211ZCKNyQ475ZE/QxZ5iuodwErCQOHjAlV7
n6FeWWZveOsVQeTkSvUOnPCocct+/Dx+sMcRO8k9HuC33bNcw9eHwBoztginIxEb
s7ee+S06AT6r7SQScgBrhD6uevW+dUVbdw/6TL8CgYEAzOyNSDZjxMV3GeAccsjt
3Oukmhy5sOYFPp/dINyI4dlxGVpqaC2Zwhp+FCdzIjwPWAARQmnCbAGQjkGJ429l
6ToaOqsMCLP9MwNstZen5AKrjmGMFyTFNkiR/X4Q6HReitT6Rp4Y/eEXHS+H+yQf
mTLn29WukDeHwavWj7jQ/ikCgYBDPYEZ+C9bH8nBvjAfHQkw3wDWsjWvrX/JwifN
82NVA3k+GbmPE89i/PXCZ066Ff9l8fItISr0P1qA5U5byZzsOLuRFsJjiUJ7vx2i
WI3leXaVBZko1r+UwBVayesKCdR7loQBN/fQqwJUB1Oa5gHN7Q8Ly+uq+SYDNRUk
LCFJNwKBgGWcVuIarQ2mCLqqZ0zxeAp3lFTNeWG2ZMQtzeuo0iGx0xTTUEaZSiNW
MSAvYjGrRzM6XpGEYasfwy0Zoc3loi9nzP5uE4tv8vE72nyMf+OhaPG+Rn+mdBv4
7emViVNVfzLW7L//IkxtEamV0yc6gYwcCfzUckxxXVRD4z2aM78q
-----END RSA PRIVATE KEY-----
================================================
FILE: config/configtls/testdata/client-2.crt
================================================
-----BEGIN CERTIFICATE-----
MIIDVDCCAjygAwIBAgIJANt5fkUlfxygMA0GCSqGSIb3DQEBCwUAMF0xCzAJBgNV
BAYTAkFVMRIwEAYDVQQIDAlBdXN0cmFsaWExDzANBgNVBAcMBlN5ZG5leTESMBAG
A1UECgwJTXlPcmdOYW1lMRUwEwYDVQQDDAxNeUNvbW1vbk5hbWUwHhcNMjIwODAz
MDQxODI3WhcNMzIwNzMxMDQxODI3WjBdMQswCQYDVQQGEwJBVTESMBAGA1UECAwJ
QXVzdHJhbGlhMQ8wDQYDVQQHDAZTeWRuZXkxEjAQBgNVBAoMCU15T3JnTmFtZTEV
MBMGA1UEAwwMTXlDb21tb25OYW1lMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEA3lTMPmGwYAC86UQWOiXxz5zOPWayTJabxZFyiBkO2kSB2oPD8UuY9Lmn
SFd3lj1d5d87sODImwBfpAl8OtWkAlQ7Gz94Qx94tkK1h5t44u2GEM/AZZbAoeaw
gLxoGXnWPIt043ZocMlwZHhuwOLTg0sPA8Nqiw6wwrTw6iPvbZZ9h5MtQTETJ2qa
BN1sKB/NsHWGfGsIcsiTvDMYfjjOv8w9xKjsVvipsA4MTN6caoJ7Ei1XT61ssp08
zUT+SCQQERlgO+rnVCsz2XbFEJBYqvV1m+QoU/4Ow9D7toT6tgMrGTIKgefiyUSW
toMosHXVxK1O7cEjO5ml1o2yhVn6DQIDAQABoxcwFTATBgNVHREEDDAKgghleGFt
cGxlMjANBgkqhkiG9w0BAQsFAAOCAQEAO17rOH/w0fZtJ6XL/uVBtcRBX6++7gx0
0hwQ6Gle/XBC/Rt9kK8bGNlULC7f/RIwe2tAvSR9ecu6e29r9tHl+YD7nrHSUsfX
iio+pTX7NwD3cERqftq8Gigb9Kgsi6zMlJucwZZFoJfZqC7I0QYUCWasuQLfAQPy
X8YFqVsuylICeKbW3WjN3c+Myfr8FGVnA7iXl28nlCMhqQHBQamG1SXlUjYi3jCv
+9yPO3fpKeY/KZTfiNVxwYo/JbXorKHjzzObP3gqvHY5ofeOfVsG9dyaTj3oTT1W
uLvKkUJqCtgFDsx/F3cUp+aBMCFRkbUHDHEl7HJizqutomTZategNg==
-----END CERTIFICATE-----
================================================
FILE: config/configtls/testdata/client-2.key
================================================
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA3lTMPmGwYAC86UQWOiXxz5zOPWayTJabxZFyiBkO2kSB2oPD
8UuY9LmnSFd3lj1d5d87sODImwBfpAl8OtWkAlQ7Gz94Qx94tkK1h5t44u2GEM/A
ZZbAoeawgLxoGXnWPIt043ZocMlwZHhuwOLTg0sPA8Nqiw6wwrTw6iPvbZZ9h5Mt
QTETJ2qaBN1sKB/NsHWGfGsIcsiTvDMYfjjOv8w9xKjsVvipsA4MTN6caoJ7Ei1X
T61ssp08zUT+SCQQERlgO+rnVCsz2XbFEJBYqvV1m+QoU/4Ow9D7toT6tgMrGTIK
gefiyUSWtoMosHXVxK1O7cEjO5ml1o2yhVn6DQIDAQABAoIBAQDTSe8YUapWch0V
6fjdpfXaAgEV5SUJGBBNf95CbN3qnDRzv8lU5S0lVdIeM9GYXBWCQdXuUJEUjRRX
RhRjrWjCNd4+FOFrmNsVCuyNRTlrH6PLEkSbxtqmgh+3GFYt79WjkDyzdnHmzekb
8j/+2xF7srdAMlRsdreRMnfJbAE8OISfk3aYoLc1Kn4YGKXLegcEAPTrEKOMNaXZ
jGRwHlj1A/HFCh8tHnzdLJOay1aKxyuo82zPV4uVCXcz5Tof21QPjDjW3SaPOq+m
UHRYGKqFzK7WWBN7xnbMIkup/HpRtx7ZJ3jaTHBAP72foQyfbBJ+1KLawPK/BBET
wcgBkvHBAoGBAPg/tgA3mjNWkwMeAao+YCOND2nhsXejnSHfA44PPOyDWqxefzDx
hyf29Rq2dDcJyJV6uyMkyu9XQb0pf+Hh+a4NRXlS6dPzIW0Z1XPoSu9VeXVLPSh0
CZg82t8UtGMTtGinfDEULM3v0sQWhND1xsX/vFCZvBEXlwAI1zuqFm/1AoGBAOVF
7GywlCdm4xn4RErA/+HqWPaNAaJAAo4OVgvj/mcu91swGrQoLy6yFEu61mkAr2nn
H+hYV/WU8nhkR6nStq1eydBKZxzyRIi8ZAmT4CemGX2C/fNF7KSNYqzFsHK/Pymh
AsMNiykvPFCeG7S2GiDEFO+AKExqy5O16Q4nBYq5AoGAUZ9BDBk8Dh0tAR1glsUj
fwzmQH4Ah8G37GcTGCZSdcFKktoPH9yJ/83nEP1kgKQq21sbJJb4UnFyH+wBLBfM
rDmY2ic00odiOikAUbfSy5Zi9PnkBeUBMpjvreF03g6ghrhq0Qg9IwjzV52/1aS5
0mgfVrD1cPk8oLpHakqmTfECgYEAvvra8MK59oRWwigyws43l7j8+AsHBF8rgadh
d7AoF01hEF1msREUFGKUU2zD8111wNKcmo8UXeX/f9eQdl6meo4Nr+p6L/uCqR+8
eNnsCzrp2soFveJON9fqDR7zVvIFrCiJw26BsAG/zSuWypYx938+LS5k4xrGjzkl
c/t/O0kCgYEA0Iwb+qWOCQ4U+wlQjYjhTn3scDZ/EpVEzuh53FEOWY1OtXm8f1qK
m5I8yj1mOJ5N11cuk7zi0THFJ/wDYSJBshG+05jx2ngk5rWsnnV0jmX6ivOUMrFf
BbZZjSvzRQlRQbrtwKWluC7yDO0X+YuG4Wbte4i6XNMn5fseWTnwGdY=
-----END RSA PRIVATE KEY-----
================================================
FILE: config/configtls/testdata/server-1.crt
================================================
-----BEGIN CERTIFICATE-----
MIIDVDCCAjygAwIBAgIJANt5fkUlfxydMA0GCSqGSIb3DQEBCwUAMF0xCzAJBgNV
BAYTAkFVMRIwEAYDVQQIDAlBdXN0cmFsaWExDzANBgNVBAcMBlN5ZG5leTESMBAG
A1UECgwJTXlPcmdOYW1lMRUwEwYDVQQDDAxNeUNvbW1vbk5hbWUwHhcNMjIwODAz
MDQxODE4WhcNMzIwNzMxMDQxODE4WjBdMQswCQYDVQQGEwJBVTESMBAGA1UECAwJ
QXVzdHJhbGlhMQ8wDQYDVQQHDAZTeWRuZXkxEjAQBgNVBAoMCU15T3JnTmFtZTEV
MBMGA1UEAwwMTXlDb21tb25OYW1lMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAzEc0zUZZhPx+70Et3YVvNv9Ss/EriG3x/Eo7gUr+JH1bi2dJe8bBhaqJ
j1XXr0qFAahOlj9+/NDS7RON8p1AeXLMzL8mJ162WUU+a4Uus78Md6l0oVKNsVm+
tUiNT2RVeJWkJtJPRnScdXqf10I498xNNYDfYcl0u/JHX33oekwKPhxn6zHJ6V/b
Z6BbOsV/MXaB6WRYHRfzxO25hoxELBS6rdlrLdeqy0hlQ3i2UE/Yet3pl1Wi18bh
Bk6kyhoqIfF5tMLNdwwYfHNVNutpv9jUcPFkxLWXVU/FxwjJrBsGVAp4fohUbSN8
lHj+BtYthyF6vL0gdXxnD01jkYXDVwIDAQABoxcwFTATBgNVHREEDDAKgghleGFt
cGxlMTANBgkqhkiG9w0BAQsFAAOCAQEAKFflP5DnHjloj3PvSQ1iVKBRTvxwEtKV
9ObCPK59uUaLZbwZ0QljgfkqxNtL/8FB/LgBdvqHioYx9F4kocpJrwci8a6oU42/
iOpw+D4d6emjPWZDf3WVgmynjyPd5VxK/TO8ly//FjPXNTDpWCs/CqIphHR64/5B
l1VunN9STqj24WP+Ud+3OxNd1GPu0r45lqO/PDdn7cWWYbNZ67zJdnZPoHQRyL1U
dZ/jYmS99y66AfG16TFFKNa3bfCS4uRFOlddNs1uXnq40yqsaE/Gv6Ts7X3uND/0
qExVGHaK2iR7qgBIV8axE3nwZ5RbIxF5LR64I43RI5HaDrRt2e1YAw==
-----END CERTIFICATE-----
================================================
FILE: config/configtls/testdata/server-1.key
================================================
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAzEc0zUZZhPx+70Et3YVvNv9Ss/EriG3x/Eo7gUr+JH1bi2dJ
e8bBhaqJj1XXr0qFAahOlj9+/NDS7RON8p1AeXLMzL8mJ162WUU+a4Uus78Md6l0
oVKNsVm+tUiNT2RVeJWkJtJPRnScdXqf10I498xNNYDfYcl0u/JHX33oekwKPhxn
6zHJ6V/bZ6BbOsV/MXaB6WRYHRfzxO25hoxELBS6rdlrLdeqy0hlQ3i2UE/Yet3p
l1Wi18bhBk6kyhoqIfF5tMLNdwwYfHNVNutpv9jUcPFkxLWXVU/FxwjJrBsGVAp4
fohUbSN8lHj+BtYthyF6vL0gdXxnD01jkYXDVwIDAQABAoIBAQCxGeLLPPyLcSTT
ZJzQ+sgq1DztSF9HjppG8kyYkV24YP4m48svhmds7ScJn5C4plCd2T8Yv7/mi1zy
sQtVlcO6By9LK0V2yIQq7P9q1DJjH3U9oSo+WoYBhh7yqA3rEL+RJZsFFTwphxvG
NiOxyfX9z5/4jNwduTx9XVVHkq8kpo9saJ0FAPCgAlwKTrUwVANm9D0UlJEotat2
qDLV2WKzJHL3v0MLku2hN1B4pOpOCIzQUNMtxvW9kWSETBBYuEDj0qmUoRQgCJS2
4dbF5nvbiK3dEdZgp1dGDX2RJY/+Oe3jDlCtvJCLShLnb2g/j8ByKRY7zICrYtIO
EBxvmvyhAoGBAOqRQl95l8zw8uaBZu8QQ9RnbSt9nk7aYJGMdUABE88Hc8sOtC2U
ayBKd1v/d46Op2otDEYWNQI0DzLdW0LBIjP1qbTiGL2FCknGq4fqbvySy21DcX+b
+sGd90fNRVG3XJGn63nhoGKGnYvzQGrmm0cVlbQ3KyMzwhsqcUrDv5/ZAoGBAN7x
dDoIximyp/98CFmCFMtxCMBvNVywmHTTyieeO36A3Mdy300IItUwBtENxMjotXB0
Kc6MX3JrJh8d+z8xsAkmeFnfL9uMjjL0EpZy11eDTi/+fUZ/Bcph/zOYFfPFccK7
cHLCJyFhnSJNR63QE0Avz6oPcFI7rvjMJnSOsq6vAoGAIznhT9lA1MQyli9EuA4n
QZSurmNVDN56tiDz0sLWqLajyxDQOjAZzmWgey5oU/5UYfuV5kibeVM8HRVlCSdb
7ZWtAL8bnAqIuv+c7vJj7IZXCnegaduQ0tbYNe47xMPWoQEoucsKfQFeU5AaUnOD
Si+RpdjLH6Q8ODwte17ePjECgYA5G/j99MluXQmT9J3e7+eLxcTMJrCwsbwcETSz
uWDcIv5rSQ3SmcbyfX8BhllmbdYsnFUpR+QbVz9IsVFu+rdxYJ1ryDRmNTcn7kXk
rD5leIlK2hIVQOymzzukZ80XyPg/PeysOPf1ISAzbUBzUd3cj2LO2W2YYxmLOiCP
sw4qmQKBgCR0EpTfcm+wzQ3LWAPbsA6DsqvDtYfQumfg1uFglHrJXHtPZj9Mp0om
ZdRWL4vOmI/o6lfmHYdqHMnwyMHJU1JkcBnnhbQo5cGJZ17d580FZ9oVPVqqVQvR
OhjWcqU7n+0ZvSWakHAvbgPSCeQpA19x0WLDR1WUbKszHIoQwzwe
-----END RSA PRIVATE KEY-----
================================================
FILE: config/configtls/testdata/server-2.crt
================================================
-----BEGIN CERTIFICATE-----
MIIDVDCCAjygAwIBAgIJANt5fkUlfxyfMA0GCSqGSIb3DQEBCwUAMF0xCzAJBgNV
BAYTAkFVMRIwEAYDVQQIDAlBdXN0cmFsaWExDzANBgNVBAcMBlN5ZG5leTESMBAG
A1UECgwJTXlPcmdOYW1lMRUwEwYDVQQDDAxNeUNvbW1vbk5hbWUwHhcNMjIwODAz
MDQxODI3WhcNMzIwNzMxMDQxODI3WjBdMQswCQYDVQQGEwJBVTESMBAGA1UECAwJ
QXVzdHJhbGlhMQ8wDQYDVQQHDAZTeWRuZXkxEjAQBgNVBAoMCU15T3JnTmFtZTEV
MBMGA1UEAwwMTXlDb21tb25OYW1lMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEA2bdn2gv8i6J0gNHilYDFVTwYUgNWJNe23sdxyuAbSoocir61LT1HBtWP
m/Slfw8xcGYomiu5iIPNWF6KGDpUsafawkxZFySNCWTXlU68Vk7iyJLGQ3e1uhFM
lOsRvrRB65OUsdyXF1e4+T1KMGiz2yEWIDxpe1o4uchenVM1WOddlZMIPB1E3UzR
DjLJUXfihWeoQwWisfBJw6bHPne1sj7OmS0feUJUza9i8bXe8TgYLkLyo5IUNQpf
EkpAu+7DoOdRfURApz4TpeY5RkQ5ztSC8LnXQ0ZR4C0D8i8DdOxW1Sj2HTkjY9KO
exPnP12UgVeo9KsdtEqIF15n3TDxzQIDAQABoxcwFTATBgNVHREEDDAKgghleGFt
cGxlMjANBgkqhkiG9w0BAQsFAAOCAQEAq05+tW+c6jg/Kes9RPpnGKgm73COu+qR
T/n9D+jeqizBszQbjvwwQWzyQEIeN5hVf0DqMa/GJPvtVu+Im4FeW9cCc/2UccvK
KsRagdLJUBih9KkY4Z+uzVcPUt6xYFQHgKlR7aR7uSoUicgw8Xtg84px4BDrKlJJ
RoPAI6+RdauRUs9XqYG4bU1rr1pbhxNMMlNYAvLekgT/5h39uxuaqgkmWZXRRQBP
rbddw+wPPhYJ0X6lzs5LbtAa1oiaLgQxWnrmKWAgEQqfr8apWBuC3CWg9GyXH5OK
5AgjxKCQJHc5WdUUxbcNYCMBubEBxGp9FTwfvYOF6sFhrC8+1kTrHg==
-----END CERTIFICATE-----
================================================
FILE: config/configtls/testdata/server-2.key
================================================
-----BEGIN RSA PRIVATE KEY-----
MIIEpgIBAAKCAQEA2bdn2gv8i6J0gNHilYDFVTwYUgNWJNe23sdxyuAbSoocir61
LT1HBtWPm/Slfw8xcGYomiu5iIPNWF6KGDpUsafawkxZFySNCWTXlU68Vk7iyJLG
Q3e1uhFMlOsRvrRB65OUsdyXF1e4+T1KMGiz2yEWIDxpe1o4uchenVM1WOddlZMI
PB1E3UzRDjLJUXfihWeoQwWisfBJw6bHPne1sj7OmS0feUJUza9i8bXe8TgYLkLy
o5IUNQpfEkpAu+7DoOdRfURApz4TpeY5RkQ5ztSC8LnXQ0ZR4C0D8i8DdOxW1Sj2
HTkjY9KOexPnP12UgVeo9KsdtEqIF15n3TDxzQIDAQABAoIBAQCoCDKiCoBG8QJD
7jmXs4QZ7cDDg4m387lTJdGAiAjoNcIjn17L5LBt6OPmtSIJ95rrqh0KKFcQstEI
tCaW3mZBm1Buh2h3QSGNL4Rn2xXm8wl7TjSxG7JpQjK9+NOAQTVjcUrhH2SJgo3j
51bcF+NAa7/c72Nl7dM8KBZGDFNvIe3uhFk67aBZ/A3Qto7ZYTz9hrowc08iehhm
i9fZ0T09UPw3Mplpx5bUx8kSZQLe15No7B5VJ31J4+64ufG9iypEW62bueROitgz
shBTifEg/9rhZbt2wDIC31oZ2jXORSFLZPKjji5ygSm7ExVvaTfgsn+XMXP4JXVt
v5F0gDthAoGBAPPk3y9ZAmTTecAiFL01FQdpEpd4qOQQenGBhhuOxaP7ZQRjxGDK
yWV7taJyAVhdXAhKLMpqsE6wLMskRp8FZwkFZc+1Wt5QE4XfNmme2s/tSltQ2uG+
7bMeGys5yhUjsWWuZ/57/1xlyUmMpYTPG66CQuqg4qGn3SFg2aiqZ5GZAoGBAOSF
5gzo4Wn/jFeRTXUqkn1smBPBZC5/zqyfX6zlzJrPFm93NJ8jZqp53qzixcCs+yls
EWIUkFCFy7GN+tU+Wkz4JWs2BTXcThraoa4hciAmYIHCB+ZTbncnaHbBP/C/0pZp
Z0HvhKR01FAXEDfxN+A0807LoHcFDlv7cRvBzqpVAoGBAJlkPsJGluzW3GHsjWKa
eglZGipN5trZSkkND01RtBf4SoZCQQYnRBchgRET5qiuvu0vyY/dHdm/j8yLmib1
fOH9lRTXmLjtX/n4cv5mvHO9Z+Car67/J/xZWPkMtX4qHq42zI0Pa4GvOrOZU5h9
sYlFv9RVL3RAYSFXCk28LrsxAoGBAMrBLoq3uQAWF0u+hM329sBHsGqexKcpCJNK
WFYMEcws/wfo6QxlGXsZ5ALatYAtOi7XTlkKS7zV6RNhGHNI/k+aP4DvDhJqo/XZ
k2fvDtYNlsSqBd5KmhEoKtxqu7N8Tnjbjh0HSVWsvo9M1zv7ToskD9gSfQ38s2/T
GNj6zMV9AoGBAJAdinxXTWMWLCWyZREPolRiRMoahYhPYuHWHU0LiWJ3VidGo8Uv
haTOyhyx3zHqRtQKBTHdQLcGa2NGwdeKWmctCn7v593ykKIwde66xu0RFNbwrxbl
D2m1zpim+nKj4YQqmSBg+Y7FixLgywKgE2djmUTHFl8Fa3d1xw9mXUS5
-----END RSA PRIVATE KEY-----
================================================
FILE: config/configtls/testdata/testCA-bad.txt
================================================
-----BEGIN CERTIFICATE-----
bad certificate
-----END CERTIFICATE-----
================================================
FILE: config/configtls/tpm.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configtls // import "go.opentelemetry.io/collector/config/configtls"
import (
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
tpmkeyfile "github.com/foxboron/go-tpm-keyfiles"
"github.com/google/go-tpm/tpm2/transport"
)
// TPMConfig defines trusted platform module configuration for storing TLS keys.
type TPMConfig struct {
Enabled bool `mapstructure:"enabled"`
// The path to the TPM device or Unix domain socket.
// For instance /dev/tpm0 or /dev/tpmrm0.
Path string `mapstructure:"path"`
OwnerAuth string `mapstructure:"owner_auth"`
Auth string `mapstructure:"auth"`
// prevent unkeyed literal initialization
_ struct{}
}
func (c TPMConfig) tpmCertificate(keyPem, certPem []byte, openTPM func() (transport.TPMCloser, error)) (tls.Certificate, error) {
tpm, err := openTPM()
if err != nil {
return tls.Certificate{}, err
}
tpmKey, err := tpmkeyfile.Decode(keyPem)
if err != nil {
return tls.Certificate{}, fmt.Errorf("failed to load TPM key: %w", err)
}
tpmSigner, err := tpmKey.Signer(tpm, []byte(c.OwnerAuth), []byte(c.Auth))
if err != nil {
return tls.Certificate{}, fmt.Errorf("failed to load TPM signer: %w", err)
}
certDER, _ := pem.Decode(certPem)
x509Cert, err := x509.ParseCertificate(certDER.Bytes)
if err != nil {
return tls.Certificate{}, fmt.Errorf("failed to parse certificate: %w", err)
}
return tls.Certificate{
Certificate: [][]byte{
x509Cert.Raw,
},
Leaf: x509Cert,
PrivateKey: tpmSigner,
}, nil
}
================================================
FILE: config/configtls/tpm_open_linux.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:build linux
package configtls // import "go.opentelemetry.io/collector/config/configtls"
import (
"errors"
"fmt"
"github.com/google/go-tpm/tpm2/transport"
"github.com/google/go-tpm/tpmutil"
)
// for testing
var tpmSimulator transport.TPMCloser
func openTPM(path string) func() (transport.TPMCloser, error) {
return func() (transport.TPMCloser, error) {
if path == "" {
return nil, errors.New("TPM path is not set")
}
if path == "simulator" {
return tpmSimulator, nil
}
tpm, err := tpmutil.OpenTPM(path)
if err != nil {
return nil, fmt.Errorf("failed to open TPM (%s): %w", path, err)
}
return transport.FromReadWriteCloser(tpm), nil
}
}
================================================
FILE: config/configtls/tpm_open_others.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:build !linux && !windows
package configtls // import "go.opentelemetry.io/collector/config/configtls"
import (
"errors"
"github.com/google/go-tpm/tpm2/transport"
)
// for testing
var tpmSimulator transport.TPMCloser
func openTPM(path string) func() (transport.TPMCloser, error) {
return func() (transport.TPMCloser, error) {
if path == "simulator" {
return tpmSimulator, nil
}
return nil, errors.New("TPM is not supported on this platform")
}
}
================================================
FILE: config/configtls/tpm_open_windows.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:build windows
package configtls // import "go.opentelemetry.io/collector/config/configtls"
import (
"fmt"
"github.com/google/go-tpm/tpm2/transport"
"github.com/google/go-tpm/tpmutil"
)
func openTPM(_ string) func() (transport.TPMCloser, error) {
return func() (transport.TPMCloser, error) {
tpm, err := tpmutil.OpenTPM()
if err != nil {
return nil, fmt.Errorf("failed to open TPM: %w", err)
}
return transport.FromReadWriteCloser(tpm), nil
}
}
================================================
FILE: config/configtls/tpm_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Don't run this test on Windows, as it requires a TPM simulator which depends on openssl headers.
//go:build !windows && !darwin
package configtls // import "go.opentelemetry.io/collector/config/configtls"
import (
"bytes"
"crypto"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"io"
"math/big"
"net"
"os"
"path/filepath"
"testing"
"time"
keyfile "github.com/foxboron/go-tpm-keyfiles"
"github.com/google/go-tpm/tpm2"
"github.com/google/go-tpm/tpm2/transport"
"github.com/google/go-tpm/tpm2/transport/simulator"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/internal/testutil"
)
func TestTPM_loadCertificate(t *testing.T) {
testutil.SkipIfFIPSOnly(t, "use of CFB is not allowed in FIPS 140-only mode")
tpm, err := simulator.OpenSimulator()
require.NoError(t, err)
defer tpm.Close()
tpmSimulator = tpm
// create a TPM key and certificate
// the TPM key will be stored in the simulator
tpmKey, cert := createTPMKeyCert(t, tpm)
tempFileKey, err := os.CreateTemp(t.TempDir(), "tpmkey.key")
require.NoError(t, err)
_, err = tempFileKey.Write(tpmKey)
require.NoError(t, err)
tempFileCrt, err := os.CreateTemp(t.TempDir(), "tpmcert.crt")
require.NoError(t, err)
_, err = tempFileCrt.Write(cert)
require.NoError(t, err)
defer func() {
tempFileKey.Close()
os.Remove(tempFileKey.Name())
tempFileCrt.Close()
os.Remove(tempFileCrt.Name())
}()
tlsCfg := Config{
CertFile: tempFileCrt.Name(),
KeyFile: tempFileKey.Name(),
TPMConfig: TPMConfig{
Enabled: true,
Path: "simulator",
},
}
tlsCertificate, err := tlsCfg.loadCertificate()
require.NoError(t, err)
require.NotNil(t, tlsCertificate)
h := crypto.SHA256.New()
h.Write([]byte("message"))
b := h.Sum(nil)
// this is delegated to the TPM
signer := tlsCertificate.PrivateKey.(crypto.Signer)
signature, _ := signer.Sign(io.Reader(nil), b, crypto.SHA256)
// load the key again to get access to verify function
loadedTPMKey, err := keyfile.Decode(tpmKey)
require.NoError(t, err)
ok, err := loadedTPMKey.Verify(crypto.SHA256, b, signature)
require.NoError(t, err)
assert.True(t, ok)
}
func TestTPM_loadCertificate_error(t *testing.T) {
tlsCfg := Config{
CertPem: "invalid",
KeyPem: "invalid",
TPMConfig: TPMConfig{
Enabled: true,
Path: "simulator",
},
}
tlsCertificate, err := tlsCfg.loadCertificate()
assert.Equal(t, "failed to load private key from TPM: failed to load TPM key: not an armored key", err.Error())
require.NotNil(t, tlsCertificate)
}
func TestTPM_tpmCertificate_errors(t *testing.T) {
testutil.SkipIfFIPSOnly(t, "use of CFB is not allowed in FIPS 140-only mode")
tpm, err := simulator.OpenSimulator()
require.NoError(t, err)
defer tpm.Close()
openTPMFunc := func() (transport.TPMCloser, error) {
return tpm, nil
}
key, _ := createTPMKeyCert(t, tpm)
invalidCert := []byte(`-----BEGIN CERTIFICATE-----
VGhpcyBpcyBub3QgYSBjZXJ0aWZpY2F0ZS4=
-----END CERTIFICATE-----`)
tests := []struct {
name string
key string
openTPM func() (transport.TPMCloser, error)
cert string
err string
}{
{
name: "invalid key",
key: "invalid",
openTPM: openTPMFunc,
err: "failed to load TPM key: not an armored key",
},
{
name: "invalid cert",
key: string(key),
cert: string(invalidCert),
openTPM: openTPMFunc,
err: "failed to parse certificate: x509: malformed certificate",
},
{
name: "failed to open TPM",
openTPM: func() (transport.TPMCloser, error) {
return nil, errors.New("failed to open TPM")
},
err: "failed to open TPM",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
tpmCfg := &TPMConfig{}
_, err = tpmCfg.tpmCertificate([]byte(test.key), []byte(test.cert), test.openTPM)
assert.EqualError(t, err, test.err)
})
}
}
func TestTPM_open(t *testing.T) {
socketPath := filepath.Join(t.TempDir(), "app.sock")
listener, err := net.Listen("unix", socketPath)
require.NoError(t, err)
defer listener.Close()
tests := []struct {
path string
err string
}{
{
path: "",
err: "TPM path is not set",
},
{
path: "/foo",
err: "failed to open TPM (/foo): stat /foo: no such file or directory",
},
{
path: socketPath,
},
}
for _, test := range tests {
t.Run(test.path, func(t *testing.T) {
tpm, openErr := openTPM(test.path)()
if test.err != "" {
require.Nil(t, tpm)
assert.Equal(t, test.err, openErr.Error())
} else {
require.NoError(t, openErr)
require.NotNil(t, tpm)
tpm.Close()
}
})
}
}
func createTPMKeyCert(t *testing.T, tpm transport.TPMCloser) ([]byte, []byte) {
tpmKey, err := keyfile.NewLoadableKey(tpm, tpm2.TPMAlgECC, 256, []byte(""))
require.NoError(t, err)
tpmKeySigner, err := tpmKey.Signer(tpm, []byte(""), []byte(""))
require.NoError(t, err)
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) // 128-bit random number
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
require.NoError(t, err)
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"OpenTelemetry"},
CommonName: "localhost",
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(366 * 24 * time.Hour),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
BasicConstraintsValid: true,
DNSNames: []string{"localhost"},
IPAddresses: []net.IP{
net.ParseIP("127.0.0.1"),
net.ParseIP("::1"),
},
}
tpmPublicKey, err := tpmKey.PublicKey()
require.NoError(t, err)
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, tpmPublicKey, tpmKeySigner)
require.NoError(t, err)
certBuffer := &bytes.Buffer{}
err = pem.Encode(certBuffer, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
require.NoError(t, err)
return tpmKey.Bytes(), certBuffer.Bytes()
}
================================================
FILE: confmap/Makefile
================================================
include ../Makefile.Common
================================================
FILE: confmap/README.md
================================================
# Confmap
| Status | |
| ------------- |-----------|
| Stability | [stable]: logs, metrics, traces |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Apkg%2Fconfmap) [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aclosed+is%3Aissue+label%3Apkg%2Fconfmap) |
| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@mx-psi](https://www.github.com/mx-psi), [@evan-bradley](https://www.github.com/evan-bradley) |
[stable]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#stable
# High Level Design
## Conf
The [Conf](confmap.go) represents the raw configuration for a service (e.g. OpenTelemetry Collector).
## Provider
The [Provider](provider.go) provides configuration, and allows to watch/monitor for changes. Any `Provider`
has a `` associated with it, and will provide configs for `configURI` that follow the ":" format.
This format is compatible with the URI definition (see [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986)).
The `` MUST be always included in the `configURI`. The scheme for any `Provider` MUST be at least 2
characters long to avoid conflicting with a driver-letter identifier as specified in
[file URI syntax](https://datatracker.ietf.org/doc/html/rfc8089#section-2).
## Converter
The [Converter](converter.go) allows implementing conversion logic for the provided configuration. One of the most
common use-case is to migrate/transform the configuration after a backwards incompatible change.
## Resolver
The `Resolver` handles the use of multiple [Providers](#provider) and [Converters](#converter)
simplifying configuration parsing, monitoring for updates, and the overall life-cycle of the used config providers.
The `Resolver` provides two main functionalities: [Configuration Resolving](#configuration-resolving) and
[Watching for Updates](#watching-for-updates).
### Configuration Resolving
The `Resolver` receives as input a set of `Providers`, a list of `Converters`, and a list of configuration identifier
`configURI` that will be used to generate the resulting, or effective, configuration in the form of a `Conf`,
that can be used by code that is oblivious to the usage of `Providers` and `Converters`.
`Providers` are used to provide an entire configuration when the `configURI` is given directly to the `Resolver`,
or an individual value (partial configuration) when the `configURI` is embedded into the `Conf` as a values using
the syntax `${configURI}`.
**Limitation:**
- When embedding a `${configURI}` the uri cannot contain dollar sign ("$") character unless it embeds another uri.
- The number of URIs is limited to 100.
```terminal
Resolver Provider
Resolve │ │
────────────────►│ │
│ │
┌─ │ Retrieve │
│ ├─────────────────────────►│
│ │ Conf │
│ │◄─────────────────────────┤
foreach │ │ │
configURI │ ├───┐ │
│ │ │Merge │
│ │◄──┘ │
└─ │ │
┌─ │ Retrieve │
│ ├─────────────────────────►│
│ │ Partial Conf Value │
│ │◄─────────────────────────┤
foreach │ │ │
embedded │ │ │
configURI │ ├───┐ │
│ │ │Replace │
│ │◄──┘ │
└─ │ │
│ Converter │
┌─ │ Convert │ │
│ ├───────────────►│ │
foreach │ │ │ │
Converter │ │◄───────────────┤ │
└─ │ │
│ │
◄────────────────┤ │
```
The `Resolve` method proceeds in the following steps:
1. Start with an empty "result" of `Conf` type.
2. For each config URI retrieves individual configurations, and merges it into the "result".
3. For each embedded config URI retrieves individual value, and replaces it into the "result".
4. For each "Converter", call "Convert" for the "result".
5. Return the "result", aka effective, configuration.
#### (Experimental) Append merging strategy for lists
You can opt-in to experimentally combine slices instead of discarding the existing ones by enabling the `confmap.enableMergeAppendOption` feature flag. Lists are appended in the order in which they appear in their configuration sources.
This will **not** become the default in the future, we are still deciding how this should be configured and want your feedback on [this issue](https://github.com/open-telemetry/opentelemetry-collector/issues/8754).
##### Example
Consider the following configs,
```yaml
# main.yaml
receivers:
otlp/in:
processors:
attributes/example:
actions:
- key: key
value: "value"
action: upsert
exporters:
otlp/out:
extensions:
file_storage:
service:
pipelines:
traces:
receivers: [ otlp/in ]
processors: [ attributes/example ]
exporters: [ otlp/out ]
extensions: [ file_storage ]
```
```yaml
# extra_extension.yaml
extensions:
healthcheckv2:
service:
extensions: [ healthcheckv2 ]
pipelines:
traces:
```
If you run the Collector with following command,
```
otelcol --config=main.yaml --config=extra_extension.yaml --feature-gates=confmap.enableMergeAppendOption
```
then the final configuration after config resolution will look like following:
```yaml
# main.yaml
receivers:
otlp/in:
processors:
attributes/example:
actions:
- key: key
value: "value"
action: upsert
exporters:
otlp/out:
extensions:
file_storage:
healthcheckv2:
service:
pipelines:
traces:
receivers: [ otlp/in ]
processors: [ attributes/example ]
exporters: [ otlp/out ]
extensions: [ file_storage, healthcheckv2 ]
```
Notice that the `service::extensions` list is a combination of both configurations. By default, the value of the last configuration source passed, `extra_extension`, would be used, so the extensions list would be: `service::extensions: [healthcheckv2]`.
> [!NOTE]
> By enabling this feature gate, all the lists in the given configuration will be merged.
### Watching for Updates
After the configuration was processed, the `Resolver` can be used as a single point to watch for updates in the
configuration retrieved via the `Provider` used to retrieve the “initial” configuration and to generate the “effective” one.
```terminal
Resolver Provider
│ │
Watch │ │
───────────►│ │
│ │
. .
. .
. .
│ onChange │
│◄────────────────────┤
◄───────────┤ │
```
The `Resolver` does that by passing an `onChange` func to each `Provider.Retrieve` call and capturing all watch events.
Calling the `onChange` func from a provider triggers the collector to re-resolve new configuration:
```terminal
Resolver Provider
│ │
Watch │ │
───────────►│ │
│ │
. .
. .
. .
│ onChange │
│◄────────────────────┤
◄───────────┤ │
| |
Resolve │ │
───────────►│ │
│ │
│ Retrieve │
├────────────────────►│
│ Conf │
│◄────────────────────┤
◄───────────┤ │
```
An example of a `Provider` with an `onChange` func that periodically gets notified can be found in provider_test.go as UpdatingProvider
## Troubleshooting
### Null Maps
Due to how our underlying merge library, [koanf](https://github.com/knadh/koanf), behaves, configuration resolution
will treat configuration such as
```yaml
processors:
```
as null, which is a valid value. As a result if you have configuration `A`:
```yaml
receivers:
nop:
processors:
nop:
exporters:
nop:
extensions:
nop:
service:
extensions: [nop]
pipelines:
traces:
receivers: [nop]
processors: [nop]
exporters: [nop]
```
and configuration `B`:
```yaml
processors:
```
and do `./otelcorecol --config A.yaml --config B.yaml`
The result will be an error:
```
Error: invalid configuration: service::pipelines::traces: references processor "nop" which is not configured
2024/06/10 14:37:14 collector server run finished with error: invalid configuration: service::pipelines::traces: references processor "nop" which is not configured
```
This happens because configuration `B` sets `processors` to null, removing the `nop` processor defined in configuration `A`,
so the `nop` processor referenced in configuration `A`'s pipeline no longer exists.
This situation can be remedied 2 ways:
1. Use `{}` when you want to represent an empty map, such as `processors: {}` instead of `processors:`.
2. Omit configuration like `processors:` from your configuration.
================================================
FILE: confmap/confmap.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:generate mdatagen metadata.yaml
package confmap // import "go.opentelemetry.io/collector/confmap"
import (
"go.opentelemetry.io/collector/confmap/internal"
)
// KeyDelimiter is used as the default key delimiter in the default koanf instance.
var KeyDelimiter = internal.KeyDelimiter
// MapstructureTag is the struct field tag used to record marshaling/unmarshaling settings.
// See https://pkg.go.dev/github.com/go-viper/mapstructure/v2 for supported values.
var MapstructureTag = internal.MapstructureTag
// New creates a new empty confmap.Conf instance.
func New() *Conf {
return internal.New()
}
// NewFromStringMap creates a confmap.Conf from a map[string]any.
func NewFromStringMap(data map[string]any) *Conf {
return internal.NewFromStringMap(data)
}
// Conf represents the raw configuration map for the OpenTelemetry Collector.
// The confmap.Conf can be unmarshalled into the Collector's config using the "service" package.
type Conf = internal.Conf
type UnmarshalOption = internal.UnmarshalOption
// WithIgnoreUnused sets an option to ignore errors if existing
// keys in the original Conf were unused in the decoding process
// (extra keys).
func WithIgnoreUnused() UnmarshalOption {
return internal.WithIgnoreUnused()
}
type MarshalOption = internal.MarshalOption
// Unmarshaler interface may be implemented by types to customize their behavior when being unmarshaled from a Conf.
// Only types with struct or pointer to struct kind are supported.
type Unmarshaler = internal.Unmarshaler
// Marshaler defines an optional interface for custom configuration marshaling.
// A configuration struct can implement this interface to override the default
// marshaling.
type Marshaler = internal.Marshaler
================================================
FILE: confmap/confmaptest/configtest.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package confmaptest // import "go.opentelemetry.io/collector/confmap/confmaptest"
import (
"fmt"
"os"
"path/filepath"
"regexp"
"go.yaml.in/yaml/v3"
"go.opentelemetry.io/collector/confmap"
)
// LoadConf loads a confmap.Conf from file, and does NOT validate the configuration.
func LoadConf(fileName string) (*confmap.Conf, error) {
// Clean the path before using it.
content, err := os.ReadFile(filepath.Clean(fileName))
if err != nil {
return nil, fmt.Errorf("unable to read the file %v: %w", fileName, err)
}
var rawConf map[string]any
if err := yaml.Unmarshal(content, &rawConf); err != nil {
return nil, err
}
return confmap.NewFromStringMap(rawConf), nil
}
var schemeValidator = regexp.MustCompile("^[A-Za-z][A-Za-z0-9+.-]+$")
// ValidateProviderScheme enforces that given confmap.Provider.Scheme() object is following the restriction defined by the collector:
// - Checks that the scheme name follows the restrictions defined https://datatracker.ietf.org/doc/html/rfc3986#section-3.1
// - Checks that the scheme name has at leas two characters per the confmap.Provider.Scheme() comment.
func ValidateProviderScheme(p confmap.Provider) error {
scheme := p.Scheme()
if len(scheme) < 2 {
return fmt.Errorf("scheme must be at least 2 characters long: %q", scheme)
}
if !schemeValidator.MatchString(scheme) {
return fmt.Errorf("scheme names consist of a sequence of characters beginning with a letter and followed by any combination of letters, digits, \"+\", \".\", or \"-\": %q", scheme)
}
return nil
}
================================================
FILE: confmap/confmaptest/configtest_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package confmaptest
import (
"context"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/confmap"
)
func TestLoadConfFileNotFound(t *testing.T) {
_, err := LoadConf("file/not/found")
assert.Error(t, err)
}
func TestLoadConfInvalidYAML(t *testing.T) {
_, err := LoadConf(filepath.Join("testdata", "invalid.yaml"))
require.Error(t, err)
}
func TestLoadConf(t *testing.T) {
cfg, err := LoadConf(filepath.Join("testdata", "simple.yaml"))
require.NoError(t, err)
assert.Equal(t, map[string]any{"floating": 3.14}, cfg.ToStringMap())
}
func TestToStringMapSanitizeEmptySlice(t *testing.T) {
cfg, err := LoadConf(filepath.Join("testdata", "empty-slice.yaml"))
require.NoError(t, err)
assert.Equal(t, map[string]any{"slice": []any{}}, cfg.ToStringMap())
}
func TestValidateProviderScheme(t *testing.T) {
assert.NoError(t, ValidateProviderScheme(&schemeProvider{scheme: "file"}))
assert.NoError(t, ValidateProviderScheme(&schemeProvider{scheme: "s3"}))
assert.NoError(t, ValidateProviderScheme(&schemeProvider{scheme: "a.l-l+"}))
// Too short.
require.Error(t, ValidateProviderScheme(&schemeProvider{scheme: "a"}))
// Invalid first character.
require.Error(t, ValidateProviderScheme(&schemeProvider{scheme: "3s"}))
// Invalid underscore character.
assert.Error(t, ValidateProviderScheme(&schemeProvider{scheme: "all_"}))
}
type schemeProvider struct {
scheme string
}
func (s schemeProvider) Retrieve(context.Context, string, confmap.WatcherFunc) (*confmap.Retrieved, error) {
return nil, nil
}
func (s schemeProvider) Scheme() string {
return s.scheme
}
func (s schemeProvider) Shutdown(context.Context) error {
return nil
}
================================================
FILE: confmap/confmaptest/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package confmaptest helps loading confmap.Conf to test packages implementing using the configuration.
package confmaptest // import "go.opentelemetry.io/collector/confmap/confmaptest"
================================================
FILE: confmap/confmaptest/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package confmaptest
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: confmap/confmaptest/provider_settings.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package confmaptest // import "go.opentelemetry.io/collector/confmap/confmaptest"
import (
"go.uber.org/zap"
"go.opentelemetry.io/collector/confmap"
)
func NewNopProviderSettings() confmap.ProviderSettings {
return confmap.ProviderSettings{Logger: zap.NewNop()}
}
================================================
FILE: confmap/confmaptest/testdata/empty-slice.yaml
================================================
slice: [] # empty slices are sanitized to nil in ToStringMap
================================================
FILE: confmap/confmaptest/testdata/invalid.yaml
================================================
[invalid,
================================================
FILE: confmap/confmaptest/testdata/simple.yaml
================================================
floating: 3.14
================================================
FILE: confmap/converter.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package confmap // import "go.opentelemetry.io/collector/confmap"
import (
"context"
"go.uber.org/zap"
)
// ConverterSettings are the settings to initialize a Converter.
type ConverterSettings struct {
// Logger is a zap.Logger that will be passed to Converters.
// Converters should be able to rely on the Logger being non-nil;
// when instantiating a Converter with a ConverterFactory,
// nil Logger references should be replaced with a no-op Logger.
Logger *zap.Logger
// prevent unkeyed literal initialization
_ struct{}
}
// ConverterFactory defines a factory that can be used to instantiate
// new instances of a Converter.
type ConverterFactory = moduleFactory[Converter, ConverterSettings]
// CreateConverterFunc is a function that creates a Converter instance.
type CreateConverterFunc = createConfmapFunc[Converter, ConverterSettings]
// NewConverterFactory can be used to create a ConverterFactory.
func NewConverterFactory(f CreateConverterFunc) ConverterFactory {
return newConfmapModuleFactory(f)
}
// Converter is a converter interface for the confmap.Conf that allows distributions
// (in the future components as well) to build backwards compatible config converters.
type Converter interface {
// Convert applies the conversion logic to the given "conf".
Convert(ctx context.Context, conf *Conf) error
}
================================================
FILE: confmap/doc_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package confmap_test
import (
"fmt"
"slices"
"time"
"go.opentelemetry.io/collector/confmap"
)
type DiskScrape struct {
Disk string `mapstructure:"disk"`
Scrape time.Duration `mapstructure:"scrape"`
}
// We can annotate a struct with mapstructure field annotations.
func Example_simpleUnmarshaling() {
conf := confmap.NewFromStringMap(map[string]any{
"disk": "c",
"scrape": "5s",
})
scrapeInfo := &DiskScrape{}
if err := conf.Unmarshal(scrapeInfo); err != nil {
panic(err)
}
fmt.Printf("Configuration contains the following:\nDisk: %q\nScrape: %s\n", scrapeInfo.Disk, scrapeInfo.Scrape)
// Output: Configuration contains the following:
// Disk: "c"
// Scrape: 5s
}
type CPUScrape struct {
Enabled bool `mapstructure:"enabled"`
}
type ComputerScrape struct {
DiskScrape `mapstructure:",squash"`
CPUScrape `mapstructure:",squash"`
}
// We can unmarshal embedded structs with mapstructure field annotations.
func Example_embeddedUnmarshaling() {
conf := confmap.NewFromStringMap(map[string]any{
"disk": "c",
"scrape": "5s",
"enabled": true,
})
scrapeInfo := &ComputerScrape{}
if err := conf.Unmarshal(scrapeInfo); err != nil {
panic(err)
}
fmt.Printf("Configuration contains the following:\nDisk: %q\nScrape: %s\nEnabled: %v\n", scrapeInfo.Disk, scrapeInfo.Scrape, scrapeInfo.Enabled)
// Output: Configuration contains the following:
// Disk: "c"
// Scrape: 5s
// Enabled: true
}
type NetworkScrape struct {
Enabled bool `mapstructure:"enabled"`
Networks []string `mapstructure:"networks"`
Wifi bool `mapstructure:"wifi"`
}
func (n *NetworkScrape) Unmarshal(c *confmap.Conf) error {
if err := c.Unmarshal(n, confmap.WithIgnoreUnused()); err != nil {
return err
}
if slices.Contains(n.Networks, "wlan0") {
n.Wifi = true
}
return nil
}
type ManualScrapeInfo struct {
Disk string
Scrape time.Duration
}
func (m *ManualScrapeInfo) Unmarshal(c *confmap.Conf) error {
m.Disk = c.Get("disk").(string)
if c.Get("vinyl") == "33" {
m.Scrape = 10 * time.Second
} else {
m.Scrape = 2 * time.Second
}
return nil
}
type RouterScrape struct {
NetworkScrape `mapstructure:",squash"`
}
// We can unmarshal an embedded struct with a custom `Unmarshal` method.
func Example_embeddedManualUnmarshaling() {
conf := confmap.NewFromStringMap(map[string]any{
"networks": []string{"eth0", "eth1", "wlan0"},
"enabled": true,
})
scrapeInfo := &RouterScrape{}
if err := conf.Unmarshal(scrapeInfo); err != nil {
panic(err)
}
fmt.Printf("Configuration contains the following:\nNetworks: %q\nWifi: %v\nEnabled: %v\n", scrapeInfo.Networks, scrapeInfo.Wifi, scrapeInfo.Enabled)
// Output: Configuration contains the following:
// Networks: ["eth0" "eth1" "wlan0"]
// Wifi: true
// Enabled: true
}
func Example_manualUnmarshaling() {
conf := confmap.NewFromStringMap(map[string]any{
"disk": "Beatles",
"vinyl": "33",
})
scrapeInfo := &ManualScrapeInfo{}
if err := conf.Unmarshal(scrapeInfo, confmap.WithIgnoreUnused()); err != nil {
panic(err)
}
fmt.Printf("Configuration contains the following:\nDisk: %q\nScrape: %s\n", scrapeInfo.Disk, scrapeInfo.Scrape)
// Output: Configuration contains the following:
// Disk: "Beatles"
// Scrape: 10s
}
================================================
FILE: confmap/documentation.md
================================================
[comment]: <> (Code generated by mdatagen. DO NOT EDIT.)
# confmap
## Feature Gates
This component has the following feature gates:
| Feature Gate | Stage | Description | From Version | To Version | Reference |
| ------------ | ----- | ----------- | ------------ | ---------- | --------- |
| `confmap.enableMergeAppendOption` | alpha | Combines lists when resolving configs from different sources. This feature gate will not be stabilized 'as is'; the current behavior will remain the default. | v0.120.0 | N/A | [Link](https://github.com/open-telemetry/opentelemetry-collector/issues/8754) |
| `confmap.newExpandedValueSanitizer` | beta | Fixes some types of decoding errors where environment variables are parsed as non-string types but assigned to string fields. | v0.144.0 | N/A | [Link](https://github.com/open-telemetry/opentelemetry-collector/pull/14413) |
For more information about feature gates, see the [Feature Gates](https://github.com/open-telemetry/opentelemetry-collector/blob/main/featuregate/README.md) documentation.
================================================
FILE: confmap/example_provider_and_converter_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package confmap_test
import (
"context"
"fmt"
"strings"
"go.opentelemetry.io/collector/confmap"
)
// mockFileProvider simulates the standard file provider behavior.
// In typical usage, it reads a single file as the root configuration.
// This mock implementation always returns a fixed configuration:
// { "my-config": "${expand:to-expand}" }
type mockFileProvider struct{}
func (d mockFileProvider) Retrieve(_ context.Context, uri string, _ confmap.WatcherFunc) (*confmap.Retrieved, error) {
expectedURI := "file:mock-file"
if uri != expectedURI {
panic("should not happen, the uri is expected to be " + expectedURI + " for mockFileProvider")
}
return confmap.NewRetrieved(map[string]any{
"my-config": "${expand:to-expand}",
})
}
func (d mockFileProvider) Scheme() string {
return "file"
}
func (d mockFileProvider) Shutdown(_ context.Context) error {
return nil
}
// mockExpandProvider simulates a typical inline expansion provider.
// In configurations, you can use expressions like ${SCHEMA:VALUE},
// where the provider associated with SCHEMA is responsible for resolving the value.
type mockExpandProvider struct{}
func (m mockExpandProvider) Retrieve(_ context.Context, uri string, _ confmap.WatcherFunc) (*confmap.Retrieved, error) {
expectedURI := "expand:to-expand"
if uri != expectedURI {
panic("should not happen, the uri is expected to be " + expectedURI + " for mockExpandProvider")
}
return confmap.NewRetrieved("expanded")
}
func (m mockExpandProvider) Scheme() string {
return "expand"
}
func (m mockExpandProvider) Shutdown(_ context.Context) error {
return nil
}
// mockUpperCaseConverter transforms the value of the `my-config` field in the configuration to uppercase.
type mockUpperCaseConverter struct{}
func (m mockUpperCaseConverter) Convert(_ context.Context, conf *confmap.Conf) error {
currentValue := conf.Get("my-config")
expectedValue := "expanded"
if currentValue != expectedValue {
panic("should not happen, the value for converter should always be " + expectedValue + " for mockUpperCaseConverter")
}
upperCaseConf := confmap.NewFromStringMap(map[string]any{
"my-config": strings.ToUpper(currentValue.(string)),
})
if conf.Merge(upperCaseConf) != nil {
panic("merge failed, this should not happen in this example.")
}
return nil
}
func Example() {
resolver, err := confmap.NewResolver(confmap.ResolverSettings{
URIs: []string{"file:mock-file"},
ProviderFactories: []confmap.ProviderFactory{
confmap.NewProviderFactory(func(_ confmap.ProviderSettings) confmap.Provider {
return &mockFileProvider{}
}),
confmap.NewProviderFactory(func(_ confmap.ProviderSettings) confmap.Provider {
return &mockExpandProvider{}
}),
},
ConverterFactories: []confmap.ConverterFactory{
confmap.NewConverterFactory(func(_ confmap.ConverterSettings) confmap.Converter {
return &mockUpperCaseConverter{}
}),
},
})
if err != nil {
panic(err)
}
conf, err := resolver.Resolve(context.Background())
if err != nil {
panic(err)
}
fmt.Printf("Configuration contains the following:\nmy-config: %s", conf.Get("my-config"))
// Output: Configuration contains the following:
// my-config: EXPANDED
}
================================================
FILE: confmap/expand.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package confmap // import "go.opentelemetry.io/collector/confmap"
import (
"context"
"errors"
"fmt"
"regexp"
"strings"
"go.opentelemetry.io/collector/confmap/internal"
)
// schemePattern defines the regexp pattern for scheme names.
// Scheme name consist of a sequence of characters beginning with a letter and followed by any
// combination of letters, digits, plus ("+"), period ("."), or hyphen ("-").
const schemePattern = `[A-Za-z][A-Za-z0-9+.-]+`
var (
// Need to match new line as well in the OpaqueValue, so setting the "s" flag. See https://pkg.go.dev/regexp/syntax.
uriRegexp = regexp.MustCompile(`(?s:^(?P` + schemePattern + `):(?P.*)$)`)
errTooManyRecursiveExpansions = errors.New("too many recursive expansions")
)
func (mr *Resolver) expandValueRecursively(ctx context.Context, value any) (any, error) {
for range 1000 {
val, changed, err := mr.expandValue(ctx, value)
if err != nil {
return nil, err
}
if !changed {
return val, nil
}
value = val
}
return nil, errTooManyRecursiveExpansions
}
func (mr *Resolver) expandValue(ctx context.Context, value any) (any, bool, error) {
switch v := value.(type) {
case internal.ExpandedValue:
expanded, changed, err := mr.expandValue(ctx, v.Value)
if err != nil {
return nil, false, err
}
switch exp := expanded.(type) {
case internal.ExpandedValue, string:
// Return expanded values or strings verbatim.
return exp, changed, nil
}
// At this point we don't know the target field type, so we need to expand the original representation as well.
originalExpanded, originalChanged, err := mr.expandValue(ctx, v.Original)
if err != nil {
// The original representation is not valid, return the expanded value.
return expanded, changed, nil
}
if originalExpanded, ok := originalExpanded.(string); ok {
// If the original representation is a string, return the expanded value with the original representation.
return internal.ExpandedValue{
Value: expanded,
Original: originalExpanded,
}, changed || originalChanged, nil
}
return expanded, changed, nil
case string:
if !strings.Contains(v, "${") || !strings.Contains(v, "}") {
// No URIs to expand.
return value, false, nil
}
// Embedded or nested URIs.
return mr.findAndExpandURI(ctx, v)
case []any:
nslice := make([]any, 0, len(v))
nchanged := false
for _, vint := range v {
val, changed, err := mr.expandValue(ctx, vint)
if err != nil {
return nil, false, err
}
nslice = append(nslice, val)
nchanged = nchanged || changed
}
return nslice, nchanged, nil
case map[string]any:
nmap := map[string]any{}
nchanged := false
for mk, mv := range v {
val, changed, err := mr.expandValue(ctx, mv)
if err != nil {
return nil, false, err
}
nmap[mk] = val
nchanged = nchanged || changed
}
return nmap, nchanged, nil
}
return value, false, nil
}
// findURI attempts to find the first potentially expandable URI in input. It returns a potentially expandable
// URI, or an empty string if none are found.
// Note: findURI is only called when input contains a closing bracket.
// We do not support escaping nested URIs (such as ${env:$${FOO}}, since that would result in an invalid outer URI (${env:${FOO}}).
func (mr *Resolver) findURI(input string) string {
closeIndex := strings.Index(input, "}")
remaining := input[closeIndex+1:]
openIndex := strings.LastIndex(input[:closeIndex+1], "${")
// if there is any of:
// - a missing "${"
// - there is no default scheme AND no scheme is detected because no `:` is found.
// then check the next URI.
if openIndex < 0 || (mr.defaultScheme == "" && !strings.Contains(input[openIndex:closeIndex+1], ":")) {
// if remaining does not contain "}", there are no URIs left: stop recursion.
if !strings.Contains(remaining, "}") {
return ""
}
return mr.findURI(remaining)
}
index := openIndex - 1
currentRune := '$'
count := 0
for index >= 0 && currentRune == '$' {
currentRune = rune(input[index])
if currentRune == '$' {
count++
}
index--
}
// if we found an odd number of immediately $ preceding ${, then the expansion is escaped
if count%2 == 1 {
return ""
}
return input[openIndex : closeIndex+1]
}
// findAndExpandURI attempts to find and expand the first occurrence of an expandable URI in input. If an expandable URI is found it
// returns the input with the URI expanded, true and nil. Otherwise, it returns the unchanged input, false and the expanding error.
// This method expects input to start with ${ and end with }
func (mr *Resolver) findAndExpandURI(ctx context.Context, input string) (any, bool, error) {
uri := mr.findURI(input)
if uri == "" {
// No URI found, return.
return input, false, nil
}
if uri == input {
// If the value is a single URI, then the return value can be anything.
// This is the case `foo: ${file:some_extra_config.yml}`.
ret, err := mr.expandURI(ctx, input)
if err != nil {
return input, false, err
}
val, err := ret.AsRaw()
if err != nil {
return input, false, err
}
if asStr, err2 := ret.AsString(); err2 == nil {
return internal.ExpandedValue{
Value: val,
Original: asStr,
}, true, nil
}
return val, true, nil
}
expanded, err := mr.expandURI(ctx, uri)
if err != nil {
return input, false, err
}
repl, err := expanded.AsString()
if err != nil {
return input, false, fmt.Errorf("expanding %v: %w", uri, err)
}
return strings.ReplaceAll(input, uri, repl), true, err
}
func (mr *Resolver) expandURI(ctx context.Context, input string) (*Retrieved, error) {
// strip ${ and }
uri := input[2 : len(input)-1]
if !strings.Contains(uri, ":") {
uri = fmt.Sprintf("%s:%s", mr.defaultScheme, uri)
}
lURI, err := newLocation(uri)
if err != nil {
return nil, err
}
if strings.Contains(lURI.opaqueValue, "$") {
return nil, fmt.Errorf("the uri %q contains unsupported characters ('$')", lURI.asString())
}
ret, err := mr.retrieveValue(ctx, lURI)
if err != nil {
return nil, err
}
mr.closers = append(mr.closers, ret.Close)
return ret, nil
}
type location struct {
scheme string
opaqueValue string
}
func (c location) asString() string {
return c.scheme + ":" + c.opaqueValue
}
func newLocation(uri string) (location, error) {
submatches := uriRegexp.FindStringSubmatch(uri)
if len(submatches) != 3 {
return location{}, fmt.Errorf("invalid uri: %q", uri)
}
return location{scheme: submatches[1], opaqueValue: submatches[2]}, nil
}
================================================
FILE: confmap/expand_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package confmap // import "go.opentelemetry.io/collector/confmap"
import (
"context"
"errors"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestResolverExpandEnvVars(t *testing.T) {
testCases := []struct {
name string // test case name (also file name containing config yaml)
}{
{name: "expand-with-no-env.yaml"},
{name: "expand-with-partial-env.yaml"},
{name: "expand-with-all-env.yaml"},
}
envs := map[string]string{
"EXTRA": "some string",
"EXTRA_MAP_VALUE_1": "some map value_1",
"EXTRA_MAP_VALUE_2": "some map value_2",
"EXTRA_LIST_MAP_VALUE_1": "some list map value_1",
"EXTRA_LIST_MAP_VALUE_2": "some list map value_2",
"EXTRA_LIST_VALUE_1": "some list value_1",
"EXTRA_LIST_VALUE_2": "some list value_2",
}
expectedCfgMap := newConfFromFile(t, filepath.Join("testdata", "expand-with-no-env.yaml"))
fileProvider := newFakeProvider("file", func(_ context.Context, uri string, _ WatcherFunc) (*Retrieved, error) {
return NewRetrieved(newConfFromFile(t, uri[5:]))
})
envProvider := newFakeProvider("env", func(_ context.Context, uri string, _ WatcherFunc) (*Retrieved, error) {
return NewRetrieved(envs[uri[4:]])
})
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
resolver, err := NewResolver(ResolverSettings{URIs: []string{filepath.Join("testdata", tt.name)}, ProviderFactories: []ProviderFactory{fileProvider, envProvider}, ConverterFactories: nil})
require.NoError(t, err)
// Test that expanded configs are the same with the simple config with no env vars.
cfgMap, err := resolver.Resolve(context.Background())
require.NoError(t, err)
assert.Equal(t, expectedCfgMap, cfgMap.ToStringMap())
})
}
}
func TestResolverDoneNotExpandOldEnvVars(t *testing.T) {
expectedCfgMap := map[string]any{"test.1": "${EXTRA}", "test.2": "$EXTRA", "test.3": "${EXTRA}:${EXTRA}"}
fileProvider := newFakeProvider("test", func(context.Context, string, WatcherFunc) (*Retrieved, error) {
return NewRetrieved(expectedCfgMap)
})
envProvider := newFakeProvider("env", func(context.Context, string, WatcherFunc) (*Retrieved, error) {
return NewRetrieved("some string")
})
resolver, err := NewResolver(ResolverSettings{URIs: []string{"test:"}, ProviderFactories: []ProviderFactory{fileProvider, envProvider}, ConverterFactories: nil})
require.NoError(t, err)
// Test that expanded configs are the same with the simple config with no env vars.
cfgMap, err := resolver.Resolve(context.Background())
require.NoError(t, err)
assert.Equal(t, expectedCfgMap, cfgMap.ToStringMap())
}
func TestResolverExpandMapAndSliceValues(t *testing.T) {
provider := newFakeProvider("input", func(context.Context, string, WatcherFunc) (*Retrieved, error) {
return NewRetrieved(map[string]any{
"test_map": map[string]any{"recv": "${test:MAP_VALUE}"},
"test_slice": []any{"${test:MAP_VALUE}"},
})
})
const receiverExtraMapValue = "some map value"
testProvider := newFakeProvider("test", func(context.Context, string, WatcherFunc) (*Retrieved, error) {
return NewRetrieved(receiverExtraMapValue)
})
resolver, err := NewResolver(ResolverSettings{URIs: []string{"input:"}, ProviderFactories: []ProviderFactory{provider, testProvider}, ConverterFactories: nil})
require.NoError(t, err)
cfgMap, err := resolver.Resolve(context.Background())
require.NoError(t, err)
expectedMap := map[string]any{
"test_map": map[string]any{"recv": receiverExtraMapValue},
"test_slice": []any{receiverExtraMapValue},
}
assert.Equal(t, expectedMap, cfgMap.ToStringMap())
}
func TestResolverExpandStringValues(t *testing.T) {
tests := []struct {
name string
input string
output any
defaultProvider bool
}{
// Embedded.
{
name: "NoMatchOldStyle",
input: "${HOST}:${PORT}",
output: "${HOST}:${PORT}",
},
{
name: "NoMatchOldStyleDefaultProvider",
input: "${HOST}:${PORT}",
output: "localhost:3044",
defaultProvider: true,
},
{
name: "NoMatchOldStyleNoBrackets",
input: "${HOST}:$PORT",
output: "${HOST}:$PORT",
},
{
name: "NoMatchOldStyleNoBracketsDefaultProvider",
input: "${HOST}:$PORT",
output: "localhost:$PORT",
defaultProvider: true,
},
{
name: "ComplexValue",
input: "${env:COMPLEX_VALUE}",
output: []any{"localhost:3042"},
},
{
name: "Embedded",
input: "${env:HOST}:3043",
output: "localhost:3043",
},
{
name: "EmbeddedMulti",
input: "${env:HOST}:${env:PORT}",
output: "localhost:3044",
},
{
name: "EmbeddedConcat",
input: "https://${env:HOST}:3045",
output: "https://localhost:3045",
},
{
name: "EmbeddedNewAndOldStyle",
input: "${env:HOST}:${PORT}",
output: "localhost:${PORT}",
},
{
name: "EmbeddedNewAndOldStyleDefaultProvider",
input: "${env:HOST}:${PORT}",
output: "localhost:3044",
defaultProvider: true,
},
{
name: "Int",
input: "test_${env:INT}",
output: "test_1",
},
{
name: "Int32",
input: "test_${env:INT32}",
output: "test_32",
},
{
name: "Int64",
input: "test_${env:INT64}",
output: "test_64",
},
{
name: "Float32",
input: "test_${env:FLOAT32}",
output: "test_3.25",
},
{
name: "Float64",
input: "test_${env:FLOAT64}",
output: "test_6.4",
},
{
name: "Bool",
input: "test_${env:BOOL}",
output: "test_true",
},
{
name: "Timestamp",
input: "test_${env:TIMESTAMP}",
output: "test_2023-03-20T03:17:55.432328Z",
},
{
name: "MultipleSameMatches",
input: "test_${env:BOOL}_test_${env:BOOL}",
output: "test_true_test_true",
},
// Nested.
{
name: "Nested",
input: "${test:localhost:${env:PORT}}",
output: "localhost:3044",
},
{
name: "NestedDefaultProvider",
input: "${test:localhost:${PORT}}",
output: "localhost:3044",
defaultProvider: true,
},
{
name: "EmbeddedInNested",
input: "${test:${env:HOST}:${env:PORT}}",
output: "localhost:3044",
},
{
name: "EmbeddedInNestedDefaultProvider",
input: "${test:${HOST}:${PORT}}",
output: "localhost:3044",
defaultProvider: true,
},
{
name: "EmbeddedAndNested",
input: "${test:localhost:${env:PORT}}?os=${env:OS}",
output: "localhost:3044?os=ubuntu",
},
{
name: "NestedMultiple",
input: "${test:1${test:2${test:3${test:4${test:5${test:6}}}}}}",
output: "123456",
},
// No expand.
{
name: "NoMatchMissingOpeningBracket",
input: "env:HOST}",
output: "env:HOST}",
},
{
name: "NoMatchMissingOpeningBracketDefaultProvider",
input: "env:HOST}",
output: "env:HOST}",
defaultProvider: true,
},
{
name: "NoMatchMissingClosingBracket",
input: "${HOST",
output: "${HOST",
},
{
name: "NoMatchMissingClosingBracketDefaultProvider",
input: "${HOST",
output: "${HOST",
defaultProvider: true,
},
{
name: "NoMatchBracketsWithout$",
input: "HO{ST}",
output: "HO{ST}",
},
{
name: "NoMatchBracketsWithout$DefaultProvider",
input: "HO{ST}",
output: "HO{ST}",
defaultProvider: true,
},
{
name: "NoMatchOnlyMissingClosingBracket",
input: "${env:HOST${env:PORT?os=${env:OS",
output: "${env:HOST${env:PORT?os=${env:OS",
},
{
name: "NoMatchOnlyMissingClosingBracketDefaultProvider",
input: "${env:HOST${env:PORT?os=${env:OS",
output: "${env:HOST${env:PORT?os=${env:OS",
defaultProvider: true,
},
{
name: "NoMatchOnlyMissingOpeningBracket",
input: "env:HOST}env:PORT}?os=env:OS}",
output: "env:HOST}env:PORT}?os=env:OS}",
},
{
name: "NoMatchOnlyMissingOpeningBracketDefaultProvider",
input: "env:HOST}env:PORT}?os=env:OS}",
output: "env:HOST}env:PORT}?os=env:OS}",
defaultProvider: true,
},
{
name: "NoMatchCloseBeforeOpen",
input: "env:HOST}${env:PORT",
output: "env:HOST}${env:PORT",
},
{
name: "NoMatchCloseBeforeOpenDefaultProvider",
input: "env:HOST}${env:PORT",
output: "env:HOST}${env:PORT",
defaultProvider: true,
},
{
name: "NoMatchOldStyleNested",
input: "${test:localhost:${PORT}}",
output: "${test:localhost:${PORT}}",
},
// Partial expand.
{
name: "PartialMatchMissingOpeningBracketFirst",
input: "env:HOST}${env:PORT}",
output: "env:HOST}3044",
},
{
name: "PartialMatchMissingOpeningBracketFirstDefaultProvider",
input: "env:HOST}${PORT}",
output: "env:HOST}3044",
defaultProvider: true,
},
{
name: "PartialMatchMissingOpeningBracketLast",
input: "${env:HOST}env:PORT}",
output: "localhostenv:PORT}",
},
{
name: "PartialMatchMissingClosingBracketFirst",
input: "${env:HOST${env:PORT}",
output: "${env:HOST3044",
},
{
name: "PartialMatchMissingClosingBracketLast",
input: "${env:HOST}${env:PORT",
output: "localhost${env:PORT",
},
{
name: "PartialMatchMultipleMissingOpen",
input: "env:HOST}env:PORT}?os=${env:OS}",
output: "env:HOST}env:PORT}?os=ubuntu",
},
{
name: "PartialMatchMultipleMissingClosing",
input: "${env:HOST${env:PORT?os=${env:OS}",
output: "${env:HOST${env:PORT?os=ubuntu",
},
{
name: "PartialMatchMoreClosingBrackets",
input: "${env:HOST}}}}}}${env:PORT?os=${env:OS}",
output: "localhost}}}}}${env:PORT?os=ubuntu",
},
{
name: "PartialMatchMoreOpeningBrackets",
input: "${env:HOST}${${${${${env:PORT}?os=${env:OS}",
output: "localhost${${${${3044?os=ubuntu",
},
{
name: "PartialMatchAlternatingMissingOpening",
input: "env:HOST}${env:PORT}?os=env:OS}&pr=${env:PR}",
output: "env:HOST}3044?os=env:OS}&pr=amd",
},
{
name: "PartialMatchAlternatingMissingClosing",
input: "${env:HOST${env:PORT}?os=${env:OS&pr=${env:PR}",
output: "${env:HOST3044?os=${env:OS&pr=amd",
},
{
name: "SchemeAfterNoSchemeIsExpanded",
input: "${HOST}${env:PORT}",
output: "${HOST}3044",
},
{
name: "SchemeAfterNoSchemeIsExpandedDefaultProvider",
input: "${HOST}${env:PORT}",
output: "localhost3044",
defaultProvider: true,
},
{
name: "SchemeBeforeNoSchemeIsExpanded",
input: "${env:HOST}${PORT}",
output: "localhost${PORT}",
},
{
name: "SchemeBeforeNoSchemeIsExpandedDefaultProvider",
input: "${env:HOST}${PORT}",
output: "localhost3044",
defaultProvider: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
provider := newFakeProvider("input", func(context.Context, string, WatcherFunc) (*Retrieved, error) {
return NewRetrieved(map[string]any{tt.name: tt.input})
})
testProvider := newFakeProvider("test", func(_ context.Context, uri string, _ WatcherFunc) (*Retrieved, error) {
return NewRetrieved(uri[5:])
})
envProvider := newEnvProvider()
set := ResolverSettings{URIs: []string{"input:"}, ProviderFactories: []ProviderFactory{provider, envProvider, testProvider}, ConverterFactories: nil}
if tt.defaultProvider {
set.DefaultScheme = "env"
}
resolver, err := NewResolver(set)
require.NoError(t, err)
cfgMap, err := resolver.Resolve(context.Background())
require.NoError(t, err)
assert.Equal(t, map[string]any{tt.name: tt.output}, cfgMap.ToStringMap())
})
}
}
func newEnvProvider() ProviderFactory {
return newFakeProvider("env", func(_ context.Context, uri string, _ WatcherFunc) (*Retrieved, error) {
// When using `env` as the default scheme for tests, the uri will not include `env:`.
// Instead of duplicating the switch cases, the scheme is added instead.
if uri[0:4] != "env:" {
uri = "env:" + uri
}
switch uri {
case "env:COMPLEX_VALUE":
return NewRetrievedFromYAML([]byte("[localhost:3042]"))
case "env:HOST":
return NewRetrievedFromYAML([]byte("localhost"))
case "env:TIMESTAMP":
return NewRetrievedFromYAML([]byte("2023-03-20T03:17:55.432328Z"))
case "env:OS":
return NewRetrievedFromYAML([]byte("ubuntu"))
case "env:PR":
return NewRetrievedFromYAML([]byte("amd"))
case "env:PORT":
return NewRetrievedFromYAML([]byte("3044"))
case "env:INT":
return NewRetrievedFromYAML([]byte("1"))
case "env:INT32":
return NewRetrieved(int32(32), withStringRepresentation("32"))
case "env:INT64":
return NewRetrieved(int64(64), withStringRepresentation("64"))
case "env:FLOAT32":
return NewRetrieved(float32(3.25), withStringRepresentation("3.25"))
case "env:FLOAT64":
return NewRetrieved(float64(6.4), withStringRepresentation("6.4"))
case "env:BOOL":
return NewRetrievedFromYAML([]byte("true"))
}
return nil, errors.New("impossible")
})
}
func TestResolverExpandReturnError(t *testing.T) {
tests := []struct {
name string
input any
}{
{
name: "string_value",
input: "${test:VALUE}",
},
{
name: "slice_value",
input: []any{"${test:VALUE}"},
},
{
name: "map_value",
input: map[string]any{"test": "${test:VALUE}"},
},
{
name: "string_embedded_value",
input: "https://${test:HOST}:3045",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
provider := newFakeProvider("input", func(context.Context, string, WatcherFunc) (*Retrieved, error) {
return NewRetrieved(map[string]any{tt.name: tt.input})
})
myErr := errors.New(tt.name)
testProvider := newFakeProvider("test", func(context.Context, string, WatcherFunc) (*Retrieved, error) {
return nil, myErr
})
resolver, err := NewResolver(ResolverSettings{URIs: []string{"input:"}, ProviderFactories: []ProviderFactory{provider, testProvider}, ConverterFactories: nil})
require.NoError(t, err)
_, err = resolver.Resolve(context.Background())
assert.ErrorIs(t, err, myErr)
})
}
}
func TestResolverInfiniteExpand(t *testing.T) {
const receiverValue = "${test:VALUE}"
provider := newFakeProvider("input", func(context.Context, string, WatcherFunc) (*Retrieved, error) {
return NewRetrieved(map[string]any{"test": receiverValue})
})
testProvider := newFakeProvider("test", func(context.Context, string, WatcherFunc) (*Retrieved, error) {
return NewRetrieved(receiverValue)
})
resolver, err := NewResolver(ResolverSettings{URIs: []string{"input:"}, ProviderFactories: []ProviderFactory{provider, testProvider}, ConverterFactories: nil})
require.NoError(t, err)
_, err = resolver.Resolve(context.Background())
assert.ErrorIs(t, err, errTooManyRecursiveExpansions)
}
func TestResolverExpandInvalidScheme(t *testing.T) {
provider := newFakeProvider("input", func(context.Context, string, WatcherFunc) (*Retrieved, error) {
return NewRetrieved(map[string]any{"test": "${g_c_s:VALUE}"})
})
testProvider := newFakeProvider("g_c_s", func(context.Context, string, WatcherFunc) (*Retrieved, error) {
panic("must not be called")
})
_, err := NewResolver(ResolverSettings{URIs: []string{"input:"}, ProviderFactories: []ProviderFactory{provider, testProvider}, ConverterFactories: nil})
assert.ErrorContains(t, err, "invalid 'confmap.Provider' scheme")
}
func TestResolverExpandInvalidOpaqueValue(t *testing.T) {
provider := newFakeProvider("input", func(context.Context, string, WatcherFunc) (*Retrieved, error) {
return NewRetrieved(map[string]any{"test": []any{map[string]any{"test": "${test:$VALUE}"}}})
})
testProvider := newFakeProvider("test", func(context.Context, string, WatcherFunc) (*Retrieved, error) {
panic("must not be called")
})
resolver, err := NewResolver(ResolverSettings{URIs: []string{"input:"}, ProviderFactories: []ProviderFactory{provider, testProvider}, ConverterFactories: nil})
require.NoError(t, err)
_, err = resolver.Resolve(context.Background())
assert.EqualError(t, err, `the uri "test:$VALUE" contains unsupported characters ('$')`)
}
func TestResolverExpandUnsupportedScheme(t *testing.T) {
provider := newFakeProvider("input", func(context.Context, string, WatcherFunc) (*Retrieved, error) {
return NewRetrieved(map[string]any{"test": "${unsupported:VALUE}"})
})
testProvider := newFakeProvider("test", func(context.Context, string, WatcherFunc) (*Retrieved, error) {
panic("must not be called")
})
resolver, err := NewResolver(ResolverSettings{URIs: []string{"input:"}, ProviderFactories: []ProviderFactory{provider, testProvider}, ConverterFactories: nil})
require.NoError(t, err)
_, err = resolver.Resolve(context.Background())
assert.EqualError(t, err, `scheme "unsupported" is not supported for uri "unsupported:VALUE"`)
}
func TestResolverDefaultProviderExpand(t *testing.T) {
provider := newFakeProvider("input", func(context.Context, string, WatcherFunc) (*Retrieved, error) {
return NewRetrieved(map[string]any{"foo": "${HOST}"})
})
resolver, err := NewResolver(ResolverSettings{URIs: []string{"input:"}, ProviderFactories: []ProviderFactory{provider, newEnvProvider()}, DefaultScheme: "env", ConverterFactories: nil})
require.NoError(t, err)
cfgMap, err := resolver.Resolve(context.Background())
require.NoError(t, err)
assert.Equal(t, map[string]any{"foo": "localhost"}, cfgMap.ToStringMap())
}
================================================
FILE: confmap/factory.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package confmap // import "go.opentelemetry.io/collector/confmap"
type moduleFactory[T any, S any] interface {
Create(s S) T
}
type createConfmapFunc[T any, S any] func(s S) T
type confmapModuleFactory[T any, S any] struct {
f createConfmapFunc[T, S]
}
func (c confmapModuleFactory[T, S]) Create(s S) T {
return c.f(s)
}
func newConfmapModuleFactory[T, S any](f createConfmapFunc[T, S]) moduleFactory[T, S] {
return confmapModuleFactory[T, S]{
f: f,
}
}
================================================
FILE: confmap/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package confmap
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: confmap/go.mod
================================================
module go.opentelemetry.io/collector/confmap
go 1.25.0
require (
github.com/go-viper/mapstructure/v2 v2.5.0
github.com/gobwas/glob v0.2.3
github.com/knadh/koanf/maps v0.1.2
github.com/knadh/koanf/providers/confmap v1.0.0
github.com/knadh/koanf/v2 v2.3.3
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/featuregate v1.54.0
go.opentelemetry.io/collector/internal/testutil v0.148.0
go.uber.org/goleak v1.3.0
go.uber.org/multierr v1.11.0
go.uber.org/zap v1.27.1
go.yaml.in/yaml/v3 v3.0.4
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
retract (
v0.76.0 // Depends on retracted pdata v1.0.0-rc10 module, use v0.76.1
v0.69.0 // Release failed, use v0.69.1
)
replace (
go.opentelemetry.io/collector/featuregate => ../featuregate
go.opentelemetry.io/collector/internal/testutil => ../internal/testutil
)
================================================
FILE: confmap/go.sum
================================================
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/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: confmap/internal/conf.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/confmap/internal"
import (
"errors"
"fmt"
"reflect"
"github.com/knadh/koanf/maps"
"github.com/knadh/koanf/providers/confmap"
"github.com/knadh/koanf/v2"
encoder "go.opentelemetry.io/collector/confmap/internal/mapstructure"
"go.opentelemetry.io/collector/confmap/internal/metadata"
)
const (
// KeyDelimiter is used as the default key delimiter in the default koanf instance.
KeyDelimiter = "::"
)
// Conf represents the raw configuration map for the OpenTelemetry Collector.
// The confmap.Conf can be unmarshalled into the Collector's config using the "service" package.
type Conf struct {
k *koanf.Koanf
// If true, upon unmarshaling do not call the Unmarshal function on the struct
// if it implements Unmarshaler and is the top-level struct.
// This avoids running into an infinite recursion where Unmarshaler.Unmarshal and
// Conf.Unmarshal would call each other.
skipTopLevelUnmarshaler bool
// isNil is true if this Conf was created from a nil field, as opposed to an empty map.
// AllKeys must return an empty slice if this is true.
isNil bool
}
// New creates a new empty confmap.Conf instance.
func New() *Conf {
return &Conf{k: koanf.New(KeyDelimiter), isNil: false}
}
// NewFromStringMap creates a confmap.Conf from a map[string]any.
func NewFromStringMap(data map[string]any) *Conf {
p := New()
if data == nil {
p.isNil = true
} else {
// Cannot return error because the koanf instance is empty.
_ = p.k.Load(confmap.Provider(data, KeyDelimiter), nil)
}
return p
}
// Unmarshal unmarshalls the config into a struct using the given options.
// Tags on the fields of the structure must be properly set.
func (l *Conf) Unmarshal(result any, opts ...UnmarshalOption) error {
set := UnmarshalOptions{}
for _, opt := range opts {
opt.apply(&set)
}
return Decode(l.toStringMapWithExpand(), result, set, l.skipTopLevelUnmarshaler)
}
// Marshal encodes the config and merges it into the Conf.
func (l *Conf) Marshal(rawVal any, opts ...MarshalOption) error {
set := MarshalOptions{}
for _, opt := range opts {
opt.apply(&set)
}
enc := encoder.New(EncoderConfig(rawVal, set))
data, err := enc.Encode(rawVal)
if err != nil {
return err
}
out, ok := data.(map[string]any)
if !ok {
return errors.New("invalid config encoding")
}
return l.Merge(NewFromStringMap(out))
}
// AllKeys returns all keys holding a value, regardless of where they are set.
// Nested keys are returned with a KeyDelimiter separator.
func (l *Conf) AllKeys() []string {
return l.k.Keys()
}
// Get can retrieve any value given the key to use.
func (l *Conf) Get(key string) any {
val := l.unsanitizedGet(key)
return sanitizeExpanded(val, false)
}
// IsSet checks to see if the key has been set in any of the data locations.
func (l *Conf) IsSet(key string) bool {
return l.k.Exists(key)
}
// Merge merges the input given configuration into the existing config.
// Note that the given map may be modified.
func (l *Conf) Merge(in *Conf) error {
if metadata.ConfmapEnableMergeAppendOptionFeatureGate.IsEnabled() {
return l.mergeAppend(in)
}
l.isNil = l.isNil && in.isNil
return l.k.Merge(in.k)
}
// Delete a path from the Conf.
// If the path exists, deletes it and returns true.
// If the path does not exist, does nothing and returns false.
func (l *Conf) Delete(key string) bool {
wasSet := l.IsSet(key)
l.k.Delete(key)
return wasSet
}
// mergeAppend merges the input given configuration into the existing config.
// Note that the given map may be modified.
// Additionally, mergeAppend performs deduplication when merging lists.
// For example, if listA = [extension1, extension2] and listB = [extension1, extension3],
// the resulting list will be [extension1, extension2, extension3].
func (l *Conf) mergeAppend(in *Conf) error {
err := l.k.Load(confmap.Provider(in.ToStringMap(), ""), nil, koanf.WithMergeFunc(mergeAppend))
if err != nil {
return err
}
l.isNil = l.isNil && in.isNil
return nil
}
// Sub returns new Conf instance representing a sub-config of this instance.
// It returns an error is the sub-config is not a map[string]any (use Get()), and an empty Map if none exists.
func (l *Conf) Sub(key string) (*Conf, error) {
// Code inspired by the koanf "Cut" func, but returns an error instead of empty map for unsupported sub-config type.
data := l.unsanitizedGet(key)
if data == nil {
c := New()
c.isNil = true
return c, nil
}
switch v := data.(type) {
case map[string]any:
return NewFromStringMap(v), nil
case ExpandedValue:
if m, ok := v.Value.(map[string]any); ok {
return NewFromStringMap(m), nil
} else if v.Value == nil {
// If the value is nil, return a new empty Conf.
c := New()
c.isNil = true
return c, nil
}
// override data with the original value to make the error message more informative.
data = v.Value
}
return nil, fmt.Errorf("unexpected sub-config value kind for key:%s value:%v kind:%v", key, data, reflect.TypeOf(data).Kind())
}
func (l *Conf) toStringMapWithExpand() map[string]any {
if l.isNil {
return nil
}
m := maps.Unflatten(l.k.All(), KeyDelimiter)
return m
}
// ToStringMap creates a map[string]any from a Conf.
// Values with multiple representations
// are normalized with the YAML parsed representation.
//
// For example, for a Conf created from `foo: ${env:FOO}` and `FOO=123`
// ToStringMap will return `map[string]any{"foo": 123}`.
//
// For any map `m`, `NewFromStringMap(m).ToStringMap() == m`.
// In particular, if the Conf was created from a nil value,
// ToStringMap will return map[string]any(nil).
func (l *Conf) ToStringMap() map[string]any {
return sanitize(l.toStringMapWithExpand()).(map[string]any)
}
func ToStringMapRaw(conf *Conf) map[string]any {
return conf.toStringMapWithExpand()
}
func (l *Conf) unsanitizedGet(key string) any {
return l.k.Get(key)
}
// sanitize recursively removes expandedValue references from the given data.
// It uses the expandedValue.Value field to replace the expandedValue references.
func sanitize(a any) any {
return sanitizeExpanded(a, false)
}
// sanitizeToStringMap recursively removes expandedValue references from the given data.
// It uses the expandedValue.Original field to replace the expandedValue references.
func sanitizeToStr(a any) any {
return sanitizeExpanded(a, true)
}
func sanitizeExpanded(a any, useOriginal bool) any {
switch m := a.(type) {
case map[string]any:
c := maps.Copy(m)
for k, v := range m {
c[k] = sanitizeExpanded(v, useOriginal)
}
return c
case []any:
// If the value is nil, return nil.
var newSlice []any
if m == nil {
return newSlice
}
newSlice = make([]any, 0, len(m))
for _, e := range m {
newSlice = append(newSlice, sanitizeExpanded(e, useOriginal))
}
return newSlice
case ExpandedValue:
if useOriginal {
return m.Original
}
return m.Value
}
return a
}
type UnsanitizedGetter struct {
Conf *Conf
}
func (ug *UnsanitizedGetter) UnsanitizedGet(key string) any {
return ug.Conf.unsanitizedGet(key)
}
================================================
FILE: confmap/internal/confmap.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/confmap/internal"
// Unmarshaler interface may be implemented by types to customize their behavior when being unmarshaled from a Conf.
// Only types with struct or pointer to struct kind are supported.
type Unmarshaler interface {
// Unmarshal a Conf into the struct in a custom way.
// The Conf for this specific component may be nil or empty if no config available.
// This method should only be called by decoding hooks when calling Conf.Unmarshal.
Unmarshal(component *Conf) error
}
// Marshaler defines an optional interface for custom configuration marshaling.
// A configuration struct can implement this interface to override the default
// marshaling.
type Marshaler interface {
// Marshal the config into a Conf in a custom way.
// The Conf will be empty and can be merged into.
Marshal(component *Conf) error
}
================================================
FILE: confmap/internal/confmap_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal
import (
"encoding"
"errors"
"math"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.yaml.in/yaml/v3"
"go.opentelemetry.io/collector/confmap/internal/metadata"
"go.opentelemetry.io/collector/featuregate"
)
func TestToStringMapFlatten(t *testing.T) {
conf := NewFromStringMap(map[string]any{"key::embedded": int64(123)})
assert.Equal(t, map[string]any{"key": map[string]any{"embedded": int64(123)}}, conf.ToStringMap())
}
func TestToStringMap(t *testing.T) {
tests := []struct {
name string
fileName string
stringMap map[string]any
}{
{
name: "Sample Collector configuration",
fileName: filepath.Join("testdata", "config.yaml"),
stringMap: map[string]any{
"receivers": map[string]any{
"nop": nil,
"nop/myreceiver": nil,
},
"processors": map[string]any{
"nop": nil,
"nop/myprocessor": nil,
},
"exporters": map[string]any{
"nop": nil,
"nop/myexporter": nil,
},
"extensions": map[string]any{
"nop": nil,
"nop/myextension": nil,
},
"service": map[string]any{
"extensions": []any{"nop"},
"pipelines": map[string]any{
"traces": map[string]any{
"receivers": []any{"nop"},
"processors": []any{"nop"},
"exporters": []any{"nop"},
},
},
},
},
},
{
name: "Sample types",
fileName: filepath.Join("testdata", "basic_types.yaml"),
stringMap: map[string]any{
"typed.options": map[string]any{
"floating.point.example": 3.14,
"integer.example": 1234,
"bool.example": false,
"string.example": "this is a string",
"nil.example": nil,
},
},
},
{
name: "Embedded keys",
fileName: filepath.Join("testdata", "embedded_keys.yaml"),
stringMap: map[string]any{
"typed": map[string]any{"options": map[string]any{
"floating": map[string]any{"point": map[string]any{"example": 3.14}},
"integer": map[string]any{"example": 1234},
"bool": map[string]any{"example": false},
"string": map[string]any{"example": "this is a string"},
"nil": map[string]any{"example": nil},
}},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.stringMap, newConfFromFile(t, test.fileName))
})
}
}
type testConfigAny struct {
AnyField any `mapstructure:"any_field"`
}
func TestNilToAnyField(t *testing.T) {
stringMap := map[string]any{
"any_field": nil,
}
conf := NewFromStringMap(stringMap)
cfg := &testConfigAny{}
require.NoError(t, conf.Unmarshal(cfg))
assert.Nil(t, cfg.AnyField)
}
func TestExpandNilStructPointersHookFunc(t *testing.T) {
stringMap := map[string]any{
"boolean": nil,
"struct": nil,
"map_struct": map[string]any{
"struct": nil,
},
}
conf := NewFromStringMap(stringMap)
cfg := &testConfig{}
assert.Nil(t, cfg.Struct)
require.NoError(t, conf.Unmarshal(cfg))
assert.Nil(t, cfg.Boolean)
// assert.False(t, *cfg.Boolean)
assert.Nil(t, cfg.Struct)
assert.NotNil(t, cfg.MapStruct)
assert.Equal(t, &myStruct{}, cfg.MapStruct["struct"])
}
func TestExpandNilStructPointersHookFuncDefaultNotNilConfigNil(t *testing.T) {
stringMap := map[string]any{
"boolean": nil,
"struct": nil,
"map_struct": map[string]any{
"struct": nil,
},
}
conf := NewFromStringMap(stringMap)
varBool := true
s1 := &myStruct{Name: "s1"}
s2 := &myStruct{Name: "s2"}
cfg := &testConfig{
Boolean: &varBool,
Struct: s1,
MapStruct: map[string]*myStruct{"struct": s2},
}
require.NoError(t, conf.Unmarshal(cfg))
assert.NotNil(t, cfg.Boolean)
assert.True(t, *cfg.Boolean)
assert.NotNil(t, cfg.Struct)
assert.Equal(t, s1, cfg.Struct)
assert.NotNil(t, cfg.MapStruct)
assert.Equal(t, &myStruct{}, cfg.MapStruct["struct"])
}
func TestUnmarshalWithIgnoreUnused(t *testing.T) {
stringMap := map[string]any{
"boolean": true,
"string": "this is a string",
}
conf := NewFromStringMap(stringMap)
require.Error(t, conf.Unmarshal(&testIDConfig{}))
assert.NoError(t, conf.Unmarshal(&testIDConfig{}, WithIgnoreUnused()))
}
type testConfig struct {
Boolean *bool `mapstructure:"boolean"`
Struct *myStruct `mapstructure:"struct"`
MapStruct map[string]*myStruct `mapstructure:"map_struct"`
}
func (t testConfig) Marshal(conf *Conf) error {
if t.Boolean != nil && !*t.Boolean {
return errors.New("unable to marshal")
}
if err := conf.Marshal(t); err != nil {
return err
}
return conf.Merge(NewFromStringMap(map[string]any{
"additional": "field",
}))
}
type myStruct struct {
Name string
}
type TestID string
func (tID *TestID) UnmarshalText(text []byte) error {
*tID = TestID(strings.TrimSuffix(string(text), "_"))
if *tID == "error" {
return errors.New("parsing error")
}
return nil
}
func (tID TestID) MarshalText() (text []byte, err error) {
out := string(tID)
if !strings.HasSuffix(out, "_") {
out += "_"
}
return []byte(out), nil
}
type testIDConfig struct {
Boolean bool `mapstructure:"bool"`
Map map[TestID]string `mapstructure:"map"`
}
func TestMapKeyStringToMapKeyTextUnmarshalerHookFunc(t *testing.T) {
stringMap := map[string]any{
"bool": true,
"map": map[string]any{
"string": "this is a string",
},
}
conf := NewFromStringMap(stringMap)
cfg := &testIDConfig{}
require.NoError(t, conf.Unmarshal(cfg))
assert.True(t, cfg.Boolean)
assert.Equal(t, map[TestID]string{"string": "this is a string"}, cfg.Map)
}
type uint32Config struct {
Value uint32 `mapstructure:"value"`
}
func TestUint32UnmarshalerSuccess(t *testing.T) {
tests := []struct {
name string
testValue uint32
}{
{
name: "Test convert 0 to uint",
testValue: 0,
},
{
name: "Test positive uint conversion",
testValue: 1000,
},
{
name: "Test largest uint64 conversion",
testValue: math.MaxUint32,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
stringMap := map[string]any{
"value": int(tt.testValue),
}
conf := NewFromStringMap(stringMap)
cfg := &uint32Config{}
err := conf.Unmarshal(cfg)
require.NoError(t, err)
assert.Equal(t, cfg.Value, tt.testValue)
})
}
}
func TestUint32UnmarshalerFailure(t *testing.T) {
stringMap := map[string]any{
"value": -1000,
}
conf := NewFromStringMap(stringMap)
cfg := &uint32Config{}
err := conf.Unmarshal(cfg)
assert.ErrorContains(t, err, "decoding failed due to the following error(s):\n\n'value' cannot parse value as 'uint32': -1000 overflows uint")
}
type uint64Config struct {
Value uint64 `mapstructure:"value"`
}
func TestUint64Unmarshaler(t *testing.T) {
// Equivalent to -1000, but converted to uint64
value := uint64(1000)
testValue := ^(value - 1)
stringMap := map[string]any{
"value": testValue,
}
conf := NewFromStringMap(stringMap)
cfg := &uint64Config{}
err := conf.Unmarshal(cfg)
require.NoError(t, err)
assert.Equal(t, cfg.Value, testValue)
}
func TestUint64UnmarshalerFailure(t *testing.T) {
stringMap := map[string]any{
"value": -1000,
}
conf := NewFromStringMap(stringMap)
cfg := &uint64Config{}
err := conf.Unmarshal(cfg)
assert.ErrorContains(t, err, "decoding failed due to the following error(s):\n\n'value' cannot parse value as 'uint64': -1000 overflows uint")
}
func TestMapKeyStringToMapKeyTextUnmarshalerHookFuncDuplicateID(t *testing.T) {
stringMap := map[string]any{
"bool": true,
"map": map[string]any{
"string": "this is a string",
"string_": "this is another string",
},
}
conf := NewFromStringMap(stringMap)
cfg := &testIDConfig{}
assert.Error(t, conf.Unmarshal(cfg))
}
func TestMapKeyStringToMapKeyTextUnmarshalerHookFuncErrorUnmarshal(t *testing.T) {
stringMap := map[string]any{
"bool": true,
"map": map[string]any{
"error": "this is a string",
},
}
conf := NewFromStringMap(stringMap)
cfg := &testIDConfig{}
assert.Error(t, conf.Unmarshal(cfg))
}
func TestMarshal(t *testing.T) {
conf := New()
cfg := &testIDConfig{
Boolean: true,
Map: map[TestID]string{
"string": "this is a string",
},
}
require.NoError(t, conf.Marshal(cfg))
assert.Equal(t, true, conf.Get("bool"))
assert.Equal(t, map[string]any{"string_": "this is a string"}, conf.Get("map"))
}
func TestMarshalDuplicateID(t *testing.T) {
conf := New()
cfg := &testIDConfig{
Boolean: true,
Map: map[TestID]string{
"string": "this is a string",
"string_": "this is another string",
},
}
assert.Error(t, conf.Marshal(cfg))
}
func TestMarshalError(t *testing.T) {
conf := New()
assert.Error(t, conf.Marshal(nil))
}
func TestMarshaler(t *testing.T) {
conf := New()
cfg := &testConfig{
Struct: &myStruct{
Name: "StructName",
},
}
require.NoError(t, conf.Marshal(cfg))
assert.Equal(t, "field", conf.Get("additional"))
conf = New()
type NestedMarshaler struct {
TestConfig *testConfig
}
nmCfg := &NestedMarshaler{
TestConfig: cfg,
}
require.NoError(t, conf.Marshal(nmCfg))
sub, err := conf.Sub("testconfig")
require.NoError(t, err)
assert.True(t, sub.IsSet("additional"))
assert.Equal(t, "field", sub.Get("additional"))
varBool := false
nmCfg.TestConfig.Boolean = &varBool
assert.Error(t, conf.Marshal(nmCfg))
}
// newConfFromFile creates a new Conf by reading the given file.
func newConfFromFile(tb testing.TB, fileName string) map[string]any {
content, err := os.ReadFile(filepath.Clean(fileName))
require.NoErrorf(tb, err, "unable to read the file %v", fileName)
var data map[string]any
require.NoError(tb, yaml.Unmarshal(content, &data), "unable to parse yaml")
return NewFromStringMap(data).ToStringMap()
}
type testConfig2 struct {
Next *nextConfig `mapstructure:"next"`
Another string `mapstructure:"another"`
EmbeddedConfig `mapstructure:",squash"`
EmbeddedConfig2 `mapstructure:",squash"`
}
type testConfigWithoutUnmarshaler struct {
Next *nextConfig `mapstructure:"next"`
Another string `mapstructure:"another"`
EmbeddedConfig `mapstructure:",squash"`
EmbeddedConfig2 `mapstructure:",squash"`
}
type testConfigWithEmbeddedError struct {
Next *nextConfig `mapstructure:"next"`
Another string `mapstructure:"another"`
EmbeddedConfigWithError `mapstructure:",squash"`
}
type testConfigWithMarshalError struct {
Next *nextConfig `mapstructure:"next"`
Another string `mapstructure:"another"`
EmbeddedConfigWithMarshalError `mapstructure:",squash"`
}
func (tc *testConfigWithEmbeddedError) Unmarshal(component *Conf) error {
if err := component.Unmarshal(tc, WithIgnoreUnused()); err != nil {
return err
}
return nil
}
type EmbeddedConfig struct {
Some string `mapstructure:"some"`
}
func (ec *EmbeddedConfig) Unmarshal(component *Conf) error {
if err := component.Unmarshal(ec, WithIgnoreUnused()); err != nil {
return err
}
ec.Some += " is also called"
return nil
}
type EmbeddedConfig2 struct {
Some2 string `mapstructure:"some_2"`
}
func (ec *EmbeddedConfig2) Unmarshal(component *Conf) error {
if err := component.Unmarshal(ec, WithIgnoreUnused()); err != nil {
return err
}
ec.Some2 += " also called2"
return nil
}
type EmbeddedConfigWithError struct{}
func (ecwe *EmbeddedConfigWithError) Unmarshal(_ *Conf) error {
return errors.New("embedded error")
}
type EmbeddedConfigWithMarshalError struct{}
func (ecwe EmbeddedConfigWithMarshalError) Marshal(_ *Conf) error {
return errors.New("marshaling error")
}
func (ecwe EmbeddedConfigWithMarshalError) Unmarshal(_ *Conf) error {
return nil
}
func (tc *testConfig2) Unmarshal(component *Conf) error {
if err := component.Unmarshal(tc); err != nil {
return err
}
tc.Another += " is only called directly"
return nil
}
type nextConfig struct {
String string `mapstructure:"string"`
private string
}
func (nc *nextConfig) Unmarshal(component *Conf) error {
if err := component.Unmarshal(nc); err != nil {
return err
}
nc.String += " is called"
return nil
}
func TestUnmarshaler(t *testing.T) {
cfgMap := NewFromStringMap(map[string]any{
"next": map[string]any{
"string": "make sure this",
},
"another": "make sure this",
"some": "make sure this",
"some_2": "this better be",
})
tc := &testConfig2{}
require.NoError(t, cfgMap.Unmarshal(tc))
assert.Equal(t, "make sure this is only called directly", tc.Another)
assert.Equal(t, "make sure this is called", tc.Next.String)
assert.Equal(t, "make sure this is also called", tc.Some)
assert.Equal(t, "this better be also called2", tc.Some2)
}
func TestEmbeddedUnmarshaler(t *testing.T) {
cfgMap := NewFromStringMap(map[string]any{
"next": map[string]any{
"string": "make sure this",
},
"another": "make sure this",
"some": "make sure this",
"some_2": "this better be",
})
tc := &testConfigWithoutUnmarshaler{}
require.NoError(t, cfgMap.Unmarshal(tc))
assert.Equal(t, "make sure this", tc.Another)
assert.Equal(t, "make sure this is called", tc.Next.String)
assert.Equal(t, "make sure this is also called", tc.Some)
assert.Equal(t, "this better be also called2", tc.Some2)
}
func TestEmbeddedUnmarshalerError(t *testing.T) {
cfgMap := NewFromStringMap(map[string]any{
"next": map[string]any{
"string": "make sure this",
},
"another": "make sure this",
"some": "make sure this",
})
tc := &testConfigWithEmbeddedError{}
assert.ErrorContains(t, cfgMap.Unmarshal(tc), "embedded error")
}
func TestEmbeddedMarshalerError(t *testing.T) {
t.Skip("This test fails because the main struct calls the embedded struct Unmarshal method, and doesn't execute the embedded struct hook.")
cfgMap := NewFromStringMap(map[string]any{
"next": map[string]any{
"string": "make sure this",
},
"another": "make sure this",
})
tc := &testConfigWithMarshalError{}
assert.EqualError(t, cfgMap.Unmarshal(tc), "error running encode hook: marshaling error")
}
// stringOpaque is similar to configopaque.String, in that
// marshaling then unmarshaling it changes its value.
type stringOpaque string
var _ encoding.TextMarshaler = stringOpaque("")
func (s stringOpaque) MarshalText() (text []byte, err error) {
return []byte("opaque"), nil
}
type squashedConfigOpaque struct {
Value stringOpaque `mapstructure:"value"`
secret bool
}
var _ Unmarshaler = (*squashedConfigOpaque)(nil)
func (ecwrt *squashedConfigOpaque) Unmarshal(conf *Conf) error {
if err := conf.Unmarshal(ecwrt); err != nil {
return err
}
ecwrt.secret = true
return nil
}
type testConfigOpaque struct {
// Don't embed, otherwise testConfigOpaque will implement Unmarshaler,
// and unmarshalerHookFunc will be called instead of unmarshalerEmbeddedStructsHookFunc.
Squashed squashedConfigOpaque `mapstructure:",squash"`
}
// Regression test: the hook processing embedded marshalers previously made the assumption that
// marshaling a struct then unmarshaling the resulting map back into the struct
// is a no-op, which is not true for configopaque.String.
func TestEmbeddedMarshalerWithoutRoundtrip(t *testing.T) {
cfgMap := NewFromStringMap(map[string]any{
"value": "hello",
})
tc := &testConfigOpaque{}
require.NoError(t, cfgMap.Unmarshal(tc))
// Check that "hello" hasn't been replaced by "opaque"
assert.Equal(t, "hello", string(tc.Squashed.Value))
// Check that the inner Unmarshal was called
assert.True(t, tc.Squashed.secret)
}
type B struct {
String string `mapstructure:"string"`
}
func (b *B) Unmarshal(conf *Conf) error {
return conf.Unmarshal(b)
}
type A struct {
B `mapstructure:",squash"`
}
func (a *A) Unmarshal(conf *Conf) error {
return conf.Unmarshal(a)
}
func TestUnmarshalerEmbeddedNilMap(t *testing.T) {
cfg := A{}
nilConf := NewFromStringMap(nil)
require.NoError(t, nilConf.Unmarshal(&cfg))
}
func TestUnmarshalerKeepAlreadyInitialized(t *testing.T) {
cfgMap := NewFromStringMap(map[string]any{
"next": map[string]any{
"string": "make sure this",
},
"another": "make sure this",
})
tc := &testConfig2{Next: &nextConfig{
private: "keep already configured members",
}}
require.NoError(t, cfgMap.Unmarshal(tc))
assert.Equal(t, "make sure this is only called directly", tc.Another)
assert.Equal(t, "make sure this is called", tc.Next.String)
assert.Equal(t, "keep already configured members", tc.Next.private)
}
func TestDirectUnmarshaler(t *testing.T) {
cfgMap := NewFromStringMap(map[string]any{
"next": map[string]any{
"string": "make sure this",
},
"another": "make sure this",
})
tc := &testConfig2{Next: &nextConfig{
private: "keep already configured members",
}}
require.NoError(t, tc.Unmarshal(cfgMap))
assert.Equal(t, "make sure this is only called directly is only called directly", tc.Another)
assert.Equal(t, "make sure this is called", tc.Next.String)
assert.Equal(t, "keep already configured members", tc.Next.private)
}
type testErrConfig struct {
Err errConfig `mapstructure:"err"`
}
type errConfig struct {
Foo string `mapstructure:"foo"`
}
func (tc *errConfig) Unmarshal(*Conf) error {
return errors.New("never works")
}
func TestUnmarshalerErr(t *testing.T) {
cfgMap := NewFromStringMap(map[string]any{
"err": map[string]any{
"foo": "will not unmarshal due to error",
},
})
tc := &testErrConfig{}
require.EqualError(t, cfgMap.Unmarshal(tc), "decoding failed due to the following error(s):\n\n'err' never works")
assert.Empty(t, tc.Err.Foo)
}
func TestZeroSliceHookFunc(t *testing.T) {
type structWithSlices struct {
Strings []string `mapstructure:"strings"`
}
tests := []struct {
name string
cfg map[string]any
provided any
expected any
}{
{
name: "overridden by slice",
cfg: map[string]any{
"strings": []string{"111"},
},
provided: &structWithSlices{
Strings: []string{"xxx", "yyyy", "zzzz"},
},
expected: &structWithSlices{
Strings: []string{"111"},
},
},
{
name: "overridden by a bigger slice",
cfg: map[string]any{
"strings": []string{"111", "222", "333"},
},
provided: &structWithSlices{
Strings: []string{"xxx", "yyyy"},
},
expected: &structWithSlices{
Strings: []string{"111", "222", "333"},
},
},
{
name: "overridden by an empty slice",
cfg: map[string]any{
"strings": []string{},
},
provided: &structWithSlices{
Strings: []string{"xxx", "yyyy"},
},
expected: &structWithSlices{
Strings: []string{},
},
},
{
name: "not overridden by nil",
cfg: map[string]any{
"strings": nil,
},
provided: &structWithSlices{
Strings: []string{"xxx", "yyyy"},
},
expected: &structWithSlices{
Strings: []string{"xxx", "yyyy"},
},
},
{
name: "not overridden by missing value",
cfg: map[string]any{},
provided: &structWithSlices{
Strings: []string{"xxx", "yyyy"},
},
expected: &structWithSlices{
Strings: []string{"xxx", "yyyy"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := NewFromStringMap(tt.cfg)
err := cfg.Unmarshal(tt.provided)
if assert.NoError(t, err) {
assert.Equal(t, tt.expected, tt.provided)
}
})
}
}
// Tests for issue that happened in https://github.com/open-telemetry/opentelemetry-collector/issues/12661.
func TestStructValuesReplaced(t *testing.T) {
type S struct {
A string `mapstructure:"A,omitempty"`
B string `mapstructure:"B,omitempty"`
}
type structWithSlices struct {
Structs []S `mapstructure:"structs"`
}
slicesStruct := structWithSlices{
Structs: []S{
{A: "A"},
},
}
bCfg := map[string]any{
"structs": []any{
map[string]any{
"B": "B",
},
},
}
bConf := NewFromStringMap(bCfg)
err := bConf.Unmarshal(&slicesStruct)
require.NoError(t, err)
assert.Equal(t, []S{{B: "B"}}, slicesStruct.Structs)
}
func TestNilValuesUnchanged(t *testing.T) {
type structWithSlices struct {
Strings []string `mapstructure:"strings"`
}
slicesStruct := &structWithSlices{}
nilCfg := map[string]any{
"strings": []any(nil),
}
nilConf := NewFromStringMap(nilCfg)
err := nilConf.Unmarshal(slicesStruct)
require.NoError(t, err)
confFromStruct := New()
err = confFromStruct.Marshal(slicesStruct)
require.NoError(t, err)
require.Equal(t, nilCfg, nilConf.ToStringMap())
require.Equal(t, confFromStruct.ToStringMap(), nilConf.ToStringMap())
}
func TestEmptySliceUnchanged(t *testing.T) {
type structWithSlices struct {
Strings []string `mapstructure:"strings"`
}
slicesStruct := &structWithSlices{}
nilCfg := map[string]any{
"strings": []any{},
}
nilConf := NewFromStringMap(nilCfg)
err := nilConf.Unmarshal(slicesStruct)
require.NoError(t, err)
confFromStruct := New()
err = confFromStruct.Marshal(slicesStruct)
require.NoError(t, err)
require.Equal(t, nilCfg, nilConf.ToStringMap())
require.Equal(t, nilConf.ToStringMap(), confFromStruct.ToStringMap())
}
type c struct {
Modifiers []string `mapstructure:"modifiers"`
}
func (c *c) Unmarshal(conf *Conf) error {
if err := conf.Unmarshal(c); err != nil {
return err
}
c.Modifiers = append(c.Modifiers, "c.Unmarshal")
return nil
}
type b struct {
Modifiers []string `mapstructure:"modifiers"`
C c `mapstructure:"c"`
}
func (b *b) Unmarshal(conf *Conf) error {
if err := conf.Unmarshal(b); err != nil {
return err
}
b.Modifiers = append(b.Modifiers, "B.Unmarshal")
b.C.Modifiers = append(b.C.Modifiers, "B.Unmarshal")
return nil
}
type a struct {
Modifiers []string `mapstructure:"modifiers"`
B b `mapstructure:"b"`
}
func (a *a) Unmarshal(conf *Conf) error {
if err := conf.Unmarshal(a); err != nil {
return err
}
a.Modifiers = append(a.Modifiers, "A.Unmarshal")
a.B.Modifiers = append(a.B.Modifiers, "A.Unmarshal")
a.B.C.Modifiers = append(a.B.C.Modifiers, "A.Unmarshal")
return nil
}
type wrapper struct {
A a `mapstructure:"a"`
}
// Test that calling the Unmarshal method on configuration structs is done from the inside out.
func TestNestedUnmarshalerImplementations(t *testing.T) {
conf := NewFromStringMap(map[string]any{"a": map[string]any{
"modifiers": []string{"conf.Unmarshal"},
"b": map[string]any{
"modifiers": []string{"conf.Unmarshal"},
"c": map[string]any{
"modifiers": []string{"conf.Unmarshal"},
},
},
}})
// Use a wrapper struct until we deprecate component.UnmarshalConfig
w := &wrapper{}
require.NoError(t, conf.Unmarshal(w))
a := w.A
assert.Equal(t, []string{"conf.Unmarshal", "A.Unmarshal"}, a.Modifiers)
assert.Equal(t, []string{"conf.Unmarshal", "B.Unmarshal", "A.Unmarshal"}, a.B.Modifiers)
assert.Equal(t, []string{"conf.Unmarshal", "c.Unmarshal", "B.Unmarshal", "A.Unmarshal"}, a.B.C.Modifiers)
}
// Test that unmarshaling the same conf twice works.
func TestUnmarshalDouble(t *testing.T) {
conf := NewFromStringMap(map[string]any{
"str": "test",
})
type Struct struct {
Str string `mapstructure:"str"`
}
s := &Struct{}
require.NoError(t, conf.Unmarshal(s))
assert.Equal(t, "test", s.Str)
type Struct2 struct {
Str string `mapstructure:"str"`
}
s2 := &Struct2{}
require.NoError(t, conf.Unmarshal(s2))
assert.Equal(t, "test", s2.Str)
}
type embeddedStructWithUnmarshal struct {
Foo string `mapstructure:"foo"`
success string
}
func (e *embeddedStructWithUnmarshal) Unmarshal(c *Conf) error {
if err := c.Unmarshal(e, WithIgnoreUnused()); err != nil {
return err
}
e.success = "success"
return nil
}
type configWithUnmarshalFromEmbeddedStruct struct {
embeddedStructWithUnmarshal
}
type topLevel struct {
Cfg *configWithUnmarshalFromEmbeddedStruct `mapstructure:"toplevel"`
}
// Test that Unmarshal is called on the embedded struct on the struct.
func TestUnmarshalThroughEmbeddedStruct(t *testing.T) {
c := NewFromStringMap(map[string]any{
"toplevel": map[string]any{
"foo": "bar",
},
})
cfg := &topLevel{}
err := c.Unmarshal(cfg)
require.NoError(t, err)
require.Equal(t, "success", cfg.Cfg.success)
require.Equal(t, "bar", cfg.Cfg.Foo)
}
type configWithOwnUnmarshalAndEmbeddedSquashedStruct struct {
embeddedStructWithUnmarshal `mapstructure:",squash"`
}
type topLevelSquashedEmbedded struct {
Cfg *configWithOwnUnmarshalAndEmbeddedSquashedStruct `mapstructure:"toplevel"`
}
// Test that the Unmarshal method is called on the squashed, embedded struct.
func TestUnmarshalOwnThroughEmbeddedSquashedStruct(t *testing.T) {
c := NewFromStringMap(map[string]any{
"toplevel": map[string]any{
"foo": "bar",
},
})
cfg := &topLevelSquashedEmbedded{}
err := c.Unmarshal(cfg)
require.NoError(t, err)
require.Equal(t, "success", cfg.Cfg.success)
require.Equal(t, "bar", cfg.Cfg.Foo)
}
type recursive struct {
Foo string `mapstructure:"foo"`
}
func (r *recursive) Unmarshal(conf *Conf) error {
newR := &recursive{}
if err := conf.Unmarshal(newR); err != nil {
return err
}
*r = *newR
return nil
}
// Tests that a struct can unmarshal itself by creating a new copy of itself, unmarshaling itself, and setting its value.
func TestRecursiveUnmarshaling(t *testing.T) {
conf := NewFromStringMap(map[string]any{
"foo": "something",
})
r := &recursive{}
require.NoError(t, conf.Unmarshal(r))
require.Equal(t, "something", r.Foo)
}
func testExpandedValue(t *testing.T, newSanitizer bool) {
err := featuregate.GlobalRegistry().Set(metadata.ConfmapNewExpandedValueSanitizerFeatureGate.ID(), newSanitizer)
require.NoError(t, err)
cm := NewFromStringMap(map[string]any{
"key": ExpandedValue{
Value: 0xdeadbeef,
Original: "original",
},
})
assert.Equal(t, 0xdeadbeef, cm.Get("key"))
assert.Equal(t, map[string]any{"key": 0xdeadbeef}, cm.ToStringMap())
type ConfigStr struct {
Key string `mapstructure:"key"`
}
cfgStr := ConfigStr{}
require.NoError(t, cm.Unmarshal(&cfgStr))
assert.Equal(t, "original", cfgStr.Key)
type ConfigStrPtr struct {
Key *string `mapstructure:"key"`
}
cfgStrPtr := ConfigStrPtr{}
if newSanitizer {
require.NoError(t, cm.Unmarshal(&cfgStrPtr))
if assert.NotNil(t, cfgStrPtr.Key) {
assert.Equal(t, "original", *cfgStrPtr.Key)
}
} else {
require.Error(t, cm.Unmarshal(&cfgStrPtr))
}
cfgMapStrPtr := map[string]*string{}
if newSanitizer {
require.NoError(t, cm.Unmarshal(&cfgMapStrPtr))
if assert.NotNil(t, cfgMapStrPtr["key"]) {
assert.Equal(t, "original", *cfgMapStrPtr["key"])
}
} else {
require.Error(t, cm.Unmarshal(&cfgMapStrPtr))
}
type ConfigInt struct {
Key int `mapstructure:"key"`
}
cfgInt := ConfigInt{}
require.NoError(t, cm.Unmarshal(&cfgInt))
assert.Equal(t, 0xdeadbeef, cfgInt.Key)
type ConfigBool struct {
Key bool `mapstructure:"key"`
}
cfgBool := ConfigBool{}
assert.Error(t, cm.Unmarshal(&cfgBool))
}
func TestExpandedValue(t *testing.T) {
before := metadata.ConfmapNewExpandedValueSanitizerFeatureGate.IsEnabled()
t.Run("old sanitizer", func(t *testing.T) {
testExpandedValue(t, false)
})
t.Run("new sanitizer", func(t *testing.T) {
testExpandedValue(t, true)
})
err := featuregate.GlobalRegistry().Set(metadata.ConfmapNewExpandedValueSanitizerFeatureGate.ID(), before)
require.NoError(t, err)
}
func TestExpandedValueNil(t *testing.T) {
cm := NewFromStringMap(map[string]any{
"key": ExpandedValue{
Value: nil,
Original: "NULL",
},
})
type ConfigStrPtr struct {
Key *string `mapstructure:"key"`
}
cfgStrPtr := ConfigStrPtr{}
require.NoError(t, cm.Unmarshal(&cfgStrPtr))
assert.Nil(t, cfgStrPtr.Key)
cfgMapStrPtr := map[string]*string{}
require.NoError(t, cm.Unmarshal(&cfgMapStrPtr))
assert.Nil(t, cfgMapStrPtr["key"])
}
func TestSubExpandedValue(t *testing.T) {
cm := NewFromStringMap(map[string]any{
"key": map[string]any{
"subkey": ExpandedValue{
Value: map[string]any{"subsubkey": "value"},
Original: "subsubkey: value",
},
},
})
assert.Equal(t, map[string]any{"subkey": map[string]any{"subsubkey": "value"}}, cm.Get("key"))
assert.Equal(t, map[string]any{"key": map[string]any{"subkey": map[string]any{"subsubkey": "value"}}}, cm.ToStringMap())
assert.Equal(t, map[string]any{"subsubkey": "value"}, cm.Get("key::subkey"))
sub, err := cm.Sub("key::subkey")
require.NoError(t, err)
assert.Equal(t, map[string]any{"subsubkey": "value"}, sub.ToStringMap())
// This should return value, but currently `Get` does not support keys within expanded values.
assert.Nil(t, cm.Get("key::subkey::subsubkey"))
}
func TestStringyTypes(t *testing.T) {
tests := []struct {
valueOfType any
isStringy bool
}{
{
valueOfType: "string",
isStringy: true,
},
{
valueOfType: 1,
isStringy: false,
},
{
valueOfType: map[string]any{},
isStringy: false,
},
{
valueOfType: []any{},
isStringy: false,
},
{
valueOfType: map[string]string{},
isStringy: true,
},
{
valueOfType: []string{},
isStringy: true,
},
{
valueOfType: map[string][]string{},
isStringy: true,
},
{
valueOfType: map[string]map[string]string{},
isStringy: true,
},
{
valueOfType: []map[string]any{},
isStringy: false,
},
{
valueOfType: []map[string]string{},
isStringy: true,
},
}
for _, tt := range tests {
// Create a reflect.Type from the value
to := reflect.TypeOf(tt.valueOfType)
assert.Equal(t, tt.isStringy, isStringyStructure(to))
}
}
func TestConfDelete(t *testing.T) {
tests := []struct {
path string
stringMap map[string]any
}{
{
path: "key",
stringMap: map[string]any{"key": "value"},
},
{
path: "map::expanded",
stringMap: map[string]any{"map": map[string]any{
"expanded": ExpandedValue{
Value: 0o1234,
Original: "01234",
},
}},
},
}
for _, test := range tests {
t.Run(test.path, func(t *testing.T) {
cm := NewFromStringMap(test.stringMap)
assert.True(t, cm.IsSet(test.path))
assert.True(t, cm.Delete(test.path))
assert.Nil(t, cm.Get(test.path))
assert.False(t, cm.IsSet(test.path))
assert.False(t, cm.Delete(test.path))
assert.Nil(t, cm.Get(test.path))
assert.False(t, cm.IsSet(test.path))
})
}
}
type structWithConfigOpaqueMap struct {
Headers map[string]string `mapstructure:"headers"`
}
func TestMapMerge(t *testing.T) {
tests := []struct {
name string
initial map[string]string
added map[string]string
expected map[string]string
}{
{
name: "both nil",
initial: nil,
added: nil,
expected: nil,
},
{
name: "nil map",
initial: map[string]string{},
added: nil,
expected: map[string]string{},
},
{
name: "initialized",
initial: map[string]string{
"foo": "bar",
},
added: nil,
expected: map[string]string{
"foo": "bar",
},
},
{
name: "both",
initial: map[string]string{
"foo": "bar",
},
added: map[string]string{
"foobar": "bar",
},
expected: map[string]string{
"foo": "bar",
"foobar": "bar",
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
s := structWithConfigOpaqueMap{
Headers: test.initial,
}
c := NewFromStringMap(map[string]any{
"headers": test.added,
})
require.NoError(t, c.Unmarshal(&s))
assert.Equal(t, test.expected, s.Headers)
})
}
}
func TestConfIsNil(t *testing.T) {
const subKey = "foo"
testCases := []struct {
name string
input map[string]any
expectIsNil bool
subExpectNil bool
subExpectErr string
}{
{
name: "nil input",
input: nil,
expectIsNil: true,
subExpectNil: true,
},
{
name: "empty map",
input: map[string]any{},
expectIsNil: false,
subExpectNil: true,
},
{
name: "nil subkey",
input: map[string]any{subKey: nil},
expectIsNil: false,
subExpectNil: true,
},
{
name: "empty subkey",
input: map[string]any{subKey: map[string]any{}},
expectIsNil: false,
subExpectNil: false,
},
{
name: "non-empty map",
input: map[string]any{subKey: map[string]any{"bar": 42}},
expectIsNil: false,
subExpectNil: false,
},
{
name: "non-map subkey",
input: map[string]any{subKey: 123},
expectIsNil: false,
subExpectErr: "unexpected sub-config value kind",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
conf := NewFromStringMap(tc.input)
if tc.expectIsNil {
assert.Empty(t, conf.AllKeys())
assert.Equal(t, map[string]any(nil), conf.ToStringMap())
} else {
assert.NotEqual(t, map[string]any(nil), conf.ToStringMap())
}
sub, err := conf.Sub(subKey)
if tc.subExpectErr != "" {
assert.ErrorContains(t, err, tc.subExpectErr)
} else {
assert.NoError(t, err)
if tc.subExpectNil {
assert.Empty(t, sub.AllKeys())
assert.Equal(t, map[string]any(nil), sub.ToStringMap())
} else {
assert.NotEqual(t, map[string]any(nil), sub.ToStringMap())
}
}
})
}
}
func TestConfmapNilMerge(t *testing.T) {
tests := []struct {
name string
left map[string]any
right map[string]any
expected map[string]any
}{
{
name: "both nil",
left: nil,
right: nil,
expected: nil,
},
{
name: "left nil",
left: nil,
right: map[string]any{"key": "value"},
expected: map[string]any{"key": "value"},
},
{
name: "right nil",
left: map[string]any{"key": "value"},
right: nil,
expected: map[string]any{"key": "value"},
},
{
name: "both non-nil",
left: map[string]any{"key1": "value1"},
right: map[string]any{"key2": "value2"},
expected: map[string]any{"key1": "value1", "key2": "value2"},
},
{
name: "left empty, right non-empty",
left: map[string]any{},
right: map[string]any{"key": "value"},
expected: map[string]any{"key": "value"},
},
{
name: "left non-empty, right empty",
left: map[string]any{"key": "value"},
right: map[string]any{},
expected: map[string]any{"key": "value"},
},
{
name: "left nil, right empty",
left: nil,
right: map[string]any{},
expected: map[string]any{},
},
{
name: "left empty, right nil",
left: map[string]any{},
right: nil,
expected: map[string]any{},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
leftConf := NewFromStringMap(test.left)
assert.Equal(t, test.left, leftConf.ToStringMap())
rightConf := NewFromStringMap(test.right)
assert.Equal(t, test.right, rightConf.ToStringMap())
err := leftConf.Merge(rightConf)
require.NoError(t, err)
assert.Equal(t, test.expected, leftConf.ToStringMap())
})
t.Run(test.name+"merge append", func(t *testing.T) {
leftConf := NewFromStringMap(test.left)
assert.Equal(t, test.left, leftConf.ToStringMap())
rightConf := NewFromStringMap(test.right)
assert.Equal(t, test.right, rightConf.ToStringMap())
err := leftConf.mergeAppend(rightConf)
require.NoError(t, err)
assert.Equal(t, test.expected, leftConf.ToStringMap())
})
}
}
type simpleUnmarshaler struct {
unmarshaled bool
Value string `mapstructure:"value"`
}
var _ Unmarshaler = (*simpleUnmarshaler)(nil)
func (s *simpleUnmarshaler) Unmarshal(c *Conf) error {
s.unmarshaled = true
return c.Unmarshal(s) // Does not call simpleUnmarshaler.Unmarshal
}
type wrapperUnmarshaler[T any] struct {
inner T
}
var _ Unmarshaler = (*wrapperUnmarshaler[simpleUnmarshaler])(nil)
func (w *wrapperUnmarshaler[T]) Unmarshal(c *Conf) error {
return c.Unmarshal(&w.inner, WithForceUnmarshaler()) // Calls T.Unmarshal
}
func TestUnmarshalWithForceUnmarshaler(t *testing.T) {
conf := NewFromStringMap(map[string]any{
"value": "test_value",
})
var out wrapperUnmarshaler[simpleUnmarshaler]
require.NoError(t, conf.Unmarshal(&out))
assert.Equal(t, "test_value", out.inner.Value)
assert.True(t, out.inner.unmarshaled)
}
================================================
FILE: confmap/internal/decoder.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/confmap/internal"
import (
"encoding"
"errors"
"fmt"
"reflect"
"slices"
"strings"
"github.com/go-viper/mapstructure/v2"
"go.opentelemetry.io/collector/confmap/internal/metadata"
"go.opentelemetry.io/collector/confmap/internal/third_party/composehook"
)
const (
// MapstructureTag is the struct field tag used to record marshaling/unmarshaling settings.
// See https://pkg.go.dev/github.com/go-viper/mapstructure/v2 for supported values.
MapstructureTag = "mapstructure"
)
// WithIgnoreUnused sets an option to ignore errors if existing
// keys in the original Conf were unused in the decoding process
// (extra keys).
func WithIgnoreUnused() UnmarshalOption {
return UnmarshalOptionFunc(func(uo *UnmarshalOptions) {
uo.IgnoreUnused = true
})
}
// WithForceUnmarshaler sets an option to run a top-level Unmarshal method,
// even if the Conf being unmarshaled is already a parameter from an Unmarshal method.
// To avoid infinite recursion, this should only be used when unmarshaling into
// a different type from the current Unmarshaler.
// For instance, this should be used in wrapper types such as configoptional.Optional
// to ensure the inner type's Unmarshal method is called.
func WithForceUnmarshaler() UnmarshalOption {
return UnmarshalOptionFunc(func(uo *UnmarshalOptions) {
uo.ForceUnmarshaler = true
})
}
// Decode decodes the contents of the Conf into the result argument, using a
// mapstructure decoder with the following notable behaviors. Ensures that maps whose
// values are nil pointer structs resolved to the zero value of the target struct (see
// expandNilStructPointers). Converts string to []string by splitting on ','. Ensures
// uniqueness of component IDs (see mapKeyStringToMapKeyTextUnmarshalerHookFunc).
// Decodes time.Duration from strings. Allows custom unmarshaling for structs implementing
// encoding.TextUnmarshaler. Allows custom unmarshaling for structs implementing confmap.Unmarshaler.
func Decode(input, result any, settings UnmarshalOptions, skipTopLevelUnmarshaler bool) error {
dc := &mapstructure.DecoderConfig{
ErrorUnused: !settings.IgnoreUnused,
Result: result,
TagName: MapstructureTag,
WeaklyTypedInput: false,
MatchName: caseSensitiveMatchName,
DecodeNil: true,
DecodeHook: composehook.ComposeDecodeHookFunc(
useExpandValue(),
expandNilStructPointersHookFunc(),
mapstructure.StringToSliceHookFunc(","),
mapKeyStringToMapKeyTextUnmarshalerHookFunc(),
mapstructure.StringToTimeDurationHookFunc(),
mapstructure.TextUnmarshallerHookFunc(),
unmarshalerHookFunc(result, skipTopLevelUnmarshaler && !settings.ForceUnmarshaler),
// after the main unmarshaler hook is called,
// we unmarshal the embedded structs if present to merge with the result:
unmarshalerEmbeddedStructsHookFunc(settings),
zeroSliceAndMapHookFunc(),
),
}
decoder, err := mapstructure.NewDecoder(dc)
if err != nil {
return err
}
if err = decoder.Decode(input); err != nil {
if strings.HasPrefix(err.Error(), "error decoding ''") {
return errors.Unwrap(err)
}
return err
}
return nil
}
// When a value has been loaded from an external source via a provider, we keep both the
// parsed value and the original string value. This allows us to expand the value to its
// original string representation when decoding into a string field, and use the parsed value otherwise.
//
// Fields containing a pointer to a string will also be set to the original string representation,
// except when the parsed value is nil (i.e. parsed from YAML `null`, `NULL`, `~`, the empty string, etc.)
func useExpandValue() mapstructure.DecodeHookFuncType {
return func(
_ reflect.Type,
to reflect.Type,
data any,
) (any, error) {
if exp, ok := data.(ExpandedValue); ok {
var useOriginal bool
if metadata.ConfmapNewExpandedValueSanitizerFeatureGate.IsEnabled() {
// Check if the target field is string, *string, **string, etc.
baseType := to
pointed := false
for baseType.Kind() == reflect.Pointer {
baseType = baseType.Elem()
pointed = true
}
useOriginal = baseType.Kind() == reflect.String
// If the parsed value is nil and the target is a pointer, use the parsed value.
if pointed && exp.Value == nil {
useOriginal = false
}
} else {
useOriginal = to.Kind() == reflect.String
}
v := castTo(exp, useOriginal)
// See https://github.com/open-telemetry/opentelemetry-collector/issues/10949
// If the `to.Kind` is not a string, then expandValue's original value is useless and
// the casted-to value will be nil. In that scenario, we need to use the default value of `to`'s kind.
if v == nil {
return reflect.Zero(to).Interface(), nil
}
return v, nil
}
if !metadata.ConfmapNewExpandedValueSanitizerFeatureGate.IsEnabled() {
switch to.Kind() {
case reflect.Array, reflect.Slice, reflect.Map:
if isStringyStructure(to) {
// If the target field is a stringy structure, sanitize to use the original string value everywhere.
return sanitizeToStr(data), nil
}
// Otherwise, sanitize to use the parsed value everywhere.
return sanitize(data), nil
}
}
return data, nil
}
}
// In cases where a config has a mapping of something to a struct pointers
// we want nil values to resolve to a pointer to the zero value of the
// underlying struct just as we want nil values of a mapping of something
// to a struct to resolve to the zero value of that struct.
//
// e.g. given a config type:
// type Config struct { Thing *SomeStruct `mapstructure:"thing"` }
//
// and yaml of:
// config:
//
// thing:
//
// we want an unmarshaled Config to be equivalent to
// Config{Thing: &SomeStruct{}} instead of Config{Thing: nil}
func expandNilStructPointersHookFunc() mapstructure.DecodeHookFuncValue {
return safeWrapDecodeHookFunc(func(from, to reflect.Value) (any, error) {
// ensure we are dealing with map to map comparison
if from.Kind() == reflect.Map && to.Kind() == reflect.Map {
toElem := to.Type().Elem()
// ensure that map values are pointers to a struct
// (that may be nil and require manual setting w/ zero value)
if toElem.Kind() == reflect.Ptr && toElem.Elem().Kind() == reflect.Struct {
fromRange := from.MapRange()
for fromRange.Next() {
fromKey := fromRange.Key()
fromValue := fromRange.Value()
// ensure that we've run into a nil pointer instance
if fromValue.IsNil() {
newFromValue := reflect.New(toElem.Elem())
from.SetMapIndex(fromKey, newFromValue)
}
}
}
}
return from.Interface(), nil
})
}
// mapKeyStringToMapKeyTextUnmarshalerHookFunc returns a DecodeHookFuncType that checks that a conversion from
// map[string]any to map[encoding.TextUnmarshaler]any does not overwrite keys,
// when UnmarshalText produces equal elements from different strings (e.g. trims whitespaces).
//
// This is needed in combination with ComponentID, which may produce equal IDs for different strings,
// and an error needs to be returned in that case, otherwise the last equivalent ID overwrites the previous one.
func mapKeyStringToMapKeyTextUnmarshalerHookFunc() mapstructure.DecodeHookFuncType {
return func(from, to reflect.Type, data any) (any, error) {
if from.Kind() != reflect.Map || from.Key().Kind() != reflect.String {
return data, nil
}
if to.Kind() != reflect.Map {
return data, nil
}
// Checks that the key type of to implements the TextUnmarshaler interface.
if _, ok := reflect.New(to.Key()).Interface().(encoding.TextUnmarshaler); !ok {
return data, nil
}
// Create a map with key value of to's key to bool.
fieldNameSet := reflect.MakeMap(reflect.MapOf(to.Key(), reflect.TypeFor[bool]()))
for k := range data.(map[string]any) {
// Create a new value of the to's key type.
tKey := reflect.New(to.Key())
// Use tKey to unmarshal the key of the map.
if err := tKey.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(k)); err != nil {
return nil, err
}
// Checks if the key has already been decoded in a previous iteration.
if fieldNameSet.MapIndex(reflect.Indirect(tKey)).IsValid() {
return nil, fmt.Errorf("duplicate name %q after unmarshaling %v", k, tKey)
}
fieldNameSet.SetMapIndex(reflect.Indirect(tKey), reflect.ValueOf(true))
}
return data, nil
}
}
// unmarshalerEmbeddedStructsHookFunc provides a mechanism for embedded structs to define their own unmarshal logic,
// by implementing the Unmarshaler interface.
func unmarshalerEmbeddedStructsHookFunc(settings UnmarshalOptions) mapstructure.DecodeHookFuncValue {
return safeWrapDecodeHookFunc(func(from, to reflect.Value) (any, error) {
if to.Type().Kind() != reflect.Struct {
return from.Interface(), nil
}
fromAsMap, ok := from.Interface().(map[string]any)
if !ok {
return from.Interface(), nil
}
// First call Unmarshaler on squashed embedded fields, if necessary.
var squashedUnmarshalers []int
for i := 0; i < to.Type().NumField(); i++ {
f := to.Type().Field(i)
if !f.IsExported() {
continue
}
tagParts := strings.Split(f.Tag.Get(MapstructureTag), ",")
if !slices.Contains(tagParts[1:], "squash") {
continue
}
unmarshaler, ok := to.Field(i).Addr().Interface().(Unmarshaler)
if !ok {
continue
}
c := NewFromStringMap(fromAsMap)
c.skipTopLevelUnmarshaler = true
if err := unmarshaler.Unmarshal(c); err != nil {
return nil, err
}
squashedUnmarshalers = append(squashedUnmarshalers, i)
}
// No squashed unmarshalers, we can let mapstructure do its job.
if len(squashedUnmarshalers) == 0 {
return fromAsMap, nil
}
// We need to unmarshal into all other fields without overwriting the output of the Unmarshal calls.
// To do that, create a custom "partial" struct containing only the non-squashed fields.
var fields []reflect.StructField
var fieldValues []reflect.Value
for i := 0; i < to.Type().NumField(); i++ {
f := to.Type().Field(i)
if !f.IsExported() {
continue
}
if slices.Contains(squashedUnmarshalers, i) {
continue
}
fields = append(fields, f)
fieldValues = append(fieldValues, to.Field(i))
}
restType := reflect.StructOf(fields)
restValue := reflect.New(restType)
// Copy initial values into partial struct.
for i, fieldValue := range fieldValues {
restValue.Elem().Field(i).Set(fieldValue)
}
// Decode into the partial struct.
// This performs a recursive call into this hook, which will be handled by the "no squashed unmarshalers" case above.
// We need to set `IgnoreUnused` to avoid errors from the map containing fields only present in the full struct.
settings.IgnoreUnused = true
if err := Decode(fromAsMap, restValue.Interface(), settings, true); err != nil {
return nil, err
}
// Copy decoding results back to the original struct.
for i, fieldValue := range fieldValues {
fieldValue.Set(restValue.Elem().Field(i))
}
return to, nil
})
}
// Provides a mechanism for individual structs to define their own unmarshal logic,
// by implementing the Unmarshaler interface, unless skipTopLevelUnmarshaler is
// true and the struct matches the top level object being unmarshaled.
func unmarshalerHookFunc(result any, skipTopLevelUnmarshaler bool) mapstructure.DecodeHookFuncValue {
return safeWrapDecodeHookFunc(func(from, to reflect.Value) (any, error) {
if !to.CanAddr() {
return from.Interface(), nil
}
toPtr := to.Addr().Interface()
// Need to ignore the top structure to avoid running into an infinite recursion
// where Unmarshaler.Unmarshal and Conf.Unmarshal would call each other.
if toPtr == result && skipTopLevelUnmarshaler {
return from.Interface(), nil
}
unmarshaler, ok := toPtr.(Unmarshaler)
if !ok {
return from.Interface(), nil
}
if _, ok = from.Interface().(map[string]any); !ok {
return from.Interface(), nil
}
// Use the current object if not nil (to preserve other configs in the object), otherwise zero initialize.
if to.Addr().IsNil() {
unmarshaler = reflect.New(to.Type()).Interface().(Unmarshaler)
}
c := NewFromStringMap(from.Interface().(map[string]any))
c.skipTopLevelUnmarshaler = true
if err := unmarshaler.Unmarshal(c); err != nil {
return nil, err
}
return unmarshaler, nil
})
}
// safeWrapDecodeHookFunc wraps a DecodeHookFuncValue to ensure fromVal is a valid `reflect.Value`
// object and therefore it is safe to call `reflect.Value` methods on fromVal.
//
// Use this only if the hook does not need to be called on untyped nil values.
// Typed nil values are safe to call and will be passed to the hook.
// See https://github.com/golang/go/issues/51649
func safeWrapDecodeHookFunc(
f mapstructure.DecodeHookFuncValue,
) mapstructure.DecodeHookFuncValue {
return func(fromVal, toVal reflect.Value) (any, error) {
if !fromVal.IsValid() {
return nil, nil
}
return f(fromVal, toVal)
}
}
// This hook is used to solve the issue: https://github.com/open-telemetry/opentelemetry-collector/issues/4001
// We adopt the suggestion provided in this issue: https://github.com/mitchellh/mapstructure/issues/74#issuecomment-279886492
// We should empty every slice before unmarshalling unless user provided slice is nil.
// Assume that we had a struct with a field of type slice called `keys`, which has default values of ["a", "b"]
//
// type Config struct {
// Keys []string `mapstructure:"keys"`
// }
//
// The configuration provided by users may have following cases
// 1. configuration have `keys` field and have a non-nil values for this key, the output should be overridden
// - for example, input is {"keys", ["c"]}, then output is Config{ Keys: ["c"]}
//
// 2. configuration have `keys` field and have an empty slice for this key, the output should be overridden by empty slices
// - for example, input is {"keys", []}, then output is Config{ Keys: []}
//
// 3. configuration have `keys` field and have nil value for this key, the output should be default config
// - for example, input is {"keys": nil}, then output is Config{ Keys: ["a", "b"]}
//
// 4. configuration have no `keys` field specified, the output should be default config
// - for example, input is {}, then output is Config{ Keys: ["a", "b"]}
//
// This hook is also used to solve https://github.com/open-telemetry/opentelemetry-collector/issues/13117.
// Since v0.127.0, we decode nil values to avoid creating empty map objects.
// The nil value is not well understood when layered on top of a default map non-nil value.
// The fix is to avoid the assignment and return the previous value.
func zeroSliceAndMapHookFunc() mapstructure.DecodeHookFuncValue {
return safeWrapDecodeHookFunc(func(from, to reflect.Value) (any, error) {
if to.CanSet() && to.Kind() == reflect.Slice && from.Kind() == reflect.Slice {
if !from.IsNil() {
// input slice is not nil, set the output slice to a new slice of the same type.
to.Set(reflect.MakeSlice(to.Type(), from.Len(), from.Cap()))
}
}
if to.CanSet() && to.Kind() == reflect.Map && from.Kind() == reflect.Map {
if from.IsNil() {
return to.Interface(), nil
}
}
return from.Interface(), nil
})
}
// case-sensitive version of the callback to be used in the MatchName property
// of the DecoderConfig. The default for MatchEqual is to use strings.EqualFold,
// which is case-insensitive.
func caseSensitiveMatchName(a, b string) bool {
return a == b
}
func castTo(exp ExpandedValue, useOriginal bool) any {
// If the target field is a string, use `exp.Original` or fail if not available.
if useOriginal {
return exp.Original
}
// Otherwise, use the parsed value (previous behavior).
return exp.Value
}
// Check if a reflect.Type is of the form T, where:
// X is any type or interface
// T = string | map[X]T | []T | [n]T
func isStringyStructure(t reflect.Type) bool {
if t.Kind() == reflect.String {
return true
}
if t.Kind() == reflect.Map {
return isStringyStructure(t.Elem())
}
if t.Kind() == reflect.Slice || t.Kind() == reflect.Array {
return isStringyStructure(t.Elem())
}
return false
}
================================================
FILE: confmap/internal/e2e/Makefile
================================================
include ../../../Makefile.Common
# Override COVER_PKGS to include the parent package that this e2e module tests
COVER_PKGS := go.opentelemetry.io/collector/confmap/internal,$(COVER_PKGS)
================================================
FILE: confmap/internal/e2e/expand_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package e2etest
import (
"context"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/internal"
"go.opentelemetry.io/collector/confmap/provider/envprovider"
"go.opentelemetry.io/collector/confmap/provider/fileprovider"
"go.opentelemetry.io/collector/confmap/xconfmap"
)
func Test_EscapedEnvVars_NoDefaultScheme(t *testing.T) {
const expandedValue = "some expanded value"
t.Setenv("ENV_VALUE", expandedValue)
t.Setenv("ENV_LIST", "['$$ESCAPE_ME','$${ESCAPE_ME}','$${env:ESCAPE_ME}']")
t.Setenv("ENV_MAP", "{'key1':'$$ESCAPE_ME','key2':'$${ESCAPE_ME}','key3':'$${env:ESCAPE_ME}'}")
t.Setenv("ENV_NESTED_DOLLARSIGN", "here is 1 $$")
t.Setenv("ENV_NESTED_DOLLARSIGN_ESCAPED", "here are 2 $$$")
t.Setenv("ENV_EXPAND_NESTED", "${env:ENV_VALUE} came from nested expansion")
expectedMap := map[string]any{
"test_map": map[string]any{
"key1": "$ENV_VALUE",
"key2": "$$ENV_VALUE",
"key3": "$${ENV_VALUE}",
"key4": "some${ENV_VALUE}text",
"key5": "some${ENV_VALUE}text",
"key6": "${ONE}${TWO}",
"key7": "text$",
"key8": "$",
"key9": "${1}${env:2}",
"key10": "some${env:ENV_VALUE}text",
"key11": "${env:${ENV_VALUE}}",
"key12": "${env:${ENV_VALUE}}",
"key13": "env:MAP_VALUE_2}${ENV_VALUE}{",
"key14": "$" + expandedValue,
"key15": "$ENV_VALUE",
"key16": []any{"$ESCAPE_ME", "${ESCAPE_ME}", "${env:ESCAPE_ME}"},
"key17": map[string]any{"key1": "$ESCAPE_ME", "key2": "${ESCAPE_ME}", "key3": "${env:ESCAPE_ME}"},
"key18": "here is 1 $",
"key19": "here are 2 $$",
"key20": "some expanded value came from nested expansion",
"key21": "default_value",
"key22": "${env:UNDEFINED_KEY:-${env:UNDEFINED_KEY}}",
},
}
resolver, err := confmap.NewResolver(confmap.ResolverSettings{
URIs: []string{filepath.Join("testdata", "expand-escaped-env.yaml")},
ProviderFactories: []confmap.ProviderFactory{fileprovider.NewFactory(), envprovider.NewFactory()},
DefaultScheme: "",
})
require.NoError(t, err)
// Test that expanded configs are the same with the simple config with no env vars.
cfgMap, err := resolver.Resolve(context.Background())
require.NoError(t, err)
m := cfgMap.ToStringMap()
assert.Equal(t, expectedMap, m)
}
func Test_EscapedEnvVars_DefaultScheme(t *testing.T) {
const expandedValue = "some expanded value"
t.Setenv("ENV_VALUE", expandedValue)
t.Setenv("ENV_LIST", "['$$ESCAPE_ME','$${ESCAPE_ME}','$${env:ESCAPE_ME}']")
t.Setenv("ENV_MAP", "{'key1':'$$ESCAPE_ME','key2':'$${ESCAPE_ME}','key3':'$${env:ESCAPE_ME}'}")
t.Setenv("ENV_NESTED_DOLLARSIGN", "here is 1 $$")
t.Setenv("ENV_NESTED_DOLLARSIGN_ESCAPED", "here are 2 $$$")
t.Setenv("ENV_EXPAND_NESTED", "${env:ENV_VALUE} came from nested expansion")
expectedMap := map[string]any{
"test_map": map[string]any{
"key1": "$ENV_VALUE",
"key2": "$$ENV_VALUE",
"key3": "$" + expandedValue,
"key4": "some" + expandedValue + "text",
"key5": "some${ENV_VALUE}text",
"key6": "${ONE}${TWO}",
"key7": "text$",
"key8": "$",
"key9": "${1}${env:2}",
"key10": "some${env:ENV_VALUE}text",
"key11": "${env:" + expandedValue + "}",
"key12": "${env:${ENV_VALUE}}",
"key13": "env:MAP_VALUE_2}${ENV_VALUE}{",
"key14": "$" + expandedValue,
"key15": "$ENV_VALUE",
"key16": []any{"$ESCAPE_ME", "${ESCAPE_ME}", "${env:ESCAPE_ME}"},
"key17": map[string]any{"key1": "$ESCAPE_ME", "key2": "${ESCAPE_ME}", "key3": "${env:ESCAPE_ME}"},
"key18": "here is 1 $",
"key19": "here are 2 $$",
"key20": "some expanded value came from nested expansion",
"key21": "default_value",
"key22": "${env:UNDEFINED_KEY:-${env:UNDEFINED_KEY}}",
},
}
resolver, err := confmap.NewResolver(confmap.ResolverSettings{
URIs: []string{filepath.Join("testdata", "expand-escaped-env.yaml")},
ProviderFactories: []confmap.ProviderFactory{fileprovider.NewFactory(), envprovider.NewFactory()},
DefaultScheme: "env",
})
require.NoError(t, err)
// Test that expanded configs are the same with the simple config with no env vars.
cfgMap, err := resolver.Resolve(context.Background())
require.NoError(t, err)
m := cfgMap.ToStringMap()
assert.Equal(t, expectedMap, m)
}
func Test_RawConfMap(t *testing.T) {
data := map[string]any{
"value": internal.ExpandedValue{
Value: 8080,
Original: "8080",
},
}
conf := confmap.NewFromStringMap(data)
rawData := xconfmap.ToStringMapRaw(conf)
_, isPresentAndExpandedValue := rawData["value"].(internal.ExpandedValue)
require.True(t, isPresentAndExpandedValue)
}
================================================
FILE: confmap/internal/e2e/fuzz_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package e2etest
import (
"context"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// targetNested tests the following property:
// > Passing a value of type T directly through an environment variable
// > should be equivalent to passing it through a nested environment variable.
func targetNested[T any](t *testing.T, value string) {
resolver := NewResolver(t, "types_expand.yaml")
// Use os.Setenv so we can check the error and return instead of failing the fuzzing.
os.Setenv("ENV", "${env:ENV2}") //nolint:usetesting
defer os.Unsetenv("ENV")
err := os.Setenv("ENV2", value) //nolint:usetesting
defer os.Unsetenv("ENV2")
if err != nil {
return
}
confNested, errResolveNested := resolver.Resolve(context.Background())
err = os.Setenv("ENV", value) //nolint:usetesting
if err != nil {
return
}
confSimple, errResolveSimple := resolver.Resolve(context.Background())
require.Equal(t, errResolveNested, errResolveSimple)
if errResolveNested != nil {
return
}
var cfgNested targetConfig[T]
errNested := confNested.Unmarshal(cfgNested)
var cfgSimple targetConfig[T]
errSimple := confSimple.Unmarshal(cfgSimple)
require.Equal(t, errNested, errSimple)
if errNested != nil {
return
}
assert.Equal(t, cfgNested, cfgSimple)
}
// testStrings for fuzzing targets
var testStrings = []string{
"123",
"opentelemetry",
"!!str 123",
"\"0123\"",
"\"",
"1111:1111:1111:1111:1111::",
"{field: value}",
"0xdeadbeef",
"0b101",
"field:",
"2006-01-02T15:04:05Z07:00",
}
func FuzzNestedString(f *testing.F) {
for _, value := range testStrings {
f.Add(value)
}
f.Fuzz(targetNested[string])
}
func FuzzNestedInt(f *testing.F) {
for _, value := range testStrings {
f.Add(value)
}
f.Fuzz(targetNested[int])
}
func FuzzNestedMap(f *testing.F) {
for _, value := range testStrings {
f.Add(value)
}
f.Fuzz(targetNested[map[string]any])
}
================================================
FILE: confmap/internal/e2e/go.mod
================================================
module go.opentelemetry.io/collector/confmap/internal/e2e
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/config/configopaque v1.54.0
go.opentelemetry.io/collector/confmap v1.54.0
go.opentelemetry.io/collector/confmap/provider/envprovider v1.54.0
go.opentelemetry.io/collector/confmap/provider/fileprovider v1.54.0
go.opentelemetry.io/collector/confmap/xconfmap v0.148.0
go.opentelemetry.io/collector/featuregate v1.54.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/confmap => ../../
replace go.opentelemetry.io/collector/confmap/provider/fileprovider => ../../provider/fileprovider
replace go.opentelemetry.io/collector/confmap/provider/envprovider => ../../provider/envprovider
replace go.opentelemetry.io/collector/config/configopaque => ../../../config/configopaque
replace go.opentelemetry.io/collector/featuregate => ../../../featuregate
replace go.opentelemetry.io/collector/confmap/xconfmap => ../../xconfmap
replace go.opentelemetry.io/collector/internal/testutil => ../../../internal/testutil
================================================
FILE: confmap/internal/e2e/go.sum
================================================
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/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: confmap/internal/e2e/maplist_expanded_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package e2etest
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/config/configopaque"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/internal"
"go.opentelemetry.io/collector/confmap/internal/metadata"
"go.opentelemetry.io/collector/featuregate"
)
type testHeadersConfig struct {
Headers configopaque.MapList `mapstructure:"headers"`
}
// TestMapListWithExpandedValue tests that MapList can handle ExpandedValue
// from environment variable expansion
func TestMapListWithExpandedValue(t *testing.T) {
// Simulate what happens when ${env:TOKEN} is expanded
// The confmap will contain an ExpandedValue instead of a plain string
data := map[string]any{
"headers": []any{
map[string]any{
"name": "Authorization",
"value": internal.ExpandedValue{
Value: "Bearer secret-token",
Original: "Bearer secret-token",
},
},
},
}
conf := confmap.NewFromStringMap(data)
var tc testHeadersConfig
err := conf.Unmarshal(&tc)
require.NoError(t, err)
val, ok := tc.Headers.Get("Authorization")
require.True(t, ok)
require.Equal(t, configopaque.String("Bearer secret-token"), val)
}
// TestMapListWithExpandedValueIntValue tests an ExpandedValue with an integer Value
func TestMapListWithExpandedValueIntValue(t *testing.T) {
// Simulate what happens when expanding a value that parses as an int
data := map[string]any{
"headers": []any{
map[string]any{
"name": "X-Port",
"value": internal.ExpandedValue{
Value: 8080, // Value is parsed as int
Original: "8080", // Original is string
},
},
},
}
originalState := metadata.ConfmapNewExpandedValueSanitizerFeatureGate.IsEnabled()
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ConfmapNewExpandedValueSanitizerFeatureGate.ID(), originalState))
}()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ConfmapNewExpandedValueSanitizerFeatureGate.ID(), true))
conf := confmap.NewFromStringMap(data)
var tc testHeadersConfig
err := conf.Unmarshal(&tc)
require.NoError(t, err)
val, ok := tc.Headers.Get("X-Port")
require.True(t, ok)
require.Equal(t, configopaque.String("8080"), val)
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ConfmapNewExpandedValueSanitizerFeatureGate.ID(), false))
// This will fail because when reverting to old behavior, ExpandedValues get decoded at collection time and doesn't
// take struct collections into account.
err = conf.Unmarshal(&tc)
require.Error(t, err)
}
// TestDirectConfigopaqueStringWithExpandedValueIntValue tests that direct unmarshaling works
func TestDirectConfigopaqueStringWithExpandedValueIntValue(t *testing.T) {
type testConfig struct {
Value configopaque.String `mapstructure:"value"`
}
// Direct configopaque.String field (not in a map/slice structure)
data := map[string]any{
"value": internal.ExpandedValue{
Value: 8080,
Original: "8080",
},
}
conf := confmap.NewFromStringMap(data)
var tc testConfig
err := conf.Unmarshal(&tc)
// This should work because useExpandValue detects the target is a string
require.NoError(t, err)
require.Equal(t, configopaque.String("8080"), tc.Value)
}
// TestStringyStructureWithExpandedValue tests the isStringyStructure path in useExpandValue
func TestStringyStructureWithExpandedValue(t *testing.T) {
type testConfig struct {
Tags []string `mapstructure:"tags"`
}
data := map[string]any{
"tags": []any{
internal.ExpandedValue{
Value: 8080,
Original: "8080",
},
},
}
originalState := metadata.ConfmapNewExpandedValueSanitizerFeatureGate.IsEnabled()
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ConfmapNewExpandedValueSanitizerFeatureGate.ID(), originalState))
}()
// With feature gate disabled, useExpandValue should detect []string as stringy
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ConfmapNewExpandedValueSanitizerFeatureGate.ID(), false))
conf := confmap.NewFromStringMap(data)
var tc testConfig
err := conf.Unmarshal(&tc)
require.NoError(t, err)
require.Equal(t, []string{"8080"}, tc.Tags)
}
================================================
FILE: confmap/internal/e2e/nil_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package e2etest
import (
"context"
"testing"
"github.com/stretchr/testify/require"
)
func TestNilToStringMap(t *testing.T) {
tests := []struct {
name string
expectedMap map[string]any
expectedSub map[string]any
}{
{
name: "subsection_null.yaml",
expectedMap: map[string]any{
"field": map[string]any{
"key": nil,
},
},
expectedSub: nil,
},
{
name: "subsection_empty_map.yaml",
expectedMap: map[string]any{
"field": map[string]any{
"key": map[string]any{},
},
},
expectedSub: map[string]any{},
},
{
name: "subsection_set_but_empty.yaml",
expectedMap: map[string]any{
"field": map[string]any{
"key": nil,
},
},
expectedSub: nil,
},
{
name: "subsection_unset.yaml",
expectedMap: map[string]any{
"field": nil,
},
expectedSub: nil,
},
{
name: "subsection_unset_empty_map.yaml",
expectedMap: map[string]any{
"field": map[string]any{},
},
expectedSub: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resolver := NewResolver(t, tt.name)
conf, err := resolver.Resolve(context.Background())
require.NoError(t, err)
require.Equal(t, tt.expectedMap, conf.ToStringMap())
sub, err := conf.Sub("field::key")
require.NoError(t, err)
require.Equal(t, tt.expectedSub, sub.ToStringMap())
})
}
}
func TestNilToStringMapEnv(t *testing.T) {
tests := []struct {
envValue string
expectedMap map[string]any
expectedSub map[string]any
}{
{
envValue: "null",
expectedMap: map[string]any{
"field": map[string]any{
"key": nil,
},
},
expectedSub: nil,
},
{
envValue: "{}",
expectedMap: map[string]any{
"field": map[string]any{
"key": map[string]any{},
},
},
expectedSub: map[string]any{},
},
{
envValue: "",
expectedMap: map[string]any{
"field": map[string]any{
"key": nil,
},
},
expectedSub: nil,
},
}
for _, tt := range tests {
t.Run(tt.envValue, func(t *testing.T) {
t.Setenv("VALUE", tt.envValue)
resolver := NewResolver(t, "types_map.yaml")
conf, err := resolver.Resolve(context.Background())
require.NoError(t, err)
require.Equal(t, tt.expectedMap, conf.ToStringMap())
sub, err := conf.Sub("field::key")
require.NoError(t, err)
require.Equal(t, tt.expectedSub, sub.ToStringMap())
})
}
}
================================================
FILE: confmap/internal/e2e/testdata/expand-escaped-env.yaml
================================================
test_map:
# $$ -> escaped $
key1: "$$ENV_VALUE"
# $$$$ -> two escaped $
key2: "$$$$ENV_VALUE"
# $$ -> escaped $ + ${ENV_VALUE} expanded
key3: "$$${ENV_VALUE}"
# expanded in the middle
key4: "some${ENV_VALUE}text"
# escaped $ in the middle
key5: "some$${ENV_VALUE}text"
# two escaped $
key6: "$${ONE}$${TWO}"
# trailing escaped $
key7: "text$$"
# escaped $ alone
key8: "$$"
# escaped number and uri
key9: "$${1}$${env:2}"
# escape provider
key10: "some$${env:ENV_VALUE}text"
# can escape outer when nested
key11: "$${env:${ENV_VALUE}}"
# can escape inner and outer when nested
key12: "$${env:$${ENV_VALUE}}"
# can escape partial
key13: "env:MAP_VALUE_2}$${ENV_VALUE}{"
# $$$ -> escaped $ + expanded env var
key14: "$$${env:ENV_VALUE}"
# $ -> $
key15: "$ENV_VALUE"
# list is escaped
key16: "${env:ENV_LIST}"
# map is escaped
key17: "${env:ENV_MAP}"
# nested $$ -> escaped $
key18: "${env:ENV_NESTED_DOLLARSIGN}"
# nested $$$ -> escaped $$
key19: "${env:ENV_NESTED_DOLLARSIGN_ESCAPED}"
# nested env var syntax is expanded
key20: "${env:ENV_EXPAND_NESTED}"
# default value
key21: "${env:UNDEFINED_KEY:-default_value}"
# escaped default value
key22: "${env:UNDEFINED_KEY:-$${env:UNDEFINED_KEY}}"
================================================
FILE: confmap/internal/e2e/testdata/indirect-slice-env-var-main.yaml
================================================
receivers:
nop:
otlp:
protocols:
grpc:
exporters:
nop:
otlp_grpc:
endpoint: localhost:4317
service:
pipelines: ${file:${env:BASE_FOLDER}/indirect-slice-env-var-pipelines.yaml}
================================================
FILE: confmap/internal/e2e/testdata/indirect-slice-env-var-pipelines.yaml
================================================
logs:
receivers: ${env:OTEL_LOGS_RECEIVER}
exporters: ${env:OTEL_LOGS_EXPORTER}
================================================
FILE: confmap/internal/e2e/testdata/issue-10787-main.yaml
================================================
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
memory_limiter:
exporters:
${file:testdata/issue-10787-snippet.yaml}
service:
telemetry:
metrics:
level: detailed
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter]
exporters: [debug]
================================================
FILE: confmap/internal/e2e/testdata/issue-10787-snippet.yaml
================================================
# ${hello.world}
debug:
verbosity: detailed
================================================
FILE: confmap/internal/e2e/testdata/subsection_empty_map.yaml
================================================
field:
key: {}
================================================
FILE: confmap/internal/e2e/testdata/subsection_null.yaml
================================================
field:
key: null
================================================
FILE: confmap/internal/e2e/testdata/subsection_set_but_empty.yaml
================================================
field:
key:
================================================
FILE: confmap/internal/e2e/testdata/subsection_unset.yaml
================================================
field:
================================================
FILE: confmap/internal/e2e/testdata/subsection_unset_empty_map.yaml
================================================
field: {}
================================================
FILE: confmap/internal/e2e/testdata/types_complex.yaml
================================================
field: [key: ["${env:VALUE}"]]
================================================
FILE: confmap/internal/e2e/testdata/types_expand.yaml
================================================
field: ${env:ENV}
================================================
FILE: confmap/internal/e2e/testdata/types_expand_inline.yaml
================================================
field: "inline field with ${env:ENV} expansion"
================================================
FILE: confmap/internal/e2e/testdata/types_map.yaml
================================================
field:
key: ${env:VALUE}
================================================
FILE: confmap/internal/e2e/testdata/types_slice.yaml
================================================
field: ["${env:VALUE}"]
================================================
FILE: confmap/internal/e2e/types_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package e2etest
import (
"context"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/config/configopaque"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/provider/envprovider"
"go.opentelemetry.io/collector/confmap/provider/fileprovider"
)
type TargetField string
const (
TargetFieldInt TargetField = "int_field"
TargetFieldString TargetField = "string_field"
TargetFieldBool TargetField = "bool_field"
TargetFieldInlineString TargetField = "inline_string_field"
TargetFieldSlice TargetField = "slice_field"
)
type Test struct {
value string
targetField TargetField
expected any
resolveErr string
unmarshalErr string
}
type targetConfig[T any] struct {
Field T `mapstructure:"field"`
}
func NewResolver(tb testing.TB, path string) *confmap.Resolver {
resolver, err := confmap.NewResolver(confmap.ResolverSettings{
URIs: []string{filepath.Join("testdata", path)},
ProviderFactories: []confmap.ProviderFactory{
fileprovider.NewFactory(),
envprovider.NewFactory(),
},
DefaultScheme: "env",
})
require.NoError(tb, err)
return resolver
}
func AssertExpectedMatch[T any](t *testing.T, tt Test, conf *confmap.Conf, cfg *targetConfig[T]) {
err := conf.Unmarshal(cfg)
if tt.unmarshalErr != "" {
require.ErrorContains(t, err, tt.unmarshalErr)
return
}
require.NoError(t, err)
require.Equal(t, tt.expected, cfg.Field)
}
func AssertResolvesTo(t *testing.T, resolver *confmap.Resolver, tt Test) {
conf, err := resolver.Resolve(context.Background())
if tt.resolveErr != "" {
require.ErrorContains(t, err, tt.resolveErr)
return
}
require.NoError(t, err)
switch tt.targetField {
case TargetFieldInt:
var cfg targetConfig[int]
AssertExpectedMatch(t, tt, conf, &cfg)
case TargetFieldString, TargetFieldInlineString:
var cfg targetConfig[string]
AssertExpectedMatch(t, tt, conf, &cfg)
case TargetFieldBool:
var cfg targetConfig[bool]
AssertExpectedMatch(t, tt, conf, &cfg)
case TargetFieldSlice:
var cfg targetConfig[[]any]
AssertExpectedMatch(t, tt, conf, &cfg)
default:
t.Fatalf("unexpected target field %q", tt.targetField)
}
}
func TestStrictTypeCasting(t *testing.T) {
t.Setenv("ENV_VALUE", "testreceiver")
values := []Test{
{
value: "123",
targetField: TargetFieldInt,
expected: 123,
},
{
value: "123",
targetField: TargetFieldString,
expected: "123",
},
{
value: "123",
targetField: TargetFieldInlineString,
expected: "inline field with 123 expansion",
},
{
value: "0123",
targetField: TargetFieldInt,
expected: 83,
},
{
value: "0123",
targetField: TargetFieldString,
expected: "0123",
},
{
value: "0123",
targetField: TargetFieldInlineString,
expected: "inline field with 0123 expansion",
},
{
value: "0xdeadbeef",
targetField: TargetFieldInt,
expected: 3735928559,
},
{
value: "0xdeadbeef",
targetField: TargetFieldString,
expected: "0xdeadbeef",
},
{
value: "0xdeadbeef",
targetField: TargetFieldInlineString,
expected: "inline field with 0xdeadbeef expansion",
},
{
value: "\"0123\"",
targetField: TargetFieldString,
expected: "\"0123\"",
},
{
value: "\"0123\"",
targetField: TargetFieldInt,
unmarshalErr: "'field' expected type 'int', got unconvertible type 'string'",
},
{
value: "\"0123\"",
targetField: TargetFieldInlineString,
expected: "inline field with \"0123\" expansion",
},
{
value: "!!str 0123",
targetField: TargetFieldString,
expected: "!!str 0123",
},
{
value: "!!str 0123",
targetField: TargetFieldInlineString,
expected: "inline field with !!str 0123 expansion",
},
{
value: "t",
targetField: TargetFieldBool,
unmarshalErr: "'field' expected type 'bool', got unconvertible type 'string'",
},
{
value: "23",
targetField: TargetFieldBool,
unmarshalErr: "'field' expected type 'bool', got unconvertible type 'int'",
},
{
value: "{\"field\": 123}",
targetField: TargetFieldInlineString,
expected: "inline field with {\"field\": 123} expansion",
},
{
value: "1111:1111:1111:1111:1111::",
targetField: TargetFieldInlineString,
expected: "inline field with 1111:1111:1111:1111:1111:: expansion",
},
{
value: "1111:1111:1111:1111:1111::",
targetField: TargetFieldString,
expected: "1111:1111:1111:1111:1111::",
},
{
value: "2006-01-02T15:04:05Z07:00",
targetField: TargetFieldString,
expected: "2006-01-02T15:04:05Z07:00",
},
{
value: "2006-01-02T15:04:05Z07:00",
targetField: TargetFieldInlineString,
expected: "inline field with 2006-01-02T15:04:05Z07:00 expansion",
},
{
value: "2023-03-20T03:17:55.432328Z",
targetField: TargetFieldString,
expected: "2023-03-20T03:17:55.432328Z",
},
{
value: "2023-03-20T03:17:55.432328Z",
targetField: TargetFieldInlineString,
expected: "inline field with 2023-03-20T03:17:55.432328Z expansion",
},
// issue 10787
{
value: "true # comment with a ${env:hello.world} reference",
targetField: TargetFieldBool,
expected: true,
},
{
value: "true # comment with a ${env:hello.world} reference",
targetField: TargetFieldString,
unmarshalErr: `expected type 'string', got unconvertible type 'bool'`,
},
{
value: "true # comment with a ${env:hello.world} reference",
targetField: TargetFieldInlineString,
resolveErr: `environment variable "hello.world" has invalid name`,
},
// issue 10759
{
value: `["a",`,
targetField: TargetFieldString,
expected: `["a",`,
},
{
value: `["a",`,
targetField: TargetFieldInlineString,
expected: `inline field with ["a", expansion`,
},
// issue 10799
{
value: `[filelog,windowseventlog/application]`,
targetField: TargetFieldSlice,
expected: []any{"filelog", "windowseventlog/application"},
},
{
value: `[filelog,windowseventlog/application]`,
targetField: TargetFieldString,
expected: "[filelog,windowseventlog/application]",
},
{
value: `[filelog,windowseventlog/application]`,
targetField: TargetFieldInlineString,
expected: "inline field with [filelog,windowseventlog/application] expansion",
},
{
value: "$$ENV",
targetField: TargetFieldString,
expected: "$ENV",
},
{
value: "$$ENV",
targetField: TargetFieldInlineString,
expected: "inline field with $ENV expansion",
},
{
value: "$${ENV}",
targetField: TargetFieldString,
expected: "${ENV}",
},
{
value: "$${ENV}",
targetField: TargetFieldInlineString,
expected: "inline field with ${ENV} expansion",
},
{
value: "$${env:ENV}",
targetField: TargetFieldString,
expected: "${env:ENV}",
},
{
value: "$${env:ENV}",
targetField: TargetFieldInlineString,
expected: "inline field with ${env:ENV} expansion",
},
{
value: `[filelog,${env:ENV_VALUE}]`,
targetField: TargetFieldString,
expected: "[filelog,testreceiver]",
},
{
value: `[filelog,${ENV_VALUE}]`,
targetField: TargetFieldString,
expected: "[filelog,testreceiver]",
},
{
value: `["filelog","$${env:ENV_VALUE}"]`,
targetField: TargetFieldString,
expected: `["filelog","${env:ENV_VALUE}"]`,
},
{
value: `["filelog","$${ENV_VALUE}"]`,
targetField: TargetFieldString,
expected: `["filelog","${ENV_VALUE}"]`,
},
{
value: `["filelog","$$ENV_VALUE"]`,
targetField: TargetFieldString,
expected: `["filelog","$ENV_VALUE"]`,
},
{
value: `["filelog","$ENV_VALUE"]`,
targetField: TargetFieldString,
expected: `["filelog","$ENV_VALUE"]`,
},
}
for _, tt := range values {
t.Run(tt.value+"/"+string(tt.targetField)+"/"+"direct", func(t *testing.T) {
testFile := "types_expand.yaml"
if tt.targetField == TargetFieldInlineString {
testFile = "types_expand_inline.yaml"
}
resolver := NewResolver(t, testFile)
t.Setenv("ENV", tt.value)
AssertResolvesTo(t, resolver, tt)
})
t.Run(tt.value+"/"+string(tt.targetField)+"/"+"indirect", func(t *testing.T) {
testFile := "types_expand.yaml"
if tt.targetField == TargetFieldInlineString {
testFile = "types_expand_inline.yaml"
}
resolver := NewResolver(t, testFile)
t.Setenv("ENV", "${env:ENV2}")
t.Setenv("ENV2", tt.value)
AssertResolvesTo(t, resolver, tt)
})
}
}
func TestRecursiveInlineString(t *testing.T) {
values := []Test{
{
value: "123",
targetField: TargetFieldString,
expected: "The value The value 123 is wrapped is wrapped",
},
{
value: "123",
targetField: TargetFieldInlineString,
expected: "inline field with The value The value 123 is wrapped is wrapped expansion",
},
{
value: "opentelemetry",
targetField: TargetFieldString,
expected: "The value The value opentelemetry is wrapped is wrapped",
},
{
value: "opentelemetry",
targetField: TargetFieldInlineString,
expected: "inline field with The value The value opentelemetry is wrapped is wrapped expansion",
},
}
for _, tt := range values {
t.Run(tt.value+"/"+string(tt.targetField), func(t *testing.T) {
testFile := "types_expand.yaml"
if tt.targetField == TargetFieldInlineString {
testFile = "types_expand_inline.yaml"
}
resolver := NewResolver(t, testFile)
t.Setenv("ENV", "The value ${env:ENV2} is wrapped")
t.Setenv("ENV2", "The value ${env:ENV3} is wrapped")
t.Setenv("ENV3", tt.value)
AssertResolvesTo(t, resolver, tt)
})
}
}
func TestRecursiveMaps(t *testing.T) {
value := "{value: 123}"
resolver := NewResolver(t, "types_expand.yaml")
t.Setenv("ENV", `{env: "${env:ENV2}", inline: "inline ${env:ENV2}"}`)
t.Setenv("ENV2", `{env2: "${env:ENV3}"}`)
t.Setenv("ENV3", value)
conf, err := resolver.Resolve(context.Background())
require.NoError(t, err)
type Value struct {
Value int `mapstructure:"value"`
}
type ENV2 struct {
Env2 Value `mapstructure:"env2"`
}
type ENV struct {
Env ENV2 `mapstructure:"env"`
Inline string `mapstructure:"inline"`
}
type Target struct {
Field ENV `mapstructure:"field"`
}
var cfg Target
err = conf.Unmarshal(&cfg)
require.NoError(t, err)
require.Equal(t,
Target{Field: ENV{
Env: ENV2{
Env2: Value{
Value: 123,
},
},
Inline: "inline {env2: \"{value: 123}\"}",
}},
cfg,
)
confStr, err := resolver.Resolve(context.Background())
require.NoError(t, err)
var cfgStr targetConfig[string]
err = confStr.Unmarshal(&cfgStr)
require.NoError(t, err)
require.Equal(t, `{env: "{env2: "{value: 123}"}", inline: "inline {env2: "{value: 123}"}"}`,
cfgStr.Field,
)
}
// Test that comments with invalid ${env:...} references do not prevent configuration from loading.
func TestIssue10787(t *testing.T) {
resolver := NewResolver(t, "issue-10787-main.yaml")
conf, err := resolver.Resolve(context.Background())
require.NoError(t, err)
assert.Equal(t, map[string]any{
"exporters": map[string]any{
"debug": map[string]any{
"verbosity": "detailed",
},
},
"processors": map[string]any{
"memory_limiter": nil,
},
"receivers": map[string]any{
"otlp": map[string]any{
"protocols": map[string]any{
"grpc": map[string]any{
"endpoint": "0.0.0.0:4317",
},
"http": map[string]any{
"endpoint": "0.0.0.0:4318",
},
},
},
},
"service": map[string]any{
"pipelines": map[string]any{
"traces": map[string]any{
"exporters": []any{"debug"},
"processors": []any{"memory_limiter"},
"receivers": []any{"otlp"},
},
},
"telemetry": map[string]any{
"metrics": map[string]any{
"level": "detailed",
},
},
},
}, conf.ToStringMap(),
)
}
func TestStructMappingIssue10787(t *testing.T) {
resolver := NewResolver(t, "types_expand.yaml")
t.Setenv("ENV", `# this is a comment
debug:
verbosity: detailed`)
conf, err := resolver.Resolve(context.Background())
require.NoError(t, err)
type Debug struct {
Verbosity string `mapstructure:"verbosity"`
}
type Exporters struct {
Debug Debug `mapstructure:"debug"`
}
type Target struct {
Field Exporters `mapstructure:"field"`
}
var cfg Target
err = conf.Unmarshal(&cfg)
require.NoError(t, err)
require.Equal(t,
Target{Field: Exporters{
Debug: Debug{
Verbosity: "detailed",
},
}},
cfg,
)
confStr, err := resolver.Resolve(context.Background())
require.NoError(t, err)
var cfgStr targetConfig[string]
err = confStr.Unmarshal(&cfgStr)
require.NoError(t, err)
require.Equal(t, `# this is a comment
debug:
verbosity: detailed`,
cfgStr.Field,
)
}
func TestStructMappingIssue10787_ExpandComment(t *testing.T) {
resolver := NewResolver(t, "types_expand.yaml")
t.Setenv("EXPAND_ME", "an expanded env var")
t.Setenv("ENV", `# this is a comment with ${EXPAND_ME}
debug:
verbosity: detailed`)
conf, err := resolver.Resolve(context.Background())
require.NoError(t, err)
type Debug struct {
Verbosity string `mapstructure:"verbosity"`
}
type Exporters struct {
Debug Debug `mapstructure:"debug"`
}
type Target struct {
Field Exporters `mapstructure:"field"`
}
var cfg Target
err = conf.Unmarshal(&cfg)
require.NoError(t, err)
require.Equal(t,
Target{Field: Exporters{
Debug: Debug{
Verbosity: "detailed",
},
}},
cfg,
)
confStr, err := resolver.Resolve(context.Background())
require.NoError(t, err)
var cfgStr targetConfig[string]
err = confStr.Unmarshal(&cfgStr)
require.NoError(t, err)
require.Equal(t, `# this is a comment with an expanded env var
debug:
verbosity: detailed`,
cfgStr.Field,
)
}
func TestIndirectSliceEnvVar(t *testing.T) {
// This replicates the situation in https://github.com/open-telemetry/opentelemetry-collector/issues/10799
// where a configuration file is loaded that contains a reference to a slice of strings in an environment variable.
t.Setenv("BASE_FOLDER", "testdata")
t.Setenv("OTEL_LOGS_RECEIVER", "[nop, otlp]")
t.Setenv("OTEL_LOGS_EXPORTER", "[otlp, nop]")
resolver := NewResolver(t, "indirect-slice-env-var-main.yaml")
conf, err := resolver.Resolve(context.Background())
require.NoError(t, err)
type CollectorConf struct {
Exporters struct {
OTLP struct {
Endpoint string `mapstructure:"endpoint"`
} `mapstructure:"otlp_grpc"`
Nop struct{} `mapstructure:"nop"`
} `mapstructure:"exporters"`
Receivers struct {
OTLP struct {
Protocols struct {
GRPC struct{} `mapstructure:"grpc"`
} `mapstructure:"protocols"`
} `mapstructure:"otlp"`
Nop struct{} `mapstructure:"nop"`
} `mapstructure:"receivers"`
Service struct {
Pipelines struct {
Logs struct {
Exporters []string `mapstructure:"exporters"`
Receivers []string `mapstructure:"receivers"`
} `mapstructure:"logs"`
} `mapstructure:"pipelines"`
} `mapstructure:"service"`
}
var collectorConf CollectorConf
err = conf.Unmarshal(&collectorConf)
require.NoError(t, err)
assert.Equal(t, "localhost:4317", collectorConf.Exporters.OTLP.Endpoint)
assert.Equal(t, []string{"nop", "otlp"}, collectorConf.Service.Pipelines.Logs.Receivers)
assert.Equal(t, []string{"otlp", "nop"}, collectorConf.Service.Pipelines.Logs.Exporters)
}
func TestIssue10937_MapType(t *testing.T) {
t.Setenv("VALUE", "1234")
resolver := NewResolver(t, "types_map.yaml")
conf, err := resolver.Resolve(context.Background())
require.NoError(t, err)
var cfg targetConfig[map[string]configopaque.String]
err = conf.Unmarshal(&cfg)
require.NoError(t, err)
require.Equal(t, map[string]configopaque.String{"key": "1234"}, cfg.Field)
}
func TestIssue10937_ArrayType(t *testing.T) {
t.Setenv("VALUE", "1234")
resolver := NewResolver(t, "types_slice.yaml")
conf, err := resolver.Resolve(context.Background())
require.NoError(t, err)
var cfgStrSlice targetConfig[[]string]
err = conf.Unmarshal(&cfgStrSlice)
require.NoError(t, err)
require.Equal(t, []string{"1234"}, cfgStrSlice.Field)
var cfgStrArray targetConfig[[1]string]
err = conf.Unmarshal(&cfgStrArray)
require.NoError(t, err)
require.Equal(t, [1]string{"1234"}, cfgStrArray.Field)
var cfgAnySlice targetConfig[[]any]
err = conf.Unmarshal(&cfgAnySlice)
require.NoError(t, err)
require.Equal(t, []any{1234}, cfgAnySlice.Field)
var cfgAnyArray targetConfig[[1]any]
err = conf.Unmarshal(&cfgAnyArray)
require.NoError(t, err)
require.Equal(t, [1]any{1234}, cfgAnyArray.Field)
}
func TestIssue10937_ComplexType(t *testing.T) {
t.Setenv("VALUE", "1234")
resolver := NewResolver(t, "types_complex.yaml")
conf, err := resolver.Resolve(context.Background())
require.NoError(t, err)
var cfgStringy targetConfig[[]map[string][]string]
err = conf.Unmarshal(&cfgStringy)
require.NoError(t, err)
require.Equal(t, []map[string][]string{{"key": {"1234"}}}, cfgStringy.Field)
var cfgNotStringy targetConfig[[]map[string][]any]
err = conf.Unmarshal(&cfgNotStringy)
require.NoError(t, err)
require.Equal(t, []map[string][]any{{"key": {1234}}}, cfgNotStringy.Field)
}
func TestIssue10949_UnsetVar(t *testing.T) {
t.Setenv("ENV", "")
resolver := NewResolver(t, "types_expand.yaml")
conf, err := resolver.Resolve(context.Background())
require.NoError(t, err)
var cfg targetConfig[int]
err = conf.Unmarshal(&cfg)
require.NoError(t, err)
require.Equal(t, 0, cfg.Field)
}
================================================
FILE: confmap/internal/encoder.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/confmap/internal"
import (
"reflect"
"github.com/go-viper/mapstructure/v2"
encoder "go.opentelemetry.io/collector/confmap/internal/mapstructure"
)
// EncoderConfig returns a default encoder.EncoderConfig that includes
// an EncodeHook that handles both TextMarshaler and Marshaler
// interfaces.
func EncoderConfig(rawVal any, _ MarshalOptions) *encoder.EncoderConfig {
return &encoder.EncoderConfig{
EncodeHook: mapstructure.ComposeDecodeHookFunc(
encoder.YamlMarshalerHookFunc(),
encoder.TextMarshalerHookFunc(),
marshalerHookFunc(rawVal),
),
}
}
// marshalerHookFunc returns a DecodeHookFuncValue that checks structs that aren't
// the original to see if they implement the Marshaler interface.
func marshalerHookFunc(orig any) mapstructure.DecodeHookFuncValue {
origType := reflect.TypeOf(orig)
return safeWrapDecodeHookFunc(func(from, _ reflect.Value) (any, error) {
if from.Kind() != reflect.Struct {
return from.Interface(), nil
}
// ignore original to avoid infinite loop.
if from.Type() == origType && reflect.DeepEqual(from.Interface(), orig) {
return from.Interface(), nil
}
marshaler, ok := from.Interface().(Marshaler)
if !ok {
return from.Interface(), nil
}
conf := NewFromStringMap(nil)
if err := marshaler.Marshal(conf); err != nil {
return nil, err
}
stringMap := conf.ToStringMap()
if stringMap == nil {
// If conf is still nil after marshaling, we want to encode it as an untyped nil
// instead of a map-typed nil. This ensures the value is a proper null value
// in the final marshaled output instead of an empty map. We hit this case
// when marshaling wrapper structs that have no direct representation
// in the marshaled output that aren't tagged with "squash" on the fields
// they're used on.
return nil, nil
}
return stringMap, nil
})
}
================================================
FILE: confmap/internal/envvar/pattern.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package envvar // import "go.opentelemetry.io/collector/confmap/internal/envvar"
import "regexp"
const ValidationPattern = `^[a-zA-Z_][a-zA-Z0-9_]*$`
var ValidationRegexp = regexp.MustCompile(ValidationPattern)
================================================
FILE: confmap/internal/expand.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/confmap/internal"
// ExpandedValue holds the YAML parsed value and original representation of a value.
// It keeps track of the original representation to be used by the 'useExpandValue' hook
// if the target field is a string. We need to keep both representations because we don't know
// what the target field type is until `Unmarshal` is called.
type ExpandedValue struct {
// Value is the expanded value.
Value any
// Original is the original representation of the value.
Original string
}
================================================
FILE: confmap/internal/mapstructure/encoder.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package mapstructure // import "go.opentelemetry.io/collector/confmap/internal/mapstructure"
import (
"encoding"
"errors"
"fmt"
"maps"
"reflect"
"strings"
"github.com/go-viper/mapstructure/v2"
"go.yaml.in/yaml/v3"
)
const (
tagNameMapStructure = "mapstructure"
optionSeparator = ","
optionOmitEmpty = "omitempty"
optionSquash = "squash"
optionRemain = "remain"
optionSkip = "-"
)
var errNonStringEncodedKey = errors.New("non string-encoded key")
// tagInfo stores the mapstructure tag details.
type tagInfo struct {
name string
omitEmpty bool
squash bool
}
// An Encoder takes structured data and converts it into an
// interface following the mapstructure tags.
type Encoder struct {
config *EncoderConfig
}
// EncoderConfig is the configuration used to create a new encoder.
type EncoderConfig struct {
// EncodeHook, if set, is a way to provide custom encoding. It
// will be called before structs and primitive types.
EncodeHook mapstructure.DecodeHookFunc
}
// New returns a new encoder for the configuration.
func New(cfg *EncoderConfig) *Encoder {
return &Encoder{config: cfg}
}
// Encode takes the input and uses reflection to encode it to
// an interface based on the mapstructure spec.
func (e *Encoder) Encode(input any) (any, error) {
return e.encode(reflect.ValueOf(input))
}
// encode processes the value based on the reflect.Kind.
func (e *Encoder) encode(value reflect.Value) (any, error) {
if value.IsValid() {
switch value.Kind() {
case reflect.Interface, reflect.Ptr:
return e.encode(value.Elem())
case reflect.Map:
return e.encodeMap(value)
case reflect.Slice:
return e.encodeSlice(value)
case reflect.Struct:
return e.encodeStruct(value)
default:
return e.encodeHook(value)
}
}
return nil, nil
}
// encodeHook calls the EncodeHook in the EncoderConfig with the value passed in.
// This is called before processing structs and for primitive data types.
func (e *Encoder) encodeHook(value reflect.Value) (any, error) {
if e.config != nil && e.config.EncodeHook != nil {
out, err := mapstructure.DecodeHookExec(e.config.EncodeHook, value, value)
if err != nil {
return nil, fmt.Errorf("error running encode hook: %w", err)
}
return out, nil
}
return value.Interface(), nil
}
// encodeStruct encodes the struct by iterating over the fields, getting the
// mapstructure tagInfo for each exported field, and encoding the value.
func (e *Encoder) encodeStruct(value reflect.Value) (any, error) {
if value.Kind() != reflect.Struct {
return nil, &reflect.ValueError{
Method: "encodeStruct",
Kind: value.Kind(),
}
}
out, err := e.encodeHook(value)
if err != nil {
return nil, err
}
value = reflect.ValueOf(out)
// if the output of encodeHook is no longer a struct,
// call encode against it.
if value.Kind() != reflect.Struct {
return e.encode(value)
}
result := make(map[string]any)
for i := 0; i < value.NumField(); i++ {
field := value.Field(i)
if field.CanInterface() {
info := getTagInfo(value.Type().Field(i))
if (info.omitEmpty && field.IsZero()) || info.name == optionSkip {
continue
}
encoded, err := e.encode(field)
if err != nil {
return nil, fmt.Errorf("error encoding field %q: %w", info.name, err)
}
if info.squash {
if m, ok := encoded.(map[string]any); ok {
maps.Copy(result, m)
}
} else {
result[info.name] = encoded
}
}
}
return result, nil
}
// encodeSlice iterates over the slice and encodes each of the elements.
func (e *Encoder) encodeSlice(value reflect.Value) (any, error) {
if value.Kind() != reflect.Slice {
return nil, &reflect.ValueError{
Method: "encodeSlice",
Kind: value.Kind(),
}
}
if value.IsNil() {
return []any(nil), nil
}
result := make([]any, value.Len())
for i := 0; i < value.Len(); i++ {
var err error
if result[i], err = e.encode(value.Index(i)); err != nil {
return nil, fmt.Errorf("error encoding element in slice at index %d: %w", i, err)
}
}
return result, nil
}
// encodeMap encodes a map by encoding the key and value. Returns errNonStringEncodedKey
// if the key is not encoded into a string.
func (e *Encoder) encodeMap(value reflect.Value) (any, error) {
if value.Kind() != reflect.Map {
return nil, &reflect.ValueError{
Method: "encodeMap",
Kind: value.Kind(),
}
}
var result map[string]any
if value.Len() > 0 || !value.IsNil() {
result = make(map[string]any)
}
iterator := value.MapRange()
for iterator.Next() {
encoded, err := e.encode(iterator.Key())
if err != nil {
return nil, fmt.Errorf("error encoding key: %w", err)
}
v := reflect.ValueOf(encoded)
var key string
switch v.Kind() {
case reflect.String:
key = v.String()
default:
return nil, fmt.Errorf("%w, key: %q, kind: %v, type: %T", errNonStringEncodedKey, iterator.Key().Interface(), iterator.Key().Kind(), encoded)
}
if _, ok := result[key]; ok {
return nil, fmt.Errorf("duplicate key %q while encoding", key)
}
if result[key], err = e.encode(iterator.Value()); err != nil {
return nil, fmt.Errorf("error encoding map value for key %q: %w", key, err)
}
}
return result, nil
}
// getTagInfo looks up the mapstructure tag and uses that if available.
// Uses the lowercase field if not found. Checks for omitempty and squash.
func getTagInfo(field reflect.StructField) *tagInfo {
info := tagInfo{}
if tag, ok := field.Tag.Lookup(tagNameMapStructure); ok {
options := strings.Split(tag, optionSeparator)
info.name = options[0]
if len(options) > 1 {
for _, option := range options[1:] {
switch option {
case optionOmitEmpty:
info.omitEmpty = true
case optionSquash, optionRemain:
info.squash = true
}
}
}
} else {
info.name = strings.ToLower(field.Name)
}
return &info
}
// TextMarshalerHookFunc returns a DecodeHookFuncValue that checks
// for the encoding.TextMarshaler interface and calls the MarshalText
// function if found.
func TextMarshalerHookFunc() mapstructure.DecodeHookFuncValue {
return func(from, _ reflect.Value) (any, error) {
marshaler, ok := from.Interface().(encoding.TextMarshaler)
if !ok {
return from.Interface(), nil
}
out, err := marshaler.MarshalText()
if err != nil {
return nil, err
}
return string(out), nil
}
}
// YamlMarshalerHookFunc returns a DecodeHookFuncValue that checks for structs
// that have yaml tags but no mapstructure tags. If found, it will convert the struct
// to map[string]any using the yaml package, which respects the yaml tags. Ultimately,
// this allows mapstructure to later marshal the map[string]any in a generic way.
func YamlMarshalerHookFunc() mapstructure.DecodeHookFuncValue {
return func(from, _ reflect.Value) (any, error) {
if from.Kind() == reflect.Struct {
for i := 0; i < from.NumField(); i++ {
if _, ok := from.Type().Field(i).Tag.Lookup("mapstructure"); ok {
// The struct has at least one mapstructure tag so don't do anything.
return from.Interface(), nil
}
if _, ok := from.Type().Field(i).Tag.Lookup("yaml"); ok {
// The struct has at least one yaml tag, so convert it to map[string]any using yaml.
yamlBytes, err := yaml.Marshal(from.Interface())
if err != nil {
return nil, err
}
var m map[string]any
err = yaml.Unmarshal(yamlBytes, &m)
if err != nil {
return nil, err
}
return m, nil
}
}
}
return from.Interface(), nil
}
}
================================================
FILE: confmap/internal/mapstructure/encoder_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package mapstructure
import (
"encoding"
"errors"
"reflect"
"strings"
"testing"
"github.com/go-viper/mapstructure/v2"
"github.com/stretchr/testify/require"
)
type TestComplexStruct struct {
Skipped TestEmptyStruct `mapstructure:",squash"`
Nested TestSimpleStruct `mapstructure:",squash"`
Slice []TestSimpleStruct `mapstructure:"slice,omitempty"`
Pointer *TestSimpleStruct `mapstructure:"ptr"`
Map map[string]TestSimpleStruct `mapstructure:"map,omitempty"`
Remain map[string]any `mapstructure:",remain"`
TranslatedYaml TestYamlStruct `mapstructure:"translated"`
SquashedYaml TestYamlStruct `mapstructure:",squash"`
PointerTranslatedYaml *TestPtrToYamlStruct `mapstructure:"translated_ptr"`
PointerSquashedYaml *TestPtrToYamlStruct `mapstructure:",squash"`
Interface encoding.TextMarshaler
}
type TestSimpleStruct struct {
Value string `mapstructure:"value"`
skipped string
err error
}
type TestEmptyStruct struct {
Value string `mapstructure:"-"`
}
type TestYamlStruct struct {
YamlValue string `yaml:"yaml_value"`
YamlOmitEmpty string `yaml:"yaml_omit,omitempty"`
YamlInline TestYamlSimpleStruct `yaml:",inline"`
}
type TestPtrToYamlStruct struct {
YamlValue string `yaml:"yaml_value_ptr"`
YamlOmitEmpty string `yaml:"yaml_omit_ptr,omitempty"`
YamlInline *TestYamlPtrToSimpleStruct `yaml:",inline"`
}
type TestYamlSimpleStruct struct {
Inline string `yaml:"yaml_inline"`
}
type TestYamlPtrToSimpleStruct struct {
InlinePtr string `yaml:"yaml_inline_ptr"`
}
type TestID string
func (tID TestID) MarshalText() (text []byte, err error) {
out := string(tID)
if out == "error" {
return nil, errors.New("parsing error")
}
if !strings.HasSuffix(out, "_") {
out += "_"
}
return []byte(out), nil
}
type TestStringLike string
func TestEncode(t *testing.T) {
enc := New(&EncoderConfig{
EncodeHook: mapstructure.ComposeDecodeHookFunc(
YamlMarshalerHookFunc(),
TextMarshalerHookFunc(),
),
})
testCases := map[string]struct {
input any
want any
}{
"WithString": {
input: "test",
want: "test",
},
"WithTextMarshaler": {
input: TestID("type"),
want: "type_",
},
"MapWithTextMarshalerKey": {
input: map[TestID]TestSimpleStruct{
TestID("type"): {Value: "value"},
},
want: map[string]any{
"type_": map[string]any{"value": "value"},
},
},
"MapWithoutTextMarshalerKey": {
input: map[TestStringLike]TestSimpleStruct{
TestStringLike("key"): {Value: "value"},
},
want: map[string]any{
"key": map[string]any{"value": "value"},
},
},
"WithSlice": {
input: []TestID{
TestID("nop"),
TestID("type_"),
},
want: []any{"nop_", "type_"},
},
"WithSimpleStruct": {
input: TestSimpleStruct{Value: "test", skipped: "skipped"},
want: map[string]any{
"value": "test",
},
},
"WithComplexStruct": {
input: &TestComplexStruct{
Skipped: TestEmptyStruct{
Value: "omitted",
},
Nested: TestSimpleStruct{
Value: "nested",
},
Slice: []TestSimpleStruct{
{Value: "slice"},
},
Map: map[string]TestSimpleStruct{
"Key": {Value: "map"},
},
Pointer: &TestSimpleStruct{
Value: "pointer",
},
Remain: map[string]any{
"remain1": 23,
"remain2": "value",
},
Interface: TestID("value"),
TranslatedYaml: TestYamlStruct{
YamlValue: "foo_translated",
YamlOmitEmpty: "",
YamlInline: TestYamlSimpleStruct{
Inline: "bar_translated",
},
},
SquashedYaml: TestYamlStruct{
YamlValue: "foo_squashed",
YamlOmitEmpty: "",
YamlInline: TestYamlSimpleStruct{
Inline: "bar_squashed",
},
},
PointerTranslatedYaml: &TestPtrToYamlStruct{
YamlValue: "foo_translated_ptr",
YamlOmitEmpty: "",
YamlInline: &TestYamlPtrToSimpleStruct{
InlinePtr: "bar_translated_ptr",
},
},
PointerSquashedYaml: &TestPtrToYamlStruct{
YamlValue: "foo_squashed_ptr",
YamlOmitEmpty: "",
YamlInline: &TestYamlPtrToSimpleStruct{
InlinePtr: "bar_squashed_ptr",
},
},
},
want: map[string]any{
"value": "nested",
"slice": []any{map[string]any{"value": "slice"}},
"map": map[string]any{
"Key": map[string]any{"value": "map"},
},
"ptr": map[string]any{"value": "pointer"},
"interface": "value_",
"yaml_value": "foo_squashed",
"yaml_inline": "bar_squashed",
"translated": map[string]any{
"yaml_value": "foo_translated",
"yaml_inline": "bar_translated",
},
"yaml_value_ptr": "foo_squashed_ptr",
"yaml_inline_ptr": "bar_squashed_ptr",
"translated_ptr": map[string]any{
"yaml_value_ptr": "foo_translated_ptr",
"yaml_inline_ptr": "bar_translated_ptr",
},
"remain1": 23,
"remain2": "value",
},
},
}
for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
got, err := enc.Encode(testCase.input)
require.NoError(t, err)
require.Equal(t, testCase.want, got)
})
}
// without the TextMarshalerHookFunc
enc.config.EncodeHook = nil
testCase := TestID("test")
got, err := enc.Encode(testCase)
require.NoError(t, err)
require.Equal(t, testCase, got)
}
func TestGetTagInfo(t *testing.T) {
testCases := []struct {
name string
field reflect.StructField
wantName string
wantOmit bool
wantSquash bool
}{
{
name: "WithoutTags",
field: reflect.StructField{
Name: "Test",
},
wantName: "test",
},
{
name: "WithoutMapStructureTag",
field: reflect.StructField{
Tag: `yaml:"hello,inline"`,
Name: "YAML",
},
wantName: "yaml",
},
{
name: "WithRename",
field: reflect.StructField{
Tag: `mapstructure:"hello"`,
Name: "Test",
},
wantName: "hello",
},
{
name: "WithOmitEmpty",
field: reflect.StructField{
Tag: `mapstructure:"hello,omitempty"`,
Name: "Test",
},
wantName: "hello",
wantOmit: true,
},
{
name: "WithSquash",
field: reflect.StructField{
Tag: `mapstructure:",squash"`,
Name: "Test",
},
wantSquash: true,
},
{
name: "WithRemain",
field: reflect.StructField{
Tag: `mapstructure:",remain"`,
Name: "Test",
},
wantSquash: true,
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
got := getTagInfo(tt.field)
require.Equal(t, tt.wantName, got.name)
require.Equal(t, tt.wantOmit, got.omitEmpty)
require.Equal(t, tt.wantSquash, got.squash)
})
}
}
func TestEncodeValueError(t *testing.T) {
enc := New(nil)
testValue := reflect.ValueOf("")
testCases := []struct {
encodeFn func(value reflect.Value) (any, error)
wantErr error
}{
{encodeFn: enc.encodeMap, wantErr: &reflect.ValueError{Method: "encodeMap", Kind: reflect.String}},
{encodeFn: enc.encodeStruct, wantErr: &reflect.ValueError{Method: "encodeStruct", Kind: reflect.String}},
{encodeFn: enc.encodeSlice, wantErr: &reflect.ValueError{Method: "encodeSlice", Kind: reflect.String}},
}
for _, tt := range testCases {
got, err := tt.encodeFn(testValue)
require.Error(t, err)
require.Equal(t, tt.wantErr, err)
require.Nil(t, got)
}
}
func TestEncodeNonStringEncodedKey(t *testing.T) {
enc := New(nil)
testCase := []struct {
Test map[string]any
}{
{
Test: map[string]any{
"test": map[TestEmptyStruct]TestSimpleStruct{
{Value: "key"}: {Value: "value"},
},
},
},
}
got, err := enc.Encode(testCase)
require.Error(t, err)
require.ErrorIs(t, err, errNonStringEncodedKey)
require.Nil(t, got)
}
func TestDuplicateKey(t *testing.T) {
enc := New(&EncoderConfig{
EncodeHook: TextMarshalerHookFunc(),
})
testCase := map[TestID]string{
"test": "value",
"test_": "other value",
}
got, err := enc.Encode(testCase)
require.Error(t, err)
require.Nil(t, got)
}
func TestTextMarshalerError(t *testing.T) {
enc := New(&EncoderConfig{
EncodeHook: TextMarshalerHookFunc(),
})
testCase := map[TestID]string{
"error": "value",
}
got, err := enc.Encode(testCase)
require.Error(t, err)
require.Nil(t, got)
}
func TestEncodeStruct(t *testing.T) {
enc := New(&EncoderConfig{
EncodeHook: testHookFunc(),
})
testCase := TestSimpleStruct{
Value: "original",
skipped: "final",
}
got, err := enc.Encode(testCase)
require.NoError(t, err)
require.Equal(t, "final", got)
}
func TestEncodeStructError(t *testing.T) {
enc := New(&EncoderConfig{
EncodeHook: testHookFunc(),
})
wantErr := errors.New("test")
testCase := map[TestSimpleStruct]string{
{err: wantErr}: "value",
}
got, err := enc.Encode(testCase)
require.Error(t, err)
require.ErrorIs(t, err, wantErr)
require.Nil(t, got)
}
func TestEncodeNil(t *testing.T) {
enc := New(nil)
got, err := enc.Encode(nil)
require.NoError(t, err)
require.Nil(t, got)
}
func testHookFunc() mapstructure.DecodeHookFuncValue {
return func(from, _ reflect.Value) (any, error) {
if from.Kind() != reflect.Struct {
return from.Interface(), nil
}
got, ok := from.Interface().(TestSimpleStruct)
if !ok {
return from.Interface(), nil
}
if got.err != nil {
return nil, got.err
}
return got.skipped, nil
}
}
================================================
FILE: confmap/internal/mapstructure/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package mapstructure
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: confmap/internal/marshaloption.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/confmap/internal"
type MarshalOption interface {
apply(*MarshalOptions)
}
// MarshalOptions is used by (*Conf).Marshal to toggle unmarshaling settings.
// It is in the `internal` package so experimental options can be added in xconfmap.
type MarshalOptions struct{}
type MarshalOptionFunc func(*MarshalOptions)
func (fn MarshalOptionFunc) apply(set *MarshalOptions) {
fn(set)
}
================================================
FILE: confmap/internal/merge.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/confmap/internal"
import (
"reflect"
"github.com/gobwas/glob"
"github.com/knadh/koanf/maps"
)
func mergeAppend(src, dest map[string]any) error {
// mergeAppend recursively merges the src map into the dest map (left to right),
// modifying and expanding the dest map in the process.
// This function does not overwrite component lists, and ensures that the
// final value is a name-aware copy of lists from src and dest.
// Compile the globs once
patterns := []string{
"service::extensions",
"service::**::receivers",
"service::**::exporters",
}
var globs []glob.Glob
for _, p := range patterns {
if g, err := glob.Compile(p); err == nil {
globs = append(globs, g)
}
}
// Flatten both source and destination maps
srcFlat, _ := maps.Flatten(src, []string{}, KeyDelimiter)
destFlat, _ := maps.Flatten(dest, []string{}, KeyDelimiter)
for sKey, sVal := range srcFlat {
if !isMatch(sKey, globs) {
continue
}
dVal, dOk := destFlat[sKey]
if !dOk {
continue // Let maps.Merge handle missing keys
}
srcVal := reflect.ValueOf(sVal)
destVal := reflect.ValueOf(dVal)
// Only merge if the value is a slice or array; let maps.Merge handle other types
if srcVal.Kind() == reflect.Slice || srcVal.Kind() == reflect.Array {
srcFlat[sKey] = mergeSlice(srcVal, destVal)
}
}
// Unflatten and merge
mergedSrc := maps.Unflatten(srcFlat, KeyDelimiter)
maps.Merge(mergedSrc, dest)
return nil
}
// isMatch checks if a key matches any glob in the list
func isMatch(key string, globs []glob.Glob) bool {
for _, g := range globs {
if g.Match(key) {
return true
}
}
return false
}
func mergeSlice(src, dest reflect.Value) any {
slice := reflect.MakeSlice(src.Type(), 0, src.Cap()+dest.Cap())
for i := 0; i < dest.Len(); i++ {
slice = reflect.Append(slice, dest.Index(i))
}
for i := 0; i < src.Len(); i++ {
if isPresent(slice, src.Index(i)) {
continue
}
slice = reflect.Append(slice, src.Index(i))
}
return slice.Interface()
}
func isPresent(slice, val reflect.Value) bool {
for i := 0; i < slice.Len(); i++ {
if reflect.DeepEqual(val.Interface(), slice.Index(i).Interface()) {
return true
}
}
return false
}
================================================
FILE: confmap/internal/metadata/generated_feature_gates.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/featuregate"
)
var ConfmapEnableMergeAppendOptionFeatureGate = featuregate.GlobalRegistry().MustRegister(
"confmap.enableMergeAppendOption",
featuregate.StageAlpha,
featuregate.WithRegisterDescription("Combines lists when resolving configs from different sources. This feature gate will not be stabilized 'as is'; the current behavior will remain the default."),
featuregate.WithRegisterReferenceURL("https://github.com/open-telemetry/opentelemetry-collector/issues/8754"),
featuregate.WithRegisterFromVersion("v0.120.0"),
)
var ConfmapNewExpandedValueSanitizerFeatureGate = featuregate.GlobalRegistry().MustRegister(
"confmap.newExpandedValueSanitizer",
featuregate.StageBeta,
featuregate.WithRegisterDescription("Fixes some types of decoding errors where environment variables are parsed as non-string types but assigned to string fields."),
featuregate.WithRegisterReferenceURL("https://github.com/open-telemetry/opentelemetry-collector/pull/14413"),
featuregate.WithRegisterFromVersion("v0.144.0"),
)
================================================
FILE: confmap/internal/testdata/basic_types.yaml
================================================
typed.options:
floating.point.example: 3.14
integer.example: 1234
bool.example: false
string.example: this is a string
nil.example:
================================================
FILE: confmap/internal/testdata/config.yaml
================================================
receivers:
nop:
nop/myreceiver:
processors:
nop:
nop/myprocessor:
exporters:
nop:
nop/myexporter:
extensions:
nop:
nop/myextension:
service:
extensions: [nop]
pipelines:
traces:
receivers: [nop]
processors: [nop]
exporters: [nop]
================================================
FILE: confmap/internal/testdata/config2.yaml
================================================
service:
extensions: [nop2]
pipelines:
traces:
receivers: [nop2]
processors: [nop]
exporters: [nop2]
================================================
FILE: confmap/internal/testdata/embedded_keys.yaml
================================================
typed::options:
floating::point::example: 3.14
integer::example: 1234
bool::example: false
string::example: this is a string
nil::example:
================================================
FILE: confmap/internal/third_party/composehook/compose_hook.go
================================================
// Copyright (c) 2013 Mitchell Hashimoto
// SPDX-License-Identifier: MIT
// This code is a modified version of https://github.com/go-viper/mapstructure
package composehook // import "go.opentelemetry.io/collector/confmap/internal/third_party/composehook"
import (
"errors"
"reflect"
"github.com/go-viper/mapstructure/v2"
)
// typedDecodeHook takes a raw DecodeHookFunc (an any) and turns
// it into the proper DecodeHookFunc type, such as DecodeHookFuncType.
func typedDecodeHook(h mapstructure.DecodeHookFunc) mapstructure.DecodeHookFunc {
// Create variables here so we can reference them with the reflect pkg
var f1 mapstructure.DecodeHookFuncType
var f2 mapstructure.DecodeHookFuncKind
var f3 mapstructure.DecodeHookFuncValue
// Fill in the variables into this interface and the rest is done
// automatically using the reflect package.
potential := []any{f3, f1, f2}
v := reflect.ValueOf(h)
vt := v.Type()
for _, raw := range potential {
pt := reflect.ValueOf(raw).Type()
if vt.ConvertibleTo(pt) {
return v.Convert(pt).Interface()
}
}
return nil
}
// cachedDecodeHook takes a raw DecodeHookFunc (an any) and turns
// it into a closure to be used directly
// if the type fails to convert we return a closure always erroring to keep the previous behavior
func cachedDecodeHook(raw mapstructure.DecodeHookFunc) func(reflect.Value, reflect.Value) (any, error) {
switch f := typedDecodeHook(raw).(type) {
case mapstructure.DecodeHookFuncType:
return func(from, to reflect.Value) (any, error) {
// CHANGE FROM UPSTREAM: check if from is valid and return nil if not
if !from.IsValid() {
return nil, nil
}
return f(from.Type(), to.Type(), from.Interface())
}
case mapstructure.DecodeHookFuncKind:
return func(from, to reflect.Value) (any, error) {
// CHANGE FROM UPSTREAM: check if from is valid and return nil if not
if !from.IsValid() {
return nil, nil
}
return f(from.Kind(), to.Kind(), from.Interface())
}
case mapstructure.DecodeHookFuncValue:
return func(from, to reflect.Value) (any, error) {
return f(from, to)
}
default:
return func(reflect.Value, reflect.Value) (any, error) {
return nil, errors.New("invalid decode hook signature")
}
}
}
// ComposeDecodeHookFunc creates a single DecodeHookFunc that
// automatically composes multiple DecodeHookFuncs.
//
// The composed funcs are called in order, with the result of the
// previous transformation.
//
// This is a copy of [mapstructure.ComposeDecodeHookFunc] but with
// validation added.
func ComposeDecodeHookFunc(fs ...mapstructure.DecodeHookFunc) mapstructure.DecodeHookFunc {
cached := make([]func(reflect.Value, reflect.Value) (any, error), 0, len(fs))
for _, f := range fs {
cached = append(cached, cachedDecodeHook(f))
}
return func(f, t reflect.Value) (any, error) {
var err error
// CHANGE FROM UPSTREAM: check if f is valid before calling f.Interface()
var data any
if f.IsValid() {
data = f.Interface()
}
newFrom := f
for _, c := range cached {
data, err = c(newFrom, t)
if err != nil {
return nil, err
}
newFrom = reflect.ValueOf(data)
}
return data, nil
}
}
================================================
FILE: confmap/internal/unmarshaloption.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/confmap/internal"
type UnmarshalOption interface {
apply(*UnmarshalOptions)
}
// UnmarshalOptions is used by (*Conf).Unmarshal to toggle unmarshaling settings.
// It is in the `internal` package so experimental options can be added in xconfmap.
type UnmarshalOptions struct {
IgnoreUnused bool
ForceUnmarshaler bool
}
type UnmarshalOptionFunc func(*UnmarshalOptions)
func (fn UnmarshalOptionFunc) apply(set *UnmarshalOptions) {
fn(set)
}
================================================
FILE: confmap/metadata.yaml
================================================
type: confmap
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
codeowners:
active:
- mx-psi
- evan-bradley
class: pkg
stability:
stable: [logs, metrics, traces]
feature_gates:
- id: confmap.enableMergeAppendOption
description: "Combines lists when resolving configs from different sources. This feature gate will not be stabilized 'as is'; the current behavior will remain the default."
stage: alpha
from_version: 'v0.120.0'
reference_url: 'https://github.com/open-telemetry/opentelemetry-collector/issues/8754'
- id: confmap.newExpandedValueSanitizer
description: 'Fixes some types of decoding errors where environment variables are parsed as non-string types but assigned to string fields.'
stage: beta
from_version: 'v0.144.0'
reference_url: 'https://github.com/open-telemetry/opentelemetry-collector/pull/14413'
================================================
FILE: confmap/provider/envprovider/Makefile
================================================
include ../../../Makefile.Common
================================================
FILE: confmap/provider/envprovider/README.md
================================================
# Environment Variable Provider
| Status | |
| ------------- |-----------|
| Stability | [stable] |
| Distributions | [core], [contrib], [k8s], [otlp] |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Aprovider%2Fenvprovider) [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aclosed+is%3Aissue+label%3Aprovider%2Fenvprovider) |
[stable]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#stable
[core]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
[k8s]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-k8s
[otlp]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-otlp
## Usage
The scheme for this provider is `env`. Usage looks like the following:
```text
env:NAME_OF_ENVIRONMENT_VARIABLE
```
To use default values when the environment variable has not been set, you can
include a suffix to specify it:
```text
env:NAME_OF_ENVIRONMENT_VARIABLE:-default_value
```
Environment variables must match the following regular expression. That is, they
must be at least one character, start with a letter or underscore, and can only
include letters, numbers, and underscores.
```text
^[a-zA-Z_][a-zA-Z0-9_]*$
```
================================================
FILE: confmap/provider/envprovider/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package envprovider
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: confmap/provider/envprovider/go.mod
================================================
module go.opentelemetry.io/collector/confmap/provider/envprovider
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/confmap v1.54.0
go.uber.org/goleak v1.3.0
go.uber.org/zap v1.27.1
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/confmap => ../../
replace go.opentelemetry.io/collector/featuregate => ../../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../../internal/testutil
================================================
FILE: confmap/provider/envprovider/go.sum
================================================
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/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: confmap/provider/envprovider/metadata.yaml
================================================
type: env
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: provider
stability:
stable: [provider]
distributions: [core, contrib, k8s, otlp]
================================================
FILE: confmap/provider/envprovider/provider.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:generate mdatagen metadata.yaml
package envprovider // import "go.opentelemetry.io/collector/confmap/provider/envprovider"
import (
"context"
"fmt"
"os"
"strings"
"go.uber.org/zap"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/internal/envvar"
)
const (
schemeName = "env"
)
type provider struct {
logger *zap.Logger
}
// NewFactory returns a factory for a confmap.Provider that reads the configuration from the given environment variable.
//
// This Provider supports "env" scheme, and can be called with a selector:
// `env:NAME_OF_ENVIRONMENT_VARIABLE`
//
// A default value for unset variable can be provided after :- suffix, for example:
// `env:NAME_OF_ENVIRONMENT_VARIABLE:-default_value`
//
// See also: https://opentelemetry.io/docs/specs/otel/configuration/data-model/#environment-variable-substitution
func NewFactory() confmap.ProviderFactory {
return confmap.NewProviderFactory(newProvider)
}
func newProvider(ps confmap.ProviderSettings) confmap.Provider {
return &provider{
logger: ps.Logger,
}
}
func (emp *provider) Retrieve(_ context.Context, uri string, _ confmap.WatcherFunc) (*confmap.Retrieved, error) {
if !strings.HasPrefix(uri, schemeName+":") {
return nil, fmt.Errorf("%q uri is not supported by %q provider", uri, schemeName)
}
envVarName, defaultValuePtr := parseEnvVarURI(uri[len(schemeName)+1:])
if !envvar.ValidationRegexp.MatchString(envVarName) {
return nil, fmt.Errorf("environment variable %q has invalid name: must match regex %s", envVarName, envvar.ValidationPattern)
}
val, exists := os.LookupEnv(envVarName)
if !exists {
if defaultValuePtr != nil {
val = *defaultValuePtr
} else {
emp.logger.Warn("Configuration references unset environment variable", zap.String("name", envVarName))
}
} else if val == "" {
emp.logger.Info("Configuration references empty environment variable", zap.String("name", envVarName))
}
return confmap.NewRetrievedFromYAML([]byte(val))
}
func (*provider) Scheme() string {
return schemeName
}
func (*provider) Shutdown(context.Context) error {
return nil
}
// returns (var name, default value)
func parseEnvVarURI(uri string) (string, *string) {
const defaultSuffix = ":-"
name, defaultValue, hasDefault := strings.Cut(uri, defaultSuffix)
if hasDefault {
return name, &defaultValue
}
return uri, nil
}
================================================
FILE: confmap/provider/envprovider/provider_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package envprovider
import (
"context"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/confmaptest"
"go.opentelemetry.io/collector/confmap/internal/envvar"
)
const envSchemePrefix = schemeName + ":"
const validYAML = `
processors:
testprocessor:
exporters:
otlp_grpc:
endpoint: "localhost:4317"
`
func TestValidateProviderScheme(t *testing.T) {
assert.NoError(t, confmaptest.ValidateProviderScheme(createProvider()))
}
func TestEmptyName(t *testing.T) {
env := createProvider()
_, err := env.Retrieve(context.Background(), "", nil)
require.Error(t, err)
assert.NoError(t, env.Shutdown(context.Background()))
}
func TestUnsupportedScheme(t *testing.T) {
env := createProvider()
_, err := env.Retrieve(context.Background(), "https://", nil)
require.Error(t, err)
assert.NoError(t, env.Shutdown(context.Background()))
}
func TestInvalidYAML(t *testing.T) {
const envName = "invalid_yaml"
t.Setenv(envName, "[invalid,")
env := createProvider()
ret, err := env.Retrieve(context.Background(), envSchemePrefix+envName, nil)
require.NoError(t, err)
raw, err := ret.AsRaw()
require.NoError(t, err)
assert.IsType(t, "", raw)
assert.NoError(t, env.Shutdown(context.Background()))
}
func TestEnv(t *testing.T) {
const envName = "default_config"
t.Setenv(envName, validYAML)
env := createProvider()
ret, err := env.Retrieve(context.Background(), envSchemePrefix+envName, nil)
require.NoError(t, err)
retMap, err := ret.AsConf()
require.NoError(t, err)
expectedMap := confmap.NewFromStringMap(map[string]any{
"processors::testprocessor": nil,
"exporters::otlp_grpc::endpoint": "localhost:4317",
})
assert.Equal(t, expectedMap.ToStringMap(), retMap.ToStringMap())
assert.NoError(t, env.Shutdown(context.Background()))
}
func TestEnvWithLogger(t *testing.T) {
const envName = "default_config"
t.Setenv(envName, validYAML)
core, ol := observer.New(zap.WarnLevel)
logger := zap.New(core)
env := NewFactory().Create(confmap.ProviderSettings{Logger: logger})
ret, err := env.Retrieve(context.Background(), envSchemePrefix+envName, nil)
require.NoError(t, err)
retMap, err := ret.AsConf()
require.NoError(t, err)
expectedMap := confmap.NewFromStringMap(map[string]any{
"processors::testprocessor": nil,
"exporters::otlp_grpc::endpoint": "localhost:4317",
})
assert.Equal(t, expectedMap.ToStringMap(), retMap.ToStringMap())
require.NoError(t, env.Shutdown(context.Background()))
assert.Equal(t, 0, ol.Len())
}
func TestUnsetEnvWithLoggerWarn(t *testing.T) {
const envName = "default_config"
core, ol := observer.New(zap.WarnLevel)
logger := zap.New(core)
env := NewFactory().Create(confmap.ProviderSettings{Logger: logger})
ret, err := env.Retrieve(context.Background(), envSchemePrefix+envName, nil)
require.NoError(t, err)
retMap, err := ret.AsConf()
require.NoError(t, err)
expectedMap := confmap.NewFromStringMap(map[string]any{})
assert.Equal(t, expectedMap.ToStringMap(), retMap.ToStringMap())
require.NoError(t, env.Shutdown(context.Background()))
assert.Equal(t, 1, ol.Len())
logLine := ol.All()[0]
assert.Equal(t, "Configuration references unset environment variable", logLine.Message)
assert.Equal(t, zap.WarnLevel, logLine.Level)
assert.Equal(t, envName, logLine.Context[0].String)
}
func TestEnvVarNameRestriction(t *testing.T) {
const envName = "default%config"
t.Setenv(envName, validYAML)
env := createProvider()
ret, err := env.Retrieve(context.Background(), envSchemePrefix+envName, nil)
assert.Equal(t, err, fmt.Errorf("environment variable \"default%%config\" has invalid name: must match regex %s", envvar.ValidationRegexp))
require.NoError(t, env.Shutdown(context.Background()))
assert.Nil(t, ret)
}
func TestEmptyEnvWithLoggerWarn(t *testing.T) {
const envName = "default_config"
t.Setenv(envName, "")
core, ol := observer.New(zap.InfoLevel)
logger := zap.New(core)
env := NewFactory().Create(confmap.ProviderSettings{Logger: logger})
ret, err := env.Retrieve(context.Background(), envSchemePrefix+envName, nil)
require.NoError(t, err)
retMap, err := ret.AsConf()
require.NoError(t, err)
expectedMap := confmap.NewFromStringMap(map[string]any{})
assert.Equal(t, expectedMap.ToStringMap(), retMap.ToStringMap())
require.NoError(t, env.Shutdown(context.Background()))
assert.Equal(t, 1, ol.Len())
logLine := ol.All()[0]
assert.Equal(t, "Configuration references empty environment variable", logLine.Message)
assert.Equal(t, zap.InfoLevel, logLine.Level)
assert.Equal(t, envName, logLine.Context[0].String)
}
func TestEnvWithDefaultValue(t *testing.T) {
env := createProvider()
tests := []struct {
name string
unset bool
value string
uri string
expectedVal string
expectedErr string
}{
{name: "unset", unset: true, uri: "env:MY_VAR:-default % value", expectedVal: "default % value"},
{name: "unset2", unset: true, uri: "env:MY_VAR:-", expectedVal: ""}, // empty default still applies
{name: "empty", value: "", uri: "env:MY_VAR:-foo", expectedVal: ""},
{name: "not empty", value: "value", uri: "env:MY_VAR:-", expectedVal: "value"},
{name: "syntax1", unset: true, uri: "env:-MY_VAR", expectedErr: "invalid name"},
{name: "syntax2", unset: true, uri: "env:MY_VAR:-test:-test", expectedVal: "test:-test"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if !tt.unset {
t.Setenv("MY_VAR", tt.value)
}
ret, err := env.Retrieve(context.Background(), tt.uri, nil)
if tt.expectedErr != "" {
require.ErrorContains(t, err, tt.expectedErr)
return
}
require.NoError(t, err)
str, err := ret.AsString()
require.NoError(t, err)
assert.Equal(t, tt.expectedVal, str)
})
}
assert.NoError(t, env.Shutdown(context.Background()))
}
func createProvider() confmap.Provider {
return NewFactory().Create(confmaptest.NewNopProviderSettings())
}
================================================
FILE: confmap/provider/fileprovider/Makefile
================================================
include ../../../Makefile.Common
================================================
FILE: confmap/provider/fileprovider/README.md
================================================
# File Provider
| Status | |
| ------------- |-----------|
| Stability | [stable] |
| Distributions | [core], [contrib], [k8s], [otlp] |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Aprovider%2Ffileprovider) [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aclosed+is%3Aissue+label%3Aprovider%2Ffileprovider) |
[stable]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#stable
[core]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
[k8s]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-k8s
[otlp]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-otlp
## Overview
The File Provider takes paths to files and reads their contents as YAML to provide configuration to the Collector.
## Usage
The scheme for this provider is `file`. Usage looks like the following:
```text
file:/path/to/file.yaml
```
================================================
FILE: confmap/provider/fileprovider/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package fileprovider
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: confmap/provider/fileprovider/go.mod
================================================
module go.opentelemetry.io/collector/confmap/provider/fileprovider
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/confmap v1.54.0
go.uber.org/goleak v1.3.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/confmap => ../../
replace go.opentelemetry.io/collector/featuregate => ../../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../../internal/testutil
================================================
FILE: confmap/provider/fileprovider/go.sum
================================================
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/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: confmap/provider/fileprovider/metadata.yaml
================================================
type: file
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: provider
stability:
stable: [provider]
distributions: [core, contrib, k8s, otlp]
================================================
FILE: confmap/provider/fileprovider/provider.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:generate mdatagen metadata.yaml
package fileprovider // import "go.opentelemetry.io/collector/confmap/provider/fileprovider"
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"go.opentelemetry.io/collector/confmap"
)
const schemeName = "file"
type provider struct{}
// NewFactory returns a factory for a confmap.Provider that reads the configuration from a file.
//
// This Provider supports "file" scheme, and can be called with a "uri" that follows:
//
// file-uri = "file:" local-path
// local-path = [ drive-letter ] file-path
// drive-letter = ALPHA ":"
//
// The "file-path" can be relative or absolute, and it can be any OS supported format.
//
// Examples:
// `file:path/to/file` - relative path (unix, windows)
// `file:/path/to/file` - absolute path (unix, windows)
// `file:c:/path/to/file` - absolute path including drive-letter (windows)
// `file:c:\path\to\file` - absolute path including drive-letter (windows)
func NewFactory() confmap.ProviderFactory {
return confmap.NewProviderFactory(newProvider)
}
func newProvider(confmap.ProviderSettings) confmap.Provider {
return &provider{}
}
func (fmp *provider) Retrieve(_ context.Context, uri string, _ confmap.WatcherFunc) (*confmap.Retrieved, error) {
if !strings.HasPrefix(uri, schemeName+":") {
return nil, fmt.Errorf("%q uri is not supported by %q provider", uri, schemeName)
}
// Clean the path before using it.
content, err := os.ReadFile(filepath.Clean(uri[len(schemeName)+1:]))
if err != nil {
return nil, fmt.Errorf("unable to read the file %v: %w", uri, err)
}
return confmap.NewRetrievedFromYAML(content)
}
func (*provider) Scheme() string {
return schemeName
}
func (*provider) Shutdown(context.Context) error {
return nil
}
================================================
FILE: confmap/provider/fileprovider/provider_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package fileprovider
import (
"context"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/confmaptest"
)
const fileSchemePrefix = schemeName + ":"
func TestValidateProviderScheme(t *testing.T) {
assert.NoError(t, confmaptest.ValidateProviderScheme(createProvider()))
}
func TestEmptyName(t *testing.T) {
fp := createProvider()
_, err := fp.Retrieve(context.Background(), "", nil)
require.Error(t, err)
require.NoError(t, fp.Shutdown(context.Background()))
}
func TestUnsupportedScheme(t *testing.T) {
fp := createProvider()
_, err := fp.Retrieve(context.Background(), "https://", nil)
require.Error(t, err)
assert.NoError(t, fp.Shutdown(context.Background()))
}
func TestNonExistent(t *testing.T) {
fp := createProvider()
_, err := fp.Retrieve(context.Background(), fileSchemePrefix+filepath.Join("testdata", "non-existent.yaml"), nil)
require.Error(t, err)
_, err = fp.Retrieve(context.Background(), fileSchemePrefix+absolutePath(t, filepath.Join("testdata", "non-existent.yaml")), nil)
require.Error(t, err)
require.NoError(t, fp.Shutdown(context.Background()))
}
func TestInvalidYAML(t *testing.T) {
fp := createProvider()
ret, err := fp.Retrieve(context.Background(), fileSchemePrefix+filepath.Join("testdata", "invalid-yaml.yaml"), nil)
require.NoError(t, err)
raw, err := ret.AsRaw()
require.NoError(t, err)
assert.IsType(t, "", raw)
ret, err = fp.Retrieve(context.Background(), fileSchemePrefix+absolutePath(t, filepath.Join("testdata", "invalid-yaml.yaml")), nil)
require.NoError(t, err)
raw, err = ret.AsRaw()
require.NoError(t, err)
assert.IsType(t, "", raw)
require.NoError(t, fp.Shutdown(context.Background()))
}
func TestRelativePath(t *testing.T) {
fp := createProvider()
ret, err := fp.Retrieve(context.Background(), fileSchemePrefix+filepath.Join("testdata", "default-config.yaml"), nil)
require.NoError(t, err)
retMap, err := ret.AsConf()
require.NoError(t, err)
expectedMap := confmap.NewFromStringMap(map[string]any{
"processors::testprocessor": nil,
"exporters::otlp_grpc::endpoint": "localhost:4317",
})
assert.Equal(t, expectedMap, retMap)
assert.NoError(t, fp.Shutdown(context.Background()))
}
func TestAbsolutePath(t *testing.T) {
fp := createProvider()
ret, err := fp.Retrieve(context.Background(), fileSchemePrefix+absolutePath(t, filepath.Join("testdata", "default-config.yaml")), nil)
require.NoError(t, err)
retMap, err := ret.AsConf()
require.NoError(t, err)
expectedMap := confmap.NewFromStringMap(map[string]any{
"processors::testprocessor": nil,
"exporters::otlp_grpc::endpoint": "localhost:4317",
})
assert.Equal(t, expectedMap, retMap)
assert.NoError(t, fp.Shutdown(context.Background()))
}
func absolutePath(t *testing.T, relativePath string) string {
dir, err := os.Getwd()
require.NoError(t, err)
return filepath.Join(dir, relativePath)
}
func createProvider() confmap.Provider {
return NewFactory().Create(confmaptest.NewNopProviderSettings())
}
================================================
FILE: confmap/provider/fileprovider/testdata/default-config.yaml
================================================
processors:
testprocessor:
exporters:
otlp_grpc:
endpoint: "localhost:4317"
================================================
FILE: confmap/provider/fileprovider/testdata/invalid-yaml.yaml
================================================
[invalid,
================================================
FILE: confmap/provider/httpprovider/Makefile
================================================
include ../../../Makefile.Common
================================================
FILE: confmap/provider/httpprovider/README.md
================================================
# HTTP Provider
| Status | |
| ------------- |-----------|
| Stability | [stable] |
| Distributions | [core], [contrib], [k8s] |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Aprovider%2Fhttpprovider) [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aclosed+is%3Aissue+label%3Aprovider%2Fhttpprovider) |
[stable]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#stable
[core]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
[k8s]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-k8s
## Overview
The HTTP Provider takes an HTTP URI to a file and reads its contents as YAML to provide configuration to the Collector.
For HTTPS endpoints, please see the [HTTPS provider](../httpsprovider/README.md).
## Usage
The scheme for this provider is `http`. Usage looks like the following passed to the Collector's command line invocation:
```text
--config=http://example.com/config.yaml
```
================================================
FILE: confmap/provider/httpprovider/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package httpprovider
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: confmap/provider/httpprovider/go.mod
================================================
module go.opentelemetry.io/collector/confmap/provider/httpprovider
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/confmap v1.54.0
go.uber.org/goleak v1.3.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/confmap => ../../
replace go.opentelemetry.io/collector/featuregate => ../../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../../internal/testutil
================================================
FILE: confmap/provider/httpprovider/go.sum
================================================
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/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: confmap/provider/httpprovider/metadata.yaml
================================================
type: http
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: provider
stability:
stable: [provider]
distributions: [core, contrib, k8s]
================================================
FILE: confmap/provider/httpprovider/provider.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:generate mdatagen metadata.yaml
package httpprovider // import "go.opentelemetry.io/collector/confmap/provider/httpprovider"
import (
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/provider/internal/configurablehttpprovider"
)
// NewFactory returns a factory for a confmap.Provider that reads the configuration from a http server.
//
// This Provider supports "http" scheme.
//
// One example for HTTP URI is: http://localhost:3333/getConfig
func NewFactory() confmap.ProviderFactory {
return confmap.NewProviderFactory(newProvider)
}
func newProvider(set confmap.ProviderSettings) confmap.Provider {
return configurablehttpprovider.New(configurablehttpprovider.HTTPScheme, set)
}
================================================
FILE: confmap/provider/httpprovider/provider_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package httpprovider
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/confmap/confmaptest"
)
func TestSupportedScheme(t *testing.T) {
fp := NewFactory().Create(confmaptest.NewNopProviderSettings())
assert.Equal(t, "http", fp.Scheme())
require.NoError(t, fp.Shutdown(context.Background()))
}
================================================
FILE: confmap/provider/httpsprovider/Makefile
================================================
include ../../../Makefile.Common
================================================
FILE: confmap/provider/httpsprovider/README.md
================================================
# HTTPS Provider
| Status | |
| ------------- |-----------|
| Stability | [stable] |
| Distributions | [core], [contrib], [k8s] |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Aprovider%2Fhttpsprovider) [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aclosed+is%3Aissue+label%3Aprovider%2Fhttpsprovider) |
[stable]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#stable
[core]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
[k8s]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-k8s
## Overview
The HTTPS Provider takes an HTTPS URI to a file and reads its contents as YAML
to provide configuration to the Collector. The validity of the certificate of
the HTTPS endpoint is verified when making the connection.
## Usage
The scheme for this provider is `https`. Usage looks like the following passed
to the Collector's command line invocation:
```text
--config=https://example.com/config.yaml
```
### Notes
The provider currently only supports communicating with servers whose
certificate can be verified using the root CA certificates installed in the
system. The process of adding more root CA certificates to the system is
Operating System-dependent. For Linux, please refer to the `update-ca-trust`
command.
================================================
FILE: confmap/provider/httpsprovider/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package httpsprovider
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: confmap/provider/httpsprovider/go.mod
================================================
module go.opentelemetry.io/collector/confmap/provider/httpsprovider
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/confmap v1.54.0
go.uber.org/goleak v1.3.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/confmap => ../../
replace go.opentelemetry.io/collector/featuregate => ../../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../../internal/testutil
================================================
FILE: confmap/provider/httpsprovider/go.sum
================================================
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/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: confmap/provider/httpsprovider/metadata.yaml
================================================
type: https
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: provider
stability:
stable: [provider]
distributions: [core, contrib, k8s]
================================================
FILE: confmap/provider/httpsprovider/provider.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:generate mdatagen metadata.yaml
package httpsprovider // import "go.opentelemetry.io/collector/confmap/provider/httpsprovider"
import (
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/provider/internal/configurablehttpprovider"
)
// NewFactory returns a factory for a confmap.Provider that reads the configuration from a https server.
//
// This Provider supports "https" scheme. One example of an HTTPS URI is: https://localhost:3333/getConfig
//
// To add extra CA certificates you need to install certificates in the system pool. This procedure is operating system
// dependent. E.g.: on Linux please refer to the `update-ca-trust` command.
func NewFactory() confmap.ProviderFactory {
return confmap.NewProviderFactory(newProvider)
}
func newProvider(set confmap.ProviderSettings) confmap.Provider {
return configurablehttpprovider.New(configurablehttpprovider.HTTPSScheme, set)
}
================================================
FILE: confmap/provider/httpsprovider/provider_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package httpsprovider
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/confmap/confmaptest"
)
func TestSupportedScheme(t *testing.T) {
fp := NewFactory().Create(confmaptest.NewNopProviderSettings())
assert.Equal(t, "https", fp.Scheme())
}
================================================
FILE: confmap/provider/internal/configurablehttpprovider/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configurablehttpprovider
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: confmap/provider/internal/configurablehttpprovider/provider.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configurablehttpprovider // import "go.opentelemetry.io/collector/confmap/provider/internal/configurablehttpprovider"
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"go.opentelemetry.io/collector/confmap"
)
type SchemeType string
const (
HTTPScheme SchemeType = "http"
HTTPSScheme SchemeType = "https"
)
type provider struct {
scheme SchemeType
caCertPath string // Used for tests
insecureSkipVerify bool // Used for tests
}
// New returns a new provider that reads the configuration from http server using the configured transport mechanism
// depending on the selected scheme.
// There are two types of transport supported: PlainText (HTTPScheme) and TLS (HTTPSScheme).
//
// One example for http-uri: http://localhost:3333/getConfig
// One example for https-uri: https://localhost:3333/getConfig
// This is used by the http and https external implementations.
func New(scheme SchemeType, _ confmap.ProviderSettings) confmap.Provider {
return &provider{scheme: scheme}
}
// Create the client based on the type of scheme that was selected.
func (fmp *provider) createClient() (*http.Client, error) {
switch fmp.scheme {
case HTTPScheme:
return &http.Client{}, nil
case HTTPSScheme:
pool, err := x509.SystemCertPool()
if err != nil {
return nil, fmt.Errorf("unable to create a cert pool: %w", err)
}
if fmp.caCertPath != "" {
cert, err := os.ReadFile(filepath.Clean(fmp.caCertPath))
if err != nil {
return nil, fmt.Errorf("unable to read CA from %q URI: %w", fmp.caCertPath, err)
}
if ok := pool.AppendCertsFromPEM(cert); !ok {
return nil, fmt.Errorf("unable to add CA from uri: %s into the cert pool", fmp.caCertPath)
}
}
return &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: fmp.insecureSkipVerify,
RootCAs: pool,
},
},
}, nil
default:
return nil, fmt.Errorf("invalid scheme type: %s", fmp.scheme)
}
}
func (fmp *provider) Retrieve(_ context.Context, uri string, _ confmap.WatcherFunc) (*confmap.Retrieved, error) {
if !strings.HasPrefix(uri, string(fmp.scheme)+":") {
return nil, fmt.Errorf("%q uri is not supported by %q provider", uri, string(fmp.scheme))
}
if _, err := url.ParseRequestURI(uri); err != nil {
return nil, fmt.Errorf("invalid uri %q: %w", uri, err)
}
client, err := fmp.createClient()
if err != nil {
return nil, fmt.Errorf("unable to configure http transport layer: %w", err)
}
// send a HTTP GET request
resp, err := client.Get(uri)
if err != nil {
return nil, fmt.Errorf("unable to download the file via HTTP GET for uri %q: %w ", uri, err)
}
defer resp.Body.Close()
// check the HTTP status code
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to load resource from uri %q. status code: %d", uri, resp.StatusCode)
}
// read the response body
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("fail to read the response body from uri %q: %w", uri, err)
}
return confmap.NewRetrievedFromYAML(body)
}
func (fmp *provider) Scheme() string {
return string(fmp.scheme)
}
func (*provider) Shutdown(context.Context) error {
return nil
}
================================================
FILE: confmap/provider/internal/configurablehttpprovider/provider_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configurablehttpprovider
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"net/http"
"net/http/httptest"
"net/url"
"os"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/confmaptest"
"go.opentelemetry.io/collector/internal/testutil"
)
func newConfigurableHTTPProvider(scheme SchemeType, set confmap.ProviderSettings) *provider {
return New(scheme, set).(*provider)
}
func answerGet(w http.ResponseWriter, _ *http.Request) {
f, err := os.ReadFile("./testdata/otel-config.yaml")
if err != nil {
w.WriteHeader(http.StatusNotFound)
_, innerErr := w.Write([]byte("Cannot find the config file"))
if innerErr != nil {
fmt.Println("Write failed: ", innerErr)
}
return
}
w.WriteHeader(http.StatusOK)
_, err = w.Write(f)
if err != nil {
fmt.Println("Write failed: ", err)
}
}
// Generate a self signed certificate specific for the tests. Based on
// https://go.dev/src/crypto/tls/generate_cert.go
func generateCertificate(t *testing.T, hostname string) (cert, key string, err error) {
testutil.SkipIfFIPSOnly(t, "x509.CreateCertificate uses SHA-1")
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return "", "", fmt.Errorf("Failed to generate private key: %w", err)
}
keyUsage := x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign
notBefore := time.Now()
notAfter := notBefore.Add(time.Hour * 12)
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return "", "", fmt.Errorf("Failed to generate serial number: %w", err)
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"Httpprovider Co"},
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: keyUsage,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
IsCA: true,
DNSNames: []string{hostname},
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
return "", "", fmt.Errorf("Failed to create certificate: %w", err)
}
tempDir := t.TempDir()
certOut, err := os.CreateTemp(tempDir, "cert*.pem")
if err != nil {
return "", "", fmt.Errorf("Failed to open cert.pem for writing: %w", err)
}
defer certOut.Close()
if err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
return "", "", fmt.Errorf("Failed to write data to cert.pem: %w", err)
}
keyOut, err := os.CreateTemp(tempDir, "key*.pem")
if err != nil {
return "", "", fmt.Errorf("Failed to open key.pem for writing: %w", err)
}
defer keyOut.Close()
privBytes, err := x509.MarshalPKCS8PrivateKey(priv)
if err != nil {
return "", "", fmt.Errorf("Unable to marshal private key: %w", err)
}
if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil {
return "", "", fmt.Errorf("Failed to write data to key.pem: %w", err)
}
return certOut.Name(), keyOut.Name(), nil
}
func TestFunctionalityDownloadFileHTTP(t *testing.T) {
fp := newConfigurableHTTPProvider(HTTPScheme, confmaptest.NewNopProviderSettings())
ts := httptest.NewServer(http.HandlerFunc(answerGet))
defer ts.Close()
_, err := fp.Retrieve(context.Background(), ts.URL, nil)
assert.NoError(t, err)
assert.NoError(t, fp.Shutdown(context.Background()))
}
func TestFunctionalityDownloadFileHTTPS(t *testing.T) {
certPath, keyPath, err := generateCertificate(t, "localhost")
require.NoError(t, err)
invalidCert, err := os.CreateTemp(t.TempDir(), "cert*.crt")
defer func() { require.NoError(t, invalidCert.Close()) }()
require.NoError(t, err)
_, err = invalidCert.Write([]byte{0, 1, 2})
require.NoError(t, err)
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
require.NoError(t, err)
ts := httptest.NewUnstartedServer(http.HandlerFunc(answerGet))
ts.TLS = &tls.Config{Certificates: []tls.Certificate{cert}}
ts.StartTLS()
defer ts.Close()
tests := []struct {
name string
certPath string
hostName string
useCertificate bool
skipHostnameValidation bool
shouldError bool
}{
{
name: "Test valid certificate and name",
certPath: certPath,
hostName: "localhost",
useCertificate: true,
skipHostnameValidation: false,
shouldError: false,
},
{
name: "Test valid certificate with invalid name",
certPath: certPath,
hostName: "127.0.0.1",
useCertificate: true,
skipHostnameValidation: false,
shouldError: true,
},
{
name: "Test valid certificate with invalid name, skip validation",
certPath: certPath,
hostName: "127.0.0.1",
useCertificate: true,
skipHostnameValidation: true,
shouldError: false,
},
{
name: "Test no certificate should fail",
certPath: certPath,
hostName: "localhost",
useCertificate: false,
skipHostnameValidation: false,
shouldError: true,
},
{
name: "Test invalid cert",
certPath: invalidCert.Name(),
hostName: "localhost",
useCertificate: true,
skipHostnameValidation: false,
shouldError: true,
},
{
name: "Test no cert",
certPath: "no_certificate",
hostName: "localhost",
useCertificate: true,
skipHostnameValidation: false,
shouldError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fp := newConfigurableHTTPProvider(HTTPSScheme, confmaptest.NewNopProviderSettings())
// Parse url of the test server to get the port number.
tsURL, err := url.Parse(ts.URL)
require.NoError(t, err)
if tt.useCertificate {
fp.caCertPath = tt.certPath
}
fp.insecureSkipVerify = tt.skipHostnameValidation
_, err = fp.Retrieve(context.Background(), fmt.Sprintf("https://%s:%s", tt.hostName, tsURL.Port()), nil)
if tt.shouldError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
func TestUnsupportedScheme(t *testing.T) {
fp := New(HTTPScheme, confmaptest.NewNopProviderSettings())
_, err := fp.Retrieve(context.Background(), "https://...", nil)
require.Error(t, err)
require.NoError(t, fp.Shutdown(context.Background()))
fp = New(HTTPSScheme, confmaptest.NewNopProviderSettings())
_, err = fp.Retrieve(context.Background(), "http://...", nil)
require.Error(t, err)
assert.NoError(t, fp.Shutdown(context.Background()))
}
func TestEmptyURI(t *testing.T) {
fp := New(HTTPScheme, confmaptest.NewNopProviderSettings())
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusBadRequest)
}))
defer ts.Close()
_, err := fp.Retrieve(context.Background(), ts.URL, nil)
require.Error(t, err)
require.NoError(t, fp.Shutdown(context.Background()))
}
func TestRetrieveFromShutdownServer(t *testing.T) {
fp := New(HTTPScheme, confmaptest.NewNopProviderSettings())
ts := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}))
ts.Close()
_, err := fp.Retrieve(context.Background(), ts.URL, nil)
require.Error(t, err)
require.NoError(t, fp.Shutdown(context.Background()))
}
func TestNonExistent(t *testing.T) {
fp := New(HTTPScheme, confmaptest.NewNopProviderSettings())
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusNotFound)
}))
defer ts.Close()
_, err := fp.Retrieve(context.Background(), ts.URL, nil)
require.Error(t, err)
require.NoError(t, fp.Shutdown(context.Background()))
}
func TestInvalidYAML(t *testing.T) {
fp := New(HTTPScheme, confmaptest.NewNopProviderSettings())
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte("wrong : ["))
if err != nil {
fmt.Println("Write failed: ", err)
}
}))
defer ts.Close()
ret, err := fp.Retrieve(context.Background(), ts.URL, nil)
require.NoError(t, err)
raw, err := ret.AsRaw()
require.NoError(t, err)
assert.Equal(t, "wrong : [", raw)
require.NoError(t, fp.Shutdown(context.Background()))
}
func TestScheme(t *testing.T) {
fp := New(HTTPScheme, confmaptest.NewNopProviderSettings())
assert.Equal(t, "http", fp.Scheme())
require.NoError(t, fp.Shutdown(context.Background()))
}
func TestValidateProviderScheme(t *testing.T) {
assert.NoError(t, confmaptest.ValidateProviderScheme(New(HTTPScheme, confmaptest.NewNopProviderSettings())))
}
func TestInvalidURI(t *testing.T) {
fp := New(HTTPScheme, confmaptest.NewNopProviderSettings())
tests := []struct {
uri string
err string
}{
{
uri: "foo://..",
err: "uri is not supported by \"http\" provider",
},
{
uri: "http://",
err: "no Host in request URL",
},
{
uri: "http://{}",
err: "invalid character \"{\" in host name",
},
}
for _, tt := range tests {
t.Run(tt.uri, func(t *testing.T) {
_, err := fp.Retrieve(context.Background(), tt.uri, nil)
assert.ErrorContains(t, err, tt.err)
})
}
}
================================================
FILE: confmap/provider/internal/configurablehttpprovider/testdata/otel-config.yaml
================================================
extensions:
zpages:
endpoint: 0.0.0.0:55679
receivers:
otlp:
protocols:
grpc:
http:
processors:
memory_limiter:
# 75% of maximum memory up to 2G
limit_mib: 1536
# 25% of limit up to 2G
spike_limit_mib: 512
check_interval: 5s
exporters:
debug:
verbosity: detailed
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter]
exporters: [debug]
metrics:
receivers: [otlp]
processors: [memory_limiter]
exporters: [debug]
extensions: [zpages]
================================================
FILE: confmap/provider/internal/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: confmap/provider/yamlprovider/Makefile
================================================
include ../../../Makefile.Common
================================================
FILE: confmap/provider/yamlprovider/README.md
================================================
# YAML Provider
| Status | |
| ------------- |-----------|
| Stability | [stable] |
| Distributions | [core], [contrib], [k8s] |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Aprovider%2Fyamlprovider) [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aclosed+is%3Aissue+label%3Aprovider%2Fyamlprovider) |
[stable]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#stable
[core]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
[k8s]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-k8s
## Overview
The YAML Provider takes a literal YAML string as Collector configuration.
## Usage
The scheme for this provider is `yaml`. Usage looks like the following passed to the Collector's command line invocation:
```text
--config="yaml:exporters::otlp_http::sending_queue::batch::flush_timeout: 2s"
```
================================================
FILE: confmap/provider/yamlprovider/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package yamlprovider
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: confmap/provider/yamlprovider/go.mod
================================================
module go.opentelemetry.io/collector/confmap/provider/yamlprovider
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/confmap v1.54.0
go.uber.org/goleak v1.3.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/confmap => ../../
replace go.opentelemetry.io/collector/featuregate => ../../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../../internal/testutil
================================================
FILE: confmap/provider/yamlprovider/go.sum
================================================
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/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: confmap/provider/yamlprovider/metadata.yaml
================================================
type: yaml
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: provider
stability:
stable: [provider]
distributions: [core, contrib, k8s]
================================================
FILE: confmap/provider/yamlprovider/provider.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package yamlprovider // import "go.opentelemetry.io/collector/confmap/provider/yamlprovider"
//go:generate mdatagen metadata.yaml
import (
"context"
"fmt"
"strings"
"go.opentelemetry.io/collector/confmap"
)
const schemeName = "yaml"
type provider struct{}
// NewFactory returns a factory for a confmap.Provider that allows to provide yaml bytes.
//
// This Provider supports "yaml" scheme, and can be called with a "uri" that follows:
//
// bytes-uri = "yaml:" yaml-bytes
//
// Examples:
// `yaml:exporters::otlp_http::sending_queue::batch::flush_timeout: 2s`
// `yaml:exporters::otlp_http/foo::sending_queue::batch::flush_timeout: 2s`
func NewFactory() confmap.ProviderFactory {
return confmap.NewProviderFactory(newProvider)
}
func newProvider(confmap.ProviderSettings) confmap.Provider {
return &provider{}
}
func (s *provider) Retrieve(_ context.Context, uri string, _ confmap.WatcherFunc) (*confmap.Retrieved, error) {
if !strings.HasPrefix(uri, schemeName+":") {
return nil, fmt.Errorf("%q uri is not supported by %q provider", uri, schemeName)
}
return confmap.NewRetrievedFromYAML([]byte(uri[len(schemeName)+1:]))
}
func (*provider) Scheme() string {
return schemeName
}
func (s *provider) Shutdown(context.Context) error {
return nil
}
================================================
FILE: confmap/provider/yamlprovider/provider_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package yamlprovider
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/confmaptest"
)
func TestValidateProviderScheme(t *testing.T) {
assert.NoError(t, confmaptest.ValidateProviderScheme(createProvider()))
}
func TestEmpty(t *testing.T) {
sp := createProvider()
_, err := sp.Retrieve(context.Background(), "", nil)
require.Error(t, err)
assert.NoError(t, sp.Shutdown(context.Background()))
}
func TestInvalidYAML(t *testing.T) {
sp := createProvider()
ret, err := sp.Retrieve(context.Background(), "yaml:[invalid,", nil)
require.NoError(t, err)
raw, err := ret.AsRaw()
require.NoError(t, err)
assert.IsType(t, "", raw)
assert.NoError(t, sp.Shutdown(context.Background()))
}
func TestOneValue(t *testing.T) {
sp := createProvider()
ret, err := sp.Retrieve(context.Background(), "yaml:processors::test::timeout: 2s", nil)
require.NoError(t, err)
retMap, err := ret.AsConf()
require.NoError(t, err)
assert.Equal(t, map[string]any{
"processors": map[string]any{
"test": map[string]any{
"timeout": "2s",
},
},
}, retMap.ToStringMap())
assert.NoError(t, sp.Shutdown(context.Background()))
}
func TestNamedComponent(t *testing.T) {
sp := createProvider()
ret, err := sp.Retrieve(context.Background(), "yaml:processors::test/foo::timeout: 3s", nil)
require.NoError(t, err)
retMap, err := ret.AsConf()
require.NoError(t, err)
assert.Equal(t, map[string]any{
"processors": map[string]any{
"test/foo": map[string]any{
"timeout": "3s",
},
},
}, retMap.ToStringMap())
assert.NoError(t, sp.Shutdown(context.Background()))
}
func TestMapEntry(t *testing.T) {
sp := createProvider()
ret, err := sp.Retrieve(context.Background(), "yaml:processors: {test/foo::timeout: 3s, test::timeout: 2s}", nil)
require.NoError(t, err)
retMap, err := ret.AsConf()
require.NoError(t, err)
assert.Equal(t, map[string]any{
"processors": map[string]any{
"test/foo": map[string]any{
"timeout": "3s",
},
"test": map[string]any{
"timeout": "2s",
},
},
}, retMap.ToStringMap())
assert.NoError(t, sp.Shutdown(context.Background()))
}
func TestArrayEntry(t *testing.T) {
sp := createProvider()
ret, err := sp.Retrieve(context.Background(), "yaml:service::extensions: [zpages, zpages/foo]", nil)
require.NoError(t, err)
retMap, err := ret.AsConf()
require.NoError(t, err)
assert.Equal(t, map[string]any{
"service": map[string]any{
"extensions": []any{
"zpages",
"zpages/foo",
},
},
}, retMap.ToStringMap())
assert.NoError(t, sp.Shutdown(context.Background()))
}
func TestNewLine(t *testing.T) {
sp := createProvider()
ret, err := sp.Retrieve(context.Background(), "yaml:processors::test/foo::timeout: 3s\nprocessors::test::timeout: 2s", nil)
require.NoError(t, err)
retMap, err := ret.AsConf()
require.NoError(t, err)
assert.Equal(t, map[string]any{
"processors": map[string]any{
"test/foo": map[string]any{
"timeout": "3s",
},
"test": map[string]any{
"timeout": "2s",
},
},
}, retMap.ToStringMap())
assert.NoError(t, sp.Shutdown(context.Background()))
}
func TestDotSeparator(t *testing.T) {
sp := createProvider()
ret, err := sp.Retrieve(context.Background(), "yaml:processors.test.timeout: 4s", nil)
require.NoError(t, err)
retMap, err := ret.AsConf()
require.NoError(t, err)
assert.Equal(t, map[string]any{"processors.test.timeout": "4s"}, retMap.ToStringMap())
assert.NoError(t, sp.Shutdown(context.Background()))
}
func createProvider() confmap.Provider {
return NewFactory().Create(confmaptest.NewNopProviderSettings())
}
================================================
FILE: confmap/provider.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package confmap // import "go.opentelemetry.io/collector/confmap"
import (
"context"
"fmt"
"time"
"go.uber.org/zap"
"go.yaml.in/yaml/v3"
)
// ProviderSettings are the settings to initialize a Provider.
type ProviderSettings struct {
// Logger is a zap.Logger that will be passed to Providers.
// Providers should be able to rely on the Logger being non-nil;
// when instantiating a Provider with a ProviderFactory,
// nil Logger references should be replaced with a no-op Logger.
Logger *zap.Logger
// prevent unkeyed literal initialization
_ struct{}
}
// ProviderFactory defines a factory that can be used to instantiate
// new instances of a Provider.
type ProviderFactory = moduleFactory[Provider, ProviderSettings]
// CreateProviderFunc is a function that creates a Provider instance.
type CreateProviderFunc = createConfmapFunc[Provider, ProviderSettings]
// NewProviderFactory can be used to create a ProviderFactory.
func NewProviderFactory(f CreateProviderFunc) ProviderFactory {
return newConfmapModuleFactory(f)
}
// Provider is an interface that helps to retrieve a config map and watch for any
// changes to the config map. Implementations may load the config from a file,
// a database or any other source.
//
// The typical usage is the following:
//
// r, err := provider.Retrieve("file:/path/to/config")
// // Use r.Map; wait for watcher to be called.
// r.Close()
// r, err = provider.Retrieve("file:/path/to/config")
// // Use r.Map; wait for watcher to be called.
// r.Close()
// // repeat retrieve/wait/close cycle until it is time to shut down the Collector process.
// // ...
// provider.Shutdown()
type Provider interface {
// Retrieve goes to the configuration source and retrieves the selected data which
// contains the value to be injected in the configuration and the corresponding watcher that
// will be used to monitor for updates of the retrieved value.
//
// `uri` must follow the ":" format. This format is compatible
// with the URI definition (see https://datatracker.ietf.org/doc/html/rfc3986). The ""
// must be always included in the `uri`. The "" supported by any provider:
// - MUST consist of a sequence of characters beginning with a letter and followed by any
// combination of letters, digits, plus ("+"), period ("."), or hyphen ("-").
// See https://datatracker.ietf.org/doc/html/rfc3986#section-3.1.
// - MUST be at least 2 characters long to avoid conflicting with a driver-letter identifier as specified
// in https://tools.ietf.org/id/draft-kerwin-file-scheme-07.html#syntax.
// - For testing, all implementation MUST check that confmaptest.ValidateProviderScheme returns no error.
//
// `watcher` callback is called when the config changes. watcher may be called from
// a different go routine. After watcher is called, Provider.Retrieve should be called
// to get the new config. See description of Retrieved for more details.
// watcher may be nil, which indicates that the caller is not interested in
// knowing about the changes.
//
// If ctx is cancelled should return immediately with an error.
// Should never be called concurrently with itself or with Shutdown.
Retrieve(ctx context.Context, uri string, watcher WatcherFunc) (*Retrieved, error)
// Scheme returns the location scheme used by Retrieve.
Scheme() string
// Shutdown signals that the configuration for which this Provider was used to
// retrieve values is no longer in use and the Provider should close and release
// any resources that it may have created.
//
// This method must be called when the Collector service ends, either in case of
// success or error. Retrieve cannot be called after Shutdown.
//
// Provider MUST shutdown and wait for any goroutine(s) that were created to call `watcher`, if any.
//
// Should never be called concurrently with itself or with Retrieve.
// If ctx is cancelled should return immediately with an error.
Shutdown(ctx context.Context) error
}
type WatcherFunc func(*ChangeEvent)
// ChangeEvent describes the particular change event that happened with the config.
type ChangeEvent struct {
// Error is nil if the config is changed and needs to be re-fetched.
// Any non-nil error indicates that there was a problem with watching the config changes.
Error error
// prevent unkeyed literal initialization
_ struct{}
}
// Retrieved holds the result of a call to the Retrieve method of a Provider object.
type Retrieved struct {
rawConf any
errorHint error
closeFunc CloseFunc
stringRepresentation string
isSetString bool
}
type retrievedSettings struct {
errorHint error
stringRepresentation string
isSetString bool
closeFunc CloseFunc
}
// RetrievedOption options to customize Retrieved values.
type RetrievedOption interface {
apply(*retrievedSettings)
}
type retrievedOptionFunc func(*retrievedSettings)
func (of retrievedOptionFunc) apply(e *retrievedSettings) {
of(e)
}
// WithRetrievedClose overrides the default Retrieved.Close function.
// The default Retrieved.Close function does nothing and always returns nil.
func WithRetrievedClose(closeFunc CloseFunc) RetrievedOption {
return retrievedOptionFunc(func(settings *retrievedSettings) {
settings.closeFunc = closeFunc
})
}
func withStringRepresentation(stringRepresentation string) RetrievedOption {
return retrievedOptionFunc(func(settings *retrievedSettings) {
settings.stringRepresentation = stringRepresentation
settings.isSetString = true
})
}
func withErrorHint(errorHint error) RetrievedOption {
return retrievedOptionFunc(func(settings *retrievedSettings) {
settings.errorHint = errorHint
})
}
// NewRetrievedFromYAML returns a new Retrieved instance that contains the deserialized data from the yaml bytes.
// * yamlBytes the yaml bytes that will be deserialized.
// * opts specifies options associated with this Retrieved value, such as CloseFunc.
func NewRetrievedFromYAML(yamlBytes []byte, opts ...RetrievedOption) (*Retrieved, error) {
var rawConf any
if err := yaml.Unmarshal(yamlBytes, &rawConf); err != nil {
// If the string is not valid YAML, we try to use it verbatim as a string.
strRep := string(yamlBytes)
return NewRetrieved(strRep, append(opts,
withStringRepresentation(strRep),
withErrorHint(fmt.Errorf("assuming string type since contents are not valid YAML: %w", err)),
)...)
}
switch rawConf.(type) {
case string:
val := string(yamlBytes)
return NewRetrieved(val, append(opts, withStringRepresentation(val))...)
default:
opts = append(opts, withStringRepresentation(string(yamlBytes)))
}
return NewRetrieved(rawConf, opts...)
}
// NewRetrieved returns a new Retrieved instance that contains the data from the raw deserialized config.
// The rawConf can be one of the following types:
// - Primitives: int, int32, int64, float32, float64, bool, string;
// - []any;
// - map[string]any;
func NewRetrieved(rawConf any, opts ...RetrievedOption) (*Retrieved, error) {
if err := checkRawConfType(rawConf); err != nil {
return nil, err
}
set := retrievedSettings{}
for _, opt := range opts {
opt.apply(&set)
}
return &Retrieved{
rawConf: rawConf,
errorHint: set.errorHint,
closeFunc: set.closeFunc,
stringRepresentation: set.stringRepresentation,
isSetString: set.isSetString,
}, nil
}
// AsConf returns the retrieved configuration parsed as a Conf.
func (r *Retrieved) AsConf() (*Conf, error) {
if r.rawConf == nil {
return New(), nil
}
val, ok := r.rawConf.(map[string]any)
if !ok {
if r.errorHint != nil {
return nil, fmt.Errorf("retrieved value (type=%T) cannot be used as a Conf: %w", r.rawConf, r.errorHint)
}
return nil, fmt.Errorf("retrieved value (type=%T) cannot be used as a Conf", r.rawConf)
}
return NewFromStringMap(val), nil
}
// AsRaw returns the retrieved configuration parsed as an any which can be one of the following types:
// - Primitives: int, int32, int64, float32, float64, bool, string;
// - []any - every member follows the same rules as the given any;
// - map[string]any - every value follows the same rules as the given any;
func (r *Retrieved) AsRaw() (any, error) {
return r.rawConf, nil
}
// AsString returns the retrieved configuration as a string.
// If the retrieved configuration is not convertible to a string unambiguously, an error is returned.
// If the retrieved configuration is a string, the string is returned.
// This method is used to resolve ${} references in inline position.
func (r *Retrieved) AsString() (string, error) {
if !r.isSetString {
if str, ok := r.rawConf.(string); ok {
return str, nil
}
return "", fmt.Errorf("retrieved value does not have unambiguous string representation: %v", r.rawConf)
}
return r.stringRepresentation, nil
}
// Close and release any watchers that Provider.Retrieve may have created.
//
// Should block until all resources are closed, and guarantee that `onChange` is not
// going to be called after it returns except when `ctx` is cancelled.
//
// Should never be called concurrently with itself.
func (r *Retrieved) Close(ctx context.Context) error {
if r.closeFunc == nil {
return nil
}
return r.closeFunc(ctx)
}
// CloseFunc a function equivalent to Retrieved.Close.
type CloseFunc func(context.Context) error
func checkRawConfType(rawConf any) error {
if rawConf == nil {
return nil
}
switch rawConf.(type) {
case int, int32, int64, float32, float64, bool, string, []any, map[string]any, time.Time:
return nil
default:
return fmt.Errorf(
"unsupported type=%T for retrieved config,"+
" ensure that values are wrapped in quotes", rawConf)
}
}
================================================
FILE: confmap/provider_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package confmap
import (
"context"
"errors"
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// This is an example of a provider that calls a provided WatcherFunc to update configuration dynamically every second.
// The example is useful for implementing Providers of configuration that changes over time.
type UpdatingProvider struct{}
func (p UpdatingProvider) getCurrentConfig(_ string) any {
return "hello"
}
func (p UpdatingProvider) Retrieve(ctx context.Context, uri string, watcher WatcherFunc) (*Retrieved, error) {
ticker := time.NewTicker(1 * time.Second)
stop := make(chan bool, 1)
retrieved, err := NewRetrieved(p.getCurrentConfig(uri), WithRetrievedClose(func(_ context.Context) error {
// the retriever should call this function when it no longer wants config updates
ticker.Stop()
stop <- true
return nil
}))
if err != nil {
return nil, err
}
// it's necessary to start a go function that can notify the caller of changes asynchronously
go func() {
for {
select {
case <-ctx.Done():
// if the context is closed, then we should stop sending updates
ticker.Stop()
return
case <-stop:
// closeFunc was called, so stop updating the watcher
return
case <-ticker.C:
// the configuration has "changed". Notify the watcher that a new config is available
// the watcher is expected to call Provider.Retrieve again to get the update
// note that the collector calls closeFunc before calling Retrieve for the second time,
// so these go functions don't accumulate indefinitely. (see otelcol/collector.go, Collector.reloadConfiguration)
watcher(&ChangeEvent{})
}
}
}()
return retrieved, nil
}
func ExampleProvider() {
provider := UpdatingProvider{}
receivedNotification := make(chan bool)
watcherFunc := func(_ *ChangeEvent) {
fmt.Println("received notification of new config")
receivedNotification <- true
}
retrieved, err := provider.Retrieve(context.Background(), "example", watcherFunc)
if err != nil {
fmt.Println("received an error")
} else {
fmt.Printf("received: %s\n", retrieved.rawConf)
}
// after one second, we should receive a notification that config has changed
<-receivedNotification
// signal that we no longer want updates
retrieved.Close(context.Background())
// Output:
// received: hello
// received notification of new config
}
func TestNewRetrieved(t *testing.T) {
ret, err := NewRetrieved(nil)
require.NoError(t, err)
retMap, err := ret.AsConf()
require.NoError(t, err)
assert.Equal(t, New(), retMap)
assert.NoError(t, ret.Close(context.Background()))
}
func TestNewRetrievedWithOptions(t *testing.T) {
want := errors.New("my error")
ret, err := NewRetrieved(nil, WithRetrievedClose(func(context.Context) error { return want }))
require.NoError(t, err)
retMap, err := ret.AsConf()
require.NoError(t, err)
assert.Equal(t, New(), retMap)
assert.Equal(t, want, ret.Close(context.Background()))
}
func TestNewRetrievedUnsupportedType(t *testing.T) {
_, err := NewRetrieved(errors.New("my error"))
require.Error(t, err)
}
func TestNewRetrievedFromYAML(t *testing.T) {
ret, err := NewRetrievedFromYAML([]byte{})
require.NoError(t, err)
retMap, err := ret.AsConf()
require.NoError(t, err)
assert.Equal(t, New(), retMap)
assert.NoError(t, ret.Close(context.Background()))
}
func TestNewRetrievedFromYAMLWithOptions(t *testing.T) {
want := errors.New("my error")
ret, err := NewRetrievedFromYAML([]byte{}, WithRetrievedClose(func(context.Context) error { return want }))
require.NoError(t, err)
retMap, err := ret.AsConf()
require.NoError(t, err)
assert.Equal(t, New(), retMap)
assert.Equal(t, want, ret.Close(context.Background()))
}
func TestNewRetrievedFromYAMLInvalidYAMLBytes(t *testing.T) {
ret, err := NewRetrievedFromYAML([]byte("[invalid:,"))
require.NoError(t, err)
_, err = ret.AsConf()
require.EqualError(t, err,
"retrieved value (type=string) cannot be used as a Conf: assuming string type since contents are not valid YAML: yaml: line 1: did not find expected node content",
)
str, err := ret.AsString()
require.NoError(t, err)
assert.Equal(t, "[invalid:,", str)
raw, err := ret.AsRaw()
require.NoError(t, err)
assert.Equal(t, "[invalid:,", raw)
}
func TestNewRetrievedFromYAMLInvalidAsMap(t *testing.T) {
ret, err := NewRetrievedFromYAML([]byte("string"))
require.NoError(t, err)
_, err = ret.AsConf()
require.EqualError(t, err, "retrieved value (type=string) cannot be used as a Conf")
str, err := ret.AsString()
require.NoError(t, err)
assert.Equal(t, "string", str)
}
func TestNewRetrievedFromYAMLString(t *testing.T) {
tests := []struct {
yaml string
value any
altStrRepr string
strReprErr string
}{
{
yaml: "string",
value: "string",
},
{
yaml: "\"string\"",
value: "\"string\"",
altStrRepr: "\"string\"",
},
{
yaml: "123",
value: 123,
},
{
yaml: "2023-03-20T03:17:55.432328Z",
value: time.Date(2023, 3, 20, 3, 17, 55, 432328000, time.UTC),
},
{
yaml: "true",
value: true,
},
{
yaml: "0123",
value: 0o123,
},
{
yaml: "0x123",
value: 0x123,
},
{
yaml: "0b101",
value: 0b101,
},
{
yaml: "0.123",
value: 0.123,
},
{
yaml: "{key: value}",
value: map[string]any{"key": "value"},
},
}
for _, tt := range tests {
t.Run(tt.yaml, func(t *testing.T) {
ret, err := NewRetrievedFromYAML([]byte(tt.yaml))
require.NoError(t, err)
raw, err := ret.AsRaw()
require.NoError(t, err)
assert.Equal(t, tt.value, raw)
str, err := ret.AsString()
if tt.strReprErr != "" {
assert.ErrorContains(t, err, tt.strReprErr)
return
}
require.NoError(t, err)
if tt.altStrRepr != "" {
assert.Equal(t, tt.altStrRepr, str)
} else {
assert.Equal(t, tt.yaml, str)
}
})
}
}
================================================
FILE: confmap/resolver.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package confmap // import "go.opentelemetry.io/collector/confmap"
import (
"context"
"errors"
"fmt"
"regexp"
"strings"
"go.uber.org/multierr"
"go.uber.org/zap"
"go.opentelemetry.io/collector/confmap/internal"
)
// follows drive-letter specification:
// https://datatracker.ietf.org/doc/html/draft-kerwin-file-scheme-07.html#section-2.2
var driverLetterRegexp = regexp.MustCompile("^[A-z]:")
// Resolver resolves a configuration as a Conf.
type Resolver struct {
uris []location
providers map[string]Provider
defaultScheme string
converters []Converter
closers []CloseFunc
watcher chan error
}
// ResolverSettings are the settings to configure the behavior of the Resolver.
type ResolverSettings struct {
// URIs locations from where the Conf is retrieved, and merged in the given order.
// It is required to have at least one location.
URIs []string
// ProviderFactories is a slice of Provider factories.
// It is required to have at least one factory.
ProviderFactories []ProviderFactory
// DefaultScheme is the scheme that is used if ${} syntax is used but no schema is provided.
// If no DefaultScheme is set, ${} with no schema will not be expanded.
// It is strongly recommended to set "env" as the default scheme to align with the
// OpenTelemetry Configuration Specification
DefaultScheme string
// ProviderSettings contains settings that will be passed to Provider
// factories when instantiating Providers.
ProviderSettings ProviderSettings
// ConverterFactories is a slice of Converter creation functions.
ConverterFactories []ConverterFactory
// ConverterSettings contains settings that will be passed to Converter
// factories when instantiating Converters.
ConverterSettings ConverterSettings
// prevent unkeyed literal initialization
_ struct{}
}
// NewResolver returns a new Resolver that resolves configuration from multiple URIs.
//
// To resolve a configuration the following steps will happen:
// 1. Retrieves individual configurations from all given "URIs", and merge them in the retrieve order.
// 2. Once the Conf is merged, apply the converters in the given order.
//
// After the configuration was resolved the `Resolver` can be used as a single point to watch for updates in
// the configuration data retrieved via the config providers used to process the "initial" configuration and to generate
// the "effective" one. The typical usage is the following:
//
// Resolver.Resolve(ctx)
// Resolver.Watch() // wait for an event.
// Resolver.Resolve(ctx)
// Resolver.Watch() // wait for an event.
// // repeat Resolve/Watch cycle until it is time to shut down the Collector process.
// Resolver.Shutdown(ctx)
//
// `uri` must follow the ":" format. This format is compatible with the URI definition
// (see https://datatracker.ietf.org/doc/html/rfc3986). An empty "" defaults to "file" schema.
func NewResolver(set ResolverSettings) (*Resolver, error) {
if len(set.URIs) == 0 {
return nil, errors.New("invalid 'confmap.ResolverSettings' configuration: no URIs")
}
if len(set.ProviderFactories) == 0 {
return nil, errors.New("invalid 'confmap.ResolverSettings' configuration: no Providers")
}
if set.ProviderSettings.Logger == nil {
set.ProviderSettings.Logger = zap.NewNop()
}
if set.ConverterSettings.Logger == nil {
set.ConverterSettings.Logger = zap.NewNop()
}
providers := make(map[string]Provider, len(set.ProviderFactories))
for _, factory := range set.ProviderFactories {
provider := factory.Create(set.ProviderSettings)
scheme := provider.Scheme()
// Check that the scheme follows the pattern.
if !regexp.MustCompile(schemePattern).MatchString(scheme) {
return nil, fmt.Errorf("invalid 'confmap.Provider' scheme %q", scheme)
}
// Check that the scheme is unique.
if _, ok := providers[scheme]; ok {
return nil, fmt.Errorf("duplicate 'confmap.Provider' scheme %q", scheme)
}
providers[scheme] = provider
}
if set.DefaultScheme != "" {
_, ok := providers[set.DefaultScheme]
if !ok {
return nil, errors.New("invalid 'confmap.ResolverSettings' configuration: DefaultScheme not found in providers list")
}
}
converters := make([]Converter, len(set.ConverterFactories))
for i, factory := range set.ConverterFactories {
converters[i] = factory.Create(set.ConverterSettings)
}
// Safe copy, ensures the slices and maps cannot be changed from the caller.
uris := make([]location, len(set.URIs))
for i, uri := range set.URIs {
// For backwards compatibility:
// - empty url scheme means "file".
// - "^[A-z]:" also means "file"
if driverLetterRegexp.MatchString(uri) || !strings.Contains(uri, ":") {
uris[i] = location{scheme: "file", opaqueValue: uri}
continue
}
lURI, err := newLocation(uri)
if err != nil {
return nil, err
}
if _, ok := providers[lURI.scheme]; !ok {
return nil, fmt.Errorf("unsupported scheme on URI %q", uri)
}
uris[i] = lURI
}
return &Resolver{
uris: uris,
providers: providers,
defaultScheme: set.DefaultScheme,
converters: converters,
watcher: make(chan error, 1),
}, nil
}
// Resolve returns the configuration as a Conf, or error otherwise.
// Should never be called concurrently with itself, Watch or Shutdown.
func (mr *Resolver) Resolve(ctx context.Context) (*Conf, error) {
// First check if already an active watching, close that if any.
if err := mr.closeIfNeeded(ctx); err != nil {
return nil, fmt.Errorf("cannot close previous watch: %w", err)
}
// Retrieves individual configurations from all URIs in the given order, and merge them in retMap.
retMap := New()
for _, uri := range mr.uris {
ret, err := mr.retrieveValue(ctx, uri)
if err != nil {
return nil, fmt.Errorf("cannot retrieve the configuration: %w", err)
}
mr.closers = append(mr.closers, ret.Close)
retCfgMap, err := ret.AsConf()
if err != nil {
return nil, err
}
if err := retMap.Merge(retCfgMap); err != nil {
return nil, err
}
}
cfgMap := make(map[string]any)
for _, k := range retMap.AllKeys() {
ug := internal.UnsanitizedGetter{Conf: retMap}
val, err := mr.expandValueRecursively(ctx, ug.UnsanitizedGet(k))
if err != nil {
return nil, err
}
cfgMap[k] = escapeDollarSigns(val)
}
retMap = NewFromStringMap(cfgMap)
// Apply the converters in the given order.
for _, confConv := range mr.converters {
if err := confConv.Convert(ctx, retMap); err != nil {
return nil, fmt.Errorf("cannot convert the confmap.Conf: %w", err)
}
}
return retMap, nil
}
func escapeDollarSigns(val any) any {
switch v := val.(type) {
case string:
return strings.ReplaceAll(v, "$$", "$")
case internal.ExpandedValue:
v.Original = strings.ReplaceAll(v.Original, "$$", "$")
v.Value = escapeDollarSigns(v.Value)
return v
case []any:
nslice := make([]any, len(v))
for i, x := range v {
nslice[i] = escapeDollarSigns(x)
}
return nslice
case map[string]any:
nmap := make(map[string]any, len(v))
for k, x := range v {
nmap[k] = escapeDollarSigns(x)
}
return nmap
default:
return val
}
}
// Watch blocks until any configuration change was detected or an unrecoverable error
// happened during monitoring the configuration changes.
//
// Error is nil if the configuration is changed and needs to be re-fetched. Any non-nil
// error indicates that there was a problem with watching the configuration changes.
//
// Should never be called concurrently with itself or Get.
func (mr *Resolver) Watch() <-chan error {
return mr.watcher
}
// Shutdown signals that the provider is no longer in use and the that should close
// and release any resources that it may have created. It terminates the Watch channel.
//
// Should never be called concurrently with itself or Get.
func (mr *Resolver) Shutdown(ctx context.Context) error {
var errs error
errs = multierr.Append(errs, mr.closeIfNeeded(ctx))
for _, p := range mr.providers {
errs = multierr.Append(errs, p.Shutdown(ctx))
}
close(mr.watcher)
return errs
}
func (mr *Resolver) onChange(event *ChangeEvent) {
mr.watcher <- event.Error
}
func (mr *Resolver) closeIfNeeded(ctx context.Context) error {
var err error
for _, ret := range mr.closers {
err = multierr.Append(err, ret(ctx))
}
mr.closers = nil
return err
}
func (mr *Resolver) retrieveValue(ctx context.Context, uri location) (*Retrieved, error) {
p, ok := mr.providers[uri.scheme]
if !ok {
return nil, fmt.Errorf("scheme %q is not supported for uri %q", uri.scheme, uri.asString())
}
return p.Retrieve(ctx, uri.asString(), mr.onChange)
}
================================================
FILE: confmap/resolver_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package confmap
import (
"context"
"encoding/json"
"errors"
"os"
"path/filepath"
"reflect"
"sync"
"sync/atomic"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"go.yaml.in/yaml/v3"
"go.opentelemetry.io/collector/confmap/internal/metadata"
"go.opentelemetry.io/collector/featuregate"
)
type mockProvider struct {
scheme string
retM any
errR error
errS error
errW error
closeFunc func(ctx context.Context) error
}
func (m *mockProvider) Retrieve(_ context.Context, _ string, watcher WatcherFunc) (*Retrieved, error) {
if m.errR != nil {
return nil, m.errR
}
if m.retM == nil {
return NewRetrieved(nil)
}
watcher(&ChangeEvent{Error: m.errW})
return NewRetrieved(m.retM, WithRetrievedClose(m.closeFunc))
}
func (m *mockProvider) Scheme() string {
if m.scheme == "" {
return "mock"
}
return m.scheme
}
func (m *mockProvider) Shutdown(context.Context) error {
return m.errS
}
func newMockProvider(m *mockProvider) ProviderFactory {
return NewProviderFactory(func(_ ProviderSettings) Provider {
return m
})
}
type fakeProvider struct {
scheme string
ret func(ctx context.Context, uri string, watcher WatcherFunc) (*Retrieved, error)
logger *zap.Logger
}
func newFileProvider(tb testing.TB) ProviderFactory {
return newFakeProvider("file", func(_ context.Context, uri string, _ WatcherFunc) (*Retrieved, error) {
return NewRetrieved(newConfFromFile(tb, uri[5:]))
})
}
func newFakeProvider(scheme string, ret func(ctx context.Context, uri string, watcher WatcherFunc) (*Retrieved, error)) ProviderFactory {
return NewProviderFactory(func(ps ProviderSettings) Provider {
return &fakeProvider{
scheme: scheme,
ret: ret,
logger: ps.Logger,
}
})
}
func newObservableFileProvider(tb testing.TB) (ProviderFactory, *fakeProvider) {
return newObservableProvider("file", func(_ context.Context, uri string, _ WatcherFunc) (*Retrieved, error) {
return NewRetrieved(newConfFromFile(tb, uri[5:]))
})
}
func newObservableProvider(scheme string, ret func(ctx context.Context, uri string, watcher WatcherFunc) (*Retrieved, error)) (ProviderFactory, *fakeProvider) {
fp := &fakeProvider{
scheme: scheme,
ret: ret,
}
return NewProviderFactory(func(ps ProviderSettings) Provider {
fp.logger = ps.Logger
return fp
}), fp
}
func (f *fakeProvider) Retrieve(ctx context.Context, uri string, watcher WatcherFunc) (*Retrieved, error) {
return f.ret(ctx, uri, watcher)
}
func (f *fakeProvider) Scheme() string {
return f.scheme
}
func (f *fakeProvider) Shutdown(context.Context) error {
return nil
}
type mockConverter struct {
err error
}
func (m *mockConverter) Convert(context.Context, *Conf) error {
return errors.New("converter_err")
}
func TestNewResolverInvalidSchemeInURI(t *testing.T) {
_, err := NewResolver(ResolverSettings{URIs: []string{"s_3:has invalid char"}, ProviderFactories: []ProviderFactory{newMockProvider(&mockProvider{scheme: "s3"})}})
assert.EqualError(t, err, `invalid uri: "s_3:has invalid char"`)
}
func TestNewResolverDuplicateScheme(t *testing.T) {
_, err := NewResolver(ResolverSettings{URIs: []string{"mock:something"}, ProviderFactories: []ProviderFactory{newMockProvider(&mockProvider{scheme: "mock"}), newMockProvider(&mockProvider{scheme: "mock"})}})
assert.EqualError(t, err, `duplicate 'confmap.Provider' scheme "mock"`)
}
func TestResolverErrors(t *testing.T) {
tests := []struct {
name string
locations []string
providers []Provider
converters []Converter
defaultScheme string
expectBuildErr bool
expectResolveErr bool
expectWatchErr bool
expectCloseErr bool
expectShutdownErr bool
}{
{
name: "unsupported location scheme",
locations: []string{"mock:", "notsupported:"},
providers: []Provider{&mockProvider{}},
expectBuildErr: true,
},
{
name: "default scheme not found",
locations: []string{"mock:", "err:"},
providers: []Provider{
&mockProvider{},
&mockProvider{scheme: "err", errR: errors.New("retrieve_err")},
},
defaultScheme: "missing",
expectBuildErr: true,
},
{
name: "retrieve location config error",
locations: []string{"mock:", "err:"},
providers: []Provider{
&mockProvider{},
&mockProvider{scheme: "err", errR: errors.New("retrieve_err")},
},
expectResolveErr: true,
},
{
name: "retrieve location not convertible to Conf",
locations: []string{"mock:", "err:"},
providers: []Provider{
&mockProvider{},
&mockProvider{scheme: "err", retM: "invalid value"},
},
expectResolveErr: true,
},
{
name: "converter error",
locations: []string{"mock:"},
providers: []Provider{&mockProvider{}},
converters: []Converter{&mockConverter{err: errors.New("converter_err")}},
expectResolveErr: true,
},
{
name: "watch error",
locations: []string{"mock:", "err:"},
providers: []Provider{
&mockProvider{},
&mockProvider{scheme: "err", retM: map[string]any{}, errW: errors.New("watch_err")},
},
expectWatchErr: true,
},
{
name: "close error",
locations: []string{"mock:", "err:"},
providers: []Provider{
&mockProvider{},
&mockProvider{scheme: "err", retM: map[string]any{}, closeFunc: func(context.Context) error { return errors.New("close_err") }},
},
expectCloseErr: true,
},
{
name: "shutdown error",
locations: []string{"mock:", "err:"},
providers: []Provider{
&mockProvider{},
&mockProvider{scheme: "err", retM: map[string]any{}, errS: errors.New("close_err")},
},
expectShutdownErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockProviderFuncs := make([]ProviderFactory, len(tt.providers))
for i, provider := range tt.providers {
p := provider
mockProviderFuncs[i] = NewProviderFactory(func(_ ProviderSettings) Provider { return p })
}
converterFuncs := make([]ConverterFactory, len(tt.converters))
for i, converter := range tt.converters {
c := converter
converterFuncs[i] = NewConverterFactory(func(_ ConverterSettings) Converter { return c })
}
resolver, err := NewResolver(ResolverSettings{URIs: tt.locations, ProviderFactories: mockProviderFuncs, DefaultScheme: tt.defaultScheme, ConverterFactories: converterFuncs})
if tt.expectBuildErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
_, errN := resolver.Resolve(context.Background())
if tt.expectResolveErr {
assert.Error(t, errN)
return
}
require.NoError(t, errN)
errW := <-resolver.Watch()
if tt.expectWatchErr {
assert.Error(t, errW)
return
}
require.NoError(t, errW)
_, errC := resolver.Resolve(context.Background())
if tt.expectCloseErr {
assert.Error(t, errC)
return
}
require.NoError(t, errN)
errS := resolver.Shutdown(context.Background())
if tt.expectShutdownErr {
assert.Error(t, errS)
return
}
assert.NoError(t, errC)
})
}
}
func TestBackwardsCompatibilityForFilePath(t *testing.T) {
tests := []struct {
name string
location string
errMessage string
expectBuildErr bool
}{
{
name: "unix",
location: `/test`,
errMessage: `file:/test`,
},
{
name: "file_unix",
location: `file:/test`,
errMessage: `file:/test`,
},
{
name: "windows_C",
location: `c:\test`,
errMessage: `file:c:\test`,
},
{
name: "windows_z",
location: `z:\test`,
errMessage: `file:z:\test`,
},
{
name: "file_windows",
location: `file:c:\test`,
errMessage: `file:c:\test`,
},
{
name: "invalid_scheme",
location: `LL:\test`,
expectBuildErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resolver, err := NewResolver(ResolverSettings{
URIs: []string{tt.location},
ProviderFactories: []ProviderFactory{
newFakeProvider("file", func(_ context.Context, uri string, _ WatcherFunc) (*Retrieved, error) {
return nil, errors.New(uri)
}),
},
ConverterFactories: nil,
})
if tt.expectBuildErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
_, err = resolver.Resolve(context.Background())
assert.ErrorContains(t, err, tt.errMessage, tt.name)
})
}
}
func TestResolver(t *testing.T) {
numCalls := atomic.Int32{}
resolver, err := NewResolver(ResolverSettings{
URIs: []string{"mock:"},
ProviderFactories: []ProviderFactory{
newMockProvider(&mockProvider{retM: map[string]any{}, closeFunc: func(context.Context) error {
numCalls.Add(1)
return nil
}}),
},
ConverterFactories: nil,
})
require.NoError(t, err)
_, errN := resolver.Resolve(context.Background())
require.NoError(t, errN)
assert.Equal(t, int32(0), numCalls.Load())
errW := <-resolver.Watch()
require.NoError(t, errW)
// Repeat Resolve/Watch.
_, errN = resolver.Resolve(context.Background())
require.NoError(t, errN)
assert.Equal(t, int32(1), numCalls.Load())
errW = <-resolver.Watch()
require.NoError(t, errW)
_, errN = resolver.Resolve(context.Background())
require.NoError(t, errN)
assert.Equal(t, int32(2), numCalls.Load())
errC := resolver.Shutdown(context.Background())
require.NoError(t, errC)
assert.Equal(t, int32(3), numCalls.Load())
}
func TestResolverNewLinesInOpaqueValue(t *testing.T) {
_, err := NewResolver(ResolverSettings{
URIs: []string{"mock:receivers:\n nop:\n"},
ProviderFactories: []ProviderFactory{newMockProvider(&mockProvider{retM: map[string]any{}})},
ConverterFactories: nil,
})
assert.NoError(t, err)
}
func TestResolverNoLocations(t *testing.T) {
_, err := NewResolver(ResolverSettings{
URIs: []string{},
ProviderFactories: []ProviderFactory{newMockProvider(&mockProvider{})},
ConverterFactories: nil,
})
assert.Error(t, err)
}
func TestResolverNoProviders(t *testing.T) {
_, err := NewResolver(ResolverSettings{
URIs: []string{filepath.Join("testdata", "config.yaml")},
ProviderFactories: nil,
ConverterFactories: nil,
})
assert.Error(t, err)
}
func TestResolverShutdownClosesWatch(t *testing.T) {
resolver, err := NewResolver(ResolverSettings{
URIs: []string{filepath.Join("testdata", "config.yaml")},
ProviderFactories: []ProviderFactory{newFileProvider(t)},
ConverterFactories: nil,
})
require.NoError(t, err)
_, errN := resolver.Resolve(context.Background())
require.NoError(t, errN)
var watcherWG sync.WaitGroup
watcherWG.Go(func() {
errW, ok := <-resolver.Watch()
// Channel is closed, no exception
require.NoError(t, errW)
assert.False(t, ok)
})
require.NoError(t, resolver.Shutdown(context.Background()))
watcherWG.Wait()
}
func TestProvidesDefaultLogger(t *testing.T) {
factory, provider := newObservableFileProvider(t)
_, err := NewResolver(ResolverSettings{
URIs: []string{filepath.Join("testdata", "config.yaml")},
ProviderFactories: []ProviderFactory{factory},
ConverterFactories: []ConverterFactory{NewConverterFactory(func(set ConverterSettings) Converter {
assert.NotNil(t, set.Logger)
return &mockConverter{}
})},
})
require.NoError(t, err)
require.NotNil(t, provider.logger)
}
func TestResolverDefaultProviderSet(t *testing.T) {
envProvider := newEnvProvider()
fileProvider := newFileProvider(t)
r, err := NewResolver(ResolverSettings{
URIs: []string{"env:"},
ProviderFactories: []ProviderFactory{fileProvider, envProvider},
DefaultScheme: "env",
})
require.NoError(t, err)
assert.NotNil(t, r.defaultScheme)
_, ok := r.providers["env"]
assert.True(t, ok)
}
type mergeTest struct {
Name string `yaml:"name"`
AppendPaths []string `yaml:"append_paths"`
Configs []map[string]any `yaml:"configs"`
Expected map[string]any `yaml:"expected"`
}
func TestMergeFunctionality(t *testing.T) {
tests := []struct {
name string
scenarioFile string
flagEnabled bool
}{
{
name: "feature-flag-enabled",
scenarioFile: "testdata/merge-append-scenarios.yaml",
flagEnabled: true,
},
{
name: "feature-flag-disabled",
scenarioFile: "testdata/merge-append-scenarios-featuregate-disabled.yaml",
flagEnabled: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.flagEnabled {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ConfmapEnableMergeAppendOptionFeatureGate.ID(), true))
defer func() {
// Restore previous value.
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ConfmapEnableMergeAppendOptionFeatureGate.ID(), false))
}()
}
runScenario(t, tt.scenarioFile)
})
}
}
func runScenario(t *testing.T, path string) {
yamlData, err := os.ReadFile(filepath.Clean(path))
require.NoError(t, err)
var testcases []*mergeTest
err = yaml.Unmarshal(yamlData, &testcases)
require.NoError(t, err)
for _, tt := range testcases {
t.Run(tt.Name, func(t *testing.T) {
configFiles := make([]string, 0)
for _, c := range tt.Configs {
// store configs into a temp file. This makes it easier for us to test feature gate functionality
file, err := os.CreateTemp(t.TempDir(), "*.yaml")
defer func() { require.NoError(t, file.Close()) }()
require.NoError(t, err)
b, err := json.Marshal(c)
require.NoError(t, err)
n, err := file.Write(b)
require.NoError(t, err)
require.Positive(t, n)
configFiles = append(configFiles, file.Name())
}
resolver, err := NewResolver(ResolverSettings{
URIs: configFiles,
ProviderFactories: []ProviderFactory{newFileProvider(t)},
DefaultScheme: "file",
})
require.NoError(t, err)
conf, err := resolver.Resolve(context.Background())
require.NoError(t, err)
mergedConf := conf.ToStringMap()
require.Truef(t, reflect.DeepEqual(mergedConf, tt.Expected), "Exp: %s\nGot: %s", tt.Expected, mergedConf)
})
}
}
// newConfFromFile creates a new Conf by reading the given file.
func newConfFromFile(tb testing.TB, fileName string) map[string]any {
content, err := os.ReadFile(filepath.Clean(fileName))
require.NoErrorf(tb, err, "unable to read the file %v", fileName)
var data map[string]any
require.NoError(tb, yaml.Unmarshal(content, &data), "unable to parse yaml")
return NewFromStringMap(data).ToStringMap()
}
type provider struct {
wg sync.WaitGroup
}
func newRaceDetectorProvider() ProviderFactory {
return NewProviderFactory(func(_ ProviderSettings) Provider {
return &provider{}
})
}
func (p *provider) Retrieve(_ context.Context, _ string, watcher WatcherFunc) (*Retrieved, error) {
p.wg.Go(func() {
// mock a config change event and wait for goroutine to return.
watcher(&ChangeEvent{})
})
return NewRetrieved(map[string]any{})
}
func (p *provider) Scheme() string {
return "race"
}
func (p *provider) Shutdown(context.Context) error {
p.wg.Wait()
return nil
}
func TestProviderRaceCondition(t *testing.T) {
resolver, err := NewResolver(ResolverSettings{
URIs: []string{"race:"},
ProviderFactories: []ProviderFactory{
newRaceDetectorProvider(),
},
ConverterFactories: nil,
})
require.NoError(t, err)
c, err := resolver.Resolve(context.Background())
require.NoError(t, err)
require.NotNil(t, c)
require.NoError(t, resolver.Shutdown(context.Background()))
}
================================================
FILE: confmap/testdata/config.yaml
================================================
receivers:
nop:
nop/myreceiver:
processors:
nop:
nop/myprocessor:
exporters:
nop:
nop/myexporter:
extensions:
nop:
nop/myextension:
service:
extensions: [nop]
pipelines:
traces:
receivers: [nop]
processors: [nop]
exporters: [nop]
================================================
FILE: confmap/testdata/expand-with-all-env.yaml
================================================
test_map:
extra: "${env:EXTRA}"
extra_map:
recv.1: "${env:EXTRA_MAP_VALUE_1}"
recv.2: "${env:EXTRA_MAP_VALUE_2}"
extra_list_map:
- { recv.1: "${env:EXTRA_LIST_MAP_VALUE_1}",recv.2: "${env:EXTRA_LIST_MAP_VALUE_2}" }
extra_list:
- "${env:EXTRA_LIST_VALUE_1}"
- "${env:EXTRA_LIST_VALUE_2}"
================================================
FILE: confmap/testdata/expand-with-no-env.yaml
================================================
test_map:
extra: "some string"
extra_map:
recv.1: "some map value_1"
recv.2: "some map value_2"
extra_list_map:
- { recv.1: "some list map value_1",recv.2: "some list map value_2" }
extra_list:
- "some list value_1"
- "some list value_2"
================================================
FILE: confmap/testdata/expand-with-partial-env.yaml
================================================
test_map:
extra: "${env:EXTRA}"
extra_map:
recv.1: "${env:EXTRA_MAP_VALUE_1}"
recv.2: "some map value_2"
extra_list_map:
- { recv.1: "some list map value_1",recv.2: "${env:EXTRA_LIST_MAP_VALUE_2}" }
extra_list:
- "some list value_1"
- "${env:EXTRA_LIST_VALUE_2}"
================================================
FILE: confmap/testdata/merge-append-scenarios-featuregate-disabled.yaml
================================================
- name: merge-mode-default
configs:
-
receivers:
nop:
nop/myreceiver:
processors:
nop:
nop/myprocessor:
exporters:
nop:
nop/myexporter:
extensions:
nop:
nop/myextension:
service:
extensions: [nop]
pipelines:
traces:
receivers: [nop]
processors: [nop]
exporters: [nop]
-
receivers:
nop2:
exporters:
nop2:
extensions:
nop2:
service:
extensions: [nop2]
pipelines:
traces:
receivers: [nop2]
processors: [nop2]
exporters: [nop2]
expected:
receivers:
nop2:
nop:
nop/myreceiver:
exporters:
nop2:
nop:
nop/myexporter:
processors:
nop:
nop/myprocessor:
extensions:
nop2:
nop:
nop/myextension:
service:
extensions: [nop2]
pipelines:
traces:
receivers: [nop2]
processors: [nop2]
exporters: [nop2]
- name: merge-mode-append
append_paths: ["service"]
configs:
-
receivers:
nop:
key: val
processors:
nop:
exporters:
nop:
key: 2
extensions:
nop:
service:
extensions: [nop]
pipelines:
traces:
receivers: [nop]
processors: [nop]
exporters: [nop]
-
receivers:
nop2:
exporters:
nop2:
nop:
key: updated_value
extensions:
nop2:
service:
extensions: [nop2]
pipelines:
traces:
receivers: [nop2]
processors: [nop2]
exporters: [nop2]
expected:
receivers:
nop2:
nop:
key: val
exporters:
nop2:
nop:
key: updated_value
processors:
nop:
extensions:
nop2:
nop:
service:
extensions: [nop2]
pipelines:
traces:
receivers: [nop2]
processors: [nop2]
exporters: [nop2]
- name: merge-mode-append-override-old-values
append_paths: ["service"]
configs:
-
receivers:
nop:
key1: "value"
key2: 1
processors:
nop:
exporters:
nop:
extensions:
nop:
ext:
key: 1
service:
extensions: [nop, ext]
pipelines:
traces:
receivers: [nop]
processors: [nop]
exporters: [nop]
-
receivers:
nop2:
exporters:
nop2:
extensions:
ext:
key: 2
service:
extensions: [ext]
pipelines:
traces:
receivers: [nop2]
exporters: [nop2]
expected:
receivers:
nop:
key1: "value"
key2: 1
nop2:
exporters:
nop2:
nop:
processors:
nop:
extensions:
nop:
ext:
key: 2
service:
extensions: [ext]
pipelines:
traces:
receivers: [nop2]
processors: [nop]
exporters: [nop2]
================================================
FILE: confmap/testdata/merge-append-scenarios.yaml
================================================
- name: merge-mode-default
configs:
-
receivers:
nop:
nop/myreceiver:
processors:
nop:
nop/myprocessor:
exporters:
nop:
nop/myexporter:
extensions:
nop:
nop/myextension:
service:
extensions: [nop]
pipelines:
traces:
receivers: [nop]
processors: [nop]
exporters: [nop]
-
receivers:
nop2:
exporters:
nop2:
extensions:
nop2:
service:
extensions: [nop2]
pipelines:
traces:
receivers: [nop2]
processors: [nop2]
exporters: [nop2]
expected:
receivers:
nop2:
nop:
nop/myreceiver:
exporters:
nop2:
nop:
nop/myexporter:
processors:
nop:
nop/myprocessor:
extensions:
nop2:
nop:
nop/myextension:
service:
extensions: [nop, nop2]
pipelines:
traces:
receivers: [nop, nop2]
processors: [nop2]
exporters: [nop, nop2]
- name: merge-mode-append
configs:
-
receivers:
nop:
key: val
processors:
nop:
exporters:
nop:
key: 2
extensions:
nop:
service:
extensions: [nop]
pipelines:
traces:
receivers: [nop]
processors: [nop]
exporters: [nop]
-
receivers:
nop2:
exporters:
nop2:
nop:
key: updated_value
extensions:
nop2:
service:
extensions: [nop2]
pipelines:
traces:
receivers: [nop2]
processors: [nop2]
exporters: [nop2]
expected:
receivers:
nop2:
nop:
key: val
exporters:
nop2:
nop:
key: updated_value
processors:
nop:
extensions:
nop2:
nop:
service:
extensions: [nop, nop2]
pipelines:
traces:
receivers: [nop, nop2]
processors: [nop2]
exporters: [nop, nop2]
- name: merge-mode-append-override-old-values
configs:
-
receivers:
nop:
key1: "value"
key2: 1
processors:
nop:
exporters:
nop:
extensions:
nop:
ext:
key: 1
service:
extensions: [nop, ext]
pipelines:
traces:
receivers: [nop]
processors: [nop]
exporters: [nop]
-
receivers:
nop2:
exporters:
nop2:
extensions:
ext:
key: 2
service:
extensions: [ext]
pipelines:
traces:
receivers: [nop2]
exporters: [nop2]
expected:
receivers:
nop:
key1: "value"
key2: 1
nop2:
exporters:
nop2:
nop:
processors:
nop:
extensions:
nop:
ext:
key: 2
service:
extensions: [nop, ext]
pipelines:
traces:
receivers: [nop, nop2]
processors: [nop]
exporters: [nop, nop2]
- name: merge-mode-append-name-aware
configs:
-
receivers:
nop:
exporters:
nop:
extensions:
nop:
ext:
ext2:
service:
extensions: [nop, ext, ext2]
pipelines:
traces:
receivers: [nop]
exporters: [nop]
-
receivers:
nop:
exporters:
nop:
extensions:
nop:
key: 1
ext3:
service:
extensions: [nop, ext3]
pipelines:
traces:
receivers: [nop]
exporters: [nop]
expected:
receivers:
nop:
exporters:
nop:
extensions:
nop:
key: 1
ext:
ext2:
ext3:
service:
extensions: [nop, ext, ext2, ext3]
pipelines:
traces:
receivers: [nop]
exporters: [nop]
- name: merge-mode-append-multiple
configs:
-
receivers:
nop:
exporters:
nop:
extensions:
nop:
service:
extensions: [nop]
pipelines:
traces:
receivers: [nop]
exporters: [nop]
-
receivers:
nop2:
exporters:
nop2:
extensions:
nop2:
service:
extensions: [nop2]
pipelines:
traces:
receivers: [nop2]
exporters: [nop2]
-
receivers:
nop3:
exporters:
nop3:
extensions:
nop3:
service:
extensions: [nop3]
pipelines:
traces:
receivers: [nop3]
exporters: [nop3]
expected:
receivers:
nop:
nop2:
nop3:
exporters:
nop:
nop2:
nop3:
extensions:
nop:
nop2:
nop3:
service:
extensions: [nop, nop2, nop3]
pipelines:
traces:
receivers: [nop, nop2, nop3]
exporters: [nop, nop2, nop3]
- name: merge-mode-append-processor-service
configs:
-
receivers:
nop:
exporters:
nop:
extensions:
nop:
processors:
processor:
path: [path]
service:
extensions: [nop]
pipelines:
traces:
receivers: [nop]
processors: [processor]
exporters: [nop]
-
receivers:
nop2:
exporters:
nop2:
extensions:
nop2:
processors:
processor:
path: [path2]
service:
extensions: [nop2]
pipelines:
traces:
receivers: [nop2]
processors: [processor]
exporters: [nop2]
expected:
receivers:
nop:
nop2:
exporters:
nop:
nop2:
extensions:
nop:
nop2:
processors:
processor:
path: [path2]
service:
extensions: [nop, nop2]
pipelines:
traces:
receivers: [nop, nop2]
processors: [processor]
exporters: [nop, nop2]
- name: merge-mode-append-entire-config
configs:
-
receivers:
nop:
exporters:
nop:
extensions:
nop:
processors:
processor:
path: [path]
service:
extensions: [nop]
pipelines:
traces:
receivers: [nop]
processors: [processor]
exporters: [nop]
-
receivers:
nop2:
exporters:
nop2:
extensions:
nop2:
processors:
processor:
path: [path2]
service:
extensions: [nop2]
pipelines:
traces:
receivers: [nop2]
processors: [processor]
exporters: [nop2]
-
receivers:
nop3:
exporters:
nop3:
extensions:
nop3:
processors:
processor:
path: [path3]
processor2:
service:
extensions: [nop3]
pipelines:
traces:
receivers: [nop3]
processors: [processor, processor2]
exporters: [nop3]
expected:
receivers:
nop:
nop2:
nop3:
exporters:
nop:
nop2:
nop3:
extensions:
nop:
nop2:
nop3:
processors:
processor:
path: [path3]
processor2:
service:
extensions: [nop, nop2, nop3]
pipelines:
traces:
receivers: [nop, nop2, nop3]
processors: [processor, processor2]
exporters: [nop, nop2, nop3]
- name: merge-mode-append-different-kinds
configs:
-
receivers:
nop:
key: val
processors:
nop:
exporters:
nop:
key: 2
extensions:
nop:
service:
extensions: [nop]
pipelines:
traces:
receivers: [nop]
processors: [nop]
exporters: [nop]
-
receivers:
nop:
key: 1.2
expected:
receivers:
nop:
key: 1.2
processors:
nop:
exporters:
nop:
key: 2
extensions:
nop:
service:
extensions: [nop]
pipelines:
traces:
receivers: [nop]
processors: [nop]
exporters: [nop]
- name: merge-mode-multiple-pipelines
configs:
-
receivers:
nop:
key: val
processors:
nop:
key: val
exporters:
nop:
key: 2
extensions:
nop:
service:
extensions: [nop]
pipelines:
traces:
receivers: [nop]
processors: [attributes/example]
exporters: [nop]
logs:
receivers: [nop]
processors: [attributes/example]
exporters: [nop]
-
receivers:
nop1:
key: val
processors:
nop1:
key: val
exporters:
nop1:
key: 2
extensions:
nop1:
service:
extensions: [nop1]
pipelines:
traces:
receivers: [nop1]
processors: [nop1]
exporters: [nop1]
logs:
receivers: [nop1]
processors: [nop1]
exporters: [nop1]
expected:
receivers:
nop:
key: val
nop1:
key: val
processors:
nop:
key: val
nop1:
key: val
exporters:
nop:
key: 2
nop1:
key: 2
extensions:
nop:
nop1:
service:
extensions: [nop, nop1]
pipelines:
traces:
receivers: [nop, nop1]
processors: [nop1]
exporters: [nop, nop1]
logs:
receivers: [nop, nop1]
processors: [nop1]
exporters: [nop, nop1]
- name: merge-mode-map
configs:
-
processors:
resource:
attributes:
- key: deployment.region
value: "nl"
action: upsert
-
processors:
resource:
attributes:
- key: app
value: "foo"
action: upsert
expected:
processors:
resource:
attributes:
# TODO: once merge append mode is configurable, comment this out.
# - key: deployment.region
# value: "nl"
# action: upsert
- key: app
value: "foo"
action: upsert
================================================
FILE: confmap/xconfmap/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: confmap/xconfmap/config.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xconfmap // import "go.opentelemetry.io/collector/confmap/xconfmap"
import (
"errors"
"fmt"
"reflect"
"strconv"
"strings"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/internal"
)
// As interface types are only used for static typing, a common idiom to find the reflection Type
// for an interface type Foo is to use a *Foo value.
var configValidatorType = reflect.TypeFor[Validator]()
// Validator defines an optional interface for configurations to implement to do validation.
type Validator interface {
// Validate the configuration and returns an error if invalid.
Validate() error
}
// Validate validates a config, by doing this:
// - Call Validate on the config itself if the config implements ConfigValidator.
func Validate(cfg any) error {
var err error
for _, validationErr := range validate(reflect.ValueOf(cfg)) {
err = errors.Join(err, validationErr)
}
return err
}
type pathError struct {
err error
path []string
}
func (pe pathError) Error() string {
if len(pe.path) > 0 {
var path string
sb := strings.Builder{}
_, _ = sb.WriteString(pe.path[len(pe.path)-1])
for i := len(pe.path) - 2; i >= 0; i-- {
_, _ = sb.WriteString(confmap.KeyDelimiter)
_, _ = sb.WriteString(pe.path[i])
}
path = sb.String()
return fmt.Sprintf("%s: %s", path, pe.err)
}
return pe.err.Error()
}
func (pe pathError) Unwrap() error {
return pe.err
}
func validate(v reflect.Value) []pathError {
errs := []pathError{}
// Validate the value itself.
switch v.Kind() {
case reflect.Invalid:
return nil
case reflect.Ptr, reflect.Interface:
return validate(v.Elem())
case reflect.Struct:
err := callValidateIfPossible(v)
if err != nil {
errs = append(errs, pathError{err: err})
}
// Reflect on the pointed data and check each of its fields.
for i := 0; i < v.NumField(); i++ {
if !v.Type().Field(i).IsExported() {
continue
}
field := v.Type().Field(i)
path := fieldName(field)
subpathErrs := validate(v.Field(i))
for _, err := range subpathErrs {
errs = append(errs, pathError{
err: err.err,
path: append(err.path, path),
})
}
}
return errs
case reflect.Slice, reflect.Array:
err := callValidateIfPossible(v)
if err != nil {
errs = append(errs, pathError{err: err})
}
// Reflect on the pointed data and check each of its fields.
for i := 0; i < v.Len(); i++ {
subPathErrs := validate(v.Index(i))
for _, err := range subPathErrs {
errs = append(errs, pathError{
err: err.err,
path: append(err.path, strconv.Itoa(i)),
})
}
}
return errs
case reflect.Map:
err := callValidateIfPossible(v)
if err != nil {
errs = append(errs, pathError{err: err})
}
iter := v.MapRange()
for iter.Next() {
keyErrs := validate(iter.Key())
valueErrs := validate(iter.Value())
key := stringifyMapKey(iter.Key())
for _, err := range keyErrs {
errs = append(errs, pathError{err: err.err, path: append(err.path, key)})
}
for _, err := range valueErrs {
errs = append(errs, pathError{err: err.err, path: append(err.path, key)})
}
}
return errs
default:
err := callValidateIfPossible(v)
if err != nil {
return []pathError{{err: err}}
}
return nil
}
}
func callValidateIfPossible(v reflect.Value) error {
// If the value type implements ConfigValidator just call Validate
if v.Type().Implements(configValidatorType) {
return v.Interface().(Validator).Validate()
}
// If the pointer type implements ConfigValidator call Validate on the pointer to the current value.
if reflect.PointerTo(v.Type()).Implements(configValidatorType) {
// If not addressable, then create a new *V pointer and set the value to current v.
if !v.CanAddr() {
pv := reflect.New(reflect.PointerTo(v.Type()).Elem())
pv.Elem().Set(v)
v = pv.Elem()
}
return v.Addr().Interface().(Validator).Validate()
}
return nil
}
func fieldName(field reflect.StructField) string {
var fieldName string
if tag, ok := field.Tag.Lookup(confmap.MapstructureTag); ok {
tags := strings.Split(tag, ",")
if len(tags) > 0 {
fieldName = tags[0]
}
}
// Even if the mapstructure tag exists, the field name may not
// be available, so set it if it is still blank.
if fieldName == "" {
fieldName = strings.ToLower(field.Name)
}
return fieldName
}
func stringifyMapKey(val reflect.Value) string {
switch v := val.Interface().(type) {
case string:
return v
case fmt.Stringer:
return v.String()
default:
switch val.Kind() {
case reflect.Ptr, reflect.Interface, reflect.Struct, reflect.Slice, reflect.Array, reflect.Map:
return fmt.Sprintf("[%T key]", val.Interface())
default:
return fmt.Sprintf("%v", val.Interface())
}
}
}
// WithForceUnmarshaler sets an option to run a top-level Unmarshal method,
// even if the Conf being unmarshaled is already a parameter from an Unmarshal method.
// To avoid infinite recursion, this should only be used when unmarshaling into
// a different type from the current Unmarshaler.
// For instance, this should be used in wrapper types such as configoptional.Optional
// to ensure the inner type's Unmarshal method is called.
func WithForceUnmarshaler() confmap.UnmarshalOption {
return internal.WithForceUnmarshaler()
}
================================================
FILE: confmap/xconfmap/config_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xconfmap
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
type configChildStruct struct {
Child errValidateConfig
ChildPtr *errValidateConfig
}
type configChildSlice struct {
Child []errValidateConfig
ChildPtr []*errValidateConfig
}
type configChildMapValue struct {
Child map[string]errValidateConfig
ChildPtr map[string]*errValidateConfig
}
type configChildMapKey struct {
Child map[errType]string
ChildPtr map[*errType]string
}
type configChildTypeDef struct {
Child errType
ChildPtr *errType
}
type config any
type configChildInterface struct {
Child config
}
type errValidateConfig struct {
err error
}
func (e *errValidateConfig) Validate() error {
return e.err
}
type errType string
func (e errType) Validate() error {
if e == "" {
return nil
}
return errors.New(string(e))
}
func newErrType(etStr string) *errType {
et := errType(etStr)
return &et
}
type errMapType map[string]string
func (e errMapType) Validate() error {
return errors.New(e["err"])
}
type structKey struct {
k string
e error
}
func (s structKey) String() string {
return s.k
}
func (s structKey) Validate() error {
return s.e
}
type configChildMapCustomKey struct {
Child map[structKey]errValidateConfig
}
func newErrMapType() *errMapType {
et := errMapType(nil)
return &et
}
type configMapstructure struct {
Valid *errValidateConfig `mapstructure:"validtag,omitempty"`
NoData *errValidateConfig `mapstructure:""`
NoName *errValidateConfig `mapstructure:",remain"`
}
type configDeeplyNested struct {
MapKeyChild map[configChildStruct]string
MapValueChild map[string]configChildStruct
SliceChild []configChildSlice
MapIntKey map[int]errValidateConfig
MapFloatKey map[float64]errValidateConfig
}
type sliceTypeAlias []configChildSlice
func (sliceTypeAlias) Validate() error {
return errors.New("sliceTypeAlias error")
}
func TestValidateConfig(t *testing.T) {
tests := []struct {
name string
cfg any
expected error
}{
{
name: "struct",
cfg: errValidateConfig{err: errors.New("struct")},
expected: errors.New("struct"),
},
{
name: "pointer struct",
cfg: &errValidateConfig{err: errors.New("pointer struct")},
expected: errors.New("pointer struct"),
},
{
name: "type",
cfg: errType("type"),
expected: errors.New("type"),
},
{
name: "pointer child",
cfg: newErrType("pointer type"),
expected: errors.New("pointer type"),
},
{
name: "child interface with nil",
cfg: configChildInterface{},
expected: nil,
},
{
name: "pointer to child interface with nil",
cfg: &configChildInterface{},
expected: nil,
},
{
name: "nil",
cfg: nil,
expected: nil,
},
{
name: "nil map type",
cfg: errMapType(nil),
expected: errors.New(""),
},
{
name: "nil pointer map type",
cfg: newErrMapType(),
expected: errors.New(""),
},
{
name: "child struct",
cfg: configChildStruct{Child: errValidateConfig{err: errors.New("child struct")}},
expected: errors.New("child: child struct"),
},
{
name: "pointer child struct",
cfg: &configChildStruct{Child: errValidateConfig{err: errors.New("pointer child struct")}},
expected: errors.New("child: pointer child struct"),
},
{
name: "child struct pointer",
cfg: &configChildStruct{ChildPtr: &errValidateConfig{err: errors.New("child struct pointer")}},
expected: errors.New("childptr: child struct pointer"),
},
{
name: "child interface",
cfg: configChildInterface{Child: errValidateConfig{err: errors.New("child interface")}},
expected: errors.New("child: child interface"),
},
{
name: "pointer to child interface",
cfg: &configChildInterface{Child: errValidateConfig{err: errors.New("pointer to child interface")}},
expected: errors.New("child: pointer to child interface"),
},
{
name: "child interface with pointer",
cfg: configChildInterface{Child: &errValidateConfig{err: errors.New("child interface with pointer")}},
expected: errors.New("child: child interface with pointer"),
},
{
name: "pointer to child interface with pointer",
cfg: &configChildInterface{Child: &errValidateConfig{err: errors.New("pointer to child interface with pointer")}},
expected: errors.New("child: pointer to child interface with pointer"),
},
{
name: "child slice",
cfg: configChildSlice{Child: []errValidateConfig{{}, {err: errors.New("child slice")}}},
expected: errors.New("child::1: child slice"),
},
{
name: "pointer child slice",
cfg: &configChildSlice{Child: []errValidateConfig{{}, {err: errors.New("pointer child slice")}}},
expected: errors.New("child::1: pointer child slice"),
},
{
name: "child slice pointer",
cfg: &configChildSlice{ChildPtr: []*errValidateConfig{{}, {err: errors.New("child slice pointer")}}},
expected: errors.New("childptr::1: child slice pointer"),
},
{
name: "child map value",
cfg: configChildMapValue{Child: map[string]errValidateConfig{"test": {err: errors.New("child map")}}},
expected: errors.New("child::test: child map"),
},
{
name: "pointer child map value",
cfg: &configChildMapValue{Child: map[string]errValidateConfig{"test": {err: errors.New("pointer child map")}}},
expected: errors.New("child::test: pointer child map"),
},
{
name: "child map value pointer",
cfg: &configChildMapValue{ChildPtr: map[string]*errValidateConfig{"test": {err: errors.New("child map pointer")}}},
expected: errors.New("childptr::test: child map pointer"),
},
{
name: "child map key",
cfg: configChildMapKey{Child: map[errType]string{"child_map_key": ""}},
expected: errors.New("child::child_map_key: child_map_key"),
},
{
name: "pointer child map key",
cfg: &configChildMapKey{Child: map[errType]string{"pointer_child_map_key": ""}},
expected: errors.New("child::pointer_child_map_key: pointer_child_map_key"),
},
{
name: "child map key pointer",
cfg: &configChildMapKey{ChildPtr: map[*errType]string{newErrType("child map key pointer"): ""}},
expected: errors.New("childptr::[*xconfmap.errType key]: child map key pointer"),
},
{
name: "map with stringified non-string key type",
cfg: &configChildMapCustomKey{Child: map[structKey]errValidateConfig{{k: "struct_key", e: errors.New("custom key error")}: {err: errors.New("value error")}}},
expected: errors.New("child::struct_key: custom key error\nchild::struct_key: value error"),
},
{
name: "child type",
cfg: configChildTypeDef{Child: "child type"},
expected: errors.New("child: child type"),
},
{
name: "pointer child type",
cfg: &configChildTypeDef{Child: "pointer child type"},
expected: errors.New("child: pointer child type"),
},
{
name: "child type pointer",
cfg: &configChildTypeDef{ChildPtr: newErrType("child type pointer")},
expected: errors.New("childptr: child type pointer"),
},
{
name: "valid mapstructure tag",
cfg: configMapstructure{Valid: &errValidateConfig{errors.New("test")}},
expected: errors.New("validtag: test"),
},
{
name: "zero-length mapstructure tag",
cfg: configMapstructure{NoData: &errValidateConfig{errors.New("test")}},
expected: errors.New("nodata: test"),
},
{
name: "no field name in mapstructure tag",
cfg: configMapstructure{NoName: &errValidateConfig{errors.New("test")}},
expected: errors.New("noname: test"),
},
{
name: "nested map key error",
cfg: configDeeplyNested{MapKeyChild: map[configChildStruct]string{{Child: errValidateConfig{err: errors.New("child key error")}}: "val"}},
expected: errors.New("mapkeychild::[xconfmap.configChildStruct key]::child: child key error"),
},
{
name: "nested map value error",
cfg: configDeeplyNested{MapValueChild: map[string]configChildStruct{"key": {Child: errValidateConfig{err: errors.New("child key error")}}}},
expected: errors.New("mapvaluechild::key::child: child key error"),
},
{
name: "nested slice value error",
cfg: configDeeplyNested{SliceChild: []configChildSlice{{Child: []errValidateConfig{{err: errors.New("child key error")}}}}},
expected: errors.New("slicechild::0::child::0: child key error"),
},
{
name: "nested map with int key",
cfg: configDeeplyNested{MapIntKey: map[int]errValidateConfig{1: {err: errors.New("int key error")}}},
expected: errors.New("mapintkey::1: int key error"),
},
{
name: "nested map with float key",
cfg: configDeeplyNested{MapFloatKey: map[float64]errValidateConfig{1.2: {err: errors.New("float key error")}}},
expected: errors.New("mapfloatkey::1.2: float key error"),
},
{
name: "slice type alias",
cfg: sliceTypeAlias{},
expected: errors.New("sliceTypeAlias error"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := Validate(tt.cfg)
if tt.expected != nil {
assert.EqualError(t, err, tt.expected.Error())
} else {
assert.NoError(t, err)
}
})
}
}
================================================
FILE: confmap/xconfmap/confmap.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xconfmap // import "go.opentelemetry.io/collector/confmap/xconfmap"
import (
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/internal"
)
// ExpandedValue represents a configuration value that has been expanded from a template
// (e.g., environment variable substitution). It contains both the parsed value and the
// original string representation.
//
// This type is exposed to allow working with configuration values returned by ToStringMapRaw.
type ExpandedValue = internal.ExpandedValue
// ToStringMapRaw returns the raw configuration map without sanitization.
// This is an experimental API and may change or be removed in future versions.
// The returned map may change at any time without prior notice.
//
// Unlike confmap.Conf.ToStringMap(), this function does not sanitize the map
// by removing expandedValue references. This allows for configmap manipulation
// without destroying internal types.
func ToStringMapRaw(conf *confmap.Conf) map[string]any {
return internal.ToStringMapRaw(conf)
}
================================================
FILE: confmap/xconfmap/example_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xconfmap
import (
"errors"
"fmt"
"time"
)
// Config represents the receiver config settings within the collector's config.yaml
type Config struct {
Interval time.Duration `mapstructure:"interval"`
NumberOfTraces int `mapstructure:"number_of_traces"`
}
// Validate checks if the receiver configuration is valid
// this function is automatically called by the collector when it loads the configurations
// for a component
func (cfg *Config) Validate() error {
if cfg.Interval.Minutes() < 1 {
return errors.New("when defined, the interval has to be set to at least 1 minute (1m)")
}
if cfg.NumberOfTraces < 1 {
return errors.New("number_of_traces must be greater or equal to 1")
}
return nil
}
// Example usage validated configuration
func Example() {
// invalid number of traces
myCfg := Config{
Interval: time.Minute,
NumberOfTraces: 0,
}
err := myCfg.Validate()
fmt.Println(err)
// invalid interval
myCfg = Config{
Interval: time.Second,
NumberOfTraces: 1,
}
err = myCfg.Validate()
fmt.Println(err)
// valid config
myCfg = Config{
Interval: time.Minute,
NumberOfTraces: 1,
}
err = myCfg.Validate()
fmt.Println(err)
// Output:
// number_of_traces must be greater or equal to 1
// when defined, the interval has to be set to at least 1 minute (1m)
//
}
================================================
FILE: confmap/xconfmap/go.mod
================================================
module go.opentelemetry.io/collector/confmap/xconfmap
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/confmap v1.54.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/confmap => ../
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
================================================
FILE: confmap/xconfmap/go.sum
================================================
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/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: confmap/xconfmap/metadata.yaml
================================================
type: xconfmap
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: connector/Makefile
================================================
include ../Makefile.Common
================================================
FILE: connector/README.md
================================================
# Connectors
A connector is both an exporter and receiver. As the name suggests a Connector connects
two pipelines: it emits data as an exporter at the end of one pipeline and consumes data
as a receiver at the start of another pipeline. It may consume and emit data of the same data
type, or of different data types. A connector may generate and emit data to summarize the
consumed data, or it may simply replicate or route data.
## Supported Data Types
Each type of connector is designed to work with one or more _pairs_ of data types and may only
be used to connect pipelines accordingly. (Recall that every pipeline is associated with a single
data type, either traces, metrics, or logs.)
For example, the `count` connector counts traces, metrics, and logs, and reports the counts as a
metric. Therefore, it may be used to connect the following types of pipelines.
| [Exporter Pipeline Type] | [Receiver Pipeline Type] |
| ------------------------ | ------------------------ |
| traces | metrics |
| metrics | metrics |
| logs | metrics |
Another example, the `router` connector, is useful for routing data onto the appropriate pipeline
so that it may be processed in distinct ways and/or exported to an appropriate backend. It does not
alter the data it consumes in any ways, nor does it produce any additional data. Therefore, it may be
used to connect the following types of pipelines.
| [Exporter Pipeline Type] | [Receiver Pipeline Type] |
| ------------------------ | ------------------------ |
| traces | traces |
| metrics | metrics |
| logs | logs |
## Configuration
### Declaration
Connectors are defined within a dedicated `connectors` section at the top level of the collector config.
The count connector may be used with default settings.
```yaml
receivers:
foo:
exporters:
bar:
connectors:
count:
router:
```
### Usage
Recall that a connector _is_ an exporter _and_ a receiver and that each connector
MUST be used as both, in separate pipelines.
```yaml
receivers:
foo:
exporters:
bar:
connectors:
count:
service:
pipelines:
traces:
receivers: [foo]
exporters: [count]
metrics:
receivers: [count]
exporters: [bar]
```
Connectors can be used alongside traditional exporters.
```yaml
receivers:
foo:
exporters:
bar/traces_backend:
bar/metrics_backend:
connectors:
count:
service:
pipelines:
traces:
receivers: [foo]
exporters: [bar/traces_backend, count]
metrics:
receivers: [count]
exporters: [bar/metrics_backend]
```
Connectors can be used alongside traditional receivers.
```yaml
receivers:
foo/traces:
foo/metrics:
exporters:
bar:
connectors:
count:
service:
pipelines:
traces:
receivers: [foo/traces]
exporters: [count]
metrics:
receivers: [foo/metrics, count]
exporters: [bar]
```
A connector can be an exporter in multiple pipelines.
```yaml
receivers:
foo/traces:
foo/metrics:
foo/logs:
exporters:
bar/traces_backend:
bar/metrics_backend:
bar/logs_backend:
connectors:
count:
service:
pipelines:
traces:
receivers: [foo/traces]
exporters: [bar/traces_backend, count]
metrics:
receivers: [foo/metrics]
exporters: [bar/metrics_backend, count]
logs:
receivers: [foo/logs]
exporters: [bar/logs_backend, count]
metrics/counts:
receivers: [count]
exporters: [bar/metrics_backend]
```
A connector can be a receiver in multiple pipelines.
```yaml
receivers:
foo/traces:
foo/metrics:
exporters:
bar/traces_backend:
bar/metrics_backend:
bar/metrics_backend/2:
connectors:
count:
service:
pipelines:
traces:
receivers: [foo/traces]
exporters: [bar/traces_backend, count]
metrics:
receivers: [count]
exporters: [bar/metrics_backend]
metrics/2:
receivers: [count]
exporters: [bar/metrics_backend/2]
```
Multiple connectors can be used in sequence.
```yaml
receivers:
foo:
exporters:
bar:
connectors:
count:
count/the_counts:
service:
pipelines:
traces:
receivers: [foo]
exporters: [count]
metrics:
receivers: [count]
exporters: [bar/metrics_backend, count/the_counts]
metrics/count_the_counts:
receivers: [count/the_counts]
exporters: [bar]
```
A connector can only be used in a pair of pipelines when it supports the combination of
[Exporter Pipeline Type] and [Receiver Pipeline Type].
```yaml
receivers:
foo:
exporters:
bar:
connectors:
count:
service:
pipelines:
traces:
receivers: [foo]
exporters: [count]
logs:
receivers: [count] # Invalid. The count connector does not support traces -> logs.
exporters: [bar]
```
#### Exporter Pipeline Type
The type of pipeline in which a connector is used as an exporter.
#### Receiver Pipeline Type
The type of pipeline in which the connector is used as a receiver.
[Exporter Pipeline Type]:#exporter-pipeline-type
[Receiver Pipeline Type]:#receiver-pipeline-type
================================================
FILE: connector/connector.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package connector // import "go.opentelemetry.io/collector/connector"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/connector/internal"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/internal/componentalias"
"go.opentelemetry.io/collector/pipeline"
)
// A Traces connector acts as an exporter from a traces pipeline and a receiver
// to one or more traces, metrics, or logs pipelines.
// Traces feeds a consumer.Traces, consumer.Metrics, or consumer.Logs with data.
//
// Examples:
// - Traces could be collected in one pipeline and routed to another traces pipeline
// based on criteria such as attributes or other content of the trace. The second
// pipeline can then process and export the trace to the appropriate backend.
// - Traces could be summarized by a metrics connector that emits statistics describing
// the number of traces observed.
// - Traces could be analyzed by a logs connector that emits events when particular
// criteria are met.
type Traces interface {
component.Component
consumer.Traces
}
// A Metrics connector acts as an exporter from a metrics pipeline and a receiver
// to one or more traces, metrics, or logs pipelines.
// Metrics feeds a consumer.Traces, consumer.Metrics, or consumer.Logs with data.
//
// Examples:
// - Latency between related data points could be modeled and emitted as traces.
// - Metrics could be collected in one pipeline and routed to another metrics pipeline
// based on criteria such as attributes or other content of the metric. The second
// pipeline can then process and export the metric to the appropriate backend.
// - Metrics could be analyzed by a logs connector that emits events when particular
// criteria are met.
type Metrics interface {
component.Component
consumer.Metrics
}
// A Logs connector acts as an exporter from a logs pipeline and a receiver
// to one or more traces, metrics, or logs pipelines.
// Logs feeds a consumer.Traces, consumer.Metrics, or consumer.Logs with data.
//
// Examples:
// - Structured logs containing span information could be consumed and emitted as traces.
// - Metrics could be extracted from structured logs that contain numeric data.
// - Logs could be collected in one pipeline and routed to another logs pipeline
// based on criteria such as attributes or other content of the log. The second
// pipeline can then process and export the log to the appropriate backend.
type Logs interface {
component.Component
consumer.Logs
}
// Settings configures Connector creators.
type Settings struct {
// ID returns the ID of the component that will be created.
ID component.ID
component.TelemetrySettings
// BuildInfo can be used by components for informational purposes
BuildInfo component.BuildInfo
// prevent unkeyed literal initialization
_ struct{}
}
// Factory is a factory interface for connectors.
//
// This interface cannot be directly implemented. Implementations must
// use the NewFactory to implement it.
type Factory interface {
component.Factory
// CreateDefaultConfig creates the default configuration for the Connector.
// 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 Connector.
// The object returned by this method needs to pass the checks implemented by
// 'configtest.CheckConfigStruct'. It is recommended to have these checks in the
// tests of any implementation of the Factory interface.
CreateDefaultConfig() component.Config
CreateTracesToTraces(ctx context.Context, set Settings, cfg component.Config, next consumer.Traces) (Traces, error)
CreateTracesToMetrics(ctx context.Context, set Settings, cfg component.Config, next consumer.Metrics) (Traces, error)
CreateTracesToLogs(ctx context.Context, set Settings, cfg component.Config, next consumer.Logs) (Traces, error)
CreateMetricsToTraces(ctx context.Context, set Settings, cfg component.Config, next consumer.Traces) (Metrics, error)
CreateMetricsToMetrics(ctx context.Context, set Settings, cfg component.Config, next consumer.Metrics) (Metrics, error)
CreateMetricsToLogs(ctx context.Context, set Settings, cfg component.Config, next consumer.Logs) (Metrics, error)
CreateLogsToTraces(ctx context.Context, set Settings, cfg component.Config, next consumer.Traces) (Logs, error)
CreateLogsToMetrics(ctx context.Context, set Settings, cfg component.Config, next consumer.Metrics) (Logs, error)
CreateLogsToLogs(ctx context.Context, set Settings, cfg component.Config, next consumer.Logs) (Logs, error)
TracesToTracesStability() component.StabilityLevel
TracesToMetricsStability() component.StabilityLevel
TracesToLogsStability() component.StabilityLevel
MetricsToTracesStability() component.StabilityLevel
MetricsToMetricsStability() component.StabilityLevel
MetricsToLogsStability() component.StabilityLevel
LogsToTracesStability() component.StabilityLevel
LogsToMetricsStability() component.StabilityLevel
LogsToLogsStability() component.StabilityLevel
unexportedFactoryFunc()
}
// FactoryOption applies changes to Factory.
type FactoryOption interface {
// apply applies the option.
apply(o *factory)
}
var _ FactoryOption = (*factoryOptionFunc)(nil)
// factoryOptionFunc is an FactoryOption created through a function.
type factoryOptionFunc func(*factory)
func (f factoryOptionFunc) apply(o *factory) {
f(o)
}
// CreateTracesToTracesFunc is the equivalent of Factory.CreateTracesToTraces().
type CreateTracesToTracesFunc func(context.Context, Settings, component.Config, consumer.Traces) (Traces, error)
// CreateTracesToMetricsFunc is the equivalent of Factory.CreateTracesToMetrics().
type CreateTracesToMetricsFunc func(context.Context, Settings, component.Config, consumer.Metrics) (Traces, error)
// CreateTracesToLogsFunc is the equivalent of Factory.CreateTracesToLogs().
type CreateTracesToLogsFunc func(context.Context, Settings, component.Config, consumer.Logs) (Traces, error)
// CreateMetricsToTracesFunc is the equivalent of Factory.CreateMetricsToTraces().
type CreateMetricsToTracesFunc func(context.Context, Settings, component.Config, consumer.Traces) (Metrics, error)
// CreateMetricsToMetricsFunc is the equivalent of Factory.CreateMetricsToTraces().
type CreateMetricsToMetricsFunc func(context.Context, Settings, component.Config, consumer.Metrics) (Metrics, error)
// CreateMetricsToLogsFunc is the equivalent of Factory.CreateMetricsToLogs().
type CreateMetricsToLogsFunc func(context.Context, Settings, component.Config, consumer.Logs) (Metrics, error)
// CreateLogsToTracesFunc is the equivalent of Factory.CreateLogsToTraces().
type CreateLogsToTracesFunc func(context.Context, Settings, component.Config, consumer.Traces) (Logs, error)
// CreateLogsToMetricsFunc is the equivalent of Factory.CreateLogsToMetrics().
type CreateLogsToMetricsFunc func(context.Context, Settings, component.Config, consumer.Metrics) (Logs, error)
// CreateLogsToLogsFunc is the equivalent of Factory.CreateLogsToLogs().
type CreateLogsToLogsFunc func(context.Context, Settings, component.Config, consumer.Logs) (Logs, error)
// WithTracesToTraces overrides the default "error not supported" implementation for WithTracesToTraces and the default "undefined" stability level.
func WithTracesToTraces(createTracesToTraces CreateTracesToTracesFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.tracesToTracesStabilityLevel = sl
o.createTracesToTracesFunc = createTracesToTraces
})
}
// WithTracesToMetrics overrides the default "error not supported" implementation for WithTracesToMetrics and the default "undefined" stability level.
func WithTracesToMetrics(createTracesToMetrics CreateTracesToMetricsFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.tracesToMetricsStabilityLevel = sl
o.createTracesToMetricsFunc = createTracesToMetrics
})
}
// WithTracesToLogs overrides the default "error not supported" implementation for WithTracesToLogs and the default "undefined" stability level.
func WithTracesToLogs(createTracesToLogs CreateTracesToLogsFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.tracesToLogsStabilityLevel = sl
o.createTracesToLogsFunc = createTracesToLogs
})
}
// WithMetricsToTraces overrides the default "error not supported" implementation for WithMetricsToTraces and the default "undefined" stability level.
func WithMetricsToTraces(createMetricsToTraces CreateMetricsToTracesFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.metricsToTracesStabilityLevel = sl
o.createMetricsToTracesFunc = createMetricsToTraces
})
}
// WithMetricsToMetrics overrides the default "error not supported" implementation for WithMetricsToMetrics and the default "undefined" stability level.
func WithMetricsToMetrics(createMetricsToMetrics CreateMetricsToMetricsFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.metricsToMetricsStabilityLevel = sl
o.createMetricsToMetricsFunc = createMetricsToMetrics
})
}
// WithMetricsToLogs overrides the default "error not supported" implementation for WithMetricsToLogs and the default "undefined" stability level.
func WithMetricsToLogs(createMetricsToLogs CreateMetricsToLogsFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.metricsToLogsStabilityLevel = sl
o.createMetricsToLogsFunc = createMetricsToLogs
})
}
// WithLogsToTraces overrides the default "error not supported" implementation for WithLogsToTraces and the default "undefined" stability level.
func WithLogsToTraces(createLogsToTraces CreateLogsToTracesFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.logsToTracesStabilityLevel = sl
o.createLogsToTracesFunc = createLogsToTraces
})
}
// WithLogsToMetrics overrides the default "error not supported" implementation for WithLogsToMetrics and the default "undefined" stability level.
func WithLogsToMetrics(createLogsToMetrics CreateLogsToMetricsFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.logsToMetricsStabilityLevel = sl
o.createLogsToMetricsFunc = createLogsToMetrics
})
}
// WithLogsToLogs overrides the default "error not supported" implementation for WithLogsToLogs and the default "undefined" stability level.
func WithLogsToLogs(createLogsToLogs CreateLogsToLogsFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.logsToLogsStabilityLevel = sl
o.createLogsToLogsFunc = createLogsToLogs
})
}
// factory implements the Factory interface.
type factory struct {
cfgType component.Type
component.CreateDefaultConfigFunc
componentalias.TypeAliasHolder
createTracesToTracesFunc CreateTracesToTracesFunc
createTracesToMetricsFunc CreateTracesToMetricsFunc
createTracesToLogsFunc CreateTracesToLogsFunc
createMetricsToTracesFunc CreateMetricsToTracesFunc
createMetricsToMetricsFunc CreateMetricsToMetricsFunc
createMetricsToLogsFunc CreateMetricsToLogsFunc
createLogsToTracesFunc CreateLogsToTracesFunc
createLogsToMetricsFunc CreateLogsToMetricsFunc
createLogsToLogsFunc CreateLogsToLogsFunc
tracesToTracesStabilityLevel component.StabilityLevel
tracesToMetricsStabilityLevel component.StabilityLevel
tracesToLogsStabilityLevel component.StabilityLevel
metricsToTracesStabilityLevel component.StabilityLevel
metricsToMetricsStabilityLevel component.StabilityLevel
metricsToLogsStabilityLevel component.StabilityLevel
logsToTracesStabilityLevel component.StabilityLevel
logsToMetricsStabilityLevel component.StabilityLevel
logsToLogsStabilityLevel component.StabilityLevel
}
// Type returns the type of component.
func (f *factory) Type() component.Type {
return f.cfgType
}
func (f *factory) unexportedFactoryFunc() {}
func (f *factory) TracesToTracesStability() component.StabilityLevel {
return f.tracesToTracesStabilityLevel
}
func (f *factory) TracesToMetricsStability() component.StabilityLevel {
return f.tracesToMetricsStabilityLevel
}
func (f *factory) TracesToLogsStability() component.StabilityLevel {
return f.tracesToLogsStabilityLevel
}
func (f *factory) MetricsToTracesStability() component.StabilityLevel {
return f.metricsToTracesStabilityLevel
}
func (f *factory) MetricsToMetricsStability() component.StabilityLevel {
return f.metricsToMetricsStabilityLevel
}
func (f *factory) MetricsToLogsStability() component.StabilityLevel {
return f.metricsToLogsStabilityLevel
}
func (f *factory) LogsToTracesStability() component.StabilityLevel {
return f.logsToTracesStabilityLevel
}
func (f *factory) LogsToMetricsStability() component.StabilityLevel {
return f.logsToMetricsStabilityLevel
}
func (f *factory) LogsToLogsStability() component.StabilityLevel {
return f.logsToLogsStabilityLevel
}
func (f *factory) CreateTracesToTraces(ctx context.Context, set Settings, cfg component.Config, next consumer.Traces) (Traces, error) {
if f.createTracesToTracesFunc == nil {
return nil, internal.ErrDataTypes(set.ID, pipeline.SignalTraces, pipeline.SignalTraces)
}
if err := componentalias.ValidateComponentType(f, set.ID); err != nil {
return nil, err
}
return f.createTracesToTracesFunc(ctx, set, cfg, next)
}
func (f *factory) CreateTracesToMetrics(ctx context.Context, set Settings, cfg component.Config, next consumer.Metrics) (Traces, error) {
if f.createTracesToMetricsFunc == nil {
return nil, internal.ErrDataTypes(set.ID, pipeline.SignalTraces, pipeline.SignalMetrics)
}
if err := componentalias.ValidateComponentType(f, set.ID); err != nil {
return nil, err
}
return f.createTracesToMetricsFunc(ctx, set, cfg, next)
}
func (f *factory) CreateTracesToLogs(ctx context.Context, set Settings, cfg component.Config, next consumer.Logs) (Traces, error) {
if f.createTracesToLogsFunc == nil {
return nil, internal.ErrDataTypes(set.ID, pipeline.SignalTraces, pipeline.SignalLogs)
}
if err := componentalias.ValidateComponentType(f, set.ID); err != nil {
return nil, err
}
return f.createTracesToLogsFunc(ctx, set, cfg, next)
}
func (f *factory) CreateMetricsToTraces(ctx context.Context, set Settings, cfg component.Config, next consumer.Traces) (Metrics, error) {
if f.createMetricsToTracesFunc == nil {
return nil, internal.ErrDataTypes(set.ID, pipeline.SignalMetrics, pipeline.SignalTraces)
}
if err := componentalias.ValidateComponentType(f, set.ID); err != nil {
return nil, err
}
return f.createMetricsToTracesFunc(ctx, set, cfg, next)
}
func (f *factory) CreateMetricsToMetrics(ctx context.Context, set Settings, cfg component.Config, next consumer.Metrics) (Metrics, error) {
if f.createMetricsToMetricsFunc == nil {
return nil, internal.ErrDataTypes(set.ID, pipeline.SignalMetrics, pipeline.SignalMetrics)
}
if err := componentalias.ValidateComponentType(f, set.ID); err != nil {
return nil, err
}
return f.createMetricsToMetricsFunc(ctx, set, cfg, next)
}
func (f *factory) CreateMetricsToLogs(ctx context.Context, set Settings, cfg component.Config, next consumer.Logs) (Metrics, error) {
if f.createMetricsToLogsFunc == nil {
return nil, internal.ErrDataTypes(set.ID, pipeline.SignalMetrics, pipeline.SignalLogs)
}
if err := componentalias.ValidateComponentType(f, set.ID); err != nil {
return nil, err
}
return f.createMetricsToLogsFunc(ctx, set, cfg, next)
}
func (f *factory) CreateLogsToTraces(ctx context.Context, set Settings, cfg component.Config, next consumer.Traces) (Logs, error) {
if f.createLogsToTracesFunc == nil {
return nil, internal.ErrDataTypes(set.ID, pipeline.SignalLogs, pipeline.SignalTraces)
}
if err := componentalias.ValidateComponentType(f, set.ID); err != nil {
return nil, err
}
return f.createLogsToTracesFunc(ctx, set, cfg, next)
}
func (f *factory) CreateLogsToMetrics(ctx context.Context, set Settings, cfg component.Config, next consumer.Metrics) (Logs, error) {
if f.createLogsToMetricsFunc == nil {
return nil, internal.ErrDataTypes(set.ID, pipeline.SignalLogs, pipeline.SignalMetrics)
}
if err := componentalias.ValidateComponentType(f, set.ID); err != nil {
return nil, err
}
return f.createLogsToMetricsFunc(ctx, set, cfg, next)
}
func (f *factory) CreateLogsToLogs(ctx context.Context, set Settings, cfg component.Config, next consumer.Logs) (Logs, error) {
if f.createLogsToLogsFunc == nil {
return nil, internal.ErrDataTypes(set.ID, pipeline.SignalLogs, pipeline.SignalLogs)
}
if err := componentalias.ValidateComponentType(f, set.ID); err != nil {
return nil, err
}
return f.createLogsToLogsFunc(ctx, set, cfg, next)
}
// NewFactory returns a Factory.
func NewFactory(cfgType component.Type, createDefaultConfig component.CreateDefaultConfigFunc, options ...FactoryOption) Factory {
f := &factory{
cfgType: cfgType,
CreateDefaultConfigFunc: createDefaultConfig,
TypeAliasHolder: componentalias.NewTypeAliasHolder(),
}
for _, opt := range options {
opt.apply(f)
}
return f
}
================================================
FILE: connector/connector_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package connector // import "go.opentelemetry.io/collector/connector"
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/connector/internal"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/pipeline"
)
var (
testType = component.MustNewType("test")
testID = component.MustNewIDWithName("test", "name")
)
func TestNewFactoryNoOptions(t *testing.T) {
defaultCfg := struct{}{}
factory := NewFactory(testType, func() component.Config { return &defaultCfg })
assert.Equal(t, testType, factory.Type())
assert.EqualValues(t, &defaultCfg, factory.CreateDefaultConfig())
_, err := factory.CreateTracesToTraces(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
assert.Equal(t, err, internal.ErrDataTypes(testID, pipeline.SignalTraces, pipeline.SignalTraces))
_, err = factory.CreateTracesToMetrics(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
assert.Equal(t, err, internal.ErrDataTypes(testID, pipeline.SignalTraces, pipeline.SignalMetrics))
_, err = factory.CreateTracesToLogs(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
assert.Equal(t, err, internal.ErrDataTypes(testID, pipeline.SignalTraces, pipeline.SignalLogs))
_, err = factory.CreateMetricsToTraces(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
assert.Equal(t, err, internal.ErrDataTypes(testID, pipeline.SignalMetrics, pipeline.SignalTraces))
_, err = factory.CreateMetricsToMetrics(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
assert.Equal(t, err, internal.ErrDataTypes(testID, pipeline.SignalMetrics, pipeline.SignalMetrics))
_, err = factory.CreateMetricsToLogs(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
assert.Equal(t, err, internal.ErrDataTypes(testID, pipeline.SignalMetrics, pipeline.SignalLogs))
_, err = factory.CreateLogsToTraces(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
assert.Equal(t, err, internal.ErrDataTypes(testID, pipeline.SignalLogs, pipeline.SignalTraces))
_, err = factory.CreateLogsToMetrics(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
assert.Equal(t, err, internal.ErrDataTypes(testID, pipeline.SignalLogs, pipeline.SignalMetrics))
_, err = factory.CreateLogsToLogs(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
assert.Equal(t, err, internal.ErrDataTypes(testID, pipeline.SignalLogs, pipeline.SignalLogs))
}
func TestNewFactoryWithSameTypes(t *testing.T) {
defaultCfg := struct{}{}
factory := NewFactory(testType, func() component.Config { return &defaultCfg },
WithTracesToTraces(createTracesToTraces, component.StabilityLevelAlpha),
WithMetricsToMetrics(createMetricsToMetrics, component.StabilityLevelBeta),
WithLogsToLogs(createLogsToLogs, component.StabilityLevelUnmaintained))
assert.Equal(t, testType, factory.Type())
assert.EqualValues(t, &defaultCfg, factory.CreateDefaultConfig())
wrongID := component.MustNewID("wrong")
wrongIDErrStr := internal.ErrIDMismatch(wrongID, testType).Error()
assert.Equal(t, component.StabilityLevelAlpha, factory.TracesToTracesStability())
_, err := factory.CreateTracesToTraces(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
require.NoError(t, err)
_, err = factory.CreateTracesToTraces(context.Background(), Settings{ID: wrongID}, &defaultCfg, consumertest.NewNop())
require.ErrorContains(t, err, wrongIDErrStr)
assert.Equal(t, component.StabilityLevelBeta, factory.MetricsToMetricsStability())
_, err = factory.CreateMetricsToMetrics(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
require.NoError(t, err)
_, err = factory.CreateMetricsToMetrics(context.Background(), Settings{ID: wrongID}, &defaultCfg, consumertest.NewNop())
require.ErrorContains(t, err, wrongIDErrStr)
assert.Equal(t, component.StabilityLevelUnmaintained, factory.LogsToLogsStability())
_, err = factory.CreateLogsToLogs(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
require.NoError(t, err)
_, err = factory.CreateLogsToLogs(context.Background(), Settings{ID: wrongID}, &defaultCfg, consumertest.NewNop())
require.ErrorContains(t, err, wrongIDErrStr)
_, err = factory.CreateTracesToMetrics(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
assert.Equal(t, err, internal.ErrDataTypes(testID, pipeline.SignalTraces, pipeline.SignalMetrics))
_, err = factory.CreateTracesToLogs(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
assert.Equal(t, err, internal.ErrDataTypes(testID, pipeline.SignalTraces, pipeline.SignalLogs))
_, err = factory.CreateMetricsToTraces(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
assert.Equal(t, err, internal.ErrDataTypes(testID, pipeline.SignalMetrics, pipeline.SignalTraces))
_, err = factory.CreateMetricsToLogs(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
assert.Equal(t, err, internal.ErrDataTypes(testID, pipeline.SignalMetrics, pipeline.SignalLogs))
_, err = factory.CreateLogsToTraces(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
assert.Equal(t, err, internal.ErrDataTypes(testID, pipeline.SignalLogs, pipeline.SignalTraces))
_, err = factory.CreateLogsToMetrics(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
assert.Equal(t, err, internal.ErrDataTypes(testID, pipeline.SignalLogs, pipeline.SignalMetrics))
}
func TestNewFactoryWithTranslateTypes(t *testing.T) {
defaultCfg := struct{}{}
factory := NewFactory(testType, func() component.Config { return &defaultCfg },
WithTracesToMetrics(createTracesToMetrics, component.StabilityLevelDevelopment),
WithTracesToLogs(createTracesToLogs, component.StabilityLevelAlpha),
WithMetricsToTraces(createMetricsToTraces, component.StabilityLevelBeta),
WithMetricsToLogs(createMetricsToLogs, component.StabilityLevelStable),
WithLogsToTraces(createLogsToTraces, component.StabilityLevelDeprecated),
WithLogsToMetrics(createLogsToMetrics, component.StabilityLevelUnmaintained))
assert.Equal(t, testType, factory.Type())
assert.EqualValues(t, &defaultCfg, factory.CreateDefaultConfig())
_, err := factory.CreateTracesToTraces(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
assert.Equal(t, err, internal.ErrDataTypes(testID, pipeline.SignalTraces, pipeline.SignalTraces))
_, err = factory.CreateMetricsToMetrics(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
assert.Equal(t, err, internal.ErrDataTypes(testID, pipeline.SignalMetrics, pipeline.SignalMetrics))
_, err = factory.CreateLogsToLogs(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
assert.Equal(t, err, internal.ErrDataTypes(testID, pipeline.SignalLogs, pipeline.SignalLogs))
assert.Equal(t, component.StabilityLevelDevelopment, factory.TracesToMetricsStability())
_, err = factory.CreateTracesToMetrics(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
require.NoError(t, err)
assert.Equal(t, component.StabilityLevelAlpha, factory.TracesToLogsStability())
_, err = factory.CreateTracesToLogs(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
require.NoError(t, err)
assert.Equal(t, component.StabilityLevelBeta, factory.MetricsToTracesStability())
_, err = factory.CreateMetricsToTraces(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
require.NoError(t, err)
assert.Equal(t, component.StabilityLevelStable, factory.MetricsToLogsStability())
_, err = factory.CreateMetricsToLogs(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
require.NoError(t, err)
assert.Equal(t, component.StabilityLevelDeprecated, factory.LogsToTracesStability())
_, err = factory.CreateLogsToTraces(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
require.NoError(t, err)
assert.Equal(t, component.StabilityLevelUnmaintained, factory.LogsToMetricsStability())
_, err = factory.CreateLogsToMetrics(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
assert.NoError(t, err)
}
func TestNewFactoryWithAllTypes(t *testing.T) {
defaultCfg := struct{}{}
factory := NewFactory(testType, func() component.Config { return &defaultCfg },
WithTracesToTraces(createTracesToTraces, component.StabilityLevelAlpha),
WithTracesToMetrics(createTracesToMetrics, component.StabilityLevelDevelopment),
WithTracesToLogs(createTracesToLogs, component.StabilityLevelAlpha),
WithMetricsToTraces(createMetricsToTraces, component.StabilityLevelBeta),
WithMetricsToMetrics(createMetricsToMetrics, component.StabilityLevelBeta),
WithMetricsToLogs(createMetricsToLogs, component.StabilityLevelStable),
WithLogsToTraces(createLogsToTraces, component.StabilityLevelDeprecated),
WithLogsToMetrics(createLogsToMetrics, component.StabilityLevelUnmaintained),
WithLogsToLogs(createLogsToLogs, component.StabilityLevelUnmaintained))
assert.Equal(t, testType, factory.Type())
assert.EqualValues(t, &defaultCfg, factory.CreateDefaultConfig())
assert.Equal(t, component.StabilityLevelAlpha, factory.TracesToTracesStability())
_, err := factory.CreateTracesToTraces(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
require.NoError(t, err)
assert.Equal(t, component.StabilityLevelDevelopment, factory.TracesToMetricsStability())
_, err = factory.CreateTracesToMetrics(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
require.NoError(t, err)
assert.Equal(t, component.StabilityLevelAlpha, factory.TracesToLogsStability())
_, err = factory.CreateTracesToLogs(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
require.NoError(t, err)
assert.Equal(t, component.StabilityLevelBeta, factory.MetricsToTracesStability())
_, err = factory.CreateMetricsToTraces(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
require.NoError(t, err)
assert.Equal(t, component.StabilityLevelBeta, factory.MetricsToMetricsStability())
_, err = factory.CreateMetricsToMetrics(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
require.NoError(t, err)
assert.Equal(t, component.StabilityLevelStable, factory.MetricsToLogsStability())
_, err = factory.CreateMetricsToLogs(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
require.NoError(t, err)
assert.Equal(t, component.StabilityLevelDeprecated, factory.LogsToTracesStability())
_, err = factory.CreateLogsToTraces(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
require.NoError(t, err)
assert.Equal(t, component.StabilityLevelUnmaintained, factory.LogsToMetricsStability())
_, err = factory.CreateLogsToMetrics(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
require.NoError(t, err)
assert.Equal(t, component.StabilityLevelUnmaintained, factory.LogsToLogsStability())
_, err = factory.CreateLogsToLogs(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
assert.NoError(t, err)
}
var nopInstance = &nopConnector{
Consumer: consumertest.NewNop(),
}
// nopConnector stores consumed traces and metrics for testing purposes.
type nopConnector struct {
component.StartFunc
component.ShutdownFunc
consumertest.Consumer
}
func createTracesToTraces(context.Context, Settings, component.Config, consumer.Traces) (Traces, error) {
return nopInstance, nil
}
func createTracesToMetrics(context.Context, Settings, component.Config, consumer.Metrics) (Traces, error) {
return nopInstance, nil
}
func createTracesToLogs(context.Context, Settings, component.Config, consumer.Logs) (Traces, error) {
return nopInstance, nil
}
func createMetricsToTraces(context.Context, Settings, component.Config, consumer.Traces) (Metrics, error) {
return nopInstance, nil
}
func createMetricsToMetrics(context.Context, Settings, component.Config, consumer.Metrics) (Metrics, error) {
return nopInstance, nil
}
func createMetricsToLogs(context.Context, Settings, component.Config, consumer.Logs) (Metrics, error) {
return nopInstance, nil
}
func createLogsToTraces(context.Context, Settings, component.Config, consumer.Traces) (Logs, error) {
return nopInstance, nil
}
func createLogsToMetrics(context.Context, Settings, component.Config, consumer.Metrics) (Logs, error) {
return nopInstance, nil
}
func createLogsToLogs(context.Context, Settings, component.Config, consumer.Logs) (Logs, error) {
return nopInstance, nil
}
================================================
FILE: connector/connectortest/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: connector/connectortest/connector.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package connectortest // import "go.opentelemetry.io/collector/connector/connectortest"
import (
"context"
"github.com/google/uuid"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/connector"
"go.opentelemetry.io/collector/connector/xconnector"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/consumer/xconsumer"
)
var NopType = component.MustNewType("nop")
// NewNopSettings returns a new nop settings for Create* functions with the given type.
func NewNopSettings(typ component.Type) connector.Settings {
return connector.Settings{
ID: component.NewIDWithName(typ, uuid.NewString()),
TelemetrySettings: componenttest.NewNopTelemetrySettings(),
BuildInfo: component.NewDefaultBuildInfo(),
}
}
type nopConfig struct{}
// NewNopFactory returns a connector.Factory that constructs nop processors.
func NewNopFactory() connector.Factory {
return xconnector.NewFactory(
NopType,
func() component.Config {
return &nopConfig{}
},
xconnector.WithTracesToTraces(createTracesToTracesConnector, component.StabilityLevelDevelopment),
xconnector.WithTracesToMetrics(createTracesToMetricsConnector, component.StabilityLevelDevelopment),
xconnector.WithTracesToLogs(createTracesToLogsConnector, component.StabilityLevelDevelopment),
xconnector.WithTracesToProfiles(createTracesToProfilesConnector, component.StabilityLevelAlpha),
xconnector.WithMetricsToTraces(createMetricsToTracesConnector, component.StabilityLevelDevelopment),
xconnector.WithMetricsToMetrics(createMetricsToMetricsConnector, component.StabilityLevelDevelopment),
xconnector.WithMetricsToLogs(createMetricsToLogsConnector, component.StabilityLevelDevelopment),
xconnector.WithMetricsToProfiles(createMetricsToProfilesConnector, component.StabilityLevelAlpha),
xconnector.WithLogsToTraces(createLogsToTracesConnector, component.StabilityLevelDevelopment),
xconnector.WithLogsToMetrics(createLogsToMetricsConnector, component.StabilityLevelDevelopment),
xconnector.WithLogsToLogs(createLogsToLogsConnector, component.StabilityLevelDevelopment),
xconnector.WithLogsToProfiles(createLogsToProfilesConnector, component.StabilityLevelAlpha),
xconnector.WithProfilesToTraces(createProfilesToTracesConnector, component.StabilityLevelAlpha),
xconnector.WithProfilesToMetrics(createProfilesToMetricsConnector, component.StabilityLevelAlpha),
xconnector.WithProfilesToLogs(createProfilesToLogsConnector, component.StabilityLevelAlpha),
xconnector.WithProfilesToProfiles(createProfilesToProfilesConnector, component.StabilityLevelAlpha),
)
}
func createTracesToTracesConnector(context.Context, connector.Settings, component.Config, consumer.Traces) (connector.Traces, error) {
return &nopConnector{Consumer: consumertest.NewNop()}, nil
}
func createTracesToMetricsConnector(context.Context, connector.Settings, component.Config, consumer.Metrics) (connector.Traces, error) {
return &nopConnector{Consumer: consumertest.NewNop()}, nil
}
func createTracesToLogsConnector(context.Context, connector.Settings, component.Config, consumer.Logs) (connector.Traces, error) {
return &nopConnector{Consumer: consumertest.NewNop()}, nil
}
func createTracesToProfilesConnector(context.Context, connector.Settings, component.Config, xconsumer.Profiles) (connector.Traces, error) {
return &nopConnector{Consumer: consumertest.NewNop()}, nil
}
func createMetricsToTracesConnector(context.Context, connector.Settings, component.Config, consumer.Traces) (connector.Metrics, error) {
return &nopConnector{Consumer: consumertest.NewNop()}, nil
}
func createMetricsToMetricsConnector(context.Context, connector.Settings, component.Config, consumer.Metrics) (connector.Metrics, error) {
return &nopConnector{Consumer: consumertest.NewNop()}, nil
}
func createMetricsToLogsConnector(context.Context, connector.Settings, component.Config, consumer.Logs) (connector.Metrics, error) {
return &nopConnector{Consumer: consumertest.NewNop()}, nil
}
func createMetricsToProfilesConnector(context.Context, connector.Settings, component.Config, xconsumer.Profiles) (connector.Metrics, error) {
return &nopConnector{Consumer: consumertest.NewNop()}, nil
}
func createLogsToTracesConnector(context.Context, connector.Settings, component.Config, consumer.Traces) (connector.Logs, error) {
return &nopConnector{Consumer: consumertest.NewNop()}, nil
}
func createLogsToMetricsConnector(context.Context, connector.Settings, component.Config, consumer.Metrics) (connector.Logs, error) {
return &nopConnector{Consumer: consumertest.NewNop()}, nil
}
func createLogsToLogsConnector(context.Context, connector.Settings, component.Config, consumer.Logs) (connector.Logs, error) {
return &nopConnector{Consumer: consumertest.NewNop()}, nil
}
func createLogsToProfilesConnector(context.Context, connector.Settings, component.Config, xconsumer.Profiles) (connector.Logs, error) {
return &nopConnector{Consumer: consumertest.NewNop()}, nil
}
func createProfilesToTracesConnector(context.Context, connector.Settings, component.Config, consumer.Traces) (xconnector.Profiles, error) {
return &nopConnector{Consumer: consumertest.NewNop()}, nil
}
func createProfilesToMetricsConnector(context.Context, connector.Settings, component.Config, consumer.Metrics) (xconnector.Profiles, error) {
return &nopConnector{Consumer: consumertest.NewNop()}, nil
}
func createProfilesToLogsConnector(context.Context, connector.Settings, component.Config, consumer.Logs) (xconnector.Profiles, error) {
return &nopConnector{Consumer: consumertest.NewNop()}, nil
}
func createProfilesToProfilesConnector(context.Context, connector.Settings, component.Config, xconsumer.Profiles) (xconnector.Profiles, error) {
return &nopConnector{Consumer: consumertest.NewNop()}, nil
}
// nopConnector stores consumed traces and metrics for testing purposes.
type nopConnector struct {
component.StartFunc
component.ShutdownFunc
consumertest.Consumer
}
================================================
FILE: connector/connectortest/connector_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package connectortest
import (
"context"
"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/connector/xconnector"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/ptrace"
)
func TestNewNopConnectorFactory(t *testing.T) {
factory := NewNopFactory()
require.NotNil(t, factory)
assert.Equal(t, component.MustNewType("nop"), factory.Type())
cfg := factory.CreateDefaultConfig()
assert.Equal(t, &nopConfig{}, cfg)
tracesToTraces, err := factory.CreateTracesToTraces(context.Background(), NewNopSettings(NopType), cfg, consumertest.NewNop())
require.NoError(t, err)
assert.NoError(t, tracesToTraces.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, tracesToTraces.ConsumeTraces(context.Background(), ptrace.NewTraces()))
assert.NoError(t, tracesToTraces.Shutdown(context.Background()))
tracesToMetrics, err := factory.CreateTracesToMetrics(context.Background(), NewNopSettings(NopType), cfg, consumertest.NewNop())
require.NoError(t, err)
assert.NoError(t, tracesToMetrics.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, tracesToMetrics.ConsumeTraces(context.Background(), ptrace.NewTraces()))
assert.NoError(t, tracesToMetrics.Shutdown(context.Background()))
tracesToLogs, err := factory.CreateTracesToLogs(context.Background(), NewNopSettings(NopType), cfg, consumertest.NewNop())
require.NoError(t, err)
assert.NoError(t, tracesToLogs.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, tracesToLogs.ConsumeTraces(context.Background(), ptrace.NewTraces()))
assert.NoError(t, tracesToLogs.Shutdown(context.Background()))
tracesToProfiles, err := factory.(xconnector.Factory).CreateTracesToProfiles(context.Background(), NewNopSettings(NopType), cfg, consumertest.NewNop())
require.NoError(t, err)
assert.NoError(t, tracesToProfiles.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, tracesToProfiles.ConsumeTraces(context.Background(), ptrace.NewTraces()))
assert.NoError(t, tracesToProfiles.Shutdown(context.Background()))
metricsToTraces, err := factory.CreateMetricsToTraces(context.Background(), NewNopSettings(NopType), cfg, consumertest.NewNop())
require.NoError(t, err)
assert.NoError(t, metricsToTraces.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, metricsToTraces.ConsumeMetrics(context.Background(), pmetric.NewMetrics()))
assert.NoError(t, metricsToTraces.Shutdown(context.Background()))
metricsToMetrics, err := factory.CreateMetricsToMetrics(context.Background(), NewNopSettings(NopType), cfg, consumertest.NewNop())
require.NoError(t, err)
assert.NoError(t, metricsToMetrics.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, metricsToMetrics.ConsumeMetrics(context.Background(), pmetric.NewMetrics()))
assert.NoError(t, metricsToMetrics.Shutdown(context.Background()))
metricsToLogs, err := factory.CreateMetricsToLogs(context.Background(), NewNopSettings(NopType), cfg, consumertest.NewNop())
require.NoError(t, err)
assert.NoError(t, metricsToLogs.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, metricsToLogs.ConsumeMetrics(context.Background(), pmetric.NewMetrics()))
assert.NoError(t, metricsToLogs.Shutdown(context.Background()))
metricsToProfiles, err := factory.(xconnector.Factory).CreateMetricsToProfiles(context.Background(), NewNopSettings(NopType), cfg, consumertest.NewNop())
require.NoError(t, err)
assert.NoError(t, metricsToProfiles.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, metricsToProfiles.ConsumeMetrics(context.Background(), pmetric.NewMetrics()))
assert.NoError(t, metricsToProfiles.Shutdown(context.Background()))
logsToTraces, err := factory.CreateLogsToTraces(context.Background(), NewNopSettings(NopType), cfg, consumertest.NewNop())
require.NoError(t, err)
assert.NoError(t, logsToTraces.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, logsToTraces.ConsumeLogs(context.Background(), plog.NewLogs()))
assert.NoError(t, logsToTraces.Shutdown(context.Background()))
logsToMetrics, err := factory.CreateLogsToMetrics(context.Background(), NewNopSettings(NopType), cfg, consumertest.NewNop())
require.NoError(t, err)
assert.NoError(t, logsToMetrics.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, logsToMetrics.ConsumeLogs(context.Background(), plog.NewLogs()))
assert.NoError(t, logsToMetrics.Shutdown(context.Background()))
logsToLogs, err := factory.CreateLogsToLogs(context.Background(), NewNopSettings(NopType), cfg, consumertest.NewNop())
require.NoError(t, err)
assert.NoError(t, logsToLogs.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, logsToLogs.ConsumeLogs(context.Background(), plog.NewLogs()))
assert.NoError(t, logsToLogs.Shutdown(context.Background()))
logsToProfiles, err := factory.(xconnector.Factory).CreateLogsToProfiles(context.Background(), NewNopSettings(NopType), cfg, consumertest.NewNop())
require.NoError(t, err)
assert.NoError(t, logsToProfiles.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, logsToProfiles.ConsumeLogs(context.Background(), plog.NewLogs()))
assert.NoError(t, logsToProfiles.Shutdown(context.Background()))
profilesToTraces, err := factory.(xconnector.Factory).CreateProfilesToTraces(context.Background(), NewNopSettings(NopType), cfg, consumertest.NewNop())
require.NoError(t, err)
assert.NoError(t, profilesToTraces.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, profilesToTraces.ConsumeProfiles(context.Background(), pprofile.NewProfiles()))
assert.NoError(t, profilesToTraces.Shutdown(context.Background()))
profilesToMetrics, err := factory.(xconnector.Factory).CreateProfilesToMetrics(context.Background(), NewNopSettings(NopType), cfg, consumertest.NewNop())
require.NoError(t, err)
assert.NoError(t, profilesToMetrics.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, profilesToMetrics.ConsumeProfiles(context.Background(), pprofile.NewProfiles()))
assert.NoError(t, profilesToMetrics.Shutdown(context.Background()))
profilesToLogs, err := factory.(xconnector.Factory).CreateProfilesToProfiles(context.Background(), NewNopSettings(NopType), cfg, consumertest.NewNop())
require.NoError(t, err)
assert.NoError(t, profilesToLogs.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, profilesToLogs.ConsumeProfiles(context.Background(), pprofile.NewProfiles()))
assert.NoError(t, profilesToLogs.Shutdown(context.Background()))
profilesToProfiles, err := factory.(xconnector.Factory).CreateProfilesToProfiles(context.Background(), NewNopSettings(NopType), cfg, consumertest.NewNop())
require.NoError(t, err)
assert.NoError(t, profilesToProfiles.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, profilesToProfiles.ConsumeProfiles(context.Background(), pprofile.NewProfiles()))
assert.NoError(t, profilesToProfiles.Shutdown(context.Background()))
}
================================================
FILE: connector/connectortest/go.mod
================================================
module go.opentelemetry.io/collector/connector/connectortest
go 1.25.0
require (
github.com/google/uuid v1.6.0
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.opentelemetry.io/collector/connector v0.148.0
go.opentelemetry.io/collector/connector/xconnector v0.148.0
go.opentelemetry.io/collector/consumer v1.54.0
go.opentelemetry.io/collector/consumer/consumertest v0.148.0
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/collector/pdata/pprofile v0.148.0
go.uber.org/goleak v1.3.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
go.opentelemetry.io/collector/internal/fanoutconsumer v0.148.0 // indirect
go.opentelemetry.io/collector/pipeline v1.54.0 // indirect
go.opentelemetry.io/collector/pipeline/xpipeline v0.148.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
golang.org/x/sys v0.41.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest
replace go.opentelemetry.io/collector/connector => ../../connector
replace go.opentelemetry.io/collector/consumer => ../../consumer
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile
replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer
replace go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest
replace go.opentelemetry.io/collector/connector/xconnector => ../xconnector
replace go.opentelemetry.io/collector/pdata/testdata => ../../pdata/testdata
replace go.opentelemetry.io/collector/pipeline => ../../pipeline
replace go.opentelemetry.io/collector/pipeline/xpipeline => ../../pipeline/xpipeline
replace go.opentelemetry.io/collector/internal/fanoutconsumer => ../../internal/fanoutconsumer
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
================================================
FILE: connector/connectortest/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: connector/connectortest/metadata.yaml
================================================
type: connector/connectortest
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: connector/connectortest/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package connectortest
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: connector/forwardconnector/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: connector/forwardconnector/README.md
================================================
# Forward Connector
| Status | |
| ------------- |-----------|
| Distributions | [core], [contrib], [k8s] |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Aconnector%2Fforward) [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aclosed+is%3Aissue+label%3Aconnector%2Fforward) |
[alpha]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#alpha
[beta]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#beta
[core]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
[k8s]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-k8s
## Supported Pipeline Types
| [Exporter Pipeline Type] | [Receiver Pipeline Type] | [Stability Level] |
| ------------------------ | ------------------------ | ----------------- |
| profiles | profiles | [alpha] |
| traces | traces | [beta] |
| metrics | metrics | [beta] |
| logs | logs | [beta] |
[Exporter Pipeline Type]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/connector/README.md#exporter-pipeline-type
[Receiver Pipeline Type]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/connector/README.md#receiver-pipeline-type
[Stability Level]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#stability-levels
The `forward` connector can merge or fork pipelines of the same type.
## Configuration
If you are not already familiar with connectors, you may find it helpful to first visit the [Connectors README].
The `forward` connector does not have any configuration settings.
```yaml
receivers:
foo:
exporters:
bar:
connectors:
forward:
```
### Example Usage
Annotate distinct log streams, then merge them together, and export.
```yaml
receivers:
foo/blue:
foo/green:
processors:
attributes/blue:
attributes/green:
exporters:
bar:
connectors:
forward:
service:
pipelines:
logs/blue:
receivers: [foo/blue]
processors: [attributes/blue]
exporters: [forward]
logs/green:
receivers: [foo/green]
processors: [attributes/green]
exporters: [forward]
logs:
receivers: [forward]
exporters: [bar]
```
Preprocess data, then replicate and handle in distinct ways.
```yaml
receivers:
foo:
processors:
resourcedetection:
sample:
attributes:
exporters:
bar/hot:
bar/cold:
connectors:
forward:
service:
pipelines:
traces:
receivers: [foo]
processors: [resourcedetection]
exporters: [forward]
traces/hot:
receivers: [forward]
processors: [sample]
exporters: [bar/hot]
traces/cold:
receivers: [forward]
processors: [attributes]
exporters: [bar/cold]
```
Add a temporary debugging exporter. (Uncomment to enable.)
```yaml
receivers:
foo:
processors:
filter:
exporters:
bar:
# connectors:
# forward:
service:
pipelines:
traces:
receivers:
- foo
processors:
- filter
exporters:
- bar
# - forward
# traces/log:
# receivers: [forward]
# exporters: [debug]
```
[Connectors README]:../README.md
================================================
FILE: connector/forwardconnector/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:generate mdatagen metadata.yaml
// Package forwardconnector passes signals from one pipeline to another.
package forwardconnector // import "go.opentelemetry.io/collector/connector/forwardconnector"
================================================
FILE: connector/forwardconnector/forward.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package forwardconnector // import "go.opentelemetry.io/collector/connector/forwardconnector"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/connector"
"go.opentelemetry.io/collector/connector/forwardconnector/internal/metadata"
"go.opentelemetry.io/collector/connector/xconnector"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/xconsumer"
)
// NewFactory returns a connector.Factory.
func NewFactory() xconnector.Factory {
return xconnector.NewFactory(
metadata.Type,
createDefaultConfig,
xconnector.WithTracesToTraces(createTracesToTraces, metadata.TracesToTracesStability),
xconnector.WithMetricsToMetrics(createMetricsToMetrics, metadata.MetricsToMetricsStability),
xconnector.WithLogsToLogs(createLogsToLogs, metadata.LogsToLogsStability),
xconnector.WithProfilesToProfiles(createProfilesToProfiles, metadata.ProfilesToProfilesStability),
)
}
type Config struct{}
// createDefaultConfig creates the default configuration.
func createDefaultConfig() component.Config {
return &Config{}
}
// createTracesToTraces creates a trace receiver based on provided config.
func createTracesToTraces(
_ context.Context,
_ connector.Settings,
_ component.Config,
nextConsumer consumer.Traces,
) (connector.Traces, error) {
return &forward{Traces: nextConsumer}, nil
}
// createMetricsToMetrics creates a metrics receiver based on provided config.
func createMetricsToMetrics(
_ context.Context,
_ connector.Settings,
_ component.Config,
nextConsumer consumer.Metrics,
) (connector.Metrics, error) {
return &forward{Metrics: nextConsumer}, nil
}
// createLogsToLogs creates a log receiver based on provided config.
func createLogsToLogs(
_ context.Context,
_ connector.Settings,
_ component.Config,
nextConsumer consumer.Logs,
) (connector.Logs, error) {
return &forward{Logs: nextConsumer}, nil
}
// createProfilesToProfiles creates a profile receiver based on provided config.
func createProfilesToProfiles(
_ context.Context,
_ connector.Settings,
_ component.Config,
nextConsumer xconsumer.Profiles,
) (xconnector.Profiles, error) {
return &forward{Profiles: nextConsumer}, nil
}
// forward is used to pass signals directly from one pipeline to another.
// This is useful when there is a need to replicate data and process it in more
// than one way. It can also be used to join pipelines together.
type forward struct {
consumer.Traces
consumer.Metrics
consumer.Logs
xconsumer.Profiles
component.StartFunc
component.ShutdownFunc
}
func (c *forward) Capabilities() consumer.Capabilities {
return consumer.Capabilities{MutatesData: false}
}
================================================
FILE: connector/forwardconnector/forward_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package forwardconnector
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/connector/connectortest"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/ptrace"
)
func TestForward(t *testing.T) {
f := NewFactory()
cfg := f.CreateDefaultConfig()
assert.Equal(t, &Config{}, cfg)
ctx := context.Background()
set := connectortest.NewNopSettings(f.Type())
host := componenttest.NewNopHost()
tracesSink := new(consumertest.TracesSink)
tracesToTraces, err := f.CreateTracesToTraces(ctx, set, cfg, tracesSink)
require.NoError(t, err)
assert.NotNil(t, tracesToTraces)
metricsSink := new(consumertest.MetricsSink)
metricsToMetrics, err := f.CreateMetricsToMetrics(ctx, set, cfg, metricsSink)
require.NoError(t, err)
assert.NotNil(t, metricsToMetrics)
logsSink := new(consumertest.LogsSink)
logsToLogs, err := f.CreateLogsToLogs(ctx, set, cfg, logsSink)
require.NoError(t, err)
assert.NotNil(t, logsToLogs)
profilesSink := new(consumertest.ProfilesSink)
profilesToProfiles, err := f.CreateProfilesToProfiles(ctx, set, cfg, profilesSink)
require.NoError(t, err)
assert.NotNil(t, profilesToProfiles)
assert.NoError(t, tracesToTraces.Start(ctx, host))
assert.NoError(t, metricsToMetrics.Start(ctx, host))
assert.NoError(t, logsToLogs.Start(ctx, host))
assert.NoError(t, profilesToProfiles.Start(ctx, host))
assert.NoError(t, tracesToTraces.ConsumeTraces(ctx, ptrace.NewTraces()))
assert.NoError(t, metricsToMetrics.ConsumeMetrics(ctx, pmetric.NewMetrics()))
assert.NoError(t, metricsToMetrics.ConsumeMetrics(ctx, pmetric.NewMetrics()))
assert.NoError(t, logsToLogs.ConsumeLogs(ctx, plog.NewLogs()))
assert.NoError(t, logsToLogs.ConsumeLogs(ctx, plog.NewLogs()))
assert.NoError(t, logsToLogs.ConsumeLogs(ctx, plog.NewLogs()))
assert.NoError(t, profilesToProfiles.ConsumeProfiles(ctx, pprofile.NewProfiles()))
assert.NoError(t, tracesToTraces.Shutdown(ctx))
assert.NoError(t, metricsToMetrics.Shutdown(ctx))
assert.NoError(t, logsToLogs.Shutdown(ctx))
assert.NoError(t, profilesToProfiles.Shutdown(ctx))
assert.Len(t, tracesSink.AllTraces(), 1)
assert.Len(t, metricsSink.AllMetrics(), 2)
assert.Len(t, logsSink.AllLogs(), 3)
assert.Len(t, profilesSink.AllProfiles(), 1)
}
================================================
FILE: connector/forwardconnector/generated_component_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package forwardconnector
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/confmap/confmaptest"
"go.opentelemetry.io/collector/connector"
"go.opentelemetry.io/collector/connector/connectortest"
"go.opentelemetry.io/collector/connector/xconnector"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/pipeline/xpipeline"
)
var typ = component.MustNewType("forward")
func TestComponentFactoryType(t *testing.T) {
require.Equal(t, typ, NewFactory().Type())
}
func TestComponentConfigStruct(t *testing.T) {
require.NoError(t, componenttest.CheckConfigStruct(NewFactory().CreateDefaultConfig()))
}
func TestComponentLifecycle(t *testing.T) {
factory := NewFactory()
tests := []struct {
createFn func(ctx context.Context, set connector.Settings, cfg component.Config) (component.Component, error)
name string
}{
{
name: "logs_to_logs",
createFn: func(ctx context.Context, set connector.Settings, cfg component.Config) (component.Component, error) {
router := connector.NewLogsRouter(map[pipeline.ID]consumer.Logs{pipeline.NewID(pipeline.SignalLogs): consumertest.NewNop()})
return factory.CreateLogsToLogs(ctx, set, cfg, router)
},
},
{
name: "metrics_to_metrics",
createFn: func(ctx context.Context, set connector.Settings, cfg component.Config) (component.Component, error) {
router := connector.NewMetricsRouter(map[pipeline.ID]consumer.Metrics{pipeline.NewID(pipeline.SignalMetrics): consumertest.NewNop()})
return factory.CreateMetricsToMetrics(ctx, set, cfg, router)
},
},
{
name: "traces_to_traces",
createFn: func(ctx context.Context, set connector.Settings, cfg component.Config) (component.Component, error) {
router := connector.NewTracesRouter(map[pipeline.ID]consumer.Traces{pipeline.NewID(pipeline.SignalTraces): consumertest.NewNop()})
return factory.CreateTracesToTraces(ctx, set, cfg, router)
},
},
{
name: "profiles_to_profiles",
createFn: func(ctx context.Context, set connector.Settings, cfg component.Config) (component.Component, error) {
router := xconnector.NewProfilesRouter(map[pipeline.ID]xconsumer.Profiles{pipeline.NewID(xpipeline.SignalProfiles): consumertest.NewNop()})
return factory.(xconnector.Factory).CreateProfilesToProfiles(ctx, set, cfg, router)
},
},
}
cm, err := confmaptest.LoadConf("metadata.yaml")
require.NoError(t, err)
cfg := factory.CreateDefaultConfig()
sub, err := cm.Sub("tests::config")
require.NoError(t, err)
require.NoError(t, sub.Unmarshal(&cfg))
for _, tt := range tests {
t.Run(tt.name+"-shutdown", func(t *testing.T) {
c, err := tt.createFn(context.Background(), connectortest.NewNopSettings(typ), cfg)
require.NoError(t, err)
err = c.Shutdown(context.Background())
require.NoError(t, err)
})
t.Run(tt.name+"-lifecycle", func(t *testing.T) {
firstConnector, err := tt.createFn(context.Background(), connectortest.NewNopSettings(typ), cfg)
require.NoError(t, err)
host := newMdatagenNopHost()
require.NoError(t, err)
require.NoError(t, firstConnector.Start(context.Background(), host))
require.NoError(t, firstConnector.Shutdown(context.Background()))
secondConnector, err := tt.createFn(context.Background(), connectortest.NewNopSettings(typ), cfg)
require.NoError(t, err)
require.NoError(t, secondConnector.Start(context.Background(), host))
require.NoError(t, secondConnector.Shutdown(context.Background()))
})
}
}
var _ component.Host = (*mdatagenNopHost)(nil)
type mdatagenNopHost struct{}
func newMdatagenNopHost() component.Host {
return &mdatagenNopHost{}
}
func (mnh *mdatagenNopHost) GetExtensions() map[component.ID]component.Component {
return nil
}
func (mnh *mdatagenNopHost) GetFactory(_ component.Kind, _ component.Type) component.Factory {
return nil
}
================================================
FILE: connector/forwardconnector/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package forwardconnector
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: connector/forwardconnector/go.mod
================================================
module go.opentelemetry.io/collector/connector/forwardconnector
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.opentelemetry.io/collector/confmap v1.54.0
go.opentelemetry.io/collector/connector v0.148.0
go.opentelemetry.io/collector/connector/connectortest v0.148.0
go.opentelemetry.io/collector/connector/xconnector v0.148.0
go.opentelemetry.io/collector/consumer v1.54.0
go.opentelemetry.io/collector/consumer/consumertest v0.148.0
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/collector/pdata/pprofile v0.148.0
go.opentelemetry.io/collector/pipeline v1.54.0
go.opentelemetry.io/collector/pipeline/xpipeline v0.148.0
go.uber.org/goleak v1.3.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
go.opentelemetry.io/collector/internal/fanoutconsumer v0.148.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/sys v0.41.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest
replace go.opentelemetry.io/collector/connector => ../
replace go.opentelemetry.io/collector/connector/connectortest => ../connectortest
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/pdata/testdata => ../../pdata/testdata
replace go.opentelemetry.io/collector/consumer => ../../consumer
replace go.opentelemetry.io/collector/confmap => ../../confmap
retract (
v0.76.0 // Depends on retracted pdata v1.0.0-rc10 module, use v0.76.1
v0.69.0 // Release failed, use v0.69.1
)
replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile
replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer
replace go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest
replace go.opentelemetry.io/collector/connector/xconnector => ../xconnector
replace go.opentelemetry.io/collector/pipeline => ../../pipeline
replace go.opentelemetry.io/collector/pipeline/xpipeline => ../../pipeline/xpipeline
replace go.opentelemetry.io/collector/internal/fanoutconsumer => ../../internal/fanoutconsumer
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
================================================
FILE: connector/forwardconnector/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: connector/forwardconnector/internal/metadata/generated_status.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/component"
)
var (
Type = component.MustNewType("forward")
ScopeName = "go.opentelemetry.io/collector/connector/forwardconnector"
)
const (
ProfilesToProfilesStability = component.StabilityLevelAlpha
TracesToTracesStability = component.StabilityLevelBeta
MetricsToMetricsStability = component.StabilityLevelBeta
LogsToLogsStability = component.StabilityLevelBeta
)
================================================
FILE: connector/forwardconnector/metadata.yaml
================================================
display_name: Forward Connector
type: forward
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: connector
stability:
alpha: [profiles_to_profiles]
beta: [traces_to_traces, metrics_to_metrics, logs_to_logs]
distributions: [core, contrib, k8s]
================================================
FILE: connector/go.mod
================================================
module go.opentelemetry.io/collector/connector
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/consumer v1.54.0
go.opentelemetry.io/collector/consumer/consumertest v0.148.0
go.opentelemetry.io/collector/internal/componentalias v0.148.0
go.opentelemetry.io/collector/internal/fanoutconsumer v0.148.0
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/collector/pdata/testdata v0.148.0
go.opentelemetry.io/collector/pipeline v1.54.0
go.uber.org/goleak v1.3.0
go.uber.org/multierr v1.11.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/pdata/pprofile v0.148.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/zap v1.27.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/component => ../component
replace go.opentelemetry.io/collector/consumer => ../consumer
replace go.opentelemetry.io/collector/pdata => ../pdata
replace go.opentelemetry.io/collector/pdata/testdata => ../pdata/testdata
replace go.opentelemetry.io/collector/pdata/pprofile => ../pdata/pprofile
replace go.opentelemetry.io/collector/consumer/xconsumer => ../consumer/xconsumer
replace go.opentelemetry.io/collector/consumer/consumertest => ../consumer/consumertest
replace go.opentelemetry.io/collector/pipeline => ../pipeline
replace go.opentelemetry.io/collector/internal/componentalias => ../internal/componentalias
replace go.opentelemetry.io/collector/internal/fanoutconsumer => ../internal/fanoutconsumer
replace go.opentelemetry.io/collector/featuregate => ../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../internal/testutil
================================================
FILE: connector/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: connector/internal/factory.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/connector/internal"
import (
"fmt"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pipeline"
)
func ErrDataTypes(id component.ID, from, to pipeline.Signal) error {
return fmt.Errorf("connector %q cannot connect from %s to %s: %w", id, from, to, pipeline.ErrSignalNotSupported)
}
func ErrIDMismatch(id component.ID, typ component.Type) error {
return fmt.Errorf("component type mismatch: component ID %q does not have type %q", id, typ)
}
================================================
FILE: connector/internal/router.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/connector/internal"
import (
"errors"
"fmt"
"maps"
"go.uber.org/multierr"
"go.opentelemetry.io/collector/pipeline"
)
type BaseRouter[T any] struct {
fanout func([]T) T
Consumers map[pipeline.ID]T
}
func NewBaseRouter[T any](fanout func([]T) T, cm map[pipeline.ID]T) BaseRouter[T] {
consumers := make(map[pipeline.ID]T, len(cm))
maps.Copy(consumers, cm)
return BaseRouter[T]{fanout: fanout, Consumers: consumers}
}
func (r *BaseRouter[T]) PipelineIDs() []pipeline.ID {
ids := make([]pipeline.ID, 0, len(r.Consumers))
for id := range r.Consumers {
ids = append(ids, id)
}
return ids
}
func (r *BaseRouter[T]) Consumer(pipelineIDs ...pipeline.ID) (T, error) {
var ret T
if len(pipelineIDs) == 0 {
return ret, errors.New("missing consumers")
}
consumers := make([]T, 0, len(pipelineIDs))
var errors error
for _, pipelineID := range pipelineIDs {
c, ok := r.Consumers[pipelineID]
if ok {
consumers = append(consumers, c)
} else {
errors = multierr.Append(errors, fmt.Errorf("missing consumer: %q", pipelineID))
}
}
if errors != nil {
// TODO potentially this could return a NewTraces with the valid consumers
return ret, errors
}
return r.fanout(consumers), nil
}
================================================
FILE: connector/logs_router.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package connector // import "go.opentelemetry.io/collector/connector"
import (
"errors"
"fmt"
"go.uber.org/multierr"
"go.opentelemetry.io/collector/connector/internal"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/internal/fanoutconsumer"
"go.opentelemetry.io/collector/pipeline"
)
// LogsRouterAndConsumer feeds the first consumer.Logs in each of the specified pipelines.
type LogsRouterAndConsumer interface {
consumer.Logs
Consumer(...pipeline.ID) (consumer.Logs, error)
PipelineIDs() []pipeline.ID
privateFunc()
}
type logsRouter struct {
consumer.Logs
internal.BaseRouter[consumer.Logs]
}
func NewLogsRouter(cm map[pipeline.ID]consumer.Logs) LogsRouterAndConsumer {
consumers := make([]consumer.Logs, 0, len(cm))
for _, cons := range cm {
consumers = append(consumers, cons)
}
return &logsRouter{
Logs: fanoutconsumer.NewLogs(consumers),
BaseRouter: internal.NewBaseRouter(fanoutconsumer.NewLogs, cm),
}
}
func (r *logsRouter) PipelineIDs() []pipeline.ID {
ids := make([]pipeline.ID, 0, len(r.Consumers))
for id := range r.Consumers {
ids = append(ids, id)
}
return ids
}
func (r *logsRouter) Consumer(pipelineIDs ...pipeline.ID) (consumer.Logs, error) {
if len(pipelineIDs) == 0 {
return nil, errors.New("missing consumers")
}
consumers := make([]consumer.Logs, 0, len(pipelineIDs))
var errors error
for _, pipelineID := range pipelineIDs {
c, ok := r.Consumers[pipelineID]
if ok {
consumers = append(consumers, c)
} else {
errors = multierr.Append(errors, fmt.Errorf("missing consumer: %q", pipelineID))
}
}
if errors != nil {
// TODO potentially this could return a NewLogs with the valid consumers
return nil, errors
}
return fanoutconsumer.NewLogs(consumers), nil
}
func (r *logsRouter) privateFunc() {}
================================================
FILE: connector/logs_router_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package connector
import (
"context"
"fmt"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/testdata"
"go.opentelemetry.io/collector/pipeline"
)
type mutatingLogsSink struct {
*consumertest.LogsSink
}
func (mts *mutatingLogsSink) Capabilities() consumer.Capabilities {
return consumer.Capabilities{MutatesData: true}
}
func TestLogsRouterMultiplexing(t *testing.T) {
num := 20
for numIDs := 1; numIDs < num; numIDs++ {
for numCons := 1; numCons < num; numCons++ {
for numLogs := 1; numLogs < num; numLogs++ {
t.Run(
fmt.Sprintf("%d-ids/%d-cons/%d-logs", numIDs, numCons, numLogs),
fuzzLogs(numIDs, numCons, numLogs),
)
}
}
}
}
func fuzzLogs(numIDs, numCons, numLogs int) func(*testing.T) {
return func(t *testing.T) {
allIDs := make([]pipeline.ID, 0, numCons)
allCons := make([]consumer.Logs, 0, numCons)
allConsMap := make(map[pipeline.ID]consumer.Logs)
// If any consumer is mutating, the router must report mutating
for i := range numCons {
allIDs = append(allIDs, pipeline.NewIDWithName(pipeline.SignalLogs, "sink_"+strconv.Itoa(numCons)))
// Random chance for each consumer to be mutating
if (numCons+numLogs+i)%4 == 0 {
allCons = append(allCons, &mutatingLogsSink{LogsSink: new(consumertest.LogsSink)})
} else {
allCons = append(allCons, new(consumertest.LogsSink))
}
allConsMap[allIDs[i]] = allCons[i]
}
r := NewLogsRouter(allConsMap)
ld := testdata.GenerateLogs(1)
// Keep track of how many logs each consumer should receive.
// This will be validated after every call to RouteLogs.
expected := make(map[pipeline.ID]int, numCons)
for i := range numLogs {
// Build a random set of ids (no duplicates)
randCons := make(map[pipeline.ID]bool, numIDs)
for j := range numIDs {
// This number should be pretty random and less than numCons
conNum := (numCons + numIDs + i + j) % numCons
randCons[allIDs[conNum]] = true
}
// Convert to slice, update expectations
conIDs := make([]pipeline.ID, 0, len(randCons))
for id := range randCons {
conIDs = append(conIDs, id)
expected[id]++
}
// Route to list of consumers
fanout, err := r.Consumer(conIDs...)
assert.NoError(t, err)
assert.NoError(t, fanout.ConsumeLogs(context.Background(), ld))
// Validate expectations for all consumers
for id := range expected {
logs := []plog.Logs{}
switch con := allConsMap[id].(type) {
case *consumertest.LogsSink:
logs = con.AllLogs()
case *mutatingLogsSink:
logs = con.AllLogs()
}
assert.Len(t, logs, expected[id])
for n := 0; n < len(logs); n++ {
assert.Equal(t, ld, logs[n])
}
}
}
}
}
func TestLogsRouterConsumers(t *testing.T) {
ctx := context.Background()
ld := testdata.GenerateLogs(1)
fooID := pipeline.NewIDWithName(pipeline.SignalLogs, "foo")
barID := pipeline.NewIDWithName(pipeline.SignalLogs, "bar")
foo := new(consumertest.LogsSink)
bar := new(consumertest.LogsSink)
r := NewLogsRouter(map[pipeline.ID]consumer.Logs{fooID: foo, barID: bar})
rcs := r.PipelineIDs()
assert.Len(t, rcs, 2)
assert.ElementsMatch(t, []pipeline.ID{fooID, barID}, rcs)
assert.Empty(t, foo.AllLogs())
assert.Empty(t, bar.AllLogs())
both, err := r.Consumer(fooID, barID)
assert.NotNil(t, both)
assert.NoError(t, err)
assert.NoError(t, both.ConsumeLogs(ctx, ld))
assert.Len(t, foo.AllLogs(), 1)
assert.Len(t, bar.AllLogs(), 1)
fooOnly, err := r.Consumer(fooID)
assert.NotNil(t, fooOnly)
assert.NoError(t, err)
assert.NoError(t, fooOnly.ConsumeLogs(ctx, ld))
assert.Len(t, foo.AllLogs(), 2)
assert.Len(t, bar.AllLogs(), 1)
barOnly, err := r.Consumer(barID)
assert.NotNil(t, barOnly)
assert.NoError(t, err)
assert.NoError(t, barOnly.ConsumeLogs(ctx, ld))
assert.Len(t, foo.AllLogs(), 2)
assert.Len(t, bar.AllLogs(), 2)
none, err := r.Consumer()
assert.Nil(t, none)
require.Error(t, err)
fake, err := r.Consumer(pipeline.NewIDWithName(pipeline.SignalLogs, "fake"))
assert.Nil(t, fake)
assert.Error(t, err)
}
================================================
FILE: connector/metadata.yaml
================================================
type: connector
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: connector/metrics_router.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package connector // import "go.opentelemetry.io/collector/connector"
import (
"go.opentelemetry.io/collector/connector/internal"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/internal/fanoutconsumer"
"go.opentelemetry.io/collector/pipeline"
)
// MetricsRouterAndConsumer feeds the first consumer.Metrics in each of the specified pipelines.
type MetricsRouterAndConsumer interface {
consumer.Metrics
Consumer(...pipeline.ID) (consumer.Metrics, error)
PipelineIDs() []pipeline.ID
privateFunc()
}
type metricsRouter struct {
consumer.Metrics
internal.BaseRouter[consumer.Metrics]
}
func NewMetricsRouter(cm map[pipeline.ID]consumer.Metrics) MetricsRouterAndConsumer {
consumers := make([]consumer.Metrics, 0, len(cm))
for _, cons := range cm {
consumers = append(consumers, cons)
}
return &metricsRouter{
Metrics: fanoutconsumer.NewMetrics(consumers),
BaseRouter: internal.NewBaseRouter(fanoutconsumer.NewMetrics, cm),
}
}
func (r *metricsRouter) privateFunc() {}
================================================
FILE: connector/metrics_router_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package connector
import (
"context"
"fmt"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/testdata"
"go.opentelemetry.io/collector/pipeline"
)
type mutatingMetricsSink struct {
*consumertest.MetricsSink
}
func (mts *mutatingMetricsSink) Capabilities() consumer.Capabilities {
return consumer.Capabilities{MutatesData: true}
}
func TestMetricsRouterMultiplexing(t *testing.T) {
num := 20
for numIDs := 1; numIDs < num; numIDs++ {
for numCons := 1; numCons < num; numCons++ {
for numMetrics := 1; numMetrics < num; numMetrics++ {
t.Run(
fmt.Sprintf("%d-ids/%d-cons/%d-logs", numIDs, numCons, numMetrics),
fuzzMetrics(numIDs, numCons, numMetrics),
)
}
}
}
}
func fuzzMetrics(numIDs, numCons, numMetrics int) func(*testing.T) {
return func(t *testing.T) {
allIDs := make([]pipeline.ID, 0, numCons)
allCons := make([]consumer.Metrics, 0, numCons)
allConsMap := make(map[pipeline.ID]consumer.Metrics)
// If any consumer is mutating, the router must report mutating
for i := range numCons {
allIDs = append(allIDs, pipeline.NewIDWithName(pipeline.SignalMetrics, "sink_"+strconv.Itoa(numCons)))
// Random chance for each consumer to be mutating
if (numCons+numMetrics+i)%4 == 0 {
allCons = append(allCons, &mutatingMetricsSink{MetricsSink: new(consumertest.MetricsSink)})
} else {
allCons = append(allCons, new(consumertest.MetricsSink))
}
allConsMap[allIDs[i]] = allCons[i]
}
r := NewMetricsRouter(allConsMap)
md := testdata.GenerateMetrics(1)
// Keep track of how many logs each consumer should receive.
// This will be validated after every call to RouteMetrics.
expected := make(map[pipeline.ID]int, numCons)
for i := range numMetrics {
// Build a random set of ids (no duplicates)
randCons := make(map[pipeline.ID]bool, numIDs)
for j := range numIDs {
// This number should be pretty random and less than numCons
conNum := (numCons + numIDs + i + j) % numCons
randCons[allIDs[conNum]] = true
}
// Convert to slice, update expectations
conIDs := make([]pipeline.ID, 0, len(randCons))
for id := range randCons {
conIDs = append(conIDs, id)
expected[id]++
}
// Route to list of consumers
fanout, err := r.Consumer(conIDs...)
assert.NoError(t, err)
assert.NoError(t, fanout.ConsumeMetrics(context.Background(), md))
// Validate expectations for all consumers
for id := range expected {
metrics := []pmetric.Metrics{}
switch con := allConsMap[id].(type) {
case *consumertest.MetricsSink:
metrics = con.AllMetrics()
case *mutatingMetricsSink:
metrics = con.AllMetrics()
}
assert.Len(t, metrics, expected[id])
for n := 0; n < len(metrics); n++ {
assert.Equal(t, md, metrics[n])
}
}
}
}
}
func TestMetricsRouterConsumers(t *testing.T) {
ctx := context.Background()
md := testdata.GenerateMetrics(1)
fooID := pipeline.NewIDWithName(pipeline.SignalMetrics, "foo")
barID := pipeline.NewIDWithName(pipeline.SignalMetrics, "bar")
foo := new(consumertest.MetricsSink)
bar := new(consumertest.MetricsSink)
r := NewMetricsRouter(map[pipeline.ID]consumer.Metrics{fooID: foo, barID: bar})
rcs := r.PipelineIDs()
assert.Len(t, rcs, 2)
assert.ElementsMatch(t, []pipeline.ID{fooID, barID}, rcs)
assert.Empty(t, foo.AllMetrics())
assert.Empty(t, bar.AllMetrics())
both, err := r.Consumer(fooID, barID)
assert.NotNil(t, both)
assert.NoError(t, err)
assert.NoError(t, both.ConsumeMetrics(ctx, md))
assert.Len(t, foo.AllMetrics(), 1)
assert.Len(t, bar.AllMetrics(), 1)
fooOnly, err := r.Consumer(fooID)
assert.NotNil(t, fooOnly)
assert.NoError(t, err)
assert.NoError(t, fooOnly.ConsumeMetrics(ctx, md))
assert.Len(t, foo.AllMetrics(), 2)
assert.Len(t, bar.AllMetrics(), 1)
barOnly, err := r.Consumer(barID)
assert.NotNil(t, barOnly)
assert.NoError(t, err)
assert.NoError(t, barOnly.ConsumeMetrics(ctx, md))
assert.Len(t, foo.AllMetrics(), 2)
assert.Len(t, bar.AllMetrics(), 2)
none, err := r.Consumer()
assert.Nil(t, none)
require.Error(t, err)
fake, err := r.Consumer(pipeline.NewIDWithName(pipeline.SignalMetrics, "fake"))
assert.Nil(t, fake)
assert.Error(t, err)
}
================================================
FILE: connector/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package connector
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: connector/traces_router.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package connector // import "go.opentelemetry.io/collector/connector"
import (
"go.opentelemetry.io/collector/connector/internal"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/internal/fanoutconsumer"
"go.opentelemetry.io/collector/pipeline"
)
// TracesRouterAndConsumer feeds the first consumer.Traces in each of the specified pipelines.
type TracesRouterAndConsumer interface {
consumer.Traces
Consumer(...pipeline.ID) (consumer.Traces, error)
PipelineIDs() []pipeline.ID
privateFunc()
}
type tracesRouter struct {
consumer.Traces
internal.BaseRouter[consumer.Traces]
}
func NewTracesRouter(cm map[pipeline.ID]consumer.Traces) TracesRouterAndConsumer {
consumers := make([]consumer.Traces, 0, len(cm))
for _, cons := range cm {
consumers = append(consumers, cons)
}
return &tracesRouter{
Traces: fanoutconsumer.NewTraces(consumers),
BaseRouter: internal.NewBaseRouter(fanoutconsumer.NewTraces, cm),
}
}
func (r *tracesRouter) privateFunc() {}
================================================
FILE: connector/traces_router_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package connector
import (
"context"
"fmt"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/pdata/testdata"
"go.opentelemetry.io/collector/pipeline"
)
type mutatingTracesSink struct {
*consumertest.TracesSink
}
func (mts *mutatingTracesSink) Capabilities() consumer.Capabilities {
return consumer.Capabilities{MutatesData: true}
}
func TestTracesRouterMultiplexing(t *testing.T) {
num := 20
for numIDs := 1; numIDs < num; numIDs++ {
for numCons := 1; numCons < num; numCons++ {
for numTraces := 1; numTraces < num; numTraces++ {
t.Run(
fmt.Sprintf("%d-ids/%d-cons/%d-logs", numIDs, numCons, numTraces),
fuzzTraces(numIDs, numCons, numTraces),
)
}
}
}
}
func fuzzTraces(numIDs, numCons, numTraces int) func(*testing.T) {
return func(t *testing.T) {
allIDs := make([]pipeline.ID, 0, numCons)
allCons := make([]consumer.Traces, 0, numCons)
allConsMap := make(map[pipeline.ID]consumer.Traces)
// If any consumer is mutating, the router must report mutating
for i := range numCons {
allIDs = append(allIDs, pipeline.NewIDWithName(pipeline.SignalTraces, "sink_"+strconv.Itoa(numCons)))
// Random chance for each consumer to be mutating
if (numCons+numTraces+i)%4 == 0 {
allCons = append(allCons, &mutatingTracesSink{TracesSink: new(consumertest.TracesSink)})
} else {
allCons = append(allCons, new(consumertest.TracesSink))
}
allConsMap[allIDs[i]] = allCons[i]
}
r := NewTracesRouter(allConsMap)
td := testdata.GenerateTraces(1)
// Keep track of how many logs each consumer should receive.
// This will be validated after every call to RouteTraces.
expected := make(map[pipeline.ID]int, numCons)
for i := range numTraces {
// Build a random set of ids (no duplicates)
randCons := make(map[pipeline.ID]bool, numIDs)
for j := range numIDs {
// This number should be pretty random and less than numCons
conNum := (numCons + numIDs + i + j) % numCons
randCons[allIDs[conNum]] = true
}
// Convert to slice, update expectations
conIDs := make([]pipeline.ID, 0, len(randCons))
for id := range randCons {
conIDs = append(conIDs, id)
expected[id]++
}
// Route to list of consumers
fanout, err := r.Consumer(conIDs...)
assert.NoError(t, err)
assert.NoError(t, fanout.ConsumeTraces(context.Background(), td))
// Validate expectations for all consumers
for id := range expected {
traces := []ptrace.Traces{}
switch con := allConsMap[id].(type) {
case *consumertest.TracesSink:
traces = con.AllTraces()
case *mutatingTracesSink:
traces = con.AllTraces()
}
assert.Len(t, traces, expected[id])
for n := 0; n < len(traces); n++ {
assert.Equal(t, td, traces[n])
}
}
}
}
}
func TestTracesRouterConsumer(t *testing.T) {
ctx := context.Background()
td := testdata.GenerateTraces(1)
fooID := pipeline.NewIDWithName(pipeline.SignalTraces, "foo")
barID := pipeline.NewIDWithName(pipeline.SignalTraces, "bar")
foo := new(consumertest.TracesSink)
bar := new(consumertest.TracesSink)
r := NewTracesRouter(map[pipeline.ID]consumer.Traces{fooID: foo, barID: bar})
rcs := r.PipelineIDs()
assert.Len(t, rcs, 2)
assert.ElementsMatch(t, []pipeline.ID{fooID, barID}, rcs)
assert.Empty(t, foo.AllTraces())
assert.Empty(t, bar.AllTraces())
both, err := r.Consumer(fooID, barID)
assert.NotNil(t, both)
assert.NoError(t, err)
assert.NoError(t, both.ConsumeTraces(ctx, td))
assert.Len(t, foo.AllTraces(), 1)
assert.Len(t, bar.AllTraces(), 1)
fooOnly, err := r.Consumer(fooID)
assert.NotNil(t, fooOnly)
assert.NoError(t, err)
assert.NoError(t, fooOnly.ConsumeTraces(ctx, td))
assert.Len(t, foo.AllTraces(), 2)
assert.Len(t, bar.AllTraces(), 1)
barOnly, err := r.Consumer(barID)
assert.NotNil(t, barOnly)
assert.NoError(t, err)
assert.NoError(t, barOnly.ConsumeTraces(ctx, td))
assert.Len(t, foo.AllTraces(), 2)
assert.Len(t, bar.AllTraces(), 2)
none, err := r.Consumer()
assert.Nil(t, none)
require.Error(t, err)
fake, err := r.Consumer(pipeline.NewIDWithName(pipeline.SignalTraces, "fake"))
assert.Nil(t, fake)
assert.Error(t, err)
}
================================================
FILE: connector/xconnector/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: connector/xconnector/connector.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xconnector // import "go.opentelemetry.io/collector/connector/xconnector"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/connector"
"go.opentelemetry.io/collector/connector/internal"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/internal/componentalias"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/pipeline/xpipeline"
)
type Factory interface {
connector.Factory
CreateTracesToProfiles(ctx context.Context, set connector.Settings, cfg component.Config, next xconsumer.Profiles) (connector.Traces, error)
CreateMetricsToProfiles(ctx context.Context, set connector.Settings, cfg component.Config, next xconsumer.Profiles) (connector.Metrics, error)
CreateLogsToProfiles(ctx context.Context, set connector.Settings, cfg component.Config, next xconsumer.Profiles) (connector.Logs, error)
TracesToProfilesStability() component.StabilityLevel
MetricsToProfilesStability() component.StabilityLevel
LogsToProfilesStability() component.StabilityLevel
CreateProfilesToProfiles(ctx context.Context, set connector.Settings, cfg component.Config, next xconsumer.Profiles) (Profiles, error)
CreateProfilesToTraces(ctx context.Context, set connector.Settings, cfg component.Config, next consumer.Traces) (Profiles, error)
CreateProfilesToMetrics(ctx context.Context, set connector.Settings, cfg component.Config, next consumer.Metrics) (Profiles, error)
CreateProfilesToLogs(ctx context.Context, set connector.Settings, cfg component.Config, next consumer.Logs) (Profiles, error)
ProfilesToProfilesStability() component.StabilityLevel
ProfilesToTracesStability() component.StabilityLevel
ProfilesToMetricsStability() component.StabilityLevel
ProfilesToLogsStability() component.StabilityLevel
}
// A Profiles connector acts as an exporter from a profiles pipeline and a receiver
// to one or more traces, metrics, logs, or profiles pipelines.
// Profiles feeds a consumer.Traces, consumer.Metrics, consumer.Logs, or xconsumer.Profiles with data.
//
// Examples:
// - Profiles could be collected in one pipeline and routed to another profiles pipeline
// based on criteria such as attributes or other content of the profile. The second
// pipeline can then process and export the profile to the appropriate backend.
// - Profiles could be summarized by a metrics connector that emits statistics describing
// the number of profiles observed.
// - Profiles could be analyzed by a logs connector that emits events when particular
// criteria are met.
type Profiles interface {
component.Component
xconsumer.Profiles
}
// CreateTracesToProfilesFunc is the equivalent of Factory.CreateTracesToProfiles().
type CreateTracesToProfilesFunc func(context.Context, connector.Settings, component.Config, xconsumer.Profiles) (connector.Traces, error)
// CreateMetricsToProfilesFunc is the equivalent of Factory.CreateMetricsToProfiles().
type CreateMetricsToProfilesFunc func(context.Context, connector.Settings, component.Config, xconsumer.Profiles) (connector.Metrics, error)
// CreateLogsToProfilesFunc is the equivalent of Factory.CreateLogsToProfiles().
type CreateLogsToProfilesFunc func(context.Context, connector.Settings, component.Config, xconsumer.Profiles) (connector.Logs, error)
// CreateProfilesToProfilesFunc is the equivalent of Factory.CreateProfilesToProfiles().
type CreateProfilesToProfilesFunc func(context.Context, connector.Settings, component.Config, xconsumer.Profiles) (Profiles, error)
// CreateProfilesToTracesFunc is the equivalent of Factory.CreateProfilesToTraces().
type CreateProfilesToTracesFunc func(context.Context, connector.Settings, component.Config, consumer.Traces) (Profiles, error)
// CreateProfilesToMetricsFunc is the equivalent of Factory.CreateProfilesToMetrics().
type CreateProfilesToMetricsFunc func(context.Context, connector.Settings, component.Config, consumer.Metrics) (Profiles, error)
// CreateProfilesToLogsFunc is the equivalent of Factory.CreateProfilesToLogs().
type CreateProfilesToLogsFunc func(context.Context, connector.Settings, component.Config, consumer.Logs) (Profiles, error)
// FactoryOption apply changes to ReceiverOptions.
type FactoryOption interface {
// applyOption applies the option.
applyOption(o *factory)
}
// factoryOptionFunc is an ReceiverFactoryOption created through a function.
type factoryOptionFunc func(*factory)
func (f factoryOptionFunc) applyOption(o *factory) {
f(o)
}
// WithTracesToTraces overrides the default "error not supported" implementation for WithTracesToTraces and the default "undefined" stability level.
func WithTracesToTraces(createTracesToTraces connector.CreateTracesToTracesFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.opts = append(o.opts, connector.WithTracesToTraces(createTracesToTraces, sl))
})
}
// WithTracesToMetrics overrides the default "error not supported" implementation for WithTracesToMetrics and the default "undefined" stability level.
func WithTracesToMetrics(createTracesToMetrics connector.CreateTracesToMetricsFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.opts = append(o.opts, connector.WithTracesToMetrics(createTracesToMetrics, sl))
})
}
// WithTracesToLogs overrides the default "error not supported" implementation for WithTracesToLogs and the default "undefined" stability level.
func WithTracesToLogs(createTracesToLogs connector.CreateTracesToLogsFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.opts = append(o.opts, connector.WithTracesToLogs(createTracesToLogs, sl))
})
}
// WithMetricsToTraces overrides the default "error not supported" implementation for WithMetricsToTraces and the default "undefined" stability level.
func WithMetricsToTraces(createMetricsToTraces connector.CreateMetricsToTracesFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.opts = append(o.opts, connector.WithMetricsToTraces(createMetricsToTraces, sl))
})
}
// WithMetricsToMetrics overrides the default "error not supported" implementation for WithMetricsToMetrics and the default "undefined" stability level.
func WithMetricsToMetrics(createMetricsToMetrics connector.CreateMetricsToMetricsFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.opts = append(o.opts, connector.WithMetricsToMetrics(createMetricsToMetrics, sl))
})
}
// WithMetricsToLogs overrides the default "error not supported" implementation for WithMetricsToLogs and the default "undefined" stability level.
func WithMetricsToLogs(createMetricsToLogs connector.CreateMetricsToLogsFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.opts = append(o.opts, connector.WithMetricsToLogs(createMetricsToLogs, sl))
})
}
// WithLogsToTraces overrides the default "error not supported" implementation for WithLogsToTraces and the default "undefined" stability level.
func WithLogsToTraces(createLogsToTraces connector.CreateLogsToTracesFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.opts = append(o.opts, connector.WithLogsToTraces(createLogsToTraces, sl))
})
}
// WithLogsToMetrics overrides the default "error not supported" implementation for WithLogsToMetrics and the default "undefined" stability level.
func WithLogsToMetrics(createLogsToMetrics connector.CreateLogsToMetricsFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.opts = append(o.opts, connector.WithLogsToMetrics(createLogsToMetrics, sl))
})
}
// WithLogsToLogs overrides the default "error not supported" implementation for WithLogsToLogs and the default "undefined" stability level.
func WithLogsToLogs(createLogsToLogs connector.CreateLogsToLogsFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.opts = append(o.opts, connector.WithLogsToLogs(createLogsToLogs, sl))
})
}
// WithTracesToProfiles overrides the default "error not supported" implementation for WithTracesToProfiles and the default "undefined" stability level.
func WithTracesToProfiles(createTracesToProfiles CreateTracesToProfilesFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.tracesToProfilesStabilityLevel = sl
o.createTracesToProfilesFunc = createTracesToProfiles
})
}
// WithMetricsToProfiles overrides the default "error not supported" implementation for WithMetricsToProfiles and the default "undefined" stability level.
func WithMetricsToProfiles(createMetricsToProfiles CreateMetricsToProfilesFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.metricsToProfilesStabilityLevel = sl
o.createMetricsToProfilesFunc = createMetricsToProfiles
})
}
// WithLogsToProfiles overrides the default "error not supported" implementation for WithLogsToProfiles and the default "undefined" stability level.
func WithLogsToProfiles(createLogsToProfiles CreateLogsToProfilesFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.logsToProfilesStabilityLevel = sl
o.createLogsToProfilesFunc = createLogsToProfiles
})
}
// WithProfilesToProfiles overrides the default "error not supported" implementation for WithProfilesToProfiles and the default "undefined" stability level.
func WithProfilesToProfiles(createProfilesToProfiles CreateProfilesToProfilesFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.profilesToProfilesStabilityLevel = sl
o.createProfilesToProfilesFunc = createProfilesToProfiles
})
}
// WithProfilesToTraces overrides the default "error not supported" implementation for WithProfilesToTraces and the default "undefined" stability level.
func WithProfilesToTraces(createProfilesToTraces CreateProfilesToTracesFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.profilesToTracesStabilityLevel = sl
o.createProfilesToTracesFunc = createProfilesToTraces
})
}
// WithProfilesToMetrics overrides the default "error not supported" implementation for WithProfilesToMetrics and the default "undefined" stability level.
func WithProfilesToMetrics(createProfilesToMetrics CreateProfilesToMetricsFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.profilesToMetricsStabilityLevel = sl
o.createProfilesToMetricsFunc = createProfilesToMetrics
})
}
// WithProfilesToLogs overrides the default "error not supported" implementation for WithProfilesToLogs and the default "undefined" stability level.
func WithProfilesToLogs(createProfilesToLogs CreateProfilesToLogsFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.profilesToLogsStabilityLevel = sl
o.createProfilesToLogsFunc = createProfilesToLogs
})
}
// WithDeprecatedTypeAlias configures a deprecated type alias for the connector. Only one alias is supported per connector.
// When the alias is used in configuration, a deprecation warning is automatically logged.
func WithDeprecatedTypeAlias(alias component.Type) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.SetDeprecatedAlias(alias)
})
}
// factory implements the Factory interface.
type factory struct {
connector.Factory
componentalias.TypeAliasHolder
opts []connector.FactoryOption
createTracesToProfilesFunc CreateTracesToProfilesFunc
createMetricsToProfilesFunc CreateMetricsToProfilesFunc
createLogsToProfilesFunc CreateLogsToProfilesFunc
createProfilesToProfilesFunc CreateProfilesToProfilesFunc
createProfilesToTracesFunc CreateProfilesToTracesFunc
createProfilesToMetricsFunc CreateProfilesToMetricsFunc
createProfilesToLogsFunc CreateProfilesToLogsFunc
tracesToProfilesStabilityLevel component.StabilityLevel
metricsToProfilesStabilityLevel component.StabilityLevel
logsToProfilesStabilityLevel component.StabilityLevel
profilesToProfilesStabilityLevel component.StabilityLevel
profilesToTracesStabilityLevel component.StabilityLevel
profilesToMetricsStabilityLevel component.StabilityLevel
profilesToLogsStabilityLevel component.StabilityLevel
}
func (f *factory) TracesToProfilesStability() component.StabilityLevel {
return f.tracesToProfilesStabilityLevel
}
func (f *factory) MetricsToProfilesStability() component.StabilityLevel {
return f.metricsToProfilesStabilityLevel
}
func (f *factory) LogsToProfilesStability() component.StabilityLevel {
return f.logsToProfilesStabilityLevel
}
func (f *factory) ProfilesToProfilesStability() component.StabilityLevel {
return f.profilesToProfilesStabilityLevel
}
func (f *factory) ProfilesToTracesStability() component.StabilityLevel {
return f.profilesToTracesStabilityLevel
}
func (f *factory) ProfilesToMetricsStability() component.StabilityLevel {
return f.profilesToMetricsStabilityLevel
}
func (f *factory) ProfilesToLogsStability() component.StabilityLevel {
return f.profilesToLogsStabilityLevel
}
func (f *factory) CreateTracesToProfiles(ctx context.Context, set connector.Settings, cfg component.Config, next xconsumer.Profiles) (connector.Traces, error) {
if f.createTracesToProfilesFunc == nil {
return nil, internal.ErrDataTypes(set.ID, pipeline.SignalTraces, xpipeline.SignalProfiles)
}
if err := componentalias.ValidateComponentType(f.Factory, set.ID); err != nil {
return nil, err
}
return f.createTracesToProfilesFunc(ctx, set, cfg, next)
}
func (f *factory) CreateMetricsToProfiles(ctx context.Context, set connector.Settings, cfg component.Config, next xconsumer.Profiles) (connector.Metrics, error) {
if f.createMetricsToProfilesFunc == nil {
return nil, internal.ErrDataTypes(set.ID, pipeline.SignalMetrics, xpipeline.SignalProfiles)
}
if err := componentalias.ValidateComponentType(f.Factory, set.ID); err != nil {
return nil, err
}
return f.createMetricsToProfilesFunc(ctx, set, cfg, next)
}
func (f *factory) CreateLogsToProfiles(ctx context.Context, set connector.Settings, cfg component.Config, next xconsumer.Profiles) (connector.Logs, error) {
if f.createLogsToProfilesFunc == nil {
return nil, internal.ErrDataTypes(set.ID, pipeline.SignalLogs, xpipeline.SignalProfiles)
}
if err := componentalias.ValidateComponentType(f.Factory, set.ID); err != nil {
return nil, err
}
return f.createLogsToProfilesFunc(ctx, set, cfg, next)
}
func (f *factory) CreateProfilesToProfiles(ctx context.Context, set connector.Settings, cfg component.Config, next xconsumer.Profiles) (Profiles, error) {
if f.createProfilesToProfilesFunc == nil {
return nil, internal.ErrDataTypes(set.ID, xpipeline.SignalProfiles, xpipeline.SignalProfiles)
}
if err := componentalias.ValidateComponentType(f.Factory, set.ID); err != nil {
return nil, err
}
return f.createProfilesToProfilesFunc(ctx, set, cfg, next)
}
func (f *factory) CreateProfilesToTraces(ctx context.Context, set connector.Settings, cfg component.Config, next consumer.Traces) (Profiles, error) {
if f.createProfilesToTracesFunc == nil {
return nil, internal.ErrDataTypes(set.ID, xpipeline.SignalProfiles, pipeline.SignalTraces)
}
if err := componentalias.ValidateComponentType(f.Factory, set.ID); err != nil {
return nil, err
}
return f.createProfilesToTracesFunc(ctx, set, cfg, next)
}
func (f *factory) CreateProfilesToMetrics(ctx context.Context, set connector.Settings, cfg component.Config, next consumer.Metrics) (Profiles, error) {
if f.createProfilesToMetricsFunc == nil {
return nil, internal.ErrDataTypes(set.ID, xpipeline.SignalProfiles, pipeline.SignalMetrics)
}
if err := componentalias.ValidateComponentType(f.Factory, set.ID); err != nil {
return nil, err
}
return f.createProfilesToMetricsFunc(ctx, set, cfg, next)
}
func (f *factory) CreateProfilesToLogs(ctx context.Context, set connector.Settings, cfg component.Config, next consumer.Logs) (Profiles, error) {
if f.createProfilesToLogsFunc == nil {
return nil, internal.ErrDataTypes(set.ID, xpipeline.SignalProfiles, pipeline.SignalLogs)
}
if err := componentalias.ValidateComponentType(f.Factory, set.ID); err != nil {
return nil, err
}
return f.createProfilesToLogsFunc(ctx, set, cfg, next)
}
// NewFactory creates a wrapped connector.Factory with experimental capabilities
func NewFactory(cfgType component.Type, createDefaultConfig component.CreateDefaultConfigFunc, options ...FactoryOption) Factory {
f := &factory{TypeAliasHolder: componentalias.NewTypeAliasHolder()}
for _, opt := range options {
opt.applyOption(f)
}
f.Factory = connector.NewFactory(cfgType, createDefaultConfig, f.opts...)
f.Factory.(componentalias.TypeAliasHolder).SetDeprecatedAlias(f.DeprecatedAlias())
return f
}
================================================
FILE: connector/xconnector/connector_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xconnector // import "go.opentelemetry.io/collector/connector/xconnector"
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/connector"
"go.opentelemetry.io/collector/connector/internal"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/internal/componentalias"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/pipeline/xpipeline"
)
var (
testType = component.MustNewType("test")
testID = component.MustNewIDWithName(testType.String(), "name")
)
func TestNewFactoryNoOptions(t *testing.T) {
defaultCfg := struct{}{}
factory := NewFactory(testType, func() component.Config { return &defaultCfg })
assert.Equal(t, testType, factory.Type())
assert.EqualValues(t, &defaultCfg, factory.CreateDefaultConfig())
_, err := factory.CreateTracesToProfiles(context.Background(), connector.Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
assert.Equal(t, err, internal.ErrDataTypes(testID, pipeline.SignalTraces, xpipeline.SignalProfiles))
_, err = factory.CreateMetricsToProfiles(context.Background(), connector.Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
assert.Equal(t, err, internal.ErrDataTypes(testID, pipeline.SignalMetrics, xpipeline.SignalProfiles))
_, err = factory.CreateLogsToProfiles(context.Background(), connector.Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
assert.Equal(t, err, internal.ErrDataTypes(testID, pipeline.SignalLogs, xpipeline.SignalProfiles))
_, err = factory.CreateProfilesToTraces(context.Background(), connector.Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
assert.Equal(t, err, internal.ErrDataTypes(testID, xpipeline.SignalProfiles, pipeline.SignalTraces))
_, err = factory.CreateProfilesToMetrics(context.Background(), connector.Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
assert.Equal(t, err, internal.ErrDataTypes(testID, xpipeline.SignalProfiles, pipeline.SignalMetrics))
_, err = factory.CreateProfilesToLogs(context.Background(), connector.Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
assert.Equal(t, err, internal.ErrDataTypes(testID, xpipeline.SignalProfiles, pipeline.SignalLogs))
}
func TestNewFactoryWithSameTypes(t *testing.T) {
defaultCfg := struct{}{}
factory := NewFactory(testType, func() component.Config { return &defaultCfg },
WithProfilesToProfiles(createProfilesToProfiles, component.StabilityLevelAlpha),
)
assert.Equal(t, testType, factory.Type())
assert.EqualValues(t, &defaultCfg, factory.CreateDefaultConfig())
wrongID := component.MustNewID("wrong")
wrongIDErrStr := internal.ErrIDMismatch(wrongID, testType).Error()
assert.Equal(t, component.StabilityLevelAlpha, factory.ProfilesToProfilesStability())
_, err := factory.CreateProfilesToProfiles(context.Background(), connector.Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
require.NoError(t, err)
_, err = factory.CreateProfilesToProfiles(context.Background(), connector.Settings{ID: wrongID}, &defaultCfg, consumertest.NewNop())
require.ErrorContains(t, err, wrongIDErrStr)
_, err = factory.CreateProfilesToTraces(context.Background(), connector.Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
assert.Equal(t, err, internal.ErrDataTypes(testID, xpipeline.SignalProfiles, pipeline.SignalTraces))
_, err = factory.CreateProfilesToMetrics(context.Background(), connector.Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
assert.Equal(t, err, internal.ErrDataTypes(testID, xpipeline.SignalProfiles, pipeline.SignalMetrics))
_, err = factory.CreateProfilesToLogs(context.Background(), connector.Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
assert.Equal(t, err, internal.ErrDataTypes(testID, xpipeline.SignalProfiles, pipeline.SignalLogs))
}
func TestNewFactoryWithTranslateTypes(t *testing.T) {
defaultCfg := struct{}{}
factory := NewFactory(testType, func() component.Config { return &defaultCfg },
WithTracesToProfiles(createTracesToProfiles, component.StabilityLevelBeta),
WithMetricsToProfiles(createMetricsToProfiles, component.StabilityLevelDevelopment),
WithLogsToProfiles(createLogsToProfiles, component.StabilityLevelAlpha),
WithProfilesToTraces(createProfilesToTraces, component.StabilityLevelBeta),
WithProfilesToMetrics(createProfilesToMetrics, component.StabilityLevelDevelopment),
WithProfilesToLogs(createProfilesToLogs, component.StabilityLevelAlpha),
)
assert.Equal(t, testType, factory.Type())
assert.EqualValues(t, &defaultCfg, factory.CreateDefaultConfig())
wrongID := component.MustNewID("wrong")
wrongIDErrStr := internal.ErrIDMismatch(wrongID, testType).Error()
_, err := factory.CreateProfilesToProfiles(context.Background(), connector.Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
assert.Equal(t, err, internal.ErrDataTypes(testID, xpipeline.SignalProfiles, xpipeline.SignalProfiles))
assert.Equal(t, component.StabilityLevelBeta, factory.TracesToProfilesStability())
_, err = factory.CreateTracesToProfiles(context.Background(), connector.Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
require.NoError(t, err)
_, err = factory.CreateTracesToProfiles(context.Background(), connector.Settings{ID: wrongID}, &defaultCfg, consumertest.NewNop())
require.ErrorContains(t, err, wrongIDErrStr)
assert.Equal(t, component.StabilityLevelDevelopment, factory.MetricsToProfilesStability())
_, err = factory.CreateMetricsToProfiles(context.Background(), connector.Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
require.NoError(t, err)
_, err = factory.CreateMetricsToProfiles(context.Background(), connector.Settings{ID: wrongID}, &defaultCfg, consumertest.NewNop())
require.ErrorContains(t, err, wrongIDErrStr)
assert.Equal(t, component.StabilityLevelAlpha, factory.LogsToProfilesStability())
_, err = factory.CreateLogsToProfiles(context.Background(), connector.Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
require.NoError(t, err)
_, err = factory.CreateLogsToProfiles(context.Background(), connector.Settings{ID: wrongID}, &defaultCfg, consumertest.NewNop())
require.ErrorContains(t, err, wrongIDErrStr)
assert.Equal(t, component.StabilityLevelBeta, factory.ProfilesToTracesStability())
_, err = factory.CreateProfilesToTraces(context.Background(), connector.Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
require.NoError(t, err)
_, err = factory.CreateProfilesToTraces(context.Background(), connector.Settings{ID: wrongID}, &defaultCfg, consumertest.NewNop())
require.ErrorContains(t, err, wrongIDErrStr)
assert.Equal(t, component.StabilityLevelDevelopment, factory.ProfilesToMetricsStability())
_, err = factory.CreateProfilesToMetrics(context.Background(), connector.Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
require.NoError(t, err)
_, err = factory.CreateProfilesToMetrics(context.Background(), connector.Settings{ID: wrongID}, &defaultCfg, consumertest.NewNop())
require.ErrorContains(t, err, wrongIDErrStr)
assert.Equal(t, component.StabilityLevelAlpha, factory.ProfilesToLogsStability())
_, err = factory.CreateProfilesToLogs(context.Background(), connector.Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
require.NoError(t, err)
_, err = factory.CreateProfilesToLogs(context.Background(), connector.Settings{ID: wrongID}, &defaultCfg, consumertest.NewNop())
require.ErrorContains(t, err, wrongIDErrStr)
}
var nopInstance = &nopConnector{
Consumer: consumertest.NewNop(),
}
// nopConnector stores consumed traces and metrics for testing purposes.
type nopConnector struct {
component.StartFunc
component.ShutdownFunc
consumertest.Consumer
}
func createTracesToProfiles(context.Context, connector.Settings, component.Config, xconsumer.Profiles) (connector.Traces, error) {
return nopInstance, nil
}
func createMetricsToProfiles(context.Context, connector.Settings, component.Config, xconsumer.Profiles) (connector.Metrics, error) {
return nopInstance, nil
}
func createLogsToProfiles(context.Context, connector.Settings, component.Config, xconsumer.Profiles) (connector.Logs, error) {
return nopInstance, nil
}
func createProfilesToProfiles(context.Context, connector.Settings, component.Config, xconsumer.Profiles) (Profiles, error) {
return nopInstance, nil
}
func createProfilesToTraces(context.Context, connector.Settings, component.Config, consumer.Traces) (Profiles, error) {
return nopInstance, nil
}
func createProfilesToMetrics(context.Context, connector.Settings, component.Config, consumer.Metrics) (Profiles, error) {
return nopInstance, nil
}
func createProfilesToLogs(context.Context, connector.Settings, component.Config, consumer.Logs) (Profiles, error) {
return nopInstance, nil
}
func TestNewFactoryWithDeprecatedAlias(t *testing.T) {
testType := component.MustNewType("newname")
aliasType := component.MustNewType("oldname")
defaultCfg := struct{}{}
f := NewFactory(
testType,
func() component.Config { return &defaultCfg },
WithProfilesToProfiles(createProfilesToProfiles, component.StabilityLevelAlpha),
WithDeprecatedTypeAlias(aliasType),
)
assert.Equal(t, testType, f.Type())
assert.Equal(t, aliasType, f.(*factory).Factory.(componentalias.TypeAliasHolder).DeprecatedAlias())
assert.EqualValues(t, &defaultCfg, f.CreateDefaultConfig())
_, err := f.CreateProfilesToProfiles(context.Background(), connector.Settings{ID: component.MustNewID("newname")}, &defaultCfg, consumertest.NewNop())
require.NoError(t, err)
_, err = f.CreateProfilesToProfiles(context.Background(), connector.Settings{ID: component.MustNewID("oldname")}, &defaultCfg, consumertest.NewNop())
require.NoError(t, err)
_, err = f.CreateProfilesToProfiles(context.Background(), connector.Settings{ID: component.MustNewID("wrongname")}, &defaultCfg, consumertest.NewNop())
require.Error(t, err)
}
================================================
FILE: connector/xconnector/go.mod
================================================
module go.opentelemetry.io/collector/connector/xconnector
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/connector v0.148.0
go.opentelemetry.io/collector/consumer v1.54.0
go.opentelemetry.io/collector/consumer/consumertest v0.148.0
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0
go.opentelemetry.io/collector/internal/componentalias v0.148.0
go.opentelemetry.io/collector/internal/fanoutconsumer v0.148.0
go.opentelemetry.io/collector/pdata/pprofile v0.148.0
go.opentelemetry.io/collector/pdata/testdata v0.148.0
go.opentelemetry.io/collector/pipeline v1.54.0
go.opentelemetry.io/collector/pipeline/xpipeline v0.148.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/pdata v1.54.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/connector => ../
replace go.opentelemetry.io/collector/consumer => ../../consumer
replace go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest
replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile
replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/pdata/testdata => ../../pdata/testdata
replace go.opentelemetry.io/collector/pipeline => ../../pipeline
replace go.opentelemetry.io/collector/pipeline/xpipeline => ../../pipeline/xpipeline
replace go.opentelemetry.io/collector/internal/fanoutconsumer => ../../internal/fanoutconsumer
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
================================================
FILE: connector/xconnector/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: connector/xconnector/metadata.yaml
================================================
type: xconnector
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
codeowners:
active:
- mx-psi
- dmathieu
stability:
alpha: [profiles]
================================================
FILE: connector/xconnector/profiles_router.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xconnector // import "go.opentelemetry.io/collector/connector/xconnector"
import (
"go.opentelemetry.io/collector/connector/internal"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/internal/fanoutconsumer"
"go.opentelemetry.io/collector/pipeline"
)
type ProfilesRouterAndConsumer interface {
xconsumer.Profiles
Consumer(...pipeline.ID) (xconsumer.Profiles, error)
PipelineIDs() []pipeline.ID
privateFunc()
}
type profilesRouter struct {
xconsumer.Profiles
internal.BaseRouter[xconsumer.Profiles]
}
func NewProfilesRouter(cm map[pipeline.ID]xconsumer.Profiles) ProfilesRouterAndConsumer {
consumers := make([]xconsumer.Profiles, 0, len(cm))
for _, cons := range cm {
consumers = append(consumers, cons)
}
return &profilesRouter{
Profiles: fanoutconsumer.NewProfiles(consumers),
BaseRouter: internal.NewBaseRouter(fanoutconsumer.NewProfiles, cm),
}
}
func (r *profilesRouter) privateFunc() {}
================================================
FILE: connector/xconnector/profiles_router_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xconnector
import (
"context"
"fmt"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/testdata"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/pipeline/xpipeline"
)
type mutatingProfilesSink struct {
*consumertest.ProfilesSink
}
func (mts *mutatingProfilesSink) Capabilities() consumer.Capabilities {
return consumer.Capabilities{MutatesData: true}
}
func TestProfilesRouterMultiplexing(t *testing.T) {
num := 20
for numIDs := 1; numIDs < num; numIDs++ {
for numCons := 1; numCons < num; numCons++ {
for numProfiles := 1; numProfiles < num; numProfiles++ {
t.Run(
fmt.Sprintf("%d-ids/%d-cons/%d-logs", numIDs, numCons, numProfiles),
fuzzProfiles(numIDs, numCons, numProfiles),
)
}
}
}
}
func fuzzProfiles(numIDs, numCons, numProfiles int) func(*testing.T) {
return func(t *testing.T) {
allIDs := make([]pipeline.ID, 0, numCons)
allCons := make([]xconsumer.Profiles, 0, numCons)
allConsMap := make(map[pipeline.ID]xconsumer.Profiles)
// If any consumer is mutating, the router must report mutating
for i := range numCons {
allIDs = append(allIDs, pipeline.NewIDWithName(xpipeline.SignalProfiles, "sink_"+strconv.Itoa(numCons)))
// Random chance for each consumer to be mutating
if (numCons+numProfiles+i)%4 == 0 {
allCons = append(allCons, &mutatingProfilesSink{ProfilesSink: new(consumertest.ProfilesSink)})
} else {
allCons = append(allCons, new(consumertest.ProfilesSink))
}
allConsMap[allIDs[i]] = allCons[i]
}
r := NewProfilesRouter(allConsMap)
td := testdata.GenerateProfiles(1)
// Keep track of how many logs each consumer should receive.
// This will be validated after every call to RouteProfiles.
expected := make(map[pipeline.ID]int, numCons)
for i := range numProfiles {
// Build a random set of ids (no duplicates)
randCons := make(map[pipeline.ID]bool, numIDs)
for j := range numIDs {
// This number should be pretty random and less than numCons
conNum := (numCons + numIDs + i + j) % numCons
randCons[allIDs[conNum]] = true
}
// Convert to slice, update expectations
conIDs := make([]pipeline.ID, 0, len(randCons))
for id := range randCons {
conIDs = append(conIDs, id)
expected[id]++
}
// Route to list of consumers
fanout, err := r.Consumer(conIDs...)
assert.NoError(t, err)
assert.NoError(t, fanout.ConsumeProfiles(context.Background(), td))
// Validate expectations for all consumers
for id := range expected {
profiles := []pprofile.Profiles{}
switch con := allConsMap[id].(type) {
case *consumertest.ProfilesSink:
profiles = con.AllProfiles()
case *mutatingProfilesSink:
profiles = con.AllProfiles()
}
assert.Len(t, profiles, expected[id])
for n := 0; n < len(profiles); n++ {
assert.Equal(t, td, profiles[n])
}
}
}
}
}
func TestProfilessRouterConsumer(t *testing.T) {
ctx := context.Background()
td := testdata.GenerateProfiles(1)
fooID := pipeline.NewIDWithName(xpipeline.SignalProfiles, "foo")
barID := pipeline.NewIDWithName(xpipeline.SignalProfiles, "bar")
foo := new(consumertest.ProfilesSink)
bar := new(consumertest.ProfilesSink)
r := NewProfilesRouter(map[pipeline.ID]xconsumer.Profiles{fooID: foo, barID: bar})
rcs := r.PipelineIDs()
assert.Len(t, rcs, 2)
assert.ElementsMatch(t, []pipeline.ID{fooID, barID}, rcs)
assert.Empty(t, foo.AllProfiles())
assert.Empty(t, bar.AllProfiles())
both, err := r.Consumer(fooID, barID)
assert.NotNil(t, both)
assert.NoError(t, err)
assert.NoError(t, both.ConsumeProfiles(ctx, td))
assert.Len(t, foo.AllProfiles(), 1)
assert.Len(t, bar.AllProfiles(), 1)
fooOnly, err := r.Consumer(fooID)
assert.NotNil(t, fooOnly)
assert.NoError(t, err)
assert.NoError(t, fooOnly.ConsumeProfiles(ctx, td))
assert.Len(t, foo.AllProfiles(), 2)
assert.Len(t, bar.AllProfiles(), 1)
barOnly, err := r.Consumer(barID)
assert.NotNil(t, barOnly)
assert.NoError(t, err)
assert.NoError(t, barOnly.ConsumeProfiles(ctx, td))
assert.Len(t, foo.AllProfiles(), 2)
assert.Len(t, bar.AllProfiles(), 2)
none, err := r.Consumer()
assert.Nil(t, none)
require.Error(t, err)
fake, err := r.Consumer(pipeline.NewIDWithName(xpipeline.SignalProfiles, "fake"))
assert.Nil(t, fake)
assert.Error(t, err)
}
================================================
FILE: consumer/Makefile
================================================
include ../Makefile.Common
================================================
FILE: consumer/consumer.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package consumer // import "go.opentelemetry.io/collector/consumer"
import (
"errors"
"go.opentelemetry.io/collector/consumer/internal"
)
// Capabilities describes the capabilities of a Processor.
type Capabilities = internal.Capabilities
var errNilFunc = errors.New("nil consumer func")
// Option to construct new consumers.
type Option = internal.Option
// WithCapabilities overrides the default GetCapabilities function for a processor.
// The default GetCapabilities function returns mutable capabilities.
func WithCapabilities(capabilities Capabilities) Option {
return internal.OptionFunc(func(o *internal.BaseImpl) {
o.Cap = capabilities
})
}
================================================
FILE: consumer/consumererror/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: consumer/consumererror/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// 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. These errors are returned by the Consume*() functions of
// the consumer interfaces.
//
// # Error handling
//
// The consumererror package provides a way to classify errors into two categories: Permanent and
// NonPermanent. The Permanent errors are those that are not expected to be resolved by retrying the
// same data.
//
// If the error is Permanent, then the Consume*() call should not be retried with the same data.
// This typically happens when the data cannot be serialized by the exporter that is attached to the
// pipeline or when the destination refuses the data because it cannot decode it.
//
// If the error is non-Permanent then the Consume*() call should be retried with the same data. This
// may be done by the component itself, however typically it is done by the original sender, after
// the receiver in the pipeline returns a response to the sender indicating that the Collector is
// currently overloaded and the request must be retried.
package consumererror // import "go.opentelemetry.io/collector/consumer/consumererror"
================================================
FILE: consumer/consumererror/downstream.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package consumererror // import "go.opentelemetry.io/collector/consumer/consumererror"
import "errors"
type downstreamError struct {
inner error
}
var _ error = downstreamError{}
func (de downstreamError) Error() string {
return de.inner.Error()
}
func (de downstreamError) Unwrap() error {
return de.inner
}
// NewDownstream wraps an error to indicate that it is a downstream error, i.e. an
// error that does not come from the current component, but from one further downstream.
// This is used by pipeline instrumentation to determine whether an operation's outcome
// was an internal failure, or if it successfully produced data that was later refused.
// This wrapper is not intended to be used manually inside components.
func NewDownstream(err error) error {
return downstreamError{
inner: err,
}
}
// IsDownstream checks if an error was wrapped with the NewDownstream function,
// or if it contains one such error in its Unwrap() tree.
func IsDownstream(err error) bool {
var de downstreamError
return errors.As(err, &de)
}
================================================
FILE: consumer/consumererror/downstream_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package consumererror
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
//nolint:testifylint // Testing properties of errors, no reason to use require
func TestDownstream(t *testing.T) {
err1 := errors.New("test error")
assert.False(t, IsDownstream(err1))
err2 := errors.New("test error 2")
assert.False(t, IsDownstream(err2))
errDownstream1 := NewDownstream(err1)
assert.True(t, IsDownstream(errDownstream1))
assert.Equal(t, err1.Error(), errDownstream1.Error())
assert.ErrorIs(t, errDownstream1, err1)
assert.NotErrorIs(t, errDownstream1, err2)
// we can access downstream wrappers through other wrappers
errWrapDownstream := NewRetryableError(errDownstream1)
assert.True(t, IsDownstream(errWrapDownstream))
errorStruct := new(Error)
assert.ErrorAs(t, errWrapDownstream, &errorStruct)
// we can access other wrappers through downstream wrappers
errDownstreamWrap := NewDownstream(NewRetryableError(err1))
assert.True(t, IsDownstream(errDownstreamWrap))
assert.ErrorAs(t, errDownstreamWrap, &errorStruct)
// downstream + downstream = downstream
errJoin2 := errors.Join(errDownstream1, NewDownstream(err2))
assert.True(t, IsDownstream(errJoin2))
// downstream + not downstream = downstream
errJoin1 := errors.Join(errDownstream1, err2)
assert.True(t, IsDownstream(errJoin1))
}
================================================
FILE: consumer/consumererror/error.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package consumererror // import "go.opentelemetry.io/collector/consumer/consumererror"
import (
"errors"
"fmt"
"net/http"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"go.opentelemetry.io/collector/consumer/consumererror/internal/statusconversion"
)
// Error is intended to be used to encapsulate various information that can add
// context to an error that occurred within a pipeline component. Error objects
// are constructed through calling `New` with the relevant options to capture
// data around the error that occurred.
//
// Error should be obtained from a given `error` object using `errors.As`.
type Error struct {
error
httpStatus int
grpcStatus *status.Status
isRetryable bool
}
var _ error = (*Error)(nil)
// NewOTLPHTTPError records an HTTP status code that was received from a server
// during data submission.
//
// NOTE: This function will panic if passed an HTTP status between 200 and 299 inclusive.
// This is to reserve the behavior for handling these codes for the future.
func NewOTLPHTTPError(origErr error, httpStatus int) error {
// Matches what is considered a successful response in the OTLP/HTTP Exporter.
if httpStatus >= 200 && httpStatus <= 299 {
panic("NewOTLPHTTPError should not be called with a success code")
}
var retryable bool
switch httpStatus {
case http.StatusTooManyRequests, http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout:
retryable = true
}
return &Error{error: origErr, httpStatus: httpStatus, isRetryable: retryable}
}
// NewOTLPGRPCError records a gRPC status code that was received from a server
// during data submission.
//
// NOTE: This function will panic if passed a *status.Status with an underlying
// code of codes.OK. This is to reserve the behavior for handling this code for
// the future.
func NewOTLPGRPCError(origErr error, status *status.Status) error {
var retryable bool
if status != nil {
switch status.Code() {
case codes.Canceled, codes.DeadlineExceeded, codes.Aborted, codes.OutOfRange, codes.Unavailable, codes.DataLoss:
retryable = true
// Matches what is considered a successful response in the OTLP Exporter.
case codes.OK:
panic("NewOTLPGRPCError should not be called with an OK code")
}
}
return &Error{error: origErr, grpcStatus: status, isRetryable: retryable}
}
// NewRetryableError records that this error is retryable according to OTLP specification.
func NewRetryableError(origErr error) error {
return &Error{error: origErr, isRetryable: true}
}
// Error implements the error interface.
//
// If an error object was given, that is used.
// Otherwise, the gRPC error from the status.Status is used,
// or an error message containing the HTTP status code is given.
func (e *Error) Error() string {
if e.error != nil {
return e.error.Error()
}
if e.grpcStatus != nil {
return e.grpcStatus.Err().Error()
} else if e.httpStatus > 0 {
return fmt.Sprintf("network error: received HTTP status %d", e.httpStatus)
}
return "uninitialized consumererror.Error"
}
// Unwrap returns the wrapped error for use by `errors.Is` and `errors.As`.
//
// If an error object was not passed but a gRPC `status.Status` was passed,
// the underlying error from the status is returned.
func (e *Error) Unwrap() error {
if e.error != nil {
return e.error
}
if e.grpcStatus != nil {
return e.grpcStatus.Err()
}
return nil
}
// IsRetryable returns true if the error was created with NewRetryableError, if
// the HTTP status code is retryable according to OTLP, or if the gRPC status is
// retryable according to OTLP. Otherwise, returns false.
//
// See https://github.com/open-telemetry/opentelemetry-proto/blob/main/docs/specification.md for retryable
// HTTP and gRPC codes.
func (e *Error) IsRetryable() bool {
return e.isRetryable
}
// ToHTTPStatus returns an HTTP status code either directly set by the source on
// an [Error] object, derived from a gRPC status code set by the source, or
// derived from Retryable. When deriving the value, the OTLP specification is
// used to map to HTTP. See
// https://github.com/open-telemetry/opentelemetry-proto/blob/main/docs/specification.md
// for more details.
//
// If a http status code cannot be derived from these three sources then 500 is
// returned.
func ToHTTPStatus(err error) int {
var e *Error
if errors.As(err, &e) {
if e.httpStatus != 0 {
return e.httpStatus
}
if e.grpcStatus != nil {
return statusconversion.GetHTTPStatusCodeFromStatus(e.grpcStatus)
}
if e.isRetryable {
return http.StatusServiceUnavailable
}
}
return http.StatusInternalServerError
}
// ToGRPCStatus returns a gRPC status code either directly set by the source on
// an [Error] object, derived from an HTTP status code set by the
// source, or derived from Retryable. When deriving the value, the OTLP
// specification is used to map to gRPC. See
// https://github.com/open-telemetry/opentelemetry-proto/blob/main/docs/specification.md
// for more details.
//
// If an [Error] object is not present, then we attempt to get a status.Status from the
// error tree.
//
// If a status.Status cannot be derived from these sources then INTERNAL is
// returned.
func ToGRPCStatus(err error) *status.Status {
var e *Error
if errors.As(err, &e) {
if e.grpcStatus != nil {
return e.grpcStatus
}
if e.httpStatus != 0 {
return statusconversion.NewStatusFromMsgAndHTTPCode(e.Error(), e.httpStatus)
}
if e.isRetryable {
return status.New(codes.Unavailable, e.Error())
}
}
if st, ok := status.FromError(err); ok {
return st
}
return status.New(codes.Unknown, e.Error())
}
================================================
FILE: consumer/consumererror/error_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package consumererror
import (
"errors"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
var errTest = errors.New("consumererror testing error")
func Test_NewOTLPHTTPError(t *testing.T) {
httpStatus := 500
wantErr := &Error{
error: errTest,
httpStatus: httpStatus,
}
newErr := NewOTLPHTTPError(errTest, httpStatus)
require.Equal(t, wantErr, newErr)
}
func Test_NewOTLPGRPCError(t *testing.T) {
grpcStatus := status.New(codes.Aborted, "aborted")
wantErr := &Error{
error: errTest,
grpcStatus: grpcStatus,
isRetryable: true,
}
newErr := NewOTLPGRPCError(errTest, grpcStatus)
require.Equal(t, wantErr, newErr)
}
func Test_NewRetryableError(t *testing.T) {
wantErr := &Error{
error: errTest,
isRetryable: true,
}
newErr := NewRetryableError(errTest)
require.Equal(t, wantErr, newErr)
}
func Test_Error(t *testing.T) {
newErr := Error{error: errTest}
require.Equal(t, errTest.Error(), newErr.Error())
}
func TestUnwrap(t *testing.T) {
grpcErr := status.New(codes.InvalidArgument, "not allowed")
testCases := []struct {
name string
err *Error
expected error
}{
{
name: "Error object",
err: &Error{
error: errTest,
grpcStatus: grpcErr,
},
expected: errTest,
},
{
name: "gRPC error",
err: &Error{
grpcStatus: grpcErr,
},
expected: grpcErr.Err(),
},
{
name: "zero value struct",
err: &Error{},
expected: nil,
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t, tt.expected, tt.err.Unwrap())
})
}
}
func TestAs(t *testing.T) {
err := &Error{
error: errTest,
}
secondError := errors.Join(errors.New("test"), err)
var e *Error
require.ErrorAs(t, secondError, &e)
assert.Equal(t, errTest.Error(), e.Error())
}
func TestError_Error(t *testing.T) {
testCases := []struct {
name string
err *Error
expected string
}{
{
name: "Error object",
err: &Error{
error: errTest,
grpcStatus: status.New(codes.InvalidArgument, "not allowed"),
httpStatus: 400,
},
expected: errTest.Error(),
},
{
name: "gRPC error",
err: &Error{
grpcStatus: status.New(codes.InvalidArgument, "not allowed"),
},
expected: "rpc error: code = InvalidArgument desc = not allowed",
},
{
name: "HTTP error",
err: &Error{
httpStatus: 400,
},
expected: "network error: received HTTP status 400",
},
{
name: "zero value struct",
err: &Error{},
expected: "uninitialized consumererror.Error",
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t, tt.expected, tt.err.Error())
})
}
}
func TestError_Unwrap(t *testing.T) {
err := &Error{
error: errTest,
}
require.Equal(t, errTest, err.Unwrap())
}
func TestError_ToHTTPStatus(t *testing.T) {
serverErr := http.StatusTooManyRequests
testCases := []struct {
name string
httpStatus int
grpcStatus *status.Status
retryable bool
want int
hasCode bool
}{
{
name: "Passes through HTTP status",
httpStatus: serverErr,
want: serverErr,
hasCode: true,
},
{
name: "Converts gRPC status",
grpcStatus: status.New(codes.ResourceExhausted, errTest.Error()),
want: serverErr,
hasCode: true,
},
{
name: "Passes through HTTP status when gRPC status also present",
httpStatus: serverErr,
grpcStatus: status.New(codes.OK, errTest.Error()),
want: serverErr,
hasCode: true,
},
{
name: "Passes through HTTP status when retryable also present",
httpStatus: serverErr,
retryable: true,
want: serverErr,
},
{
name: "No statuses set with retryable",
retryable: true,
want: http.StatusServiceUnavailable,
},
{
name: "No statuses set",
want: http.StatusInternalServerError,
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
err := &Error{
error: errTest,
httpStatus: tt.httpStatus,
grpcStatus: tt.grpcStatus,
isRetryable: tt.retryable,
}
s := ToHTTPStatus(err)
require.Equal(t, tt.want, s)
})
}
}
func TestError_ToGRPCStatus(t *testing.T) {
httpStatus := http.StatusTooManyRequests
otherOTLPHTTPStatus := http.StatusOK
serverErr := status.New(codes.ResourceExhausted, errTest.Error())
testCases := []struct {
name string
httpStatus int
grpcStatus *status.Status
retryable bool
want *status.Status
hasCode bool
}{
{
name: "Converts HTTP status",
httpStatus: httpStatus,
want: serverErr,
hasCode: true,
},
{
name: "Passes through gRPC status",
grpcStatus: serverErr,
want: serverErr,
hasCode: true,
},
{
name: "Passes through gRPC status when HTTP status also present",
httpStatus: otherOTLPHTTPStatus,
grpcStatus: serverErr,
want: serverErr,
hasCode: true,
},
{
name: "Passes through gRPC status when retryable also present",
grpcStatus: serverErr,
retryable: true,
want: serverErr,
},
{
name: "No statuses set with retryable",
retryable: true,
want: status.New(codes.Unavailable, errTest.Error()),
},
{
name: "No statuses set",
want: status.New(codes.Unknown, errTest.Error()),
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
err := &Error{
error: errTest,
httpStatus: tt.httpStatus,
grpcStatus: tt.grpcStatus,
isRetryable: tt.retryable,
}
s := ToGRPCStatus(err)
require.Equal(t, tt.want, s)
})
}
}
func TestStatus_ToGRPCStatus(t *testing.T) {
st := status.New(codes.ResourceExhausted, errTest.Error())
require.Equal(t, st, ToGRPCStatus(st.Err()))
}
func TestError_Retryable(t *testing.T) {
retryableCodes := []codes.Code{codes.Canceled, codes.DeadlineExceeded, codes.Aborted, codes.OutOfRange, codes.Unavailable, codes.DataLoss}
retryableStatuses := []*status.Status{}
for _, code := range retryableCodes {
retryableStatuses = append(retryableStatuses, status.New(code, errTest.Error()))
}
nonretryableCodes := []codes.Code{codes.Unauthenticated, codes.Unknown, codes.NotFound, codes.InvalidArgument}
nonretryableStatuses := []*status.Status{}
for _, code := range nonretryableCodes {
nonretryableStatuses = append(nonretryableStatuses, status.New(code, errTest.Error()))
}
testCases := []struct {
name string
httpStatuses []int
grpcStatuses []*status.Status
want bool
}{
{
name: "HTTP statuses: retryable",
httpStatuses: []int{http.StatusTooManyRequests, http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout},
want: true,
},
{
name: "HTTP statuses: non-retryable",
httpStatuses: []int{0, http.StatusInternalServerError, http.StatusNotFound, http.StatusUnauthorized},
want: false,
},
{
name: "gRPC statuses: retryable",
grpcStatuses: retryableStatuses,
want: true,
},
{
name: "gRPC statuses: non-retryable",
grpcStatuses: nonretryableStatuses,
want: false,
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
for _, httpStatus := range tt.httpStatuses {
err := NewOTLPHTTPError(errTest, httpStatus)
var httpErr *Error
if errors.As(err, &httpErr) {
require.Equal(t, tt.want, httpErr.IsRetryable(), "Expected %d to be retryable=%t", httpStatus, tt.want)
} else {
require.Fail(t, "NewOTLPHTTPError didn't return an *Error")
}
}
for _, grpcStatus := range tt.grpcStatuses {
err := NewOTLPGRPCError(errTest, grpcStatus)
var grpcErr *Error
if errors.As(err, &grpcErr) {
require.Equal(t, tt.want, grpcErr.IsRetryable(), "Expected %q to be retryable=%t", grpcStatus.Code().String(), tt.want)
} else {
require.Fail(t, "NewOTLPGRPCError didn't return an *Error")
}
}
})
}
}
func TestSuccessCodes(t *testing.T) {
require.Panics(t, func() {
_ = NewOTLPHTTPError(nil, 200)
})
require.Panics(t, func() {
_ = NewOTLPHTTPError(nil, 299)
})
require.NotPanics(t, func() {
require.Error(t, NewOTLPHTTPError(nil, 199))
})
require.NotPanics(t, func() {
require.Error(t, NewOTLPHTTPError(nil, 300))
})
require.Panics(t, func() {
_ = NewOTLPGRPCError(nil, status.New(codes.OK, ""))
})
require.NotPanics(t, func() {
require.Error(t, NewOTLPGRPCError(nil, status.New(codes.AlreadyExists, "")))
})
}
================================================
FILE: consumer/consumererror/go.mod
================================================
module go.opentelemetry.io/collector/consumer/consumererror
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/collector/pdata/pprofile v0.148.0
go.opentelemetry.io/collector/pdata/testdata v0.148.0
go.uber.org/goleak v1.3.0
google.golang.org/grpc v1.79.3
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/sys v0.41.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/pdata/testdata => ../../pdata/testdata
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
================================================
FILE: consumer/consumererror/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: consumer/consumererror/internal/retryable.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/consumer/consumererror/internal"
import (
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/ptrace"
)
type Retryable[V ptrace.Traces | pmetric.Metrics | plog.Logs | pprofile.Profiles] struct {
Err error
Value V
}
// Error provides the error message
func (err Retryable[V]) Error() string {
return err.Err.Error()
}
// Unwrap returns the wrapped error for functions Is and As in standard package errors.
func (err Retryable[V]) Unwrap() error {
return err.Err
}
// Data returns the telemetry data that failed to be processed or sent.
func (err Retryable[V]) Data() V {
return err.Value
}
================================================
FILE: consumer/consumererror/internal/statusconversion/conversion.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package statusconversion // import "go.opentelemetry.io/collector/consumer/consumererror/internal/statusconversion"
import (
"net/http"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func GetHTTPStatusCodeFromStatus(s *status.Status) int {
// See https://github.com/open-telemetry/opentelemetry-proto/blob/main/docs/specification.md#failures
// to see if a code is retryable.
// See https://github.com/open-telemetry/opentelemetry-proto/blob/main/docs/specification.md#failures-1
// to see a list of retryable http status codes.
switch s.Code() {
// Retryable
case codes.Canceled, codes.DeadlineExceeded, codes.Aborted, codes.OutOfRange, codes.Unavailable, codes.DataLoss:
return http.StatusServiceUnavailable
// Retryable
case codes.ResourceExhausted:
return http.StatusTooManyRequests
// Not Retryable
case codes.InvalidArgument:
return http.StatusBadRequest
// Not Retryable
case codes.Unauthenticated:
return http.StatusUnauthorized
// Not Retryable
case codes.PermissionDenied:
return http.StatusForbidden
// Not Retryable
case codes.Unimplemented:
return http.StatusNotFound
// Not Retryable
default:
return http.StatusInternalServerError
}
}
func NewStatusFromMsgAndHTTPCode(errMsg string, statusCode int) *status.Status {
var c codes.Code
// Mapping based on https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md
// 429 mapping to ResourceExhausted and 400 mapping to StatusBadRequest are exceptions.
switch statusCode {
case http.StatusBadRequest:
c = codes.InvalidArgument
case http.StatusUnauthorized:
c = codes.Unauthenticated
case http.StatusForbidden:
c = codes.PermissionDenied
case http.StatusNotFound:
c = codes.Unimplemented
case http.StatusTooManyRequests:
c = codes.ResourceExhausted
case http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout:
c = codes.Unavailable
default:
c = codes.Unknown
}
return status.New(c, errMsg)
}
================================================
FILE: consumer/consumererror/internal/statusconversion/conversion_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package statusconversion // import "go.opentelemetry.io/collector/consumer/consumererror/internal/statusconversion"
import (
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func Test_GetHTTPStatusCodeFromStatus(t *testing.T) {
tests := []struct {
name string
input *status.Status
expected int
}{
{
name: "Retryable Status",
input: status.New(codes.Unavailable, "test"),
expected: http.StatusServiceUnavailable,
},
{
name: "Non-retryable Status",
input: status.New(codes.InvalidArgument, "test"),
expected: http.StatusBadRequest,
},
{
name: "Specifically 429",
input: status.New(codes.ResourceExhausted, "test"),
expected: http.StatusTooManyRequests,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := GetHTTPStatusCodeFromStatus(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}
func Test_ErrorMsgAndHTTPCodeToStatus(t *testing.T) {
tests := []struct {
name string
errMsg string
statusCode int
expected *status.Status
}{
{
name: "Bad Request",
errMsg: "test",
statusCode: http.StatusBadRequest,
expected: status.New(codes.InvalidArgument, "test"),
},
{
name: "Unauthorized",
errMsg: "test",
statusCode: http.StatusUnauthorized,
expected: status.New(codes.Unauthenticated, "test"),
},
{
name: "Forbidden",
errMsg: "test",
statusCode: http.StatusForbidden,
expected: status.New(codes.PermissionDenied, "test"),
},
{
name: "Not Found",
errMsg: "test",
statusCode: http.StatusNotFound,
expected: status.New(codes.Unimplemented, "test"),
},
{
name: "Too Many Requests",
errMsg: "test",
statusCode: http.StatusTooManyRequests,
expected: status.New(codes.ResourceExhausted, "test"),
},
{
name: "Bad Gateway",
errMsg: "test",
statusCode: http.StatusBadGateway,
expected: status.New(codes.Unavailable, "test"),
},
{
name: "Service Unavailable",
errMsg: "test",
statusCode: http.StatusServiceUnavailable,
expected: status.New(codes.Unavailable, "test"),
},
{
name: "Gateway Timeout",
errMsg: "test",
statusCode: http.StatusGatewayTimeout,
expected: status.New(codes.Unavailable, "test"),
},
{
name: "Unsupported Media Type",
errMsg: "test",
statusCode: http.StatusUnsupportedMediaType,
expected: status.New(codes.Unknown, "test"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := NewStatusFromMsgAndHTTPCode(tt.errMsg, tt.statusCode)
assert.Equal(t, tt.expected, result)
})
}
}
================================================
FILE: consumer/consumererror/metadata.yaml
================================================
type: consumer/consumererror
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: consumer/consumererror/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package consumererror
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: consumer/consumererror/permanent.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package consumererror // import "go.opentelemetry.io/collector/consumer/consumererror"
import "errors"
// permanent is an error that will be always returned if its source
// receives the same inputs.
type permanent struct {
err error
}
// NewPermanent 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 NewPermanent(err error) error {
return permanent{err: err}
}
func (p permanent) Error() string {
return "Permanent error: " + p.err.Error()
}
// Unwrap returns the wrapped error for functions Is and As in standard package errors.
func (p permanent) Unwrap() error {
return p.err
}
// IsPermanent checks if an error was wrapped with the NewPermanent function, which
// 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 {
return false
}
return errors.As(err, &permanent{})
}
================================================
FILE: consumer/consumererror/permanent_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package consumererror
import (
"errors"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type testErrorType struct {
s string
}
func (t testErrorType) Error() string {
return ""
}
func TestIsPermanent(t *testing.T) {
var err error
assert.False(t, IsPermanent(err))
err = errors.New("testError")
assert.False(t, IsPermanent(err))
err = NewPermanent(err)
assert.True(t, IsPermanent(err))
err = fmt.Errorf("%w", err)
assert.True(t, IsPermanent(err))
}
func TestPermanent_Unwrap(t *testing.T) {
var err error = testErrorType{"testError"}
require.False(t, IsPermanent(err))
// Wrapping testErrorType err with permanent error.
permanentErr := NewPermanent(err)
require.True(t, IsPermanent(permanentErr))
target := testErrorType{}
require.NotEqual(t, err, target)
isTestErrorTypeWrapped := errors.As(permanentErr, &target)
require.True(t, isTestErrorTypeWrapped)
require.Equal(t, err, target)
}
================================================
FILE: consumer/consumererror/signalerrors.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package consumererror // import "go.opentelemetry.io/collector/consumer/consumererror"
import (
"go.opentelemetry.io/collector/consumer/consumererror/internal"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
)
// Traces is an error that may carry associated Trace data for a subset of received data
// that failed to be processed or sent.
type Traces struct {
internal.Retryable[ptrace.Traces]
}
// NewTraces creates a Traces that can encapsulate received data that failed to be processed or sent.
func NewTraces(err error, data ptrace.Traces) error {
return Traces{
Retryable: internal.Retryable[ptrace.Traces]{
Err: NewRetryableError(err),
Value: data,
},
}
}
// Logs is an error that may carry associated Log data for a subset of received data
// that failed to be processed or sent.
type Logs struct {
internal.Retryable[plog.Logs]
}
// NewLogs creates a Logs that can encapsulate received data that failed to be processed or sent.
func NewLogs(err error, data plog.Logs) error {
return Logs{
Retryable: internal.Retryable[plog.Logs]{
Err: NewRetryableError(err),
Value: data,
},
}
}
// Metrics is an error that may carry associated Metrics data for a subset of received data
// that failed to be processed or sent.
type Metrics struct {
internal.Retryable[pmetric.Metrics]
}
// NewMetrics creates a Metrics that can encapsulate received data that failed to be processed or sent.
func NewMetrics(err error, data pmetric.Metrics) error {
return Metrics{
Retryable: internal.Retryable[pmetric.Metrics]{
Err: NewRetryableError(err),
Value: data,
},
}
}
================================================
FILE: consumer/consumererror/signalerrors_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package consumererror
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/testdata"
)
func TestTraces(t *testing.T) {
td := testdata.GenerateTraces(1)
err := errors.New("some error")
traceErr := NewTraces(err, td)
require.EqualError(t, err, traceErr.Error())
var target Traces
assert.NotErrorAs(t, nil, &target)
assert.NotErrorAs(t, err, &target)
require.ErrorAs(t, traceErr, &target)
assert.Equal(t, td, target.Data())
}
func TestTraces_Unwrap(t *testing.T) {
td := testdata.GenerateTraces(1)
var err error = testErrorType{"some error"}
// Wrapping err with error Traces.
traceErr := NewTraces(err, td)
target := testErrorType{}
require.NotEqual(t, err, target)
// Unwrapping traceErr for err and assigning to target.
require.ErrorAs(t, traceErr, &target)
require.Equal(t, err, target)
var e *Error
require.ErrorAs(t, traceErr, &e)
assert.True(t, e.IsRetryable())
}
func TestLogs(t *testing.T) {
td := testdata.GenerateLogs(1)
err := errors.New("some error")
logsErr := NewLogs(err, td)
require.EqualError(t, err, logsErr.Error())
var target Logs
assert.NotErrorAs(t, nil, &target)
assert.NotErrorAs(t, err, &target)
require.ErrorAs(t, logsErr, &target)
assert.Equal(t, td, target.Data())
}
func TestLogs_Unwrap(t *testing.T) {
td := testdata.GenerateLogs(1)
var err error = testErrorType{"some error"}
// Wrapping err with error Logs.
logsErr := NewLogs(err, td)
target := testErrorType{}
require.NotEqual(t, err, target)
// Unwrapping logsErr for err and assigning to target.
require.ErrorAs(t, logsErr, &target)
require.Equal(t, err, target)
var e *Error
require.ErrorAs(t, logsErr, &e)
assert.True(t, e.IsRetryable())
}
func TestMetrics(t *testing.T) {
td := testdata.GenerateMetrics(1)
err := errors.New("some error")
metricErr := NewMetrics(err, td)
require.EqualError(t, err, metricErr.Error())
var target Metrics
assert.NotErrorAs(t, nil, &target)
assert.NotErrorAs(t, err, &target)
require.ErrorAs(t, metricErr, &target)
assert.Equal(t, td, target.Data())
}
func TestMetrics_Unwrap(t *testing.T) {
td := testdata.GenerateMetrics(1)
var err error = testErrorType{"some error"}
// Wrapping err with error Metrics.
metricErr := NewMetrics(err, td)
target := testErrorType{}
require.NotEqual(t, err, target)
// Unwrapping metricErr for err and assigning to target.
require.ErrorAs(t, metricErr, &target)
require.Equal(t, err, target)
var e *Error
require.ErrorAs(t, metricErr, &e)
assert.True(t, e.IsRetryable())
}
================================================
FILE: consumer/consumererror/xconsumererror/Makefile
================================================
include ../../../Makefile.Common
================================================
FILE: consumer/consumererror/xconsumererror/go.mod
================================================
module go.opentelemetry.io/collector/consumer/consumererror/xconsumererror
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/consumer/consumererror v0.148.0
go.opentelemetry.io/collector/pdata/pprofile v0.148.0
go.opentelemetry.io/collector/pdata/testdata v0.148.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/pdata v1.54.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/pdata => ../../../pdata
replace go.opentelemetry.io/collector/pdata/testdata => ../../../pdata/testdata
replace go.opentelemetry.io/collector/pdata/pprofile => ../../../pdata/pprofile
replace go.opentelemetry.io/collector/consumer/consumererror => ../../consumererror
replace go.opentelemetry.io/collector/featuregate => ../../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../../internal/testutil
================================================
FILE: consumer/consumererror/xconsumererror/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: consumer/consumererror/xconsumererror/metadata.yaml
================================================
type: consumererror/xconsumererror
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: consumer
stability:
alpha: [profiles]
================================================
FILE: consumer/consumererror/xconsumererror/signalerrors.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xconsumererror // import "go.opentelemetry.io/collector/consumer/consumererror/xconsumererror"
import (
"go.opentelemetry.io/collector/consumer/consumererror/internal"
"go.opentelemetry.io/collector/pdata/pprofile"
)
// Profiles is an error that may carry associated Profile data for a subset of received data
// that failed to be processed or sent.
type Profiles struct {
internal.Retryable[pprofile.Profiles]
}
// NewProfiles creates a Profiles that can encapsulate received data that failed to be processed or sent.
func NewProfiles(err error, data pprofile.Profiles) error {
return Profiles{
Retryable: internal.Retryable[pprofile.Profiles]{
Err: err,
Value: data,
},
}
}
================================================
FILE: consumer/consumererror/xconsumererror/signalerrors_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xconsumererror // import "go.opentelemetry.io/collector/consumer/consumererror/xconsumererror"
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/testdata"
)
func TestProfiles(t *testing.T) {
td := testdata.GenerateProfiles(1)
err := errors.New("some error")
profileErr := NewProfiles(err, td)
assert.Equal(t, err.Error(), profileErr.Error())
var target Profiles
assert.NotErrorAs(t, nil, &target)
assert.NotErrorAs(t, err, &target)
require.ErrorAs(t, profileErr, &target)
assert.Equal(t, td, target.Data())
}
func TestProfiles_Unwrap(t *testing.T) {
td := testdata.GenerateProfiles(1)
var err error = testErrorType{"some error"}
// Wrapping err with error Profiles.
profileErr := NewProfiles(err, td)
target := testErrorType{}
require.NotEqual(t, err, target)
// Unwrapping profileErr for err and assigning to target.
require.ErrorAs(t, profileErr, &target)
require.Equal(t, err, target)
}
type testErrorType struct {
s string
}
func (t testErrorType) Error() string {
return ""
}
================================================
FILE: consumer/consumertest/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: consumer/consumertest/consumer.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package consumertest // import "go.opentelemetry.io/collector/consumer/consumertest"
import (
"context"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/ptrace"
)
// Consumer is a convenience interface that implements all consumer interfaces.
// It has a private function on it to forbid external users from implementing it
// and, as a result, to allow us to add extra functions without breaking
// compatibility.
type Consumer interface {
// Capabilities to implement the base consumer functionality.
Capabilities() consumer.Capabilities
// ConsumeTraces to implement the consumer.Traces.
ConsumeTraces(context.Context, ptrace.Traces) error
// ConsumeMetrics to implement the consumer.Metrics.
ConsumeMetrics(context.Context, pmetric.Metrics) error
// ConsumeLogs to implement the consumer.Logs.
ConsumeLogs(context.Context, plog.Logs) error
// ConsumeProfiles to implement the xconsumer.Profiles.
ConsumeProfiles(context.Context, pprofile.Profiles) error
unexported()
}
var (
_ consumer.Logs = Consumer(nil)
_ consumer.Metrics = Consumer(nil)
_ consumer.Traces = Consumer(nil)
_ xconsumer.Profiles = Consumer(nil)
)
type nonMutatingConsumer struct{}
// Capabilities returns the base consumer capabilities.
func (bc nonMutatingConsumer) Capabilities() consumer.Capabilities {
return consumer.Capabilities{MutatesData: false}
}
type baseConsumer struct {
nonMutatingConsumer
consumer.ConsumeTracesFunc
consumer.ConsumeMetricsFunc
consumer.ConsumeLogsFunc
xconsumer.ConsumeProfilesFunc
}
func (bc baseConsumer) unexported() {}
================================================
FILE: consumer/consumertest/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package consumertest defines types and functions used to help test packages
// implementing the consumer package interfaces.
package consumertest // import "go.opentelemetry.io/collector/consumer/consumertest"
================================================
FILE: consumer/consumertest/err.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package consumertest // import "go.opentelemetry.io/collector/consumer/consumertest"
import (
"context"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/ptrace"
)
// NewErr returns a Consumer that just drops all received data and returns the specified error to Consume* callers.
func NewErr(err error) Consumer {
return &baseConsumer{
ConsumeTracesFunc: func(context.Context, ptrace.Traces) error { return err },
ConsumeMetricsFunc: func(context.Context, pmetric.Metrics) error { return err },
ConsumeLogsFunc: func(context.Context, plog.Logs) error { return err },
ConsumeProfilesFunc: func(context.Context, pprofile.Profiles) error { return err },
}
}
================================================
FILE: consumer/consumertest/err_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package consumertest
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/ptrace"
)
func TestErr(t *testing.T) {
err := errors.New("my error")
ec := NewErr(err)
require.NotNil(t, ec)
assert.NotPanics(t, ec.unexported)
assert.Equal(t, err, ec.ConsumeLogs(context.Background(), plog.NewLogs()))
assert.Equal(t, err, ec.ConsumeMetrics(context.Background(), pmetric.NewMetrics()))
assert.Equal(t, err, ec.ConsumeTraces(context.Background(), ptrace.NewTraces()))
assert.Equal(t, err, ec.ConsumeProfiles(context.Background(), pprofile.NewProfiles()))
}
================================================
FILE: consumer/consumertest/go.mod
================================================
module go.opentelemetry.io/collector/consumer/consumertest
go 1.25.0
replace go.opentelemetry.io/collector/consumer => ../
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/consumer v1.54.0
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/collector/pdata/pprofile v0.148.0
go.opentelemetry.io/collector/pdata/testdata v0.148.0
go.uber.org/goleak v1.3.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/consumer/xconsumer => ../xconsumer
replace go.opentelemetry.io/collector/pdata/testdata => ../../pdata/testdata
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
================================================
FILE: consumer/consumertest/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: consumer/consumertest/metadata.yaml
================================================
type: consumer/consumertest
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: consumer/consumertest/nop.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package consumertest // import "go.opentelemetry.io/collector/consumer/consumertest"
import (
"context"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/ptrace"
)
// NewNop returns a Consumer that just drops all received data and returns no error.
func NewNop() Consumer {
return &baseConsumer{
ConsumeTracesFunc: func(context.Context, ptrace.Traces) error { return nil },
ConsumeMetricsFunc: func(context.Context, pmetric.Metrics) error { return nil },
ConsumeLogsFunc: func(context.Context, plog.Logs) error { return nil },
ConsumeProfilesFunc: func(context.Context, pprofile.Profiles) error { return nil },
}
}
================================================
FILE: consumer/consumertest/nop_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package consumertest
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/ptrace"
)
func TestNop(t *testing.T) {
nc := NewNop()
require.NotNil(t, nc)
assert.NotPanics(t, nc.unexported)
assert.NoError(t, nc.ConsumeLogs(context.Background(), plog.NewLogs()))
assert.NoError(t, nc.ConsumeMetrics(context.Background(), pmetric.NewMetrics()))
assert.NoError(t, nc.ConsumeTraces(context.Background(), ptrace.NewTraces()))
assert.NoError(t, nc.ConsumeProfiles(context.Background(), pprofile.NewProfiles()))
}
================================================
FILE: consumer/consumertest/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package consumertest
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: consumer/consumertest/sink.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package consumertest // import "go.opentelemetry.io/collector/consumer/consumertest"
import (
"context"
"sync"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/ptrace"
)
// TracesSink is a consumer.Traces that acts like a sink that
// stores all traces and allows querying them for testing.
type TracesSink struct {
nonMutatingConsumer
mu sync.Mutex
traces []ptrace.Traces
contexts []context.Context
spanCount int
}
var _ consumer.Traces = (*TracesSink)(nil)
// ConsumeTraces stores traces to this sink.
func (ste *TracesSink) ConsumeTraces(ctx context.Context, td ptrace.Traces) error {
ste.mu.Lock()
defer ste.mu.Unlock()
ste.traces = append(ste.traces, td)
ste.contexts = append(ste.contexts, ctx)
ste.spanCount += td.SpanCount()
return nil
}
// AllTraces returns the traces stored by this sink since last Reset.
func (ste *TracesSink) AllTraces() []ptrace.Traces {
ste.mu.Lock()
defer ste.mu.Unlock()
copyTraces := make([]ptrace.Traces, len(ste.traces))
copy(copyTraces, ste.traces)
return copyTraces
}
// Contexts returns the contexts stored by this sink since last Reset.
func (ste *TracesSink) Contexts() []context.Context {
ste.mu.Lock()
defer ste.mu.Unlock()
copyContexts := make([]context.Context, len(ste.contexts))
copy(copyContexts, ste.contexts)
return copyContexts
}
// SpanCount returns the number of spans sent to this sink.
func (ste *TracesSink) SpanCount() int {
ste.mu.Lock()
defer ste.mu.Unlock()
return ste.spanCount
}
// Reset deletes any stored data.
func (ste *TracesSink) Reset() {
ste.mu.Lock()
defer ste.mu.Unlock()
ste.traces = nil
ste.contexts = nil
ste.spanCount = 0
}
// MetricsSink is a consumer.Metrics that acts like a sink that
// stores all metrics and allows querying them for testing.
type MetricsSink struct {
nonMutatingConsumer
mu sync.Mutex
metrics []pmetric.Metrics
contexts []context.Context
dataPointCount int
}
var _ consumer.Metrics = (*MetricsSink)(nil)
// ConsumeMetrics stores metrics to this sink.
func (sme *MetricsSink) ConsumeMetrics(ctx context.Context, md pmetric.Metrics) error {
sme.mu.Lock()
defer sme.mu.Unlock()
sme.metrics = append(sme.metrics, md)
sme.contexts = append(sme.contexts, ctx)
sme.dataPointCount += md.DataPointCount()
return nil
}
// AllMetrics returns the metrics stored by this sink since last Reset.
func (sme *MetricsSink) AllMetrics() []pmetric.Metrics {
sme.mu.Lock()
defer sme.mu.Unlock()
copyMetrics := make([]pmetric.Metrics, len(sme.metrics))
copy(copyMetrics, sme.metrics)
return copyMetrics
}
// Contexts returns the contexts stored by this sink since last Reset.
func (sme *MetricsSink) Contexts() []context.Context {
sme.mu.Lock()
defer sme.mu.Unlock()
copyContexts := make([]context.Context, len(sme.contexts))
copy(copyContexts, sme.contexts)
return copyContexts
}
// DataPointCount returns the number of metrics stored by this sink since last Reset.
func (sme *MetricsSink) DataPointCount() int {
sme.mu.Lock()
defer sme.mu.Unlock()
return sme.dataPointCount
}
// Reset deletes any stored data.
func (sme *MetricsSink) Reset() {
sme.mu.Lock()
defer sme.mu.Unlock()
sme.metrics = nil
sme.contexts = nil
sme.dataPointCount = 0
}
// LogsSink is a consumer.Logs that acts like a sink that
// stores all logs and allows querying them for testing.
type LogsSink struct {
nonMutatingConsumer
mu sync.Mutex
logs []plog.Logs
contexts []context.Context
logRecordCount int
}
var _ consumer.Logs = (*LogsSink)(nil)
// ConsumeLogs stores logs to this sink.
func (sle *LogsSink) ConsumeLogs(ctx context.Context, ld plog.Logs) error {
sle.mu.Lock()
defer sle.mu.Unlock()
sle.logs = append(sle.logs, ld)
sle.logRecordCount += ld.LogRecordCount()
sle.contexts = append(sle.contexts, ctx)
return nil
}
// AllLogs returns the logs stored by this sink since last Reset.
func (sle *LogsSink) AllLogs() []plog.Logs {
sle.mu.Lock()
defer sle.mu.Unlock()
copyLogs := make([]plog.Logs, len(sle.logs))
copy(copyLogs, sle.logs)
return copyLogs
}
// LogRecordCount returns the number of log records stored by this sink since last Reset.
func (sle *LogsSink) LogRecordCount() int {
sle.mu.Lock()
defer sle.mu.Unlock()
return sle.logRecordCount
}
// Reset deletes any stored data.
func (sle *LogsSink) Reset() {
sle.mu.Lock()
defer sle.mu.Unlock()
sle.logs = nil
sle.contexts = nil
sle.logRecordCount = 0
}
// Contexts returns the contexts stored by this sink since last Reset.
func (sle *LogsSink) Contexts() []context.Context {
sle.mu.Lock()
defer sle.mu.Unlock()
copyContexts := make([]context.Context, len(sle.contexts))
copy(copyContexts, sle.contexts)
return copyContexts
}
// ProfilesSink is a xconsumer.Profiles that acts like a sink that
// stores all profiles and allows querying them for testing.
type ProfilesSink struct {
nonMutatingConsumer
mu sync.Mutex
profiles []pprofile.Profiles
contexts []context.Context
sampleCount int
profileCount int
}
var _ xconsumer.Profiles = (*ProfilesSink)(nil)
// ConsumeProfiles stores profiles to this sink.
func (ste *ProfilesSink) ConsumeProfiles(ctx context.Context, td pprofile.Profiles) error {
ste.mu.Lock()
defer ste.mu.Unlock()
ste.profiles = append(ste.profiles, td)
ste.contexts = append(ste.contexts, ctx)
ste.sampleCount += td.SampleCount()
ste.profileCount += td.ProfileCount()
return nil
}
// AllProfiles returns the profiles stored by this sink since last Reset.
func (ste *ProfilesSink) AllProfiles() []pprofile.Profiles {
ste.mu.Lock()
defer ste.mu.Unlock()
copyProfiles := make([]pprofile.Profiles, len(ste.profiles))
copy(copyProfiles, ste.profiles)
return copyProfiles
}
// SampleCount returns the number of samples stored by this sink since last Reset.
func (ste *ProfilesSink) SampleCount() int {
ste.mu.Lock()
defer ste.mu.Unlock()
return ste.sampleCount
}
// ProfileCount returns the number of profiles stored by this sink since last Reset.
func (ste *ProfilesSink) ProfileCount() int {
ste.mu.Lock()
defer ste.mu.Unlock()
return ste.profileCount
}
// Reset deletes any stored data.
func (ste *ProfilesSink) Reset() {
ste.mu.Lock()
defer ste.mu.Unlock()
ste.profiles = nil
ste.contexts = nil
ste.sampleCount = 0
ste.profileCount = 0
}
// Contexts returns the contexts stored by this sink since last Reset.
func (ste *ProfilesSink) Contexts() []context.Context {
ste.mu.Lock()
defer ste.mu.Unlock()
copyContexts := make([]context.Context, len(ste.contexts))
copy(copyContexts, ste.contexts)
return copyContexts
}
================================================
FILE: consumer/consumertest/sink_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package consumertest
import (
"context"
"fmt"
"sync"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/pdata/testdata"
)
type (
ctxKey string
testKey int
)
func TestTracesSink(t *testing.T) {
sink := new(TracesSink)
td := testdata.GenerateTraces(1)
want := make([]ptrace.Traces, 0, 7)
for range 7 {
require.NoError(t, sink.ConsumeTraces(context.Background(), td))
want = append(want, td)
}
assert.Equal(t, want, sink.AllTraces())
assert.Equal(t, len(want), sink.SpanCount())
sink.Reset()
assert.Empty(t, sink.AllTraces())
assert.Equal(t, 0, sink.SpanCount())
}
func TestMetricsSink(t *testing.T) {
sink := new(MetricsSink)
md := testdata.GenerateMetrics(1)
want := make([]pmetric.Metrics, 0, 7)
for range 7 {
require.NoError(t, sink.ConsumeMetrics(context.Background(), md))
want = append(want, md)
}
assert.Equal(t, want, sink.AllMetrics())
assert.Equal(t, 2*len(want), sink.DataPointCount())
sink.Reset()
assert.Empty(t, sink.AllMetrics())
assert.Equal(t, 0, sink.DataPointCount())
}
func TestLogsSink(t *testing.T) {
sink := new(LogsSink)
md := testdata.GenerateLogs(1)
want := make([]plog.Logs, 0, 7)
for range 7 {
require.NoError(t, sink.ConsumeLogs(context.Background(), md))
want = append(want, md)
}
assert.Equal(t, want, sink.AllLogs())
assert.Equal(t, len(want), sink.LogRecordCount())
sink.Reset()
assert.Empty(t, sink.AllLogs())
assert.Equal(t, 0, sink.LogRecordCount())
}
func TestProfilesSink(t *testing.T) {
sink := new(ProfilesSink)
td := testdata.GenerateProfiles(1)
want := make([]pprofile.Profiles, 0, 7)
for range 7 {
require.NoError(t, sink.ConsumeProfiles(context.Background(), td))
want = append(want, td)
}
assert.Equal(t, want, sink.AllProfiles())
// Each Profile in Profiles holds a single sample.
// So SampleCount() equals ProfileCount() in this case.
assert.Equal(t, len(want), sink.SampleCount())
assert.Equal(t, len(want), sink.ProfileCount())
sink.Reset()
assert.Empty(t, sink.AllProfiles())
assert.Equal(t, 0, sink.SampleCount())
assert.Equal(t, 0, sink.ProfileCount())
}
func TestTracesSinkWithContext(t *testing.T) {
sink := new(TracesSink)
td := testdata.GenerateTraces(1)
want := make([]ptrace.Traces, 0, 7)
wantCtx := make([]context.Context, 0, 7)
for i := range 7 {
ctx := context.WithValue(context.Background(), testKey(i), fmt.Sprintf("value-%d", i))
require.NoError(t, sink.ConsumeTraces(ctx, td))
want = append(want, td)
wantCtx = append(wantCtx, ctx)
}
assert.Equal(t, want, sink.AllTraces())
assert.Equal(t, len(want), sink.SpanCount())
// Verify contexts
gotCtx := sink.Contexts()
assert.Len(t, gotCtx, len(wantCtx))
for i, ctx := range gotCtx {
assert.Equal(t, fmt.Sprintf("value-%d", i), ctx.Value(testKey(i)))
}
sink.Reset()
assert.Empty(t, sink.AllTraces())
assert.Empty(t, sink.Contexts())
assert.Equal(t, 0, sink.SpanCount())
}
func TestMetricsSinkWithContext(t *testing.T) {
sink := new(MetricsSink)
md := testdata.GenerateMetrics(1)
want := make([]pmetric.Metrics, 0, 7)
wantCtx := make([]context.Context, 0, 7)
for i := range 7 {
ctx := context.WithValue(context.Background(), testKey(i), fmt.Sprintf("value-%d", i))
require.NoError(t, sink.ConsumeMetrics(ctx, md))
want = append(want, md)
wantCtx = append(wantCtx, ctx)
}
assert.Equal(t, want, sink.AllMetrics())
assert.Equal(t, 2*len(want), sink.DataPointCount())
// Verify contexts
gotCtx := sink.Contexts()
assert.Len(t, gotCtx, len(wantCtx))
for i, ctx := range gotCtx {
assert.Equal(t, fmt.Sprintf("value-%d", i), ctx.Value(testKey(i)))
}
sink.Reset()
assert.Empty(t, sink.AllMetrics())
assert.Empty(t, sink.Contexts())
assert.Equal(t, 0, sink.DataPointCount())
}
func TestLogsSinkWithContext(t *testing.T) {
sink := new(LogsSink)
md := testdata.GenerateLogs(1)
want := make([]plog.Logs, 0, 7)
wantCtx := make([]context.Context, 0, 7)
for i := range 7 {
ctx := context.WithValue(context.Background(), testKey(i), fmt.Sprintf("value-%d", i))
require.NoError(t, sink.ConsumeLogs(ctx, md))
want = append(want, md)
wantCtx = append(wantCtx, ctx)
}
assert.Equal(t, want, sink.AllLogs())
assert.Equal(t, len(want), sink.LogRecordCount())
// Verify contexts
gotCtx := sink.Contexts()
assert.Len(t, gotCtx, len(wantCtx))
for i, ctx := range gotCtx {
assert.Equal(t, fmt.Sprintf("value-%d", i), ctx.Value(testKey(i)))
}
sink.Reset()
assert.Empty(t, sink.AllLogs())
assert.Empty(t, sink.Contexts())
assert.Equal(t, 0, sink.LogRecordCount())
}
func TestProfilesSinkWithContext(t *testing.T) {
sink := new(ProfilesSink)
td := testdata.GenerateProfiles(1)
want := make([]pprofile.Profiles, 0, 7)
wantCtx := make([]context.Context, 0, 7)
for i := range 7 {
ctx := context.WithValue(context.Background(), testKey(i), fmt.Sprintf("value-%d", i))
require.NoError(t, sink.ConsumeProfiles(ctx, td))
want = append(want, td)
wantCtx = append(wantCtx, ctx)
}
assert.Equal(t, want, sink.AllProfiles())
assert.Equal(t, len(want), sink.SampleCount())
// Verify contexts
gotCtx := sink.Contexts()
assert.Len(t, gotCtx, len(wantCtx))
for i, ctx := range gotCtx {
assert.Equal(t, fmt.Sprintf("value-%d", i), ctx.Value(testKey(i)))
}
sink.Reset()
assert.Empty(t, sink.AllProfiles())
assert.Empty(t, sink.Contexts())
assert.Equal(t, 0, sink.SampleCount())
}
// TestSinkContextTransformation verifies that the context is stored and transformed correctly
func TestSinkContextTransformation(t *testing.T) {
testCases := []struct {
name string
sink interface {
Contexts() []context.Context
}
consumeFunc func(any, context.Context) error
testData any
}{
{
name: "TracesSink",
sink: new(TracesSink),
consumeFunc: func(sink any, ctx context.Context) error {
return sink.(*TracesSink).ConsumeTraces(ctx, testdata.GenerateTraces(1))
},
},
{
name: "MetricsSink",
sink: new(MetricsSink),
consumeFunc: func(sink any, ctx context.Context) error {
return sink.(*MetricsSink).ConsumeMetrics(ctx, testdata.GenerateMetrics(1))
},
},
{
name: "LogsSink",
sink: new(LogsSink),
consumeFunc: func(sink any, ctx context.Context) error {
return sink.(*LogsSink).ConsumeLogs(ctx, testdata.GenerateLogs(1))
},
},
{
name: "ProfilesSink",
sink: new(ProfilesSink),
consumeFunc: func(sink any, ctx context.Context) error {
return sink.(*ProfilesSink).ConsumeProfiles(ctx, testdata.GenerateProfiles(1))
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Create a context with initial values
initialCtx := context.WithValue(context.Background(), ctxKey("initial-key"), "initial-value")
// Create a context chain to simulate transformation
transformedCtx := context.WithValue(initialCtx, ctxKey("transformed-key"), "transformed-value")
// Consume data with the transformed context
err := tc.consumeFunc(tc.sink, transformedCtx)
require.NoError(t, err)
// Verify context storage and transformation
storedContexts := tc.sink.Contexts()
assert.Len(t, storedContexts, 1, "Should have stored exactly one context")
storedCtx := storedContexts[0]
// Verify both initial and transformed values are preserved
assert.Equal(t, "initial-value", storedCtx.Value(ctxKey("initial-key")),
"Initial context value should be preserved")
assert.Equal(t, "transformed-value", storedCtx.Value(ctxKey("transformed-key")),
"Transformed context value should be stored")
})
}
}
// TestContextTransformationChain verifies that the context is stored and transformed correctly in a chain of transformations
func TestContextTransformationChain(t *testing.T) {
sink := new(TracesSink)
// Create a context transformation chain
baseCtx := context.Background()
ctx1 := context.WithValue(baseCtx, ctxKey("step1"), "value1")
ctx2 := context.WithValue(ctx1, ctxKey("step2"), "value2")
ctx3 := context.WithValue(ctx2, ctxKey("step3"), "value3")
// Consume traces with the transformed context
td := testdata.GenerateTraces(1)
err := sink.ConsumeTraces(ctx3, td)
require.NoError(t, err)
// Verify the complete transformation chain
storedContexts := sink.Contexts()
require.Len(t, storedContexts, 1)
finalCtx := storedContexts[0]
// Verify each transformation step
assert.Equal(t, "value1", finalCtx.Value(ctxKey("step1")), "First transformation should be preserved")
assert.Equal(t, "value2", finalCtx.Value(ctxKey("step2")), "Second transformation should be preserved")
assert.Equal(t, "value3", finalCtx.Value(ctxKey("step3")), "Third transformation should be preserved")
}
// TestConcurrentContextTransformations verifies context handling under concurrent operations
func TestConcurrentContextTransformations(t *testing.T) {
sink := new(TracesSink)
const numGoroutines = 10
errChan := make(chan error, numGoroutines)
var wg sync.WaitGroup
wg.Add(numGoroutines)
for i := range numGoroutines {
go func(idx int) {
defer wg.Done()
key := ctxKey(fmt.Sprintf("goroutine-%d", idx))
value := fmt.Sprintf("value-%d", idx)
ctx := context.WithValue(context.Background(), key, value)
td := testdata.GenerateTraces(1)
if err := sink.ConsumeTraces(ctx, td); err != nil {
errChan <- err
}
}(i)
}
wg.Wait()
close(errChan)
// Check for any errors that occurred in goroutines
for err := range errChan {
t.Errorf("Error in goroutine: %v", err)
}
// Verify all contexts were stored correctly
storedContexts := sink.Contexts()
assert.Len(t, storedContexts, numGoroutines)
// Create a map to verify all expected values are present
contextValues := make(map[string]bool)
for _, ctx := range storedContexts {
for i := range numGoroutines {
key := ctxKey(fmt.Sprintf("goroutine-%d", i))
expectedValue := fmt.Sprintf("value-%d", i)
if val := ctx.Value(key); val == expectedValue {
contextValues[fmt.Sprintf("goroutine-%d", i)] = true
}
}
}
// Verify all goroutines' contexts were preserved
assert.Len(t, contextValues, numGoroutines,
"Should have stored contexts from all goroutines")
}
================================================
FILE: consumer/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package consumer contains interfaces that receive and process data.
package consumer // import "go.opentelemetry.io/collector/consumer"
================================================
FILE: consumer/go.mod
================================================
module go.opentelemetry.io/collector/consumer
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/pdata v1.54.0
go.uber.org/goleak v1.3.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/pdata => ../pdata
retract (
v0.76.0 // Depends on retracted pdata v1.0.0-rc10 module, use v0.76.1
v0.69.0 // Release failed, use v0.69.1
)
replace go.opentelemetry.io/collector/featuregate => ../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../internal/testutil
================================================
FILE: consumer/go.sum
================================================
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: consumer/internal/consumer.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/consumer/internal"
// Capabilities describes the capabilities of a Processor.
type Capabilities struct {
// MutatesData is set to true if Consume* function of the
// processor modifies the input Traces, Logs or Metrics 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.
MutatesData bool
}
type BaseConsumer interface {
Capabilities() Capabilities
}
type BaseImpl struct {
Cap Capabilities
}
// Option to construct new consumers.
type Option interface {
apply(*BaseImpl)
}
type OptionFunc func(*BaseImpl)
func (of OptionFunc) apply(e *BaseImpl) {
of(e)
}
// Capabilities returns the capabilities of the component
func (bs BaseImpl) Capabilities() Capabilities {
return bs.Cap
}
func NewBaseImpl(options ...Option) *BaseImpl {
bs := &BaseImpl{
Cap: Capabilities{MutatesData: false},
}
for _, op := range options {
op.apply(bs)
}
return bs
}
================================================
FILE: consumer/logs.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package consumer // import "go.opentelemetry.io/collector/consumer"
import (
"context"
"go.opentelemetry.io/collector/consumer/internal"
"go.opentelemetry.io/collector/pdata/plog"
)
// Logs is an interface that receives plog.Logs, processes it
// as needed, and sends it to the next processing node if any or to the destination.
type Logs interface {
internal.BaseConsumer
// ConsumeLogs processes the logs. After the function returns, the logs are no longer accessible,
// and accessing them is considered undefined behavior.
ConsumeLogs(ctx context.Context, ld plog.Logs) error
}
// ConsumeLogsFunc is a helper function that is similar to ConsumeLogs.
type ConsumeLogsFunc func(ctx context.Context, ld plog.Logs) error
// ConsumeLogs calls f(ctx, ld).
func (f ConsumeLogsFunc) ConsumeLogs(ctx context.Context, ld plog.Logs) error {
return f(ctx, ld)
}
type baseLogs struct {
*internal.BaseImpl
ConsumeLogsFunc
}
// NewLogs returns a Logs configured with the provided options.
func NewLogs(consume ConsumeLogsFunc, options ...Option) (Logs, error) {
if consume == nil {
return nil, errNilFunc
}
return &baseLogs{
BaseImpl: internal.NewBaseImpl(options...),
ConsumeLogsFunc: consume,
}, nil
}
================================================
FILE: consumer/logs_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package consumer
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/plog"
)
func TestDefaultLogs(t *testing.T) {
cp, err := NewLogs(func(context.Context, plog.Logs) error { return nil })
assert.NoError(t, err)
assert.NoError(t, cp.ConsumeLogs(context.Background(), plog.NewLogs()))
assert.Equal(t, Capabilities{MutatesData: false}, cp.Capabilities())
}
func TestNilFuncLogs(t *testing.T) {
_, err := NewLogs(nil)
assert.Equal(t, errNilFunc, err)
}
func TestWithCapabilitiesLogs(t *testing.T) {
cp, err := NewLogs(
func(context.Context, plog.Logs) error { return nil },
WithCapabilities(Capabilities{MutatesData: true}))
assert.NoError(t, err)
assert.NoError(t, cp.ConsumeLogs(context.Background(), plog.NewLogs()))
assert.Equal(t, Capabilities{MutatesData: true}, cp.Capabilities())
}
func TestConsumeLogs(t *testing.T) {
consumeCalled := false
cp, err := NewLogs(func(context.Context, plog.Logs) error { consumeCalled = true; return nil })
assert.NoError(t, err)
assert.NoError(t, cp.ConsumeLogs(context.Background(), plog.NewLogs()))
assert.True(t, consumeCalled)
}
func TestConsumeLogs_ReturnError(t *testing.T) {
want := errors.New("my_error")
cp, err := NewLogs(func(context.Context, plog.Logs) error { return want })
require.NoError(t, err)
assert.Equal(t, want, cp.ConsumeLogs(context.Background(), plog.NewLogs()))
}
================================================
FILE: consumer/metadata.yaml
================================================
type: consumer
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: consumer/metrics.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package consumer // import "go.opentelemetry.io/collector/consumer"
import (
"context"
"go.opentelemetry.io/collector/consumer/internal"
"go.opentelemetry.io/collector/pdata/pmetric"
)
// Metrics is an interface that receives pmetric.Metrics, processes it
// as needed, and sends it to the next processing node if any or to the destination.
type Metrics interface {
internal.BaseConsumer
// ConsumeMetrics processes the metrics. After the function returns, the metrics are no longer accessible,
// and accessing them is considered undefined behavior.
ConsumeMetrics(ctx context.Context, md pmetric.Metrics) error
}
// ConsumeMetricsFunc is a helper function that is similar to ConsumeMetrics.
type ConsumeMetricsFunc func(ctx context.Context, md pmetric.Metrics) error
// ConsumeMetrics calls f(ctx, md).
func (f ConsumeMetricsFunc) ConsumeMetrics(ctx context.Context, md pmetric.Metrics) error {
return f(ctx, md)
}
type baseMetrics struct {
*internal.BaseImpl
ConsumeMetricsFunc
}
// NewMetrics returns a Metrics configured with the provided options.
func NewMetrics(consume ConsumeMetricsFunc, options ...Option) (Metrics, error) {
if consume == nil {
return nil, errNilFunc
}
return &baseMetrics{
BaseImpl: internal.NewBaseImpl(options...),
ConsumeMetricsFunc: consume,
}, nil
}
================================================
FILE: consumer/metrics_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package consumer
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/pmetric"
)
func TestDefaultMetrics(t *testing.T) {
cp, err := NewMetrics(func(context.Context, pmetric.Metrics) error { return nil })
assert.NoError(t, err)
assert.NoError(t, cp.ConsumeMetrics(context.Background(), pmetric.NewMetrics()))
assert.Equal(t, Capabilities{MutatesData: false}, cp.Capabilities())
}
func TestNilFuncMetrics(t *testing.T) {
_, err := NewMetrics(nil)
assert.Equal(t, errNilFunc, err)
}
func TestWithCapabilitiesMetrics(t *testing.T) {
cp, err := NewMetrics(
func(context.Context, pmetric.Metrics) error { return nil },
WithCapabilities(Capabilities{MutatesData: true}))
assert.NoError(t, err)
assert.NoError(t, cp.ConsumeMetrics(context.Background(), pmetric.NewMetrics()))
assert.Equal(t, Capabilities{MutatesData: true}, cp.Capabilities())
}
func TestConsumeMetrics(t *testing.T) {
consumeCalled := false
cp, err := NewMetrics(func(context.Context, pmetric.Metrics) error { consumeCalled = true; return nil })
assert.NoError(t, err)
assert.NoError(t, cp.ConsumeMetrics(context.Background(), pmetric.NewMetrics()))
assert.True(t, consumeCalled)
}
func TestConsumeMetrics_ReturnError(t *testing.T) {
want := errors.New("my_error")
cp, err := NewMetrics(func(context.Context, pmetric.Metrics) error { return want })
require.NoError(t, err)
assert.Equal(t, want, cp.ConsumeMetrics(context.Background(), pmetric.NewMetrics()))
}
================================================
FILE: consumer/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package consumer
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: consumer/traces.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package consumer // import "go.opentelemetry.io/collector/consumer"
import (
"context"
"go.opentelemetry.io/collector/consumer/internal"
"go.opentelemetry.io/collector/pdata/ptrace"
)
// Traces is an interface that receives ptrace.Traces, processes it
// as needed, and sends it to the next processing node if any or to the destination.
type Traces interface {
internal.BaseConsumer
// ConsumeTraces processes the traces. After the function returns, the traces are no longer accessible,
// and accessing them is considered undefined behavior.
ConsumeTraces(ctx context.Context, td ptrace.Traces) error
}
// ConsumeTracesFunc is a helper function that is similar to ConsumeTraces.
type ConsumeTracesFunc func(ctx context.Context, td ptrace.Traces) error
// ConsumeTraces calls f(ctx, td).
func (f ConsumeTracesFunc) ConsumeTraces(ctx context.Context, td ptrace.Traces) error {
return f(ctx, td)
}
type baseTraces struct {
*internal.BaseImpl
ConsumeTracesFunc
}
// NewTraces returns a Traces configured with the provided options.
func NewTraces(consume ConsumeTracesFunc, options ...Option) (Traces, error) {
if consume == nil {
return nil, errNilFunc
}
return &baseTraces{
BaseImpl: internal.NewBaseImpl(options...),
ConsumeTracesFunc: consume,
}, nil
}
================================================
FILE: consumer/traces_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package consumer
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/ptrace"
)
func TestDefaultTraces(t *testing.T) {
cp, err := NewTraces(func(context.Context, ptrace.Traces) error { return nil })
assert.NoError(t, err)
assert.NoError(t, cp.ConsumeTraces(context.Background(), ptrace.NewTraces()))
assert.Equal(t, Capabilities{MutatesData: false}, cp.Capabilities())
}
func TestNilFuncTraces(t *testing.T) {
_, err := NewTraces(nil)
assert.Equal(t, errNilFunc, err)
}
func TestWithCapabilitiesTraces(t *testing.T) {
cp, err := NewTraces(
func(context.Context, ptrace.Traces) error { return nil },
WithCapabilities(Capabilities{MutatesData: true}))
assert.NoError(t, err)
assert.NoError(t, cp.ConsumeTraces(context.Background(), ptrace.NewTraces()))
assert.Equal(t, Capabilities{MutatesData: true}, cp.Capabilities())
}
func TestConsumeTraces(t *testing.T) {
consumeCalled := false
cp, err := NewTraces(func(context.Context, ptrace.Traces) error { consumeCalled = true; return nil })
assert.NoError(t, err)
assert.NoError(t, cp.ConsumeTraces(context.Background(), ptrace.NewTraces()))
assert.True(t, consumeCalled)
}
func TestConsumeTraces_ReturnError(t *testing.T) {
want := errors.New("my_error")
cp, err := NewTraces(func(context.Context, ptrace.Traces) error { return want })
require.NoError(t, err)
assert.Equal(t, want, cp.ConsumeTraces(context.Background(), ptrace.NewTraces()))
}
================================================
FILE: consumer/xconsumer/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: consumer/xconsumer/go.mod
================================================
module go.opentelemetry.io/collector/consumer/xconsumer
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/consumer v1.54.0
go.opentelemetry.io/collector/pdata/pprofile v0.148.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/pdata v1.54.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile
replace go.opentelemetry.io/collector/consumer => ../
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
================================================
FILE: consumer/xconsumer/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: consumer/xconsumer/metadata.yaml
================================================
type: xconsumer
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: consumer
codeowners:
active:
- mx-psi
- dmathieu
stability:
alpha: [profiles]
================================================
FILE: consumer/xconsumer/profiles.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xconsumer // import "go.opentelemetry.io/collector/consumer/xconsumer"
import (
"context"
"errors"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/internal"
"go.opentelemetry.io/collector/pdata/pprofile"
)
var errNilFunc = errors.New("nil consumer func")
// Profiles is an interface that receives pprofile.Profiles, processes it
// as needed, and sends it to the next processing node if any or to the destination.
type Profiles interface {
internal.BaseConsumer
// ConsumeProfiles processes the profiles. After the function returns, the profiles are no longer accessible,
// and accessing them is considered undefined behavior.
ConsumeProfiles(ctx context.Context, td pprofile.Profiles) error
}
// ConsumeProfilesFunc is a helper function that is similar to ConsumeProfiles.
type ConsumeProfilesFunc func(ctx context.Context, td pprofile.Profiles) error
// ConsumeProfiles calls f(ctx, td).
func (f ConsumeProfilesFunc) ConsumeProfiles(ctx context.Context, td pprofile.Profiles) error {
return f(ctx, td)
}
type baseProfiles struct {
*internal.BaseImpl
ConsumeProfilesFunc
}
// NewProfiles returns a Profiles configured with the provided options.
func NewProfiles(consume ConsumeProfilesFunc, options ...consumer.Option) (Profiles, error) {
if consume == nil {
return nil, errNilFunc
}
return &baseProfiles{
BaseImpl: internal.NewBaseImpl(options...),
ConsumeProfilesFunc: consume,
}, nil
}
================================================
FILE: consumer/xconsumer/profiles_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xconsumer
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/pdata/pprofile"
)
func TestDefaultProfiles(t *testing.T) {
cp, err := NewProfiles(func(context.Context, pprofile.Profiles) error { return nil })
assert.NoError(t, err)
assert.NoError(t, cp.ConsumeProfiles(context.Background(), pprofile.NewProfiles()))
assert.Equal(t, consumer.Capabilities{MutatesData: false}, cp.Capabilities())
}
func TestNilFuncProfiles(t *testing.T) {
_, err := NewProfiles(nil)
assert.Equal(t, errNilFunc, err)
}
func TestWithCapabilitiesProfiles(t *testing.T) {
cp, err := NewProfiles(
func(context.Context, pprofile.Profiles) error { return nil },
consumer.WithCapabilities(consumer.Capabilities{MutatesData: true}))
assert.NoError(t, err)
assert.NoError(t, cp.ConsumeProfiles(context.Background(), pprofile.NewProfiles()))
assert.Equal(t, consumer.Capabilities{MutatesData: true}, cp.Capabilities())
}
func TestConsumeProfiles(t *testing.T) {
consumeCalled := false
cp, err := NewProfiles(func(context.Context, pprofile.Profiles) error { consumeCalled = true; return nil })
assert.NoError(t, err)
assert.NoError(t, cp.ConsumeProfiles(context.Background(), pprofile.NewProfiles()))
assert.True(t, consumeCalled)
}
func TestConsumeProfiles_ReturnError(t *testing.T) {
want := errors.New("my_error")
cp, err := NewProfiles(func(context.Context, pprofile.Profiles) error { return want })
require.NoError(t, err)
assert.Equal(t, want, cp.ConsumeProfiles(context.Background(), pprofile.NewProfiles()))
}
================================================
FILE: distributions.yaml
================================================
# A collection of distributions that can be referenced in the metadata.yaml files.
# The rules below apply to every distribution added to this list:
# - The distribution is open source and maintained by the OpenTelemetry project.
# - The link must point to a publicly accessible repository.
- name: core
url: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol
- name: contrib
url: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
- name: k8s
url: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-k8s
- name: otlp
url: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-otlp
================================================
FILE: docs/README.md
================================================
# OpenTelemetry Collector
**Status**: [Beta](https://github.com/open-telemetry/opentelemetry-specification/blob/main/oteps/0232-maturity-of-otel.md#beta)
The OpenTelemetry Collector consists of the following components:
* A mechanism that _MUST_ be able to load and parse an [OpenTelemetry Collector configuration
file](#configuration-file).
* A mechanism that _MUST_ be able to include compatible
[Collector components](#opentelemetry-collector-components) that
the user wishes to include.
These combined provide users the ability to easily switch between
[OpenTelemetry Collector Distributions](#opentelemetry-collector-distribution) while also ensuring that components produced by
the OpenTelemetry Collector SIG are able to work with any vendor who claims
support for an OpenTelemetry Collector.
## Configuration file
An OpenTelemetry Collector configuration file is defined as YAML and _MUST_ support
the following [minimum structure](https://pkg.go.dev/go.opentelemetry.io/collector/otelcol#Config):
```yaml
receivers:
processors:
exporters:
connectors:
extensions:
service:
telemetry:
pipelines:
```
## OpenTelemetry Collector components
For a library to be considered an OpenTelemetry Collector component, it _MUST_
implement a [Component interface](https://pkg.go.dev/go.opentelemetry.io/collector/component#Component)
defined by the OpenTelemetry Collector SIG.
Components require a [unique identifier](https://pkg.go.dev/go.opentelemetry.io/collector/component#ID)
to be included in an OpenTelemetry Collector. In the event of a name collision,
the components resulting in the collision cannot be used simultaneously in a single OpenTelemetry
Collector. In order to resolve this, the clashing components must use different identifiers.
### Compatibility requirements
A component is defined as compatible with an OpenTelemetry Collector when its dependencies are
source- and version-compatible with the Component interfaces of that Collector.
For example, a Collector derived from version tag v0.100.0 of the [OpenTelemetry Collector](https://github.com/open-telemetry/opentelemetry-collector) _MUST_ support all components that
are version-compatible with the Golang Component API defined in the `github.com/open-telemetry/opentelemetry-collector/component` module found in that repository for that version tag.
## OpenTelemetry Collector Distribution
An OpenTelemetry Collector Distribution (Distro) is a compiled instance
of an OpenTelemetry Collector with a specific set of components and features. A
Distribution author _MAY_ choose to produce a distribution by utilizing tools
and/or documentation supported by the OpenTelemetry project. Alternatively, a
Distribution author _MUST_ provide end users with the capability for adding
their own components to the Distribution's components. Note that the resulting
binary from updating a Distribution to include new components
is a different Distribution.
================================================
FILE: docs/coding-guidelines.md
================================================
# Coding guidelines
We consider the 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).
## Naming convention
### Component naming
Components (receivers, processors, exporters, extensions, and connectors) MUST use `lower_snake_case` naming convention. This ensures consistency and enhances readability for end users.
This naming convention applies to the component identifier used in configuration files and component registration, not to Go package names which follow standard Go naming conventions (lowercase, no underscores).
Examples of correct component names:
- `memory_limiter` (not `memorylimiter`)
- `otlp_http` (not `otlphttp`)
For example, a component with identifier `memory_limiter` would typically have a Go package name like `memorylimiterprocessor`.
#### Migration for existing components
Components that currently use a different naming convention:
- SHOULD add the `lower_snake_case` name as the primary identifier
- MAY support the old name as a deprecated alias for backwards compatibility
- MUST document the migration path in their README
Only components following the `lower_snake_case` naming convention should be marked as stable.
### Go API naming conventions
To keep naming patterns consistent across the project, naming patterns are enforced to make intent clear by:
- Methods that return a variable that uses the zero value or values provided via the method MUST have the prefix `New`. For example:
- `func NewKinesisExporter(kpl aws.KinesisProducerLibrary)` allocates a variable that uses
the variables passed on creation.
- `func NewKeyValueBuilder()` SHOULD allocate internal variables to a safe zero value.
- Methods that return a variable that uses non-zero value(s) that impacts business logic MUST use the prefix `NewDefault`. For example:
- `func NewDefaultKinesisConfig()` would return a configuration that is the suggested default
and can be updated without concern of causing a race condition.
- Methods that act upon an input variable MUST have a signature that reflects concisely the logic being done. For example:
- `func FilterAttributes(attrs []Attribute, match func(attr Attribute) bool) []Attribute` MUST only filter attributes out of the passed input
slice and return a new slice with values that `match` returns true. It may not do more work than what the method name implies, ie, it
must not key a global history of all the slices that have been filtered.
- Methods that get the value of a field i.e. a getterMethod MUST use an uppercase first letter and NOT a `get` prefix. For example:
- `func (p *Person) Name() string {return p.name} ` Name (with an uppercase N, exported) method is used here to get the value of the name field and not `getName`.The use of upper-case names for export provides the hook to discriminate the field from the method.
- Methods that set the value of a field i.e. a setterMethod MUST use a `set` prefix. For example:
- `func (p *Person) SetName(newName string) {p.name = newName}` SetName method here sets the value of the name field.
- Variable assigned in a package's global scope that is preconfigured with a default set of values MUST use `Default` as the prefix. For example:
- `var DefaultMarshallers = map[string]pdata.Marshallers{...}` is defined with an exporter package that allows for converting an encoding name,
`zipkin`, and return the preconfigured marshaller to be used in the export process.
- Types that are specific to a signal MUST be worded with the signal used as an adjective, i.e. `SignalType`. For example:
- `type TracesSink interface {...}`
- Types that deal with multiple signal types should use the relationship between the signals to describe the type, e.g. `SignalToSignalType` or `SignalAndSignalType`. For example:
- `type TracesToTracesFunc func(...) ...`
- Functions dealing with specific signals or signal-specific types MUST be worded with the signal or type as a direct object, i.e. `VerbSignal`, or `VerbType` where `Type` is the full name of the type including the signal name. For example:
- `func ConsumeTraces(...) {...}`
- `func CreateTracesExport(...) {...}`
- `func CreateTracesToTracesFunc(...) {...}`
#### Configuration structs
When naming configuration structs, use the following guidelines:
- Separate the configuration set by end users in their YAML configuration from the configuration set by developers in the code into different structs.
- Use the `Config` suffix for configuration structs that have end user configuration (i.e. that set in their YAML configuration). For example, `configgrpc.ClientConfig` ends in `Config` since it contains end user configuration.
- Use the `Settings` suffix for configuration structs that are set by developers in the code. For example, `component.TelemetrySettings` ends in `Settings` since it is set by developers in the code.
- Avoid redundant prefixes that are already implied by the package name. For example, use`configgrpc.ClientConfig` instead of `configgrpc.GRPCClientConfig`.
#### Avoid Embedded Structs
When defining configuration structs, avoid using embedded (anonymous) struct fields. Instead, use explicitly named fields.
**Rationale:**
1. **Unmarshal Compatibility**: Embedded structs can break custom `Unmarshal` implementations. If an embedded struct requires special unmarshaling logic, it may not function correctly when embedded.
2. **Naming Conflicts**: Embedded structs can cause field name collisions. Even if the YAML configuration nests them properly (e.g., under `sending_queue`), having identical field names in embedded structs creates ambiguity in the Go code.
3. **Clarity**: Named fields make the configuration structure more explicit and easier to understand.
**Example:**
```go
// ❌ BAD: Using embedded structs
type ExporterConfig struct {
exporterhelper.TimeoutConfig // embedded
exporterhelper.QueueConfig // embedded
exporterhelper.RetryConfig // embedded
}
// ✅ GOOD: Using named fields
type ExporterConfig struct {
Timeout exporterhelper.TimeoutConfig `mapstructure:"timeout"`
Queue exporterhelper.QueueConfig `mapstructure:"sending_queue"`
Retry exporterhelper.RetryConfig `mapstructure:"retry_on_failure"`
}
```
This practice ensures better maintainability and prevents subtle bugs related to struct composition and configuration unmarshaling.
**Preserving Flat YAML Structure with the `squash` Tag:**
In some cases, you may need to maintain backward compatibility with an existing flat YAML configuration structure while still using named fields in Go. The `mapstructure:",squash"` tag achieves this by flattening the nested struct's fields into the parent configuration:
```go
// Using named fields with squash tag for flat YAML structure
type Config struct {
ClientConfig confighttp.ClientConfig `mapstructure:",squash"`
}
```
This allows the YAML configuration to remain flat (fields at the top level) while the Go code uses a named field. However, prefer explicitly nested configurations (without `squash`) for new components, as the nested structure is clearer and avoids the issues mentioned above.
## Module organization
As usual in Go projects, organize your code into packages grouping related functionality. To ensure
that we can evolve different parts of the API independently, you should also group related packages
into modules.
We use the following rules for some common situations where we split into separate modules:
1. Each top-level directory should be a separate module.
1. Each component referenceable by the OpenTelemetry Collector Builder should be in a separate
module. For example, the OTLP receiver is in its own module, different from that of other
receivers.
1. Consider splitting into separate modules if the API may evolve independently in separate groups
of packages. For example, the configuration related to HTTP and gRPC evolve independently, so
`config/configgrpc` and `config/confighttp` are separate modules.
1. For component names, add the component kind as a suffix for the module name. For example, the
OTLP receiver is in the `receiver/otlpreceiver` module.
1. Modules that add specific functionality related to a parent folder should have a prefix in the
name that relates to the parent module. For example, `configauth` has the `config` prefix since
it is part of the `config` folder, and `extensionauth` has `extension` as a prefix since it is
part of the `extension` module.
1. Testing helpers should be in a separate submodule with the suffix `test`. For example, if you
have a module `component`, the helpers should be in `component/componenttest`. Testing helpers
that are used across multiple modules should be in the [`internal/testutil`](https://github.com/open-telemetry/opentelemetry-collector/tree/main/internal/testutil)
module.
1. Experimental packages that will later be added to another module should be in their own module,
named as they will be after integration. For example, if adding a `pprofile` package to `pdata`,
you should add a separate module `pdata/pprofile` for the experimental code.
1. Experimental code that will be added to an existing package in a stable module can be a submodule
with the same name, but prefixed with an `x`. For example, `config/confighttp` module can have an
experimental module named `config/confighttp/xconfighttp` that contains experimental APIs.
When adding a new module remember to update the following:
1. Add a changelog note for the new module.
1. Add the module in `versions.yaml`.
1. Use `make crosslink` to make sure the module replaces are added correctly throughout the
codebase. You may also have to manually add some of the replaces.
1. Update the [otelcorecol
manifest](https://github.com/open-telemetry/opentelemetry-collector/blob/main/cmd/otelcorecol/builder-config.yaml)
and [builder
tests](https://github.com/open-telemetry/opentelemetry-collector/blob/main/cmd/builder/internal/builder/main_test.go).
1. Open a follow up PR to update pseudo-versions in all go.mod files. See [this example
PR](https://github.com/open-telemetry/opentelemetry-collector/pull/11668).
## Enumerations
To keep naming patterns consistent across the project, enumeration patterns are enforced to make intent clear:
- Enumerations should be defined using a type definition, such as `type Level int32`.
- Enumerations should use either `int` or `string` as the underlying type
- The enumeration name should succinctly describe its purpose
- If the package name represents the entity described by the enumeration then the package name should be factored into the name of the enumeration. For example, `component.Type` instead of `component.ComponentType`.
- The name should convey a sense of limited categorization. For example, `pcommon.ValueType` is better than `pcommon.Value` and `component.Kind` is better than `component.KindType`, since `Kind` already conveys categorization.
- Constant values of an enumeration should be prefixed with the enumeration type name in the name:
- `pcommon.ValueTypeStr` for `pcommon.ValueType`
- `pmetric.MetricTypeGauge` for `pmetric.MetricType`
## Recommended Libraries / Defaults
In order to simplify development within the project, we have made certain library recommendations that should be followed.
| Scenario | Recommended | Rationale |
|------------|------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------|
| Hashing | ["hashing/fnv"](https://pkg.go.dev/hash/fnv) | The project adopted this as the default hashing method due to the efficiency and is reasonable for non-cryptographic use |
| Testing | Use `t.Parallel()` where possible | Enabling more tests to be run in parallel will speed up the feedback process when working on the project. |
Within the project, there are some packages that have yet to follow the recommendations and are being addressed. However, any new code should adhere to the recommendations.
## Default Configuration
To guarantee backward-compatible behavior, all configuration packages should supply a `NewDefault[config name]` functions that create a default version of the config. The package does not need to guarantee that `NewDefault[config name]` returns a usable configuration—only that default values will be set. For example, if the configuration requires that a field, such as `Endpoint` be set, but there is no valid default value, then `NewDefault[config name]` may set that value to `""` with the expectation that the user will set a valid value.
Users should always initialize the config struct with this function and overwrite anything as needed.
## 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. data that 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 the 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 an 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 the 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, and keep the number of success messages 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 a 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 incrementing it when
the event happens.
Make log messages human readable and also include data that is needed for easier
understanding of what happened and in what context.
## Executing External Processes
The components should avoid executing arbitrary external processes with arbitrary command
line arguments based on user input, including input received from the network or input
read from the configuration file. Failure to follow this rule can result in arbitrary
remote code execution, compelled by malicious actors that can craft the input.
The following limitations are recommended:
- If an external process needs to be executed limit and hard-code the location where
the executable file may be located, instead of allowing the input to dictate the
full path to the executable.
- If possible limit the name of the executable file to be pulled from a hard-coded
list defined at compile time.
- If command line arguments need to be passed to the process do not take the arguments
from the user input directly. Instead, compose the command line arguments indirectly,
if necessary, deriving the value from the user input. Limit as much as possible the
size of the possible space of values for command line arguments.
## Observability
Out of the box, your users should be able to observe the state of your
component. See [observability.md](observability.md) for more details.
When using the regular helpers, you should have some metrics added around key
events automatically. For instance, exporters should have
`otelcol_exporter_sent_spans` tracked without your exporter doing anything.
Custom metrics can be defined as part of the `metadata.yaml` for your component.
The authoritative source of information for this is [the
schema](https://github.com/open-telemetry/opentelemetry-collector/blob/main/cmd/mdatagen/metadata-schema.yaml),
but here are a few examples for reference, adapted from the tail sampling
processor:
```yaml
telemetry:
metrics:
# example of a histogram
processor.tailsampling.samplingdecision.latency:
description: Latency (in microseconds) of a given sampling policy.
unit: µs # from https://ucum.org/ucum
enabled: true
histogram:
value_type: int
# bucket boundaries can be overridden
bucket_boundaries: [1, 2, 5, 10, 25, 50, 75, 100, 150, 200, 300, 400, 500, 750, 1000, 2000, 3000, 4000, 5000, 10000, 20000, 30000, 50000]
# example of a counter
processor.tailsampling.policyevaluation.errors:
description: Count of sampling policy evaluation errors.
unit: "{errors}"
enabled: true
sum:
value_type: int
monotonic: true
# example of a gauge
processor.tailsampling.tracesonmemory:
description: Tracks the number of traces current on memory.
unit: "{traces}"
enabled: true
gauge:
value_type: int
```
Running `go generate ./...` at the root of your component should generate the
following files:
- `documentation.md`, with the metrics and their descriptions
- `internal/metadata/generated_telemetry.go`, with code that defines the metric
using the OTel API
- `internal/metadata/generated_telemetry_test.go`, with sanity tests for the
generated code
On your component's code, you can use the metric by initializing the telemetry
builder and storing it on a component's field:
```go
type tailSamplingSpanProcessor struct {
ctx context.Context
telemetry *metadata.TelemetryBuilder
}
func newTracesProcessor(ctx context.Context, settings component.TelemetrySettings, nextConsumer consumer.Traces, cfg Config, opts ...Option) (processor.Traces, error) {
telemetry, err := metadata.NewTelemetryBuilder(settings)
if err != nil {
return nil, err
}
tsp := &tailSamplingSpanProcessor{
ctx: ctx,
telemetry: telemetry,
}
}
```
To record the measurement, you can then call the metric stored in the telemetry
builder:
```go
tsp.telemetry.ProcessorTailsamplingSamplingdecisionLatency.Record(ctx, ...)
```
## Resource Usage
Limit usage of CPU, RAM, and 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 limits many times over. 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 the 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.
## Semantic Conventions compatibility
When adding new metrics, attributes or entity attributes to a Collector's component (receiver, processor etc), the
[Semantic Conventions](https://github.com/open-telemetry/semantic-conventions) project should be checked first
to see if those are already defined as Semantic Conventions.
It's also important to check for any open issues that may already propose these or similar Semantic Conventions.
If no such Semantic Conventions are defined in the Semantic Conventions project, the component’s code owners
should consider initiating that process first
(refer to Semantic Conventions'
[contribution guidelines](https://github.com/open-telemetry/semantic-conventions/blob/main/CONTRIBUTING.md)
for specific details).
The implementation of the component can still be submitted as a draft PR to demonstrate how the proposed
Semantic Conventions would be used while working in parallel to contribute the relevant updates to
the Semantic Conventions project.
The components's code owners can review the Semantic Conventions PR in collaboration with any existing domain-specific
SemConv approvers.
At their discretion, the code owners may choose to block the component’s implementation PR until the related
Semantic Conventions changes are completed.
## Telemetry Stability Levels
### Metrics
Metrics emitted by Collector scrapers/receivers (e.g. `system.cpu.time`) follow the same stability levels
as the Collector's internal metrics (e.g. `otelcol_process_cpu_seconds`), as documented in
[Internal Telemetry Stability](https://opentelemetry.io/docs/collector/internal-telemetry/#metrics).
In particular, for beta and stable levels the following guidelines apply:
#### Beta stability level
It is highly encouraged that metrics in beta stage
are also defined as Semantic Conventions based on the [Semantic Conventions compatibility](#semantic-conventions-compatibility),
ensuring cross-project consistency.
#### Stable stability level
Before promoting a metric to stable, it should be discussed whether it needs to
be defined as a Semantic Convention, following the [Semantic Conventions compatibility](#semantic-conventions-compatibility).
Promoting a metric to stable without it being a Semantic Convention involves
the risk of potential divergence within OpenTelemetry's projects.
For example, a stable metric in the Collector might be introduced in a slightly
different way in another OpenTelemetry project in the future, or it might be proposed
as a Semantic Convention in the future.
In case of such divergence, a stable Collector metric won't be allowed to
change, and if wider alignment is needed, the metric should be deprecated and
removed in order to come into alignment with the Semantic Conventions.
Consequently, the Collector's maintainers and components' code owners should
acknowledge that risk before marking a metric as stable without it being a
stable Semantic Convention and should provide justification for the decision. In
any case, [Semantic Conventions' guidelines](https://opentelemetry.io/docs/specs/semconv/how-to-write-conventions/)
should be advised when metrics are defined within the Collector directly.
### Testing Library Recommendations
To keep testing practices consistent across the project, it is advised to use these libraries under
these circumstances:
- For assertions to validate expectations, use `"github.com/stretchr/testify/assert"`
- For assertions that are required to continue the test, use `"github.com/stretchr/testify/require"`
- For mocking external resources, use `"github.com/stretchr/testify/mock"`
- For validating HTTP traffic interactions, `"net/http/httptest"`
## Integration Testing
Integration testing is encouraged throughout the project, container images can be used in order to facilitate
a local version. In their absence, it is strongly advised to mock the integration.
## Using CGO
Using CGO is prohibited due to the lack of portability and complexity
that comes with managing external libraries with different operating systems and configurations.
However, if the package MUST use CGO, this should be explicitly called out within the readme
with clear instructions on how to install the required libraries.
Furthermore, if your package requires CGO, it MUST be able to compile and operate in a no-op mode
or report a warning back to the collector with a clear error saying CGO is required to work.
## Breaking changes
Whenever possible, we adhere to [semver](https://semver.org/) as our minimum standards. Even before v1, we strive not to break compatibility
without a good reason. Hence, when a change is known to cause a breaking change, we intend to follow these principles:
- Breaking changes MUST have migration guidelines that clearly explain how to adapt to them.
- Users SHOULD be able to adopt the breaking change at their own pace, independent of other Collector updates.
- Users SHOULD be proactively notified about the breaking change before a migration is required.
- Users SHOULD be able to easily tell whether they have completed the migration for a breaking change.
Not all changes have the same effects on users, so some of the steps may be unnecessary for some changes.
### API breaking changes
We strive to perform API breaking changes in two stages, deprecating it first (`vM.N`) and breaking it in a subsequent
version (`vM.N+1`).
- when we need to remove something, we MUST mark a feature as deprecated in one version and MAY remove it in a
subsequent one
- when renaming or refactoring types, functions, or attributes, we MUST create the new name and MUST deprecate the old
one in one version (step 1), and MAY remove it in a subsequent version (step 2). For simple renames, the old name
SHALL call the new one.
- when a feature is being replaced in favor of an existing one, we MUST mark a feature as deprecated in one version, and
MAY remove it in a subsequent one.
Deprecation notice SHOULD contain a version starting from which the deprecation takes effect for tracking purposes. For
example, if `GetFoo` function is going to be deprecated in `v0.45.0` version, it gets the following godoc line:
```golang
package test
// Deprecated: [v0.45.0] Use MustDoFoo instead.
func DoFoo() {}
```
If applicable, add the [`//go:fix inline`
directive](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/inline#hdr-Analyzer_inline) on
the deprecated function to help with the migration.
#### Example #1 - Renaming a function
1. Current version `v0.N` has `func GetFoo() Bar`
1. We now decided that `GetBar` is a better name. As such, on `v0.N+1` we add a new `func GetBar() Bar` function,
changing the existing `func GetFoo() Bar` to be an alias of the new function. Additionally, a log entry with a
warning is added to the old function, along with an entry to the changelog.
1. On `v0.N+2`, we MAY remove `func GetFoo() Bar`.
#### Example #2 - Changing the return values of a function
1. Current version `v0.N` has `func GetFoo() Foo`
1. We now need to also return an error. We do it by creating a new function that will be equivalent to the existing one
so that current users can easily migrate to that: `func MustGetFoo() Foo`, which panics on errors. We release this in
`v0.N+1`, deprecating the existing `func GetFoo() Foo` with it, adding an entry to the changelog and perhaps a log
entry with a warning.
1. On `v0.N+2`, we change `func GetFoo() Foo` to `func GetFoo() (Foo, error)`.
#### Example #3 - Changing the arguments of a function
1. Current version `v0.N` has `func GetFoo() Foo`
2. We now decide to do something that might be blocking as part of `func GetFoo() Foo`, so, we start accepting a
context: `func GetFooWithContext(context.Context) Foo`. We release this in `v0.N+1`, deprecating the existing `func
GetFoo() Foo` with it, adding an entry to the changelog and perhaps a log entry with a warning. The existing `func
GetFoo() Foo` is changed to call `func GetFooWithContext(context.Background()) Foo`.
3. On `v0.N+2`, we change `func GetFoo() Foo` to `func GetFoo(context.Context) Foo` if desired or remove it entirely if
needed.
#### Exceptions
For changes to modules that do not have a version of `v1` or higher, we may skip the deprecation process described above
for the following situations. Note that these changes should still be recorded as breaking changes in the changelog.
* **Variadic arguments.** Functions that are not already variadic may have a variadic parameter added as a method of
supporting optional parameters, particularly through the functional options pattern. If a variadic parameter is
added to a function with no change in functionality when no variadic arguments are passed, the deprecation process
may be skipped. Calls to updated functions without the new argument will continue to work before, but users who depend
on the exact function signature as a type, for example as an argument to another function, will experience a
breaking change. For this reason, the deprecation process should only be skipped when it is not expected that
the function is commonly passed as a value.
### End-user impacting changes
For end user breaking changes, we follow the [feature gate](https://github.com/open-telemetry/opentelemetry-collector/tree/main/featuregate#feature-lifecycle)
approach. This is a well-known approach in other projects such as Kubernetes. A feature gate has
three stages: alpha, beta and stable. The intent of these stages is to decouple other software
changes from the breaking change; some users may adopt the change early, while other users may delay
its adoption.
#### Feature gate IDs
Feature gate IDs should be namespaced using dots to denote the hierarchy. The namespace should be as
specific as possible; in particular, for feature gates specific to a certain component the ID should
have the following structure: `..`. The "base ID" should be
written with a verb that describes what happens when the feature gate is enabled. For example, if
you want to add a feature gate for the OTLP receiver that changes the default endpoint to bind to an
unspecified host, you could name your feature gate `receiver.otlp.UseUnspecifiedHostAsDefaultHost`.
#### Lifecycle of a breaking change
##### Alpha stage
At the alpha stage, the change is opt-in. At this stage we want to notify users that a change is
coming, so they can start preparing for it and we have some early adopters that provide us with
feedback. Consider the following items before the initial release of an alpha feature gate:
* If **docs and examples** can be updated in a way that prevents the breaking change from affecting
users, this is the time to update them!
* Provide users with tools to understand the breaking change
* (Optional) Create or update a **Github issue** to document what the change is about, who it
affects and what its effects are
* (Optional) Consider adding **telemetry** that allows users to track their migration. For
example, you can add a counter for the times that you see a payload that would be affected by
the breaking change.
* Notify users about the upcoming change
* Add a **changelog entry** that describes the feature gate. It should include its name, when you
may want to use it, and what its effects are. The changelog entry can be given the `enhancement`
classification at this stage.
* (Optional but strongly recommended) Log a **warning** if the user is using the software in a way
that would be affected by the breaking change. Point the user to the feature gate and any
official docs.
* (Optional) Try to **test this in a realistic setting.** If this solves an issue, ask the poster to
try to use it and check that everything works.
##### Beta stage
At the beta stage, the change is opt-out. At this stage we want to notify users that the change is
happening, and help them understand how to revert back to the previous behavior temporarily if they
need to do so. You may directly start from this stage for breaking changes that are less impactful
or for changes that should not have a functional impact such as performance changes. Consider the
following items before moving from alpha to beta:
* Schedule the **docs and examples** update to align with the breaking change release if you
couldn’t do it before
* Provide users with tools to understand the breaking change
* Update the **Github issue** with the new default behavior (or create one if starting from here)
* Update the feature gate to add the ‘to version’ to the feature gate
* Notify users about the change
* Add a second **changelog entry** that describes the change one more time and is marked as
‘breaking’.
* If applicable, add an **error message** that tells you this is the result of a breaking change
that can be temporarily reverted disabling the feature gate and points to any issue or docs
about it.
##### Stable stage
At the stable stage, the change cannot be reverted. In some cases, you may directly start here and
just do the change, in which case you do not need a feature gate, but you should still follow the
checklist below (notify, update docs and examples). Consider the following items before moving from
beta to stable:
* Remove the dead code
* Provide users with tools to understand the breaking change
* Update the **documentation** **and examples** to remove any references to the feature gate and
the previous behavior. Close the **Github issue** if you opened one before.
* Notify users about the change
* Add one last **changelog entry** so users know the range where the feature gate was in beta
* Amend the **error message** to remove any references to the feature gate.
## Specification Tracking
The [OpenTelemetry Specification](https://github.com/open-telemetry/opentelemetry-specification) can be a rapidly
moving target at times. While it may seem efficient to get an early start on implementing new features or
functionality under development in the specification, this can also lead to significant churn and a risk that
changes in the specification can result in breaking changes to the implementation. For this reason, it is the
policy of the Collector SIG to not implement, or accept implementations of, new or changed specification language
prior to inclusion in a stable release of the specification.
================================================
FILE: docs/component-stability.md
================================================
# Stability Levels and versioning
## Table of Contents
- [Stability levels](#stability-levels)
- [Development](#development)
- [Alpha](#alpha)
- [Beta](#beta)
- [Stable](#stable)
- [Deprecated](#deprecated)
- [Unmaintained](#unmaintained)
- [Moving between stability levels](#moving-between-stability-levels)
- [Graduation criteria](#graduation-criteria)
- [In development to alpha](#in-development-to-alpha)
- [Alpha to beta](#alpha-to-beta)
- [Beta to stable](#beta-to-stable)
- [Deprecation Information](#deprecation-information)
- [Versioning](#versioning)
- [Component Graduation to Stable](#component-graduation-to-stable)
- [Difference between signal and component graduation](#difference-between-signal-and-component-graduation)
- [Requirements for component graduation](#requirements-for-component-graduation)
- [Adoption evidence](#adoption-evidence)
- [Graduation process](#graduation-process)
## Stability levels
The Collector components and implementation are in different stages of stability, and usually split between
functionality and configuration. While we intend to provide high-quality components as part of this repository,
we acknowledge that not all of them are ready for prime time. Moreover, the stability of components that can
handle multiple signals can depend on the signal in question.
As such, each component should list its current stability level for each telemetry signal in its README file, according to
the following definitions:
### Development
Not all pieces of the component are in place yet and it might not be available as part of any distributions yet. Bugs and performance issues should be reported, but it is likely that the component owners might not give them much attention. Your feedback is still desired, especially when it comes to the user-experience (configuration options, component observability, technical implementation details, ...). Configuration options might break often depending on how things evolve. The component should not be used in production.
### Alpha
The component is ready to be used for limited non-critical workloads and the authors of this component would welcome your feedback. Bugs and performance problems should be reported, but component owners might not work on them right away.
#### Configuration changes
Configuration for alpha components can be changed with minimal notice. Documenting them as part of the changelog is
sufficient. We still recommend giving users one or two minor versions' notice before breaking the configuration, such as
when removing or renaming a configuration option. Providing a migration path in the component's repository is NOT
required for alpha components, although it is still recommended.
- when adding a new configuration option, components MAY mark the new option as required and are not required to provide
a reasonable default.
- when renaming a configuration option, components MAY treat the old name as an alias to the new one and log a WARN
level message in case the old option is being used.
- when removing a configuration option, components MAY keep the old option for a few minor releases and log a WARN level
message instructing users to remove the option.
#### Documentation requirements
Alpha components should document how to use them in the most common situations, including:
- One or more example configuration snippets for the most common use cases.
### Beta
Same as Alpha, but the configuration options are deemed stable. While there might be breaking changes between releases, component owners should try to minimize them. A component at this stage is expected to have had exposure to non-critical production workloads already during its **Alpha** phase, making it suitable for broader usage.
#### Configuration changes
Backward incompatible changes should be rare events for beta components. Users of those components are not expecting to
have their Collector instances failing at startup because of a configuration change. When doing backward incompatible
changes, component owners should add the migration path to a place within the component's repository, linked from the
component's main README. This is to ensure that people using older instructions can understand how to migrate to the
latest version of the component.
When adding a new required option:
- the option MUST come with a sensible default value
When renaming or removing a configuration option:
- the option MUST be deprecated in one version
- a WARN level message should be logged, with a link to a place within the component's repository where the change is
documented and a migration path is provided
- the option MUST be kept for at least N+1 version and MAY be hidden behind a feature gate in N+2
- the option and the WARN level message MUST NOT be removed earlier than N+2 or 6 months, whichever comes later
Additionally, when removing an option:
- the option MAY be made non-operational already by the same version where it is deprecated
#### Documentation requirements
Beta components should have a set of documentation that documents its usage in most cases,
including:
- One or more example configuration snippets for the most common use cases.
- Advanced configuration options that are known to be used in common environments.
- All component-specific feature gates including a description for them and when they should be
used.
- Warnings about known limitations and ways to misuse the component.
Receivers that produce a fixed set of telemetry should document the telemetry they produce,
including:
- For all signals, the resource attributes that are expected to be present in telemetry.
- For metrics, the name, description, type, units and attributes of each metric.
### Stable
The component is ready for general availability. Bugs and performance problems should be reported and there's an expectation that the component owners will work on them. Breaking changes, including configuration options and the component's output are not expected to happen without prior notice, unless under special circumstances.
#### Configuration changes
Stable components MUST be compatible between minor versions unless critical security issues are found. In that case, the
component owner MUST provide a migration path and a reasonable time frame for users to upgrade. The same rules from beta
components apply to stable when it comes to configuration changes.
#### Testing requirements
Stable components MUST have a comprehensive test suite. In particular they MUST have:
1. A **test coverage** that exceeds the highest between 80% coverage and the repository-wide
minimum. The unit test suite SHOULD cover all configuration options. The coverage MUST be shown as
part of the component documentation.
2. At least one **lifecycle test** that tests the component's initialization with a valid
configuration and ensures proper context propagation if applicable.
3. At least one **benchmark test** for each stable signal. The component's documentation MUST
include a link to the latest run of benchmark results.
#### Documentation requirements
Stable components should have a complete set of documentation, including:
- One or more example configuration snippets for the most common use cases.
- All configuration options supported by the component and a description for each of them.
- All component-specific feature gates including a description for them and when they should be
used.
- All component-specific self-observability features that are not available for other components and
what they provide.
- Compatibility guarantees with external dependencies including the versions it is compatible with
and under what conditions.
- Guidance related to the component's usage in production environments, including how to scale a deployment of this component properly if it needs special considerations.
- If stateful, how to configure the component to use persistent storage and how to gracefully
shutdown and restart the component.
- Warnings about known limitations and ways to misuse the component.
Receivers that produce a fixed set of telemetry should document the telemetry they produce,
including:
- For all signals, the resource attributes that are expected to be present in telemetry.
- For metrics, the name, description, type, units and attributes of each metric.
#### Observability requirements
Stable components should emit enough internal telemetry to let users detect errors, as well as data
loss and performance issues inside the component, and to help diagnose them if possible.
For extension components, this means some way to monitor errors (for example through logs or span
events), and some way to monitor performance (for example through spans or histograms). Because
extensions can be so diverse, the details will be up to the component authors, and no further
constraints are set out in this document.
For pipeline components however, this section details the kinds of values that should be observable
via internal telemetry for all stable components.
> [!NOTE]
> - The following categories MUST all be covered, unless justification is given as to why one may
> not be applicable.
> - However, for each category, many reasonable implementations are possible, as long as the
> relevant information can be derived from the emitted telemetry; everything after the basic
> category description is a recommendation, and is not normative.
> - Of course, a component may define additional internal telemetry which is not in this list.
> - Some of this internal telemetry may already be provided by pipeline auto-instrumentation or
> helper modules (such as `receiverhelper`, `scraperhelper`, `processorhelper`, or
> `exporterhelper`). Please check the documentation to verify which parts, if any, need to be
> implemented manually.
**Definition:** In the following, an "item" refers generically to a single log record, metric point,
or span.
The internal telemetry of a stable pipeline component should allow observing the following:
1. How much data the component receives.
For receivers, this could be a metric counting requests, received bytes, scraping attempts, etc.
For other components, this would typically be the number of items received through the
`Consumer` API.
2. How much data the component outputs.
For exporters, this could be a metric counting requests, sent bytes, etc.
For other components, this would typically be the number of items forwarded to the next
component through the `Consumer` API.
3. How much data is dropped because of errors.
For receivers, this could include a metric counting payloads that could not be parsed in.
For receivers and exporters that interact with an external service, this could include a metric
counting requests that failed because of network errors.
For processors, this could be an `outcome` (`success` or `failure`) attribute on a "received
items" metric defined for point 1.
The goal is to be able to easily pinpoint the source of data loss in the Collector pipeline, so
this should either:
- only include errors internal to the component, or;
- allow distinguishing said errors from ones originating in an external service, or propagated
from downstream Collector components.
4. Details for error conditions.
This could be in the form of logs or spans detailing the reason for an error. As much detail as
necessary should be provided to ease debugging. Processed signal data should not be included for
security and privacy reasons.
5. Other possible discrepancies between input and output, if any. This may include:
- How much data is dropped as part of normal operation (eg. filtered out).
- How much data is created by the component.
- How much data is currently held by the component, and how much can be held if there is a fixed
capacity.
This would typically be an UpDownCounter keeping track of the size of an internal queue, along
with a gauge exposing the queue's capacity.
6. Processing performance.
This could include spans for each operation of the component, or a histogram of end-to-end
component latency.
The goal is to be able to easily pinpoint the source of latency in the Collector pipeline, so
this should either:
- only include time spent processing inside the component, or;
- allow distinguishing this latency from that caused by an external service, or from time spent
in downstream Collector components.
As an application of this, components which hold items in a queue should allow differentiating
between time spent processing a batch of data and time where the batch is simply waiting in the
queue.
If multiple spans are emitted for a given batch (before and after a queue for example), they
should either belong to the same trace, or have span links between them, so that they can be
correlated.
When measuring amounts of data, it is recommended to use "items" as your unit of measure. Where this
can't easily be done, any relevant unit may be used, as long as zero is a reliable indicator of the
absence of data. In any case, all metrics should have a defined unit (not "1").
All internal telemetry emitted by a component should have attributes identifying the specific
component instance that it originates from. This should follow the same conventions as the
[pipeline universal telemetry](rfcs/component-universal-telemetry.md).
If data can be dropped/created/held at multiple distinct points in a component's pipeline (eg.
scraping, validation, processing, etc.), it is recommended to define additional attributes to help
diagnose the specific source of the discrepancy, or to define different signals for each.
The breakdown of emitted telemetry per telemetry level (basic / normal / detailed) should follow
the guidelines in [the Go package documentation for `configtelemetry`](/config/configtelemetry/doc.go).
### Deprecated
The component is planned to be removed in a future version and no further support will be provided. Note that new issues will likely not be worked on. When a component enters "deprecated" mode, it is expected to exist for at least two minor releases. See the component's readme file for more details on when a component will cease to exist.
### Unmaintained
A component identified as unmaintained does not have an active code owner. Such component may have never been assigned a code owner or a previously active code owner has not responded to requests for feedback within 6 weeks of being contacted. Issues and pull requests for unmaintained components will be labelled as such. After 3 months of being unmaintained, these components will be removed from official distribution. Components that are unmaintained are actively seeking contributors to become code owners.
Components that were accepted based on being vendor-specific components will be marked as unmaintained if
they have no active code owners from the vendor even if there are other code owners listed. As part of being marked unmaintained, we'll attempt to contact the vendor to notify them of the change. Other active code
owners may petition for its continued maintenance if they want, at which point the component will no
longer be considered vendor-specific.
## Moving between stability levels
Components can move between stability levels. The valid transitions are described in the following diagram:
```mermaid
stateDiagram-v2
state Maintained {
InDevelopment --> Alpha
Alpha --> Beta
Beta --> Stable
}
InDevelopment: In Development
Maintained --> Unmaintained
Unmaintained --> Maintained
Maintained --> Deprecated
Deprecated --> Maintained: (should be rare)
```
To move within the 'Maintained' ladder ("graduate"), the process for doing so is as follows:
1. One of the component owners should file an issue with the 'Graduation' issue template to request
the graduation.
2. An approver is assigned in a rotating basis to evaluate the request and provide feedback. For
vendor specific components, the approver should be from a different employer to the one owning
the component.
3. If approved, a PR to change the stability level should be opened and MUST be approved by all
listed code owners.
## Graduation criteria
In addition to the requirements outlined above, additional criteria should be met before a component
can graduate to a higher stability level. These ensure that the component is ready for the increased
usage and scrutiny that comes with a higher stability level, and that the community around it is
sufficiently healthy.
If the graduation criteria are not met, the approver should provide feedback on what is missing and
how to address it. The component owners can then address the feedback and re-request graduation on
the same issue.
## In development to alpha
No additional criteria are required to graduate from development to alpha.
The component still needs to meet the general requirements for alpha components.
## Alpha to beta
To graduate any signal from alpha to beta on a component:
1. The component MUST have at least two active code owners.
3. Within the 30 days prior to the graduation request, the code owners MUST have reviewed and
replied to at least 80% of the issues and pull requests opened against the component. This
excludes general PRs or issues that are not specific to the component itself (e.g. repo-wide API
updates). It is not necessary that the issues and PRs are closed or merged, but that they have
been reviewed and replied to appropriately.
## Beta to stable
To graduate any signal from beta to stable on a component:
1. The component MUST have at least three active code owners.
2. The component benchmark results MUST have been updated within the last 30 days and published in the component's README.
3. Within the 60 days prior to the graduation request, the code owners MUST have reviewed and
replied to at least 80% of the issues and pull requests opened against the component. This
excludes general PRs or issues that are not specific to the component itself (e.g. repo-wide API
updates). It is not necessary that the issues and PRs are closed or merged, but that they have
been reviewed and replied to appropriately.
## Deprecation Information
When a component is moved to deprecated, a deprecation section should indicate the date it was deprecated
as well as any migration guidance. In some occasions might not be offered migration guidance but reviewers should
explicitly agree on this, and use a "No migration is offered for this component" hint.
## Versioning
For a component to be marked as 1.x it MUST be stable for at least one signal.
Even if a component has a 1.x or greater version, its behavior for specific signals might change in ways that break end users if the component is not stable for a particular signal.
However, components are Go modules and as such follow [semantic versioning](https://semver.org/). Go API stability guarantees are covered in the [VERSIONING.md](../VERSIONING.md) document.
The versioning of a component, and the Go API stability guarantees that come with it, apply to ALL signals simultaneously, regardless of their stability level.
This means that, once a component is marked as 1.x, signal-specific configuration options MUST NOT be removed or changed in a way that breaks our Go API compatibility promise, even if the signal is not stable.
## Component Graduation to Stable
This section describes the process for graduating a component as a whole from beta to stable. This
is distinct from signal-level graduation, which is covered in the [Graduation criteria](#graduation-criteria)
section above.
### Difference between signal and component graduation
A component can support multiple telemetry signals (traces, metrics, logs and profiles), and each signal has its own stability level. The sections above describe the requirements and process for graduating
individual signals within a component.
**Component graduation** refers to declaring the component as a whole as stable. This is a higher
bar than having individual signals stable, as it represents a commitment that the component is
production-ready, well-maintained, and has demonstrated real-world adoption.
A component MAY have some signals at stable while the component itself is not yet graduated.
Component graduation is optional but signals a stronger commitment to the end-user community.
### Requirements for component graduation
Before opening a PR to graduate a component to stable, the code owners MUST gather evidence that the
following requirements are met.
#### Signal requirements
1. All supported signals MUST be at beta stability or higher.
2. At least one signal MUST be at stable stability.
#### Code owner requirements
1. The component MUST have at least three active code owners.
2. Within the 60 days prior to the graduation request, the code owners MUST have reviewed and
replied to at least 80% of the issues and pull requests opened against the component. This
excludes general PRs or issues that are not specific to the component itself (e.g. repo-wide API
updates).
#### Technical requirements
1. The component MUST meet all [testing requirements](#testing-requirements) for stable components.
2. The component MUST meet all [documentation requirements](#documentation-requirements) for stable
components.
3. The component MUST meet all [observability requirements](#observability-requirements) for stable
components.
4. The component MUST follow the [coding guidelines](coding-guidelines.md), including the naming
conventions for components.
5. The component MUST have evidence of real-world adoption. See [Adoption evidence](#adoption-evidence)
for details on what constitutes acceptable evidence.
### Adoption evidence
Adoption evidence demonstrates that the component has been validated in real-world environments and
that there is a community invested in its continued success. Code owners MUST provide at least one
of the following forms of evidence:
1. **Public adopter testimonials**: At least two organizations have publicly stated (in blog posts,
conference talks, GitHub issues, or other public forums) that they use the component in
production.
2. **Private attestation**: If adopters cannot be named publicly, code owners MAY provide private
attestation to the reviewing maintainer. The attestation MUST include a general description of the
scale of usage (e.g., "processing millions of spans per day"). The maintainer verifying the
attestation MUST confirm they find it credible, but is not required to disclose the details.
The adoption evidence MUST be documented in the graduation request issue.
### Graduation process
The process for graduating a component to stable is as follows:
1. **Code owners prepare the request**: One of the code owners files an issue using the 'Component
Graduation' issue template. The issue MUST include:
- Evidence that all [requirements](#requirements-for-component-graduation) are met.
- Links to documentation, test coverage reports, and benchmark results.
- [Adoption evidence](#adoption-evidence).
2. **Maintainer assignment**: A maintainer is assigned on a rotating basis to verify the graduation
request. For vendor-specific components, the assigned maintainer SHOULD be from a different
employer than the one owning the component.
3. **Maintainer verification**: The assigned maintainer reviews the evidence provided by the code
owners and verifies that all requirements are met. The maintainer is verifying the evidence, not
generating it. If requirements are not met, the maintainer provides feedback on what is missing.
4. **PR submission**: Once the maintainer confirms the requirements are met, the code owners open a
PR to update the component's stability level. The PR MUST be approved by:
- The assigned maintainer.
- All listed code owners.
5. **Merge**: After all approvals are obtained, the PR can be merged.
If there are disputes about whether the requirements are met, the issue should be escalated to the
maintainers for discussion in a Collector SIG meeting.
================================================
FILE: docs/component-status.md
================================================
# Component Status Reporting
Component status reporting is a collector feature that allows components to report their status (aka health) via status events to extensions. In order for an extension receive these events it must implement the [StatusWatcher interface](https://github.com/open-telemetry/opentelemetry-collector/blob/f05f556780632d12ef7dbf0656534d771210aa1f/extension/extension.go#L54-L63).
### Status Definitions
The system defines six statuses, listed in the table below:
| Status | Meaning |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| Starting | The component is starting. |
| OK | The component is running without issue. |
| RecoverableError | The component has experienced a transient error and may recover. |
| PermanentError | The component has detected a condition at runtime that will need human intervention to fix. The collector will continue to run in a degraded mode. |
| FatalError | A component has experienced a fatal error and the collector will shutdown. |
| Stopping | The component is in the process of shutting down. |
| Stopped | The component has completed shutdown. |
Statuses can be categorized into two groups: lifecycle and runtime.
**Lifecycle Statuses**
- Starting
- Stopping
- Stopped
**Runtime Statuses**
- OK
- RecoverableError
- PermanentError
- FatalError
### Transitioning Between Statuses
There is a finite state machine underlying the status reporting API that governs the allowable state transitions. See the state diagram below:

The finite state machine ensures that components progress through the lifecycle properly and it manages transitions through runtime states so that components do not need to track their state internally. Only changes in status result in new events being generated; repeat reports of the same status are ignored. PermanentError is a permanent runtime state. A component in a PermanentError state cannot transition to OK or RecoverableError, but it can transition to Stopping. FatalError is a final state. A component in a FatalError state cannot make any further state transitions.

### Automation
The collector's service implementation is responsible for starting and stopping components. Since it knows when these events occur and their outcomes, it can automate status reporting of lifecycle events for components.
**Start**
The collector will report a Starting event when starting a component. If an error is returned from Start, the collector will report a PermanentError event. If start returns without an error and the component hasn't reported status itself, the collector will report an OK event.
**Shutdown**
The collector will report a Stopping event when shutting down a component. If Shutdown returns an error, the collector will report a PermanentError event. If Shutdown completes without an error, the collector will report a Stopped event.
### Best Practices
**Start**
Under most circumstances, a component does not need to report explicit status during component.Start. An exception to this rule is components that start async work (e.g. spawn a go routine). This is because async work may or may not complete before start returns and timing can vary between executions. A component can halt startup by returning an error from start. If start returns an error, automated status reporting will report a PermanentError on behalf of the component. If start returns without an error automated status reporting will report OK, so long has the component hasn't already reported for itself.
**Runtime**

During runtime a component should not have to keep track of its state. A component should report status as operations succeed or fail and the finite state machine will handle the rest. Changes in status will result in new status events being emitted. Repeat reports of the same status will no-op. Similarly, attempts to make an invalid state transition, such as PermanentError to OK, will have no effect.
We intend to define guidelines to help component authors distinguish between recoverable and permanent errors on a per-component type basis and we'll update this document as we make decisions. See [this issue](https://github.com/open-telemetry/opentelemetry-collector/issues/9957) for current thoughts and discussions.
**Shutdown**
A component should never have to report explicit status during shutdown. Automated status reporting should handle all cases. To recap, the collector will report Stopping before Shutdown is called. If a component returns an error from shutdown the collector will report a PermanentError and it will report Stopped if Shutdown returns without an error.
### Implementation Details
There are a couple of implementation details that are worth discussing for those who work on or wish to understand the collector internals.
**component.TelemetrySettings**
The API for components to report status is the ReportStatus method on the component.TelemetrySettings instance that is part of the CreateSettings passed to a component's factory during creation. It takes a single argument, a status event. The StatusWatcher interface takes both a component instance ID and a status event. The ReportStatus function is customized for each component and passes along the instance ID with each event. A component doesn't know its instance ID, but its ReportStatus method does.
**servicetelemetry.TelemetrySettings**
The service gets a slightly different TelemetrySettings object, a servicetelemetry.TelemetrySettings, which references the ReportStatus method on a status.Reporter. Unlike the ReportStatus method on component.TelemetrySettings, this version takes two arguments, a component instance ID and a status event. The service uses this function to report status on behalf of the components it manages. This is what the collector uses for the automated status reporting of lifecycle events.
**sharedcomponent**
The collector has the concept of a shared component. A shared component is represented as a single component to the collector, but represents multiple logical components elsewhere. The most common usage of this is the OTLP receiver, where a single shared component represents a logical instance for each signal: traces, metrics, and logs (although this can vary based on configuration). When a shared component reports status it must report an event for each of the logical instances it represents. In the current implementation, shared component reports status for all its logical instances during [Start](https://github.com/open-telemetry/opentelemetry-collector/blob/31ac3336d956d93abede6db76453730613e1f076/internal/sharedcomponent/sharedcomponent.go#L89-L98) and [Shutdown](https://github.com/open-telemetry/opentelemetry-collector/blob/31ac3336d956d93abede6db76453730613e1f076/internal/sharedcomponent/sharedcomponent.go#L105-L117). It also [modifies the ReportStatus method](https://github.com/open-telemetry/opentelemetry-collector/blob/31ac3336d956d93abede6db76453730613e1f076/internal/sharedcomponent/sharedcomponent.go#L34-L44) on component.TelemetrySettings to report status for each logical instance when called.
================================================
FILE: docs/ga-roadmap.md
================================================
# Collector v1 Roadmap
This document contains the roadmap for the Collector. The main goal of this roadmap is to provide clarity on the areas of focus in order to release a v1 of the Collector.
## Proposal
The proposed approach to delivering a stable release of the OpenTelemetry Collector is to produce a distribution of the Collector that contains a minimum set of components which have been stabilized. By doing so, the project contributors will ensure dependencies of those components have also been released under a stable version.
The proposed distribution is set to include the following components only:
- OTLP receiver
- OTLP exporter
- OTLP HTTP exporter
These modules depend on a list of other modules, the full list is available in issue [#9375](https://github.com/open-telemetry/opentelemetry-collector/issues/9375).
All stabilized modules will conform to the API expectations outlined in the [VERSIONING.md](../VERSIONING.md) document.
### Scope within each module
The Collector is already used in production at scale and has been tested in a variety of
environments. The focus of the stabilization is primarily not to add missing features but to ensure
the maintainability of the project and to provide a predictable and consistent experience for
end-users.
In particular when considering enhancement proposals we will focus on:
1. Binary end-users impact above other audiences.
2. Parts of the proposals that imply breaking changes to end-users.
3. Small, predictable or self-contained changes that don't imply a major change in the end-user
experience.
Additionally, when considering bug reports we will prioritize:
1. [Critical bugs](release.md#bugfix-release-criteria) that affect the stability of the Collector.
2. Regressions from previous behavior caused by 1.0-related changes.
## Out of scope
Explicitly, the following are not in the scope of v1 for the purposes of this document:
* stabilization of additional components/APIs needed by distribution maintainers. Vendors are not the audience
* This explicitly excludes the `service` and `otelcol` modules, for which we will only guarantee that there are no breaking changes impacting end-users of the binary after 1.0, while Go API only changes will continue to be admissible until these modules are tagged as 1.0.
* Collector Builder
* telemetrygen
* mdatagen
* Operator
Those components are free to pursue v1 at their own pace and may be the focus of future stability work.
## Additional Requirements
The following is a list of requirements for this minimal Collector distribution to be deemed as 1.0:
* The Collector must be observable
* Metrics and traces should be produced for data in the hot path
* Metrics should be documented in the end-user documentation
* Metrics, or a subset of them, should be marked as stable in the documentation
* Logs should be produced for Collector lifecycle events
* Stability expectations and lifecycle for telemetry should be documented, so that users can know what they can rely on for their dashboards and alerts
* The Collector must be scalable
* Backpressure from the exporter all the way back to the receiver should be supported
* Queueing must be supported to handle increased loads
* Performance metrics are in place and follow best practices for benchmarking
* Individual components must:
* Have their lifecycle expectations enshrined in tests
* Have goleak enabled
* End-user documentation should be provided as part of the official project’s documentation under opentelemetry.io, including:
* Getting started with the Collector
* Available (stable) components and how to use them
* Blueprints for common use cases
* Error scenarios and error propagation
* Troubleshooting and how to obtain telemetry from the Collector for the purposes of bug reporting
* Queueing, batching, and handling of backpressure
* The Collector must be supported
* Processes, workflows and expectations regarding support, bug reporting and questions should be documented.
* A minimum support period for 1.0 is documented, similarly to [API and SDK](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md#api-support) stability guarantees.
================================================
FILE: docs/internal-architecture.md
================================================
## Internal architecture
This document describes the Collector internal architecture and startup flow. It can be helpful if you are starting to contribute to the Collector codebase.
For the end-user focused architecture document, please see the [opentelemetry.io's Architecture documentation](https://opentelemetry.io/docs/collector/architecture/). While it is end user focused, it's still a good place to start if you're trying to learn about the Collector codebase.
### Startup Diagram
```mermaid
flowchart TD
A("`**command.NewCommand**`") -->|1| B("`**updateSettingsUsingFlags**`")
A --> |2| C("`**NewCollector**
Creates and returns a new instance of Collector`")
A --> |3| D("`**Collector.Run**
Starts the collector and blocks until it shuts down`")
D --> E("`**setupConfigurationComponents**`")
E --> |1| F("`**getConfMap**`")
E ---> |2| G("`**Service.New**
Initializes telemetry, then initializes the pipelines`")
E --> |3| Q("`**Service.Start**
1. Start all extensions.
2. Notify extensions about Collector configuration
3. Start all pipelines.
4. Notify extensions that the pipeline is ready.
`")
Q --> R("`**Graph.StartAll**
Calls Start on each component in reverse topological order`")
G --> H("`**initExtensionsAndPipeline**
Creates extensions and then builds the pipeline graph`")
H --> I("`**Graph.Build**
Converts the settings to an internal graph representation`")
I --> |1| J("`**createNodes**
Builds the node objects from pipeline configuration and adds to graph. Also validates connectors`")
I --> |2| K("`**createEdges**
Iterates through the pipelines and creates edges between components`")
I --> |3| L("`**buildComponents**
Topological sort the graph, and create each component in reverse order`")
L --> M(Receiver Factory) & N(Processor Factory) & O(Exporter Factory) & P(Connector Factory)
```
### Where to start to read the code
Here is a brief list of useful and/or important files and interfaces that you may find valuable to glance through.
Most of these have package-level documentation and function/struct-level comments that help explain the Collector!
- [collector.go](../otelcol/collector.go)
- [graph.go](../service/internal/graph/graph.go)
- [component.go](../component/component.go)
#### Factories
Each component type contains a `Factory` interface along with its corresponding `NewFactory` function.
Implementations of new components use this `NewFactory` function in their implementation to register key functions with
the Collector. An example of this is in [receiver.go](../receiver/receiver.go).
For example, the Collector uses this interface to give receivers a handle to a `nextConsumer` -
which represents where the receiver will send its data next in its telemetry pipeline.
================================================
FILE: docs/observability.md
================================================
# OpenTelemetry Collector internal observability
The [Internal telemetry] page on OpenTelemetry's website contains the
documentation for the Collector's internal observability, including:
- Which types of observability are emitted by the Collector.
- How to enable and configure these signals.
- How to use this telemetry to monitor your Collector instance.
If you need to troubleshoot the Collector, see [Troubleshooting].
Read on to learn about experimental features and the project's overall vision
for internal telemetry.
- [Goals of internal telemetry](#goals-of-internal-telemetry)
* [Observable elements](#observable-elements)
* [Impact](#impact)
* [Configurable level of observability](#configurable-level-of-observability)
* [Internal telemetry properties](#internal-telemetry-properties)
+ [Units](#units)
+ [Process for defining new metrics](#process-for-defining-new-metrics)
- [Experimental trace telemetry](#experimental-trace-telemetry)
## Goals of internal telemetry
The Collector's internal telemetry is an important part of fulfilling
OpenTelemetry's [project vision](vision.md). The following section explains the
priorities for making the Collector an observable service.
### Observable elements
The following aspects of the Collector need to be observable.
- [Current values]
- Some of the current values and rates might be calculated as derivatives of
cumulative values in the backend, so it's an open question whether to expose
them separately or not.
- [Cumulative values]
- [Trace or log events]
- For start or stop events, an appropriate hysteresis must be defined to avoid
generating too many events. Note that start and stop events can't 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]
- Host metrics can help users determine if the observed problem in a service
is caused by a different process on the same host.
### Impact
The impact of these observability improvements on the core performance of the
Collector must be assessed.
### Configurable level of observability
Some metrics and traces can be high volume and users might not always want to
observe them. An observability verbosity “level” allows configuration of the
Collector to send more or less observability data or with even finer
granularity, to allow turning on or off specific metrics.
The default level of observability must be defined in a way that has
insignificant performance impact on the service.
### Internal telemetry properties
Telemetry produced by the Collector has the following properties:
- metrics produced by Collector components use the prefix `otelcol_`
- metrics produced by any instrumentation library used by Collector components will *not* be prefixed with `otelcol_`
- code is instrumented using the OpenTelemetry API for metrics, and traces. Logs are instrumented using zap. Telemetry is collected and produced via the OpenTelemetry Go SDK
- instrumentation scope defaults to the package name of the component recording telemetry. It can be configured
via the `scope_name` option in mdatagen, but the recommendation is to keep the default
- metrics are defined via `metadata.yaml` except in components that have specific cases where
it is not possible to do so. See the [issue](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/33523)
which list such components
- whenever possible, components should leverage core components or helper libraries to capture
telemetry, ensuring that all components of the Collector can be consistently observed
- telemetry produced by components should include attributes that identify specific instances
of the components
#### Units
The following units should be used for metrics emitted by the Collector
for the purpose of its internal telemetry:
| Field type | Unit |
| -------------------------------------------------------------------------- | -------------- |
| Metric counting the number of log records received, processed, or exported | `{records}` |
| Metric counting the number of spans received, processed, or exported | `{spans}` |
| Metric counting the number of data points received, processed, or exported | `{datapoints}` |
#### Process for defining new metrics
Metrics in the Collector are defined via `metadata.yaml`, which is used by [mdatagen] to
produce:
- code to create metric instruments that can be used by components
- documentation for internal metrics
- a consistent prefix for all internal metrics
- convenience accessors for meter and tracer
- a consistent instrumentation scope for components
- test methods for validating the telemetry
The process to generate new metrics is to configure them via
`metadata.yaml`, and run `go generate` on the component.
## Experimental trace telemetry
The Collector does not expose traces by default, but can be configured.
The Collector's internal telemetry uses OpenTelemetry SDK.
The following configuration can be used in combination with the aforementioned
feature gates to emit internal metrics and traces from the Collector to an OTLP
backend:
```yaml
service:
telemetry:
metrics:
readers:
- periodic:
interval: 5000
exporter:
otlp:
protocol: grpc
endpoint: https://backend:4317
traces:
processors:
- batch:
exporter:
otlp:
protocol: grpc
endpoint: https://backend2:4317
```
See the [example configuration][kitchen-sink] for additional options.
> This configuration does not support emitting logs as there is no support for
> [logs] in the OpenTelemetry Go SDK at this time.
You can also configure the Collector to send its own traces using the OTLP
exporter. Send the traces to an OTLP server running on the same Collector, so it
goes through configured pipelines. For example:
```yaml
service:
telemetry:
traces:
processors:
batch:
exporter:
otlp:
protocol: grpc
endpoint: ${MY_POD_IP}:4317
```
[Internal telemetry]:
https://opentelemetry.io/docs/collector/internal-telemetry/
[Troubleshooting]: https://opentelemetry.io/docs/collector/troubleshooting/
[issue7532]:
https://github.com/open-telemetry/opentelemetry-collector/issues/7532
[issue7454]:
https://github.com/open-telemetry/opentelemetry-collector/issues/7454
[logs]: https://github.com/open-telemetry/opentelemetry-go/issues/3827
[OpenTelemetry Configuration]:
https://github.com/open-telemetry/opentelemetry-configuration
[kitchen-sink]:
https://github.com/open-telemetry/opentelemetry-configuration/blob/main/examples/kitchen-sink.yaml
[Current values]:
https://opentelemetry.io/docs/collector/internal-telemetry/#summary-of-values-observable-with-internal-metrics
[Cumulative values]:
https://opentelemetry.io/docs/collector/internal-telemetry/#summary-of-values-observable-with-internal-metrics
[Trace or log events]:
https://opentelemetry.io/docs/collector/internal-telemetry/#events-observable-with-internal-logs
[Host metrics]:
https://opentelemetry.io/docs/collector/internal-telemetry/#lists-of-internal-metrics
[mdatagen]:
https://github.com/open-telemetry/opentelemetry-collector/tree/main/cmd/mdatagen
================================================
FILE: docs/platform-support.md
================================================
# Platform Support
The OpenTelemetry Collector will be supported following a tiered platform support model to balance between the aim to support as many platforms as possible and to guarantee stability for the most important platforms. A platform is described by the pair of operating system and processor architecture family as they are defined by the Go programming language as [known operating systems and architectures for use with the GOOS and GOARCH values](https://go.dev/src/internal/syslist/syslist.go).
For a supported platform, the OpenTelemetry Collector is supported when the [minimum requirements](https://github.com/golang/go/wiki/MinimumRequirements) of the Go release used by the collector are met for the operating system and architecture. Each supported platform requires the naming of designated owners. The platform support for the OpenTelemetry Collector is broken into three tiers with different levels of support for each tier and aligns with the current test strategy.
For platforms not listed as supported by any of the tiers, support cannot be assumed to be provided. While the project may accept specific changes related to these platforms, there will be no official builds, support of issues and development of enhancements or bug fixes for these platforms. Future development of project for supported platforms may break the functionality of unsupported platforms.
## Current Test Strategy
The current verification process of the OpenTelemetry Collector includes unit and performance tests for core and additional end-to-end and integration tests for contrib. In the end-to-end tests, receivers, processors, and exporters etc. are tested in a testbed, while the integration tests rely on actual instances and available container images. Additional stability tests are in preparation for the future as well. All verification tests are run on the linux/amd64 as the primary platform today. In addition, unit tests are run for the _contrib_ collector on windows/amd64. The tests use as execution environments the latest Ubuntu and Windows Server versions [supported as Github runners](https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources).
The cross compile supports the following targets:
- darwin/amd64 and darwin/arm64
- linux/amd64, linux/arm64, linux/386, linux/arm and linux/ppc64le
- windows/amd64, windows/arm64 and windows/386.
Except of the mentioned tests for linux/amd64 and windows/amd64, no other platforms are tested by the CI/CD tooling.
Container images of the _core_ and _contrib_ collector are built and published to Docker Hub and ghcr.io for the platforms specified in the [goreleaser configuration](https://github.com/open-telemetry/opentelemetry-collector-releases/blob/bf8002ec6d2109cdb4184fc6eb6f8bda59ea96a2/.goreleaser.yaml#L137). End-to-end tests of the _contrib_ container images are run on the latest Ubuntu Linux supported by GitHub runners and for the four most recent Kubernetes versions.
## Tiered platform support model
The platform support for the OpenTelemetry Collector is broken into three tiers with different levels of support for each tier.
### Platform Support - Summary
The following tables summarized the platform tiers of support by the verification tests performed for them and by the specification if dummy implementations are allowed for selected features, the availability of precompiled binaries incl. container images and if bugfix releases are provided for previous releases in case of critical defects.
| Tier | Unit tests | Performance tests | End-to-end tests | Integrations tests | Dummy implementations | Precompiled binaries | Bugfix releases |
|------|------------|-------------------|------------------|--------------------|-----------------------|----------------------|-----------------|
| 1 | yes | yes | yes | yes | no | yes | yes |
| 2 | yes | optional | optional | optional | yes | yes | no |
| 3 | no | no | no | no | yes | yes | no |
### Tier 1 – Primary Support
The Tier 1 supported platforms are _guaranteed to work_. Precompiled binaries are built on the platform, fully supported for all collector add-ons (receivers, processor, exporters etc.), and continuously tested as part of the development processes to ensure any proposed change will function correctly. Build and test infrastructure is provided by the project. All tests are executed on the platform as part of automated continuous integration (CI) for each pull request and the [release cycle](https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/release.md#release-schedule). Any build or test failure block the release of the collector distribution for all platforms. Defects are addressed with priority and depending on severity fixed for previous release(s) in a bug fix release.
Tier 1 platforms are currently:
| Platform | Owner(s) |
|-------------|-------------------------------------------------------------------------------------------------------------|
| linux/amd64 | [OpenTelemetry Collector approvers](https://github.com/open-telemetry/opentelemetry-collector#contributing) |
### Tier 2 – Secondary Support
Tier 2 platforms are _guaranteed to work with specified limitations_. Precompiled binaries are built and tested on the platform as part of the release cycle. Build and test infrastructure is provided by the platform maintainers. All tests are executed on the platform as far as they are applicable, and all prerequisites are fulfilled. Not executed tests and not tested collector add-ons (receivers, processors, exporters, etc.) are published on release of the collector distribution. Any build or test failure delays the release of the binaries for the respective platform but not the collector distribution for all other platforms. Defects are addressed but not with the priority as for Tier 1 and, if specific to the platform, require the support of the platform maintainers.
Tier 2 platforms are currently:
| Platform | Owner(s) |
|---------------|----------------------------------------------------|
| darwin/arm64 | [@MovieStoreGuy](https://github.com/MovieStoreGuy) |
| linux/arm64 | [@atoulme](https://github.com/atoulme) |
| windows/amd64 | [@pjanotti](https://github.com/pjanotti) |
### Tier 3 - Community Support
Tier 3 platforms are _guaranteed to build_. Precompiled binaries are made available as part of the release process and as result of a cross compile build on Linux amd64 but the binaries are not tested at all. Any build failure delays the release of the binaries for the respective platform but not the collector distribution for all other platforms. Defects are addressed based on community contributions. Core developers might provide guidance or code reviews, but direct fixes may be limited.
Tier 3 platforms are currently:
| Platform | Owner(s) |
|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| aix/ppc64 | [@Dylan-M](https://github.com/Dylan-M) [@atoulme](https://github.com/atoulme) |
| darwin/amd64 | [@h0cheung](https://github.com/h0cheung) |
| js/wasm | [@evan-bradley](https://github.com/evan-bradley), [@mx-psi](https://github.com/mx-psi) |
| linux/386 | [@andrzej-stencel](https://github.com/andrzej-stencel) |
| linux/arm | [@Wal8800](https://github.com/Wal8800), [@atoulme](https://github.com/atoulme) |
| linux/ppc64le | [@IBM-Currency-Helper](https://github.com/IBM-Currency-Helper), [@adilhusain-s](https://github.com/adilhusain-s), [@seth-priya](https://github.com/seth-priya) |
| linux/riscv64 | [@shanduur](https://github.com/shanduur) |
| linux/s390x | [@bwalk-at-ibm](https://github.com/bwalk-at-ibm), [@rrschulze](https://github.com/rrschulze) |
| windows/386 | [@pjanotti](https://github.com/pjanotti) |
| windows/arm64 | [@pjanotti](https://github.com/pjanotti) |
================================================
FILE: docs/release.md
================================================
# 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 three sections. Each section is assigned to an approver or maintainer of the corresponding repository. The sections are:
1. The [Core](#releasing-opentelemetry-collector) collector, including the collector builder CLI tool.
2. The [Contrib](#releasing-opentelemetry-collector-contrib) collector repository, containing Collector components.
3. The [artifacts](#producing-the-artifacts)
**Important Note:** 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/authentication/managing-commit-signature-verification/signing-commits) to set it up.
## Release managers
A release manager is the person responsible for a specific release. While the manager might request help from other folks, they are ultimately responsible for the success of a release.
In order to have more people comfortable with the release process, and in order to decrease the burden on a small number of volunteers, all core, contrib and releases approvers are release managers from time to time, listed under the [Release Schedule](#release-schedule) section. That table is updated at every release, with the current core release manager adding themselves to the bottom of the table, removing themselves from the top of the table.
The assigned release managers should coordinate with each other to ensure a smooth release process. The release managers may be the same person for different repositories, but it is not required.
To ensure the rest of the community is informed about the release and can properly help the release manager, the core release manager should open a thread on the #otel-collector-dev CNCF Slack channel and provide updates there.
The thread should be shared with all Collector leads (core, contrib and releases approvers and maintainers).
Before the release, make sure there are no open release blockers in [core](https://github.com/open-telemetry/opentelemetry-collector/labels/release%3Ablocker), [contrib](https://github.com/open-telemetry/opentelemetry-collector-contrib/labels/release%3Ablocker) and [releases](https://github.com/open-telemetry/opentelemetry-collector-releases/labels/release%3Ablocker) repos.
## Releasing opentelemetry-collector (core release manager)
1. Update Contrib to use the latest in development version of Core by running [Update contrib to the latest core source](https://github.com/open-telemetry/opentelemetry-collector-contrib/actions/workflows/update-otel.yaml). This is to ensure that the latest core does not break contrib in any way. If the job is failing for any reason, you can do it locally by running `make update-otel` in Contrib root directory and pushing a PR. If you are unable to run `make update-otel`, it is possible to skip this step and resolve conflicts with Contrib after Core is released, but this is generally inadvisable.
- While this PR is open, all merging in Core is automatically halted via the `Merge freeze / Check` CI check.
- 🛑 **Do not move forward until this PR is merged.**
2. Determine the version number that will be assigned to the release. Usually, we increment the minor version number and set the patch number to 0. In this document, we are using `v0.85.0` as the version to be released, following `v0.84.0`.
3. Manually run the action [Automation - Prepare Release](https://github.com/open-telemetry/opentelemetry-collector/actions/workflows/prepare-release.yml). This action will create an issue to track the progress of the release and a pull request to update the changelog and version numbers in the repo.
- When prompted, enter the version numbers determined in Step 2, but do not include a leading `v`.
- While this PR is open all merging in Core is automatically halted via the `Merge freeze / Check` CI check.
- If the PR needs updated in any way you can make the changes in a fork and PR those changes into the `prepare-release-prs/x` branch. You do not need to wait for the CI to pass in this prep-to-prep PR.
- 🛑 **Do not move forward until this PR is merged.** 🛑
4. On your local machine, make sure you are on the `main` branch and that the PR from step 3 is incorporated **at the head of your branch** (this is required to ensure the proper commit is used for the release tags and branch creation below). Tag the module groups with the new release version by running:
⚠️ If you set your remote using `https` you need to include `REMOTE=https://github.com/open-telemetry/opentelemetry-collector.git` in each command. ⚠️
- `make push-tags MODSET=beta` for the beta modules group,
- `make push-tags MODSET=stable` for the stable modules group.
**Note**: Pushing the **beta** tags will automatically trigger the [Automation - Release Branch](https://github.com/open-telemetry/opentelemetry-collector/actions/workflows/release-branch.yml) GitHub Action, which will create the release branch (e.g. `release/v0.127.x`) from the commit that prepared the release. Pushing stable tags, if required, will not trigger creation of an additional release branch.
5. Wait for the "Automation - Release Branch" workflow to complete successfully. This workflow will automatically:
- Detect the version from the pushed beta tags
- Use the commit on which the tags were pushed as the "prepare release" commit
- Create a new release branch (e.g. `release/v0.127.x`) from that commit
If the workflow fails, you can check the [Actions tab](https://github.com/open-telemetry/opentelemetry-collector/actions) for details. The underlying script (./.github/workflows/scripts/release-branch.sh) can also be tested and run locally if needed by setting the GITHUB_REF environment variable (e.g., `GITHUB_REF=refs/tags/v0.85.0 ./.github/workflows/scripts/release-branch.sh`).
6. Wait for the tag-triggered build workflows to pass successfully.
7. A new `v0.85.0` source code release should be automatically created on Github by now. Its description should already contain the corresponding CHANGELOG.md and CHANGELOG-API.md contents.
## Releasing opentelemetry-collector-contrib (contrib release manager)
See the [opentelemetry-collector-contrib release documentation](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/docs/release.md) for the release process in that repository.
## Producing the artifacts ('releases' release manager)
See the [opentelemetry-collector-releases release documentation](https://github.com/open-telemetry/opentelemetry-collector-releases/blob/main/docs/release.md) for the release process in that repository.
## Post-release steps (all release managers)
After the release is complete, the release manager should do the following steps:
1. Create an issue or update existing issues for each problem encountered throughout the release in
the appropriate repositories and label them with the `release:retro` label. The release manager
should share the list of issues that affected the release with the Collector leads.
2. Update the [release schedule](#release-schedule) section of this document to remove the completed
releases and add new schedules to the bottom of the list. To update the release schedule, follow these rules:
1. If the core release manager is also eligible as a contrib and 'releases' release manager, assign them to all roles they can perform.
2. Otherwise, pick a contrib/'releases' approver/maintainer that is not a core approver/maintainer, rotating through the list of eligible people. The contrib approvers/maintainers are all members of the [@collector-contrib-approvers](https://github.com/orgs/open-telemetry/teams/collector-contrib-approvers) team, and the 'releases' approvers/maintainers are all members of the [@collector-releases-approvers](https://github.com/orgs/open-telemetry/teams/collector-releases-approvers) team.
## Troubleshooting
1. `unknown revision internal/coreinternal/v0.85.0` -- This is typically an indication that there's a dependency on a new module. You can fix it by adding a new `replaces` entry to the `go.mod` for the affected module.
2. `unable to tag modules: unable to load repo config: branch config: invalid merge` when running `make push-tags` -- This is a [known issue](https://github.com/open-telemetry/opentelemetry-go-build-tools/issues/47) with our release tooling, caused by a bug in `go-git`.
It occurs if you have branches in your local repository whose entry in `.git/config` has a `merge` attribute not starting with `refs/heads`. This can typically happen when checking out PR branches using the Github CLI tool, for which the `merge` attribute starts with `refs/pull`.
A possible workaround is to:
- Comment out the problematic lines with `sed -E -i.bak 's/(merge = refs\/pull)/#\1/' .git/config`;
- Try `make push-tags` again;
- Restore the config with `mv .git/config.bak .git/config`.
If that doesn't work, you can clone a fresh copy of the repository and try again. Note that you may need to set up a `fork` remote pointing to your own fork for the release tooling to work properly.
3. `could not run Go Mod Tidy: go mod tidy failed` when running `multimod` -- This is a [known issue](https://github.com/open-telemetry/opentelemetry-go-build-tools/issues/46) with our release tooling. The current workaround is to run `make gotidy` manually after the multimod tool fails and commit the result.
4. `Incorrect version "X" of "go.opentelemetry.io/collector/component" is included in "X"` in CI after `make update-otel` -- It could be because the make target was run too soon after updating Core and the goproxy hasn't updated yet. Try running `export GOPROXY=direct` and then `make update-otel`.
5. `error: failed to push some refs to 'https://github.com/open-telemetry/opentelemetry-collector-contrib.git'` during `make push-tags` -- If you encounter this error the `make push-tags` target will terminate without pushing all the tags. Using the output of the `make push-tags` target, save all the un-pushed the tags in `tags.txt` and then use this make target to complete the push:
```bash
.PHONY: temp-push-tags
temp-push-tags:
for tag in `cat tags.txt`; do \
echo "pushing tag $${tag}"; \
git push ${REMOTE} $${tag}; \
done;
```
6. `unable to tag modules: git tag failed for v0.112.0: unable to create tag:
"error: gpg failed to sign the data:`. Make sure you have GPG set up to sign
commits. You can run `gpg --gen-key` to generate a GPG key.
7. When using a new GitHub Actions workflow in opentelemetry-collector-releases
for the first time during a release, a workflow may fail. If it is possible
to fix the workflow, a maintainer can update the release tag to the commit
with the fix and re-run the release. (Note: This cannot be done by
approvers.)
It is safe to re-run the workflows that already succeeded. Publishing
container images can be done multiple times, and publishing artifacts or
pushing OCB/Supervisor tags to GitHub will fail without any adverse effects.
## Bugfix releases
### Bugfix release criteria
All OpenTelemetry Collector repositories have very short 2 week release cycles. Because of this, we put a high bar when considering making a patch release, to avoid wasting engineering time unnecessarily.
When considering making a bugfix release on the `v0.N.x` release cycle, the bug in question needs to fulfill the following criteria:
1. The bug has no workaround or the workaround is significantly harder to put in place than updating the version. Examples of simple workarounds are:
- Reverting a feature gate.
- Changing the configuration to an easy to find value.
2. The bug happens in common setups. To gauge this, maintainers can consider the following:
- If the bug is specific to a certain platform, and if that platform is in [Tier 1](../docs/platform-support.md#tiered-platform-support-model).
- The bug happens with the default configuration or with one that is known to be used in production.
3. The bug is sufficiently severe. For example (non-exhaustive list):
- The bug makes the Collector crash reliably
- The bug makes the Collector fail to start under an accepted configuration
- The bug produces significant data loss
- The bug makes the Collector negatively affect its environment (e.g. significantly affects its host machine)
- The bug makes it difficult to troubleshoot or debug Collector setups
We aim to provide a release that fixes security-related issues in at most 30 days since they are publicly announced; with the current release schedule this means security issues will typically not warrant a bugfix release. An exception is critical vulnerabilities (CVSSv3 score >= 9.0), which will warrant a release within five business days.
The OpenTelemetry Collector maintainers will ultimately have the responsibility to assess if a given bug or security issue fulfills all the necessary criteria and may grant exceptions in a case-by-case basis.
If the maintainers are unable to reach consensus within one working day, we will lean towards releasing a bugfix version.
### Bugfix release procedure
The release manager of a minor version is responsible for releasing any bugfix versions on this release series for their repository. The following documents the procedure to release a bugfix:
1. Create a pull request against the `release/` (e.g. `release/v0.90.x`) branch to apply the fix.
2. Make sure you are on `release/`. Prepare release commits with `prepare-release` make target, e.g. `make prepare-release PREVIOUS_VERSION=0.90.0 RELEASE_CANDIDATE=0.90.1 MODSET=beta`, and create a pull request against the `release/` branch.
3. Once those changes have been merged, create a pull request to the `main` branch from the `release/` branch.
4. If you see merge conflicts when creating the pull request, do the following:
1. Create a new branch from `origin:main`.
2. Merge the `release/` branch into the new branch.
3. Resolve the conflicts.
4. Create another pull request to the `main` branch from the new branch to replace the pull request from the `release/` branch.
5. Disable the merge queue. An admin of the repo needs to be available for this.
6. Enable the **Merge pull request** setting in the repository's **Settings** tab.
7. Make sure you are on `release/`. Push the new release version tags for a target module set by running `make push-tags MODSET=`. Wait for the new tag build to pass successfully.
8. **IMPORTANT**: The pull request to bring the changes from the release branch *MUST* be merged using the **Merge pull request** method, and *NOT* squashed using “**Squash and merge**”. This is important as it allows us to ensure the commit SHA from the release branch is also on the main branch. **Not following this step will cause much go dependency sadness.**
9. If the pull request was created from the `release/` branch, it will be auto-deleted. Restore the release branch via GitHub.
10. Once the patch is released, disable the **Merge pull request** setting and re-enable the merge queue.
## 1.0 release
Stable modules adhere to our [versioning document guarantees](../VERSIONING.md), so we need to be careful before releasing. Before adding a module to the stable module set and making a first 1.x release, please [open a new stabilization issue](https://github.com/open-telemetry/opentelemetry-collector/issues/new/choose) and follow the instructions in the issue template.
Once a module is ready to be released under the `1.x` version scheme, file a PR to move the module to the `stable` module set and remove it from the `beta` module set. Note that we do not make `v1.x.y-rc.z` style releases for new stable modules; we instead treat the last two beta minor releases as release candidates and the module moves directly from the `0.x` to the `1.x` release series.
## Release schedule
| Date | Version | Core Release manager | Contrib release manager | 'Releases' release manager |
|------------|----------|-----------------------|-------------------------|----------------------------|
| 2026-03-16 | v0.148.0 | [@dmitryax][7] | [@dmitryax][7] | [@dmitryax][7] |
| 2026-03-30 | v0.149.0 | [@codeboten][8] | [@codeboten][8] | [@codeboten][8] |
| 2026-04-13 | v0.150.0 | [@dmathieu][12] | [@andrzej-stencel][4] | [@crobert-1][20] |
| 2026-04-27 | v0.151.0 | [@bogdandrutu][9] | [@bogdandrutu][9] | [@bogdandrutu][9] |
| 2026-05-11 | v0.152.0 | [@jade-guiton-dd][10] | [@ChrsMark][19] | [@dehaansa][16] |
| 2026-05-25 | v0.153.0 | [@axw][18] | [@braydonk][13] | [@MovieStoreGuy][17] |
| 2025-06-08 | v0.154.0 | [@atoulme][5] | [@atoulme][5] | [@atoulme][5] |
| 2026-06-22 | v0.155.0 | [@jmacd][1] | [@ArthurSens][11] | [@TylerHelmuth][3] |
| 2026-07-06 | v0.156.0 | [@mx-psi][14] | [@mx-psi][14] | [@mx-psi][14] |
| 2026-07-20 | v0.157.0 | [@TylerHelmuth][3] | [@TylerHelmuth][3] | [@mowies][15] |
| 2026-08-03 | v0.158.0 | [@evan-bradley][2] | [@evan-bradley][2] | [@evan-bradley][2] |
| 2026-08-17 | v0.159.0 | [@songy23][6] | [@songy23][6] | [@songy23][6] |
[1]: https://github.com/jmacd
[2]: https://github.com/evan-bradley
[3]: https://github.com/TylerHelmuth
[4]: https://github.com/andrzej-stencel
[5]: https://github.com/atoulme
[6]: https://github.com/songy23
[7]: https://github.com/dmitryax
[8]: https://github.com/codeboten
[9]: https://github.com/bogdandrutu
[10]: https://github.com/jade-guiton-dd
[11]: https://github.com/ArthurSens
[12]: https://github.com/dmathieu
[13]: https://github.com/braydonk
[14]: https://github.com/mx-psi
[15]: https://github.com/mowies
[16]: https://github.com/dehaansa
[17]: https://github.com/MovieStoreGuy
[18]: https://github.com/axw
[19]: https://github.com/ChrsMark
[20]: https://github.com/crobert-1
================================================
FILE: docs/rfcs/README.md
================================================
# Collector RFCs
This folder contains accepted design documents for the Collector. Proposals here imply changes only
on the OpenTelemetry Collector and not on other parts of OpenTelemetry; if you have a cross-cutting
proposal, file an [OTEP][1] instead.
# RFC Process
## Scope
The Request For Comments (RFC) process is intended to be used for significant changes to the Collector. Major design
decisions, especially those that imply a change that is hard to reverse, should generally be
documented as an RFC. Controversial changes should also be documented as an RFC, so that the
community can have a chance to provide feedback. The goal of this process is to ensure we have a
coherent vision before embarking on significant work.
Ultimately, if any opentelemetry-collector maintainer feels that a change requires an RFC, then a
merged RFC is a requirement for said change.
## Contents
We are purposefully light on the structure of RFCs, with the focus being the decision process and
not the document itself. When in doubt, the [OTEP template][2] can be a good starting point.
Regardless of the structure, the RFC should make clear what commitments are being made by the
Collector SIG when merging it.
## Announcement
RFCs should be announced in a Collector SIG meeting and on the #otel-collector-dev Slack channel.
## Approval process
An RFC is just like any other PR in this repository, with the exception of more stringent criteria
for merging it.
### Stakeholders
To define merge criteria and voting, each RFC has a set of 'stakeholders'. All
opentelemetry-collector approvers are considered stakeholders. Additional stakeholders (e.g.
maintainers of related SIGs or experts) may be explicitly noted in the RFC.
### Merge rules
We use a [Lazy Consensus](https://www.apache.org/foundation/glossary.html#LazyConsensus) method with the following rules:
1. *Quorum*: For an RFC to be mergeable, it needs to have at least **two approvals** from the
approvers set as well as approvals from any additional stakeholders.
2. *Waiting period*: Maintainers need to announce their intent to merge the RFC with a GitHub
comment. They will need to add a `rfc:final-comment-period` label to the PR, comment on the PR
and note the final comment period in the #otel-collector-dev CNCF Slack channel, and wait for at
least **4 business days** after making the announcement to merge the RFC.
3. *Objections*: Objections should be communicated as a 'request changes' review. All objections
must be addressed before merging an RFC. If addressing an objection does not appear feasible, any
maintainer may call for a vote to be made on the objection (see below). This will be signified by
adding a `rfc:vote-needed` label to the PR. The voting result is binding and a maintainer can
drop any 'request changes' reviews based on the vote results or consensus.
4. *Modifications*: Non-trivial modifications to an RFC reset the waiting period. RFC authors must
re-request any approving, comment, or 'request changes' reviews if the RFC has been modified significantly.
5. *All conversations are resolved*: All Github conversations on the PR must be marked as resolved
before merging. The RFC author may resolve conversations at their discretion, but they must
explain in the conversation thread why they believe it is appropriate to do so.
### Voting
If there is no consensus on a particular issue, a vote may be called by any of the maintainers. The
vote will be open to all stakeholders for at least **5 business days** or until at least one third
of the stakeholders have voted, whichever comes last. The vote will be decided by simple majority,
restricting the set to approvers and then maintainers in case of a tie among stakeholders.
Voting should be done on a dedicated issue via comments. The related discussion threads should be
linked in the issue. The voting result should be documented in the RFC itself.
# Modifications to existing RFCs and to this document
Non-trivial modifications to this document and to existing RFCs should be done through the RFC
process itself and have the same merge criteria.
[1]: https://github.com/open-telemetry/oteps
[2]: https://github.com/open-telemetry/oteps/blob/main/0000-template.md
================================================
FILE: docs/rfcs/component-configuration-schema-roadmap.md
================================================
# Component Configuration Management Roadmap
## Motivation
The OpenTelemetry Collector ecosystem lacks a unified approach to configuration management, leading to several problems:
1. **Documentation Drift**: Go configuration structs and documentation exist independently and frequently diverge over time
2. **Inconsistent Developer Experience**: No standardized patterns for defining component configurations
3. **No config validation capabilities**: Lack of JSON schemas prevents autocompletion and validation in configuration editors
## Current state
- Go configuration structs in each component with validation implemented via custom code and defaults set in `setDefaultConfig` functions
- Manual documentation that often becomes outdated
- No standardized JSON schemas for configuration validation
## Desired state
**Goal**: Establish a single source of truth for component configuration that generates:
1. **Go configuration structs** with proper mapstructure tags, validation, and default values.
2. **JSON schemas** for configuration validation and editor autocompletion
3. **Documentation** that stays automatically synchronized with implementation
## Previous and current approaches
### Past attempts
- [Previously available contrib configschema tool](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/v0.102.0/cmd/configschema): Retired due to incompleteness, complexity and maintenance burden. It required dynamic analysis of Go code and pulling all dependencies.
- [PR #27003](https://github.com/open-telemetry/opentelemetry-collector-contrib/pull/27003): Failed due to trying to cover all corner-cases in the design phase instead of quickly iterating from a simpler approach.
- [PR #10694](https://github.com/open-telemetry/opentelemetry-collector/pull/10694): An attempt to generate config structs from the schema defined in metadata.yaml using github.com/atombender/go-jsonschema. It faced some limitations of the library. However, it was abandoned mostly due to a lack of involvement from the reviewers.
### Current initiatives
- [opentelemetry-collector-contrib/cmd/schemagen/](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/cmd/schemagen): Generates JSON schemas from Go structs with limited support for validation and default values. It uses AST parsing with module-aware loading of dependencies to handle shared libraries.
- [PR #14288](https://github.com/open-telemetry/opentelemetry-collector/pull/14288): Also uses AST parsing to generate JSON schemas from Go structs for the component configurations without using shared config support. Written as part of mdatagen tool.
Parsing Go code to generate schemas is inherently limited. Community consensus recommends reversing the process: generate Go code from schemas instead. There is already widely established practice in other ecosystems to generate go code and documentation for other parts of the OTel Collector.
## Suggested approach
### Overview
This RFC proposes an approach that transitions from the current Go-struct-first model to a schema-first configuration generation system:
1. **Bootstrap Phase**: Use existing `schemagen` tool to generate initial schema specifications
2. **Tool Development Phase**: Create new tooling that generates Go structs, JSON schemas, and documentation from YAML schema specifications
3. **Migration Phase**: Migrate all components to the new schema-first approach
Use of the `schemagen` tool is dictated by the modularity of the Collector components. It allows generating schemas for shared libraries (e.g., scraperhelper) that can be referenced by individual components.
### Reasoning behind this approach
- **Explicit validation**: Schema specifications can explicitly capture validation rules and default config values that cannot be extracted from Go code
- **Rich documentation**: Schemas can include descriptions, examples, and constraints that enhance generated documentation
- **Simplified tooling**: Template-based code generation is more predictable than AST parsing
**Why YAML schema format for the source of truth?**
- **Human-readable**: Easier for component developers to author and maintain than JSON
- **Integration with existing infrastructure**: Natural extension of `metadata.yaml` approach used by `mdatagen` given that it already uses YAML to generate metrics builder configs
- **Extensibility**: YAML allows for custom fields to capture domain-specific configuration and provide escape-hatches to generate config fields that still require custom implementation, validation or default value setters.
### Example schema format
```yaml
config:
allOf:
- $ref: "go.opentelemetry.io/collector/scraper/scraperhelper#/$defs/ControllerConfig"
- $ref: "#/$defs/MetricsBuilderConfig"
properties:
targets:
type: array
items:
type: object
properties:
host:
type: string
description: "Target hostname or IP address"
ping_count:
type: integer
description: "Number of pings to send"
default: 3
ping_interval:
type: string
format: duration
x-customType: "time.Duration"
description: "Interval between pings"
default: "1s"
required: ["host"]
```
`#/$defs/MetricsBuilderConfig` would be automatically generated by mdatagen with the same process used to generate the go structs and documentation today.
`go.opentelemetry.io/collector/scraper/scraperhelper#/$defs/ControllerConfig` would be generated by the new tool from the schema definition in the scraperhelper component.
#### Extensibility
The YAML schema specification can be extended with custom fields (e.g., `x-customType`) to capture domain-specific types and validation rules that are not natively supported in JSON schema. Additionally, we may introduce custom fields that generate fields that will produce references to structs or validation functions that require more complex logic and manual implementation.
### Roadmap
#### Phase 1: Bootstrap initial schemas
**Objective**: Use `schemagen` tool to generate initial schema specifications for all components
**Success Criteria:**
- YAML schemas generated for all components in core and contrib repositories
- Setup CI check to ensure schemas remain up-to-date with Go structs
#### Phase 2: Implement new generation tool
**Objective**: Implement a new tool that takes YAML schema from the user and generates Go structs, combined JSON schema, and documentation per component.
**Success Criteria:**
- New tool generates Go structs that are API-compatible with existing implementations with the following features:
- Parses YAML schema specifications
- Generates Go configuration structs with proper validation
- Produces JSON schemas for config validation
- Creates synchronized documentation
- Generated JSON schemas pass validation tests with real collector configurations
- Generated documentation accurately reflects all configuration options
- Pilot components successfully replace hand-written implementations
If existing config structs don't follow the established naming patterns produced by the generated code, the implementation may allow breaking the Go API compatibility in favor of consistent Go API naming standards and long-term maintainability. However, the configuration file format MUST remain compatible for end users.
#### Phase 3: Migrate all components
**Objective**: Migrate all components to the new tool introduced in Phase 2
**Success Criteria:**
- All core and contrib components migrated to schema-first approach
- All new components use schema-first tooling by default
================================================
FILE: docs/rfcs/component-status-reporting.md
================================================
# Component Status Reporting
## Overview
Since the OpenTelemetry Collector is made up of pipelines with components, it needs a way for the components within those pipelines to emit information about their health. This information allows the collector service, or other interested software or people, to make decisions about how to proceed when something goes wrong. This document describes:
1. The historical state of how components reported health
2. The current state of how components report health
3. The goals component health reporting should achieve
4. Existing deviations from those goals
5. Desired behavior for 1.0
For context throughout this document, component defines a `component.Host` interface, which components may use to interact with the struct that is managing all the collector pipelines and the components. In this repository, our implementation of `component.Host` can be found in `service/internal/graph.Host`.
## Out Of Scope
How to get from the current to desired behavior is also considered out of scope and will be discussed on individual PRs. It will likely involve one or multiple feature gates, warnings and transition periods.
## The Collector’s Historical method of reporting component health
Until recently, the Collector relied on four ways to report health.
1. The `error` returned by the Component’s Start method. During startup, if any component decided to return an error, the Collector would stop gracefully.
2. The `component.Host.ReportFatalError` method. This method let components tell the `component.Host` that something bad happened and the collector needed to shut down. While this method could be used anywhere in the component, it was primarily used with a Component’s Start method to report errors in async work, such as starting a server.
```golang
if errHTTP := fmr.server.Serve(listener); errHTTP != nil && !errors.Is(errHTTP, http.ErrServerClosed) {
host.ReportFatalError(errHTTP)
}
```
3. The error returned by `Shutdown`. This error was indicative that the collector did not cleanly shut down, but did not prevent the shutdown process from moving forward.
4. Panicking. During runtime, if the collector experienced an unhandled error, it crashes.
These are all the way the components in a collector could report that they were unhealthy.
There are several major gaps in the Collector’s historic reporting of component health. First, many components return recoverable errors from Start, causing the collector to shutdown, while it could recover if the collector was allowed to run. Second, when a component experienced a transient error, such as an endpoint suddenly not working, the component would simply log the error and return it up the pipeline. There was no mechanism for the component to tell the `component.Host` or anything else that something was going wrong. Last, when a component experienced an issue it would never be able to recover from, such as receiving a 404 response from an endpoint, the component would log the error and return it up the pipeline. This situation was handled in the same way as the transient error, which means the component could not tell the `component.Host` or anything else that something was wrong, but worse is that the issue would never get better.
## Current State of Component Health Reporting
See [Component Status Reporting](../component-status.md)
## The Goals the Component Health Reporting Should Achieve
The following are the goals, as of June 2024 and with Collector 1.0 looming, for a component health reporting system.
1. A `component.Host` implementation, such as `service/internal/graph.Host`, may report statuses Starting, Ok, Stopping and PermanentError on behalf of components.
- Additional status may be reported in the future
2. Components may opt-in to reporting health status at runtime. Components must not be required to report health statuses themselves.
- The consumers of the health reporting system must be able to identify which components are and are not opting to report their own statuses.
3. Component health reporting must be opt-in for collector users. While the underlying components are always allowed to report their health via the system, the `component.Host` implementation, such as `service/internal/graph.Host`, or any other listener may only take action when the user has configured the collector accordingly.
- As one example of compliance, the current health reporting system is dependent on the user configuring an extension that can watch for status updates.
4. Component health must be representable as a finite state machine with clear transitions between states.
5. Component health reporting must only be a mechanism for reporting health - it should have no mechanisms for taking actions on the health it reports. How consumers of the health reporting system respond to component updates is not a concern of the health reporting system.
## Existing deviations from those goals
### Fatal Error Reporting
Before the current implementation of component status reporting, a component could stop the collector by using `component.Host.ReportFatalError`. Now, a component MUST use component status reporting and emit a `FatalError`. This fact is in conflict with Goal 1, which states component health reporting must be opt-in for components.
A couple solutions:
1. Accept this reality as an exception to Goal 2.
2. Add back `component.Host.ReportFatalError`.
3. Remove the ability for components to stop the collector be removing `FatalError`.
### No way to identify components that are not reporting status
Goal 2 states that consumers of component status reporting must be able to identify components in use that have not opted in to component status reporting. Our current implementation does not have this feature.
### Should component health reporting be an opt-in for `component.Host` implementations?
The current implementation of component status reporting does not add anything to `component.Host` to force a `component.Host` implementation, such as `service/internal/graph.Host`, to be compatible with component status reporting. Instead, it adds `ReportStatus func(*StatusEvent)` to `component.TelemetrySettings` and things that instantiate components, such as `service/internal/graph.Host`, should, but are not required, to pass in a value for `ReportStatus`.
As a result, `component.Host` implementation is not required to engage with the component status reporting system. This could lead to situations where a user adds a status watcher extension that can do nothing because the `component.Host` is not reporting component status updates.
Is this acceptable? Should we:
1. Require the `component.Host` implementations be compatible with the component status reporting framework?
2. Add some sort of configuration/build flag then enforces the `component.Host` implementation be compatible (or not) with component status reporting?
3. Accept this edge case.
### Component TelemetrySettings Requirements
The current implementation of component status reporting added a new field to `component.TelemetrySettings`, `ReportStatus`. This field is technically optional, but would be marked as stable with component 1.0. Are we ok with 1 of the following?
1. Including a component status reporting feature, `component.TelemetrySettings.ReportStatus`, in the 1.0 version of `component.TelemetrySettings`?
2. Marking `component.TelemetrySettings.ReportStatus` as experimental via godoc comments in the 1.0 version of `component.TelemetrySettings`?
Or should we refactor `component` somehow to remove `ReportStatus` from `component.TelemetrySettings`?
## Desired Behavior for 1.0
For each listed deviation, the solution for unblocking component 1.0 is:
- `Fatal Error Reporting` :white_check_mark:: The `component` module provides no mechanism for a component to stop a collector after it has started. It is expected that an error returned from `Start` will terminate a starting Collector, but it is ultimately up to the caller of `Start` how to handle the returned error. A `component.Host` implementation may choose to provide a mechanism to stop a running collector via a different Interface, but doing so is not required.
- As part of this stance, we agree that the `component.Component.Start` method will continue returning an error.
- `No way to identify components that are not reporting status` :white_check_mark:: This can be implemented as a feature addition to component status reporting without blocking `component` 1.0
- `Should component health reporting be an opt-in for component.Host implementations?` :white_check_mark:: Yes. A `component.Host` implementation is not required to provide a component status reporting feature. They may do so via an additional interface, such as `componentstatus.Reporter`.
- `Component TelemetrySettings Requirements` :white_check_mark:: `component.TelemetrySettings.ReportStatus` has been removed. Instead, component status reporting is expected to be provided via an additional interface that `component.Host` implements. Components can check if the `component.Host` implements the desired interface, such as `componentstatus.Reporter` to access component status reporting features.
## Reference
- Remove FatalError? Looking for opinions either way: https://github.com/open-telemetry/opentelemetry-collector/issues/9823
- In order to prioritize lifecycle events over runtime events for status reporting, allow a component to transition from PermanentError -> Stopping: https://github.com/open-telemetry/opentelemetry-collector/issues/10058
- Runtime status reporting for components in core: https://github.com/open-telemetry/opentelemetry-collector/issues/9957
- Should Start return an error: https://github.com/open-telemetry/opentelemetry-collector/issues/9324
- Should Shutdown return an error: https://github.com/open-telemetry/opentelemetry-collector/issues/9325
- Status reporting doc incoming; preview here: https://github.com/mwear/opentelemetry-collector/blob/cc870fd2a7160da298acdda447511ea9a83455e0/docs/component-status.md
- Issues
- Closed: https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/8349
- Open: https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/8816
- Status Reporting PRs
- Closed
- https://github.com/open-telemetry/opentelemetry-collector/pull/5304
- https://github.com/open-telemetry/opentelemetry-collector/pull/6550
- https://github.com/open-telemetry/opentelemetry-collector/pull/6560
- Merged
- https://github.com/open-telemetry/opentelemetry-collector/pull/8169
================================================
FILE: docs/rfcs/component-universal-telemetry.md
================================================
# Pipeline Component Telemetry
## Motivation and Scope
The collector should be observable and this must naturally include observability of its pipeline components. Pipeline components
are those components of the collector which directly interact with data, specifically receivers, processors, exporters, and connectors.
It is understood that each _type_ (`filelog`, `otlp`, etc) of component may emit telemetry describing its internal workings,
and that these internally derived signals may vary greatly based on the concerns and maturity of each component. Naturally
though, there is much we can do to normalize the telemetry emitted from and about pipeline components.
Two major challenges in pursuit of broadly normalized telemetry are (1) consistent attributes, and (2) automatic capture.
This RFC represents an evolving consensus about the desired end state of component telemetry. It does _not_ claim
to describe the final state of all component telemetry, but rather seeks to document some specific aspects. It proposes a set of
attributes which are both necessary and sufficient to identify components and their instances. It also articulates one specific
mechanism by which some telemetry can be automatically captured. Finally, it describes some specific metrics and logs which should
be automatically captured for each kind of pipeline component.
## Goals
1. Define attributes that are (A) specific enough to describe individual component [_instances_](https://github.com/open-telemetry/opentelemetry-collector/issues/10534)
and (B) consistent enough for correlation across signals.
2. Articulate a mechanism which enables us to _automatically_ capture telemetry from _all pipeline components_.
3. Define specific metrics for each kind of pipeline component.
4. Define specific logs for all kinds of pipeline component.
## Attributes
Traces, logs, and metrics should carry the following instrumentation scope attributes:
### Receivers
- `otelcol.component.kind`: `receiver`
- `otelcol.component.id`: The component ID
- `otelcol.signal`: `logs`, `metrics`, `traces`, `profiles`
### Processors
- `otelcol.component.kind`: `processor`
- `otelcol.component.id`: The component ID
- `otelcol.pipeline.id`: The pipeline ID
- `otelcol.signal`: `logs`, `metrics`, `traces`, `profiles`
### Exporters
- `otelcol.component.kind`: `exporter`
- `otelcol.component.id`: The component ID
- `otelcol.signal`: `logs`, `metrics`, `traces`, `profiles`
### Connectors
- `otelcol.component.kind`: `connector`
- `otelcol.component.id`: The component ID
- `otelcol.signal`: `logs`, `metrics` `traces`
- `otelcol.signal.output`: `logs`, `metrics`, `traces`, `profiles`
Note: The `otelcol.signal`, `otelcol.signal.output`, or `otelcol.pipeline.id` attributes may be omitted if the corresponding component instances
are unified by the component implementation. For example, the `otlp` receiver is a singleton, so its telemetry is not specific to a signal.
Similarly, the `memory_limiter` processor is a singleton, so its telemetry is not specific to a pipeline.
These instrumentation scope attributes are automatically injected into the telemetry associated with a component, by wrapping the Logger, TracerProvider, and MeterProvider provided to it.
## Auto-Instrumentation Mechanism
The mechanism of telemetry capture should be _external_ to components. Specifically, we should observe telemetry at each point where a
component passes data to another component, and, at each point where a component consumes data from another component. In terms of the
component graph, every _edge_ in the graph will have two layers of instrumentation - one for the producing component and one for the
consuming component. Importantly, each layer generates telemetry ascribed to a single component instance, so by having two layers per
edge we can describe both sides of each handoff independently.
Telemetry captured by this mechanism should be associated with an instrumentation scope with a name corresponding to the package which implements
the mechanism. Currently, that package is `go.opentelemetry.io/collector/service`, but this may change in the future. Notably, this telemetry is not
ascribed to individual component packages, both because the instrumentation scope is intended to describe the origin of the telemetry,
and because no mechanism is presently identified which would allow us to determine the characteristics of a component-specific scope.
### Instrumentation Scope
All telemetry described in this RFC should include a scope name which corresponds to the package which implements the telemetry. If the
package is internal, then the scope name should be that of the module which contains the package. For example,
`go.opentelemetry.io/service` should be used instead of `go.opentelemetry.io/service/internal/graph`.
### Auto-Instrumented Metrics
There are two straightforward measurements that can be made on any pdata:
1. A count of "items" (spans, data points, or log records). These are low cost but broadly useful, so they should be enabled by default.
2. A measure of size, based on [ProtoMarshaler.Sizer()](https://github.com/open-telemetry/opentelemetry-collector/blob/9907ba50df0d5853c34d2962cf21da42e15a560d/pdata/ptrace/pb.go#l11).
These may be high cost to compute, so by default they should be disabled (and not calculated). This default setting may change in the future if it is demonstrated that the cost is generally acceptable.
The location of these measurements can be described in terms of whether the data is "consumed" or "produced", from the perspective of the
component to which the telemetry is attributed. Metrics which contain the term "produced" describe data which is emitted from the component,
while metrics which contain the term "consumed" describe data which is received by the component.
For both metrics, an `otelcol.component.outcome` attribute with possible values `success`, `failure`, and `refused` should be automatically recorded,
based on whether the corresponding function call returned successfully, returned an error originating from the associated component, or propagated an error from a component further downstream.
Specifically, a call to `ConsumeX` is recorded with:
- `otelcol.component.outcome = success` if the call returns `nil`;
- `otelcol.component.outcome = failure` if the call returns a regular error;
- `otelcol.component.outcome = refused` if the call returns an error tagged as coming from downstream.
After inspecting the error, the instrumentation layer should tag it as coming from downstream before returning it to the caller. Since there are two instrumentation layers between each pair of successive components (one recording produced data and one recording consumed data), this means that a call recorded with `outcome = failure` by the "consumer" layer will be recorded with `outcome = refused` by the "producer" layer, reflecting the fact that only the "consumer" component failed. In all other cases, the `outcome` recorded by both layers should be identical.
Errors should be "tagged as coming from downstream" the same way permanent errors are currently handled: they can be wrapped in a `type downstreamError struct { err error }` wrapper error type, then checked with `errors.As`. Note that care may need to be taken when dealing with the `multiError`s returned by the `fanoutconsumer`. If PR #11085 introducing a single generic `Error` type is merged, an additional `downstream bool` field can be added to it to serve the same purpose instead.
```yaml
otelcol.receiver.produced.items:
enabled: true
description: Number of items emitted from the receiver.
unit: "{item}"
sum:
value_type: int
monotonic: true
otelcol.processor.consumed.items:
enabled: true
description: Number of items passed to the processor.
unit: "{item}"
sum:
value_type: int
monotonic: true
otelcol.processor.produced.items:
enabled: true
description: Number of items emitted from the processor.
unit: "{item}"
sum:
value_type: int
monotonic: true
otelcol.connector.consumed.items:
enabled: true
description: Number of items passed to the connector.
unit: "{item}"
sum:
value_type: int
monotonic: true
otelcol.connector.produced.items:
enabled: true
description: Number of items emitted from the connector.
unit: "{item}"
sum:
value_type: int
monotonic: true
otelcol.exporter.consumed.items:
enabled: true
description: Number of items passed to the exporter.
unit: "{item}"
sum:
value_type: int
monotonic: true
otelcol.receiver.produced.size:
enabled: false
description: Size of items emitted from the receiver.
unit: "By"
sum:
value_type: int
monotonic: true
otelcol.processor.consumed.size:
enabled: false
description: Size of items passed to the processor.
unit: "By"
sum:
value_type: int
monotonic: true
otelcol.processor.produced.size:
enabled: false
description: Size of items emitted from the processor.
unit: "By"
sum:
value_type: int
monotonic: true
otelcol.connector.consumed.size:
enabled: false
description: Size of items passed to the connector.
unit: "By"
sum:
value_type: int
monotonic: true
otelcol.connector.produced.size:
enabled: false
description: Size of items emitted from the connector.
unit: "By"
sum:
value_type: int
monotonic: true
otelcol.exporter.consumed.size:
enabled: false
description: Size of items passed to the exporter.
unit: "By"
sum:
value_type: int
monotonic: true
```
#### Additional Attribute for Connectors
Connectors can route telemetry to specific pipelines. Therefore, `otelcol.connector.produced.*` metrics should carry an
additional data point attribute, `otelcol.pipeline.id`, to describe the pipeline ID to which the data is sent.
### Auto-Instrumented Logs
Metrics provide most of the observability we need but there are some gaps which logs can fill. Although metrics would describe the overall
item counts, it is helpful in some cases to record more granular events. For example, if a produced batch of 10,000 spans results in an error, but
100 batches of 100 spans succeed, this may be a matter of batch size that can be detected by analyzing logs, while the corresponding metric
reports only that a 50% success rate is observed.
For security and performance reasons, it would not be appropriate to log the contents of telemetry.
It's very easy for logs to become too noisy. Even if errors are occurring frequently in the data pipeline, only the errors that are not
handled automatically will be of interest to most users.
With the above considerations, this proposal includes only that we add a DEBUG log for each error, with the attributes from the corresponding
metrics as well as the error message and item count. This should be sufficient for detailed troubleshooting but does not impact users otherwise.
In the future, it may be helpful to define triggers for reporting repeated failures at a higher severity level. e.g. N number of failures in
a row, or a moving average success %. For now, the criteria and necessary configurability is unclear so this is mentioned only as an example
of future possibilities.
### Auto-Instrumented Spans
It is not clear that any spans can be captured automatically with the proposed mechanism. We have the ability to insert instrumentation both
before and after processors and connectors. However, we generally cannot assume a 1:1 relationship between consumed and produced data.
## Additional Context
This proposal pulls from a number of issues and PRs:
- [Demonstrate graph-based metrics](https://github.com/open-telemetry/opentelemetry-collector/pull/11311)
- [Attributes for component instancing](https://github.com/open-telemetry/opentelemetry-collector/issues/11179)
- [Simple processor metrics](https://github.com/open-telemetry/opentelemetry-collector/issues/10708)
- [Component instancing is complicated](https://github.com/open-telemetry/opentelemetry-collector/issues/10534)
================================================
FILE: docs/rfcs/configuration-merging-strategy.md
================================================
# Configuration merging
## Background
As part of issue [#8754](https://github.com/open-telemetry/opentelemetry-collector/issues/8754), a new feature gate has been introduced to support merging component lists instead of replacing them ([first PR](https://github.com/open-telemetry/opentelemetry-collector/pull/12097)). This enhancement enables configurations from multiple sources to be combined, preserving all defined components in the final configuration.
More information about this feature can be found in the [confmap README](https://github.com/open-telemetry/opentelemetry-collector/blob/d4539dd6b4e554e15066226fa975b156af7b1510/confmap/README.md#experimental-append-merging-strategy-for-lists).
The main motivation for this change was to allow users to define configuration fragments in different sources, and have them merged in such a way that all specified components are included under `service::pipeline` in the final configuration.
Previously, we relied on Koanf’s default merging strategy, which overrides static values and slices (such as strings, numbers, and lists). This behavior often resulted in configurations being unintentionally overwritten when merged from multiple sources.
This issue has been highlighted in several discussions and feature requests:
- https://github.com/open-telemetry/opentelemetry-collector/issues/8394
- https://github.com/open-telemetry/opentelemetry-collector/issues/8754
- https://github.com/open-telemetry/opentelemetry-collector/issues/10370
## Motivation and Scope
We’ve already implemented a feature gate and foundational logic that supports merging lists across configuration files. Currently, this logic is hardcoded to merge lists only for specific keys: receivers, exporters, and extensions. The relevant implementation can be found [here](https://github.com/open-telemetry/opentelemetry-collector/blob/main/confmap/internal/merge.go).
The current implementation lacks flexibility. Ideally, users should be able to specify which configuration paths should be merged, rather than relying on hardcoded defaults.
This RFC proposes extending the existing functionality by introducing a user-configurable mechanism to define merge behavior.
This RFC builds on top of feedback gathered from the [original PR](https://github.com/open-telemetry/opentelemetry-collector/pull/12097).
More specifically, this RFC aims to:
1. Add an option to specify which configuration paths should be merged.
2. Introduce support for prepend and append operations when merging list values:
- This is good to have for lists that rely on certain ordering, such as:
- `processors`
- `transformprocessor` statements
## Proposed approaches:
### Approach 1 (Recommended): Use yaml tags
The first approach relies on the concept of [yaml tags](https://tutorialreference.com/yaml/yaml-tags). We can specify a custom tag in our configuration files to indicate the lists we want to merge.
Consider following two configurations:
```yaml
#main.yaml
receivers:
...
exporters:
...
extensions:
extension1:
service:
extensions: [extension1]
pipelines:
logs:
receivers: [...]
exporters: [...]
```
```yaml
#extra.yaml
extensions:
extension2:
service:
extensions: !mode=append [extension2]
```
After running the collector with above configurations, the `service::extensions` path will get merged and final configuration will look like:
```yaml
#final.yaml
receivers:
...
exporters:
...
extensions:
extension1:
extension2:
service:
extensions: [extension1, extension2]
pipelines:
logs:
receivers: [...]
exporters: [...]
```
This approach completely relies on the configuration files and doesn't introduce any command line option.
Internally, we will loop through the yaml tree and fetch the paths we want to merged based on user-defined tags, then merge the lists found at those paths.
Our "custom" yaml tag will be in the format of URI query parameters. For starters, we can support following options:
1. `mode`:
- This setting will control the ordering of merged list.
- The value will be either of `append` or `prepend`.
- Default: `append`
2. `duplicates`:
- This setting controls the duplication of elements in the final list.
- If the user wants to allow duplicates, they can simply turn this flag on.
- Default: `false`
3. `recursive`:
- This setting controls the merging of child elements of the given yaml path.
- This is useful if user wants to merge all the lists under a give sub-tree.
- For eg. merging all the lists under the `service` section.
#### Examples of first approach
1. _Merge the `service::extensions` list_:
```yaml
#main.yaml
receivers:
...
exporters:
...
extensions:
extension1:
service:
extensions: [extension1]
pipelines:
logs:
receivers: [...]
exporters: [...]
---
#extra.yaml
extensions:
extension2:
service:
extensions: !mode=append [extension2]
```
```yaml
#final.yaml
receivers:
...
exporters:
...
extensions:
extension1:
extension2:
service:
extensions: [extension1, extension2]
pipelines:
logs:
receivers: [...]
exporters: [...]
```
2. _Merge all the lists under the `service::*` section_:
```yaml
#main.yaml
receivers:
...
exporters:
...
extensions:
extension1:
service:
extensions: [extension1]
pipelines:
logs:
receivers: [...]
exporters: [...]
---
#extra.yaml
extensions:
extension2:
receiver:
receiver2:
service: !mode=append&recursive=true
extensions: [extension2]
pipelines:
logs:
receivers: [receiver2]
```
```yaml
#final.yaml
receivers:
...
receiver2:
exporters:
...
extensions:
extension1:
extension2:
service:
extensions: [extension1, extension2]
pipelines:
logs:
receivers: [..., receiver2]
exporters: [...]
```
### Approach 2: URI params
The proposed approach will rely on concept of URI query parameters([_RFC 3986_](https://datatracker.ietf.org/doc/html/rfc3986#page-23)). Our configuration URIs already adhere to this syntax and we can extend it to support query params instead adding new CLI flags.
For now, the new merging strategy is only enabled under `confmap.enableMergeAppendOption` gate. If user specifies the options and tries to run the collector without gate, we will merge as per default behaviour.
We will support new parameters to config URIs as follows:
1. `merge_paths`: A comma-separated list of glob patterns which will be used while config merging
- This setting will control the paths user wants to merge from the given config.
- Example:
- `otelcol --config main.yaml --config extra.yaml?merge_paths=service::extensions,service::**::receivers`
- In this example, we will merge the list of extensions and receivers from pipeline, excluding lists in the rest of the config.
- `otelcol --config main.yaml --config ext.yaml?merge_paths=service::extensions --config rec.yaml?merge_paths=service::**::receivers`
- In this example, we will merge all list of extensions from `ext.yml` and list of receivers from `rec.yaml`, excluding lists in the rest of the config.
2. `merge_mode`: One of `prepend` or `append`.
- This setting will control the ordering of merged list.
#### Examples of second approach
Here are some examples:
1. _Append to default mergeable components_:
```bash
otelcol --config=main.yaml --config=extra_components.yaml?merge_mode=append --feature-gates=confmap.enableMergeAppendOption
```
- After running above command, the final configuration will include:
- Merged component(s) (`receivers`, `exporters` and `extensions`) from `extra_components.yaml`
2. _Specify exact paths for merging_:
```bash
otelcol \
--config=main.yaml \
--config=extra_extension.yaml?merge_mode=append&merge_paths=service::extensions \
--config=extra_receiver.yaml?merge_mode=append&merge_paths=service::**::receivers \
--feature-gates=confmap.enableMergeAppendOption
```
- After running above command, the final configuration will include:
- Merged extension(s) from `extra_extension.yaml`
- Merged receiver(s) from `extra_receiver.yaml`
3. _Prepend processors_:
```bash
otelcol --config=main.yaml --config=extra_processor.yaml?merge_mode=prepend&merge_paths=service::**::processors --feature-gates=confmap.enableMergeAppendOption
```
- After running above command, the final configuration will include:
- Merged processor(s) from `extra_processor.yaml`, but prepend the existing list.
4. _Exclude a config file from lists merging process_:
```bash
otelcol --config=main.yaml --config=extra_components.yaml?merge_mode=append --config override_components.yaml --feature-gates=confmap.enableMergeAppendOption
```
- In the above command, we have no specified any options for `override_components.yaml`. Hence, it will override all the conflicting lists from previous configuration, which is the default behaviour.
## Open questions
- What to do if an invalid option is provided for `merge_mode` or `merge_paths`?
- I can think of two possibilities:
1. Error out.
2. Log an error and merge the default way
- What to do if an invalid query param is provided in config URI?
- In this case, I strongly feel that we should error out.
## Extensibility
This URI-based approach is highly extensible. In the future, it can enable advanced operations such as map overriding. Currently, it's impossible to do so.
================================================
FILE: docs/rfcs/configuring-confmap-providers.md
================================================
# Configuration of confmap Providers
## Motivation
The `confmap.Provider` interface is used by the Collector to retrieve map
objects representing the Collector's configuration or a subset thereof. Sources
of config may include locally-available information such as files on disk or
environment variables, or may be remotely accessed over the network. In the
process of obtaining configuration from a source, the user may wish to modify
the behavior of how the source is obtained.
For example, consider the case where the Collector obtains configuration over
HTTP from an HTTP endpoint. A user may want to:
1. Poll the HTTP endpoint for configuration at a configurable interval and
reload the Collector service if the configuration changes.
2. Authenticate the request to get configuration by including a header in the
request. Additional headers may be necessary as part of this flow.
This would produce a set of options like the following:
- `poll-interval`: Sets an interval for the Provider to check the HTTP endpoint
for changes. If the config has changed, the service will be reloaded.
- `headers`: Specifies a map of headers to be put into the HTTP request to the
server.
## Current state
No upstream Providers currently offer any configuration options. The exported
interfaces are still able to change before the `confmap` module is declared
stable, but avoiding breaking changes in the API would be preferable.
## Desired state
We would like the following features available to users to configure Providers:
1. Global configuration of a certain type of Provider (`file`, `http`, etc.).
This allows for users to express things such as "all files should be watched
for changes" and "all HTTP requests should include authentication".
2. Named configuration for a certain type of provider that can be applied to
particular URIs. This will allow users to express things such as "some HTTP
URLs should be watched for changes with a certain set of settings applied".
3. Configuration options applied to specific URIs.
## Resolution
The `confmap` module APIs will not substantially change for 1.0. The following
steps will be taken to ensure that configuration can be made to work post-1.0:
1. Restrict URIs sufficiently to allow for extension after 1.0, e.g. restricting
the scheme to allow for things like "named schemes" (`file/auth:`).
2. Stabilize confmap Providers individually, so they can impose any desired
restrictions on their own.
3. Offer configuration as an optional interface for things like options that are
applied to all instances of a Provider.
## Possible technical solutions
*NOTE*: This section is speculative and may not reflect the final implementation
for providing options to confmap Providers.
Providers are invoked through passing `--config` flags to the Collector binary
or by using the braces syntax inside a Collector config file (`${scheme:uri}`).
Each invocation contains a scheme specifying how to obtain config and URI
specifying the config to be obtained. A single instance of a Provider is created
for each scheme and is tasked with retrieving config for its scheme for each
corresponding URI passed to the Collector.
With the above in mind, we have the following places where it may make sense to
support specifying options for Providers:
1. Parts of the URI we are requesting.
1. Separate flags to configure Providers per config URI.
1. Use a separate config file that specifies config sources inside a map
structure.
1. Extend the Collector's config schema to support specifying additional places
to obtain configuration.
All of the above options are targeted toward configuring how specific URIs are
resolved into config. To configure how a Provider resolves every URI it
receives, we should consider how to extend the above options to be specified
without a URI and to ensure the options are always applied to all URI
resolutions.
### Configure options inside the URI
[RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986#section-3), which
specifies the format of a URI, specifies the different parts of a URI and
suggests two places where we could pass options to Providers: queries and
fragments.
#### Queries
Breaking changes:
- confmap Providers would have breaking changes since they would now consume
unescaped URI queries. There would be no breaking changes to the confmap API.
Advantages:
- Explicitly intended to specify non-hierarchical data in a URI.
- Often used for this purpose.
- Fits into existing config URIs for URL-based Providers.
Disadvantages:
- Only allows easily specifying key-value pairs.
- Query parameters are somewhat frequently used, which may extend to backend
requests, and this may cause some churn for users who are unfamiliar that we
would be consuming them.
#### Fragments
We could specify options in a query parameter-encoded string placed into the URI
fragment.
Breaking changes:
- confmap Providers would have breaking changes since they would now consume
fragments. There would be no breaking changes to the confmap API.
Advantages:
- Not likely to be used by config backends for any of our supported protocols,
so has a low chance of conflict when using unescaped fragments.
- Fits into existing config URIs for URL-based Providers.
Disadvantages:
- Even if fragments are likely not useful to backends, we are still preventing
unescaped use in upstream Providers.
- Doesn't conform to the spirit of how fragments should be used according to RFC
3986.
- Only allows easily specifying key-value pairs.
We could likely partially circumvent the key-value pair limitation by
recursively calling confmap Providers to resolve files, env vars, HTTP URLs,
etc. For example:
```text
https://config.com/config#refresh-interval=env:REFRESH_INTERVAL&headers=file:headers.yaml
```
Using this strategy would also allow us to more easily get env vars and to get
values from files for things like API tokens.
### Separate flags to configure Providers per config URI
Breaking changes:
- Will need factory options if we provide config through a mechanism similar to
`component.Factory`, along with making a Provider instance per URI.
- Otherwise will need to break `confmap.Provider` interface to support providing
options in `Retrieve`.
Advantages:
- Allows us to keep config URIs opaque.
- Options live right next to config URIs on the command line.
Disadvantages:
- The flags would need to be placed in a certain position in the arguments list
to specify which URI they apply to.
- Configuring URIs present in files requires users to look in two places for
each URI and is suboptimal UX.
- Complicating the flags like this would be suboptimal UX.
### Specify additional config sources inside the main Collector configuration
This is a variant of providing a separate config source-only configuration file
that instead puts those URIs and their options inside the main configuration
file.
API changes:
- Need a way to specify options, either through a factory option, or an optional
interface.
Advantages:
- Allows us to keep URIs opaque.
- Map structures are easier to work with than command-line arguments for complex
config.
Disadvantages:
- There are now two ways to include config inside a Collector config file.
- Complicates the config schema and the config resolution process.
================================================
FILE: docs/rfcs/env-vars.md
================================================
# Stabilizing environment variable resolution
## Overview
The OpenTelemetry Collector supports three different syntaxes for
environment variable resolution which differ in their syntax, semantics
and allowed variable names. Before we stabilize confmap, we need to
address several issues related to environment variables. This document
describes:
- the current (as of v0.97.0) behavior of the Collector
- the goals that an environment variable resolution should aim for
- existing deviations from these goals
- the desired behavior after making some changes
### Out of scope
CLI environment variable resolution has a single syntax (`--config env:ENV`)
and it is considered out of scope for this document, focusing
instead on expansion within the Collector configuration.
How to get from the current to desired behavior is also considered out
of scope and will be discussed on individual PRs. It will likely involve
one or multiple feature gates, warnings and transition periods.
## Goals of an expansion system
The following are considered goals of the expansion system:
1. ***Expansion should happen only when the user expects it***. We
should aim to expand when the user expects it and keep the original
value when we don't (e.g. because the syntax is used for something
different).
2. ***Expansion must have predictable behavior***.
3. ***Multiple expansion methods, if present, should have similar behavior.***
Switching from `${env:ENV}` to `${ENV}` or vice versa
should not lead to any surprises.
4. ***When the syntax overlaps, expansion should be aligned with***
[***the expansion defined by the Configuration Working Group***](https://github.com/open-telemetry/opentelemetry-specification/blob/032213cedde54a2171dfbd234a371501a3537919/specification/configuration/file-configuration.md#environment-variable-substitution). See [opentelemetry-specification/issues/3963](https://github.com/open-telemetry/opentelemetry-specification/issues/3963) for the counterpart to this line of work in the SDK File spec.
## Current behavior
The Collector supports three different syntaxes for environment variable
resolution:
1. The *naked syntax*, `$ENV`.
2. The *braces syntax*, `${ENV}`.
3. The *env provider syntax*, `${env:ENV}`.
These differ in the character set allowed for environment variable names
as well as the type of parsing they return. Escaping is supported in all
syntaxes by using two dollar signs.
### Type casting rules
A provider or converter takes a string and returns some sort of value
after potentially doing some parsing. This gets stored in a
`confmap.Conf`. When unmarshalling, we use [mapstructure](https://github.com/mitchellh/mapstructure) with
`WeaklyTypedInput` enabled, which does a lot of implicit casting. The
details of this type casting are complex and are outlined on issue
[#9532](https://github.com/open-telemetry/opentelemetry-collector/issues/9532).
When using this notation in inline mode (e.g.
`http://endpoint/${env:PATH}`) we also do manual implicit type
casting with a similar approach to mapstructure. These are outlined
[here](https://github.com/open-telemetry/opentelemetry-collector/blob/fc4c13d3c2822bec39fa9d9658836d1a020c6844/confmap/expand.go#L124-L139).
### Naked syntax
The naked syntax is supported via the expand converter. It is
implemented using the [`os.Expand`](https://pkg.go.dev/os#Expand) stdlib
function. This syntax supports identifiers made up of:
1. ASCII alphanumerics and the `_` character
2. Certain special characters if they appear alone typically used in
Bash: `*`, `#`, `$`, `@`, `!`, `?` and `-`.
You can see supported identifiers in this example:
[`go.dev/play/p/YfxLtYbsL6j`](https://go.dev/play/p/YfxLtYbsL6j).
The environment variable value is taken as-is and the type is always
string.
### Braces syntax
The braces syntax is supported via the expand converter. It is also
implemented using the os.Expand stdlib function. This syntax supports
any identifiers that don't contain `}`. Again, refer to the os.Expand
example to see how it works in practice:
[`go.dev/play/p/YfxLtYbsL6j`](https://go.dev/play/p/YfxLtYbsL6j).
The environment variable value is taken as-is and the type is always
string.
### `env` provider
The `env` provider syntax is supported via the `env`
provider. It is a custom implementation with a syntax that supports any
identifier that does not contain a `$`. This is done to support recursive
resolution (e.g. `${env:${http://example.com}}` would get the
environment variable whose name is stored in the URL
`http://example.com`).
The environment variable value is parsed by the yaml.v3 parser to an
any-typed variable. The yaml.v3 parser mostly follows the YAML v1.2
specification with [*some exceptions*](https://github.com/go-yaml/yaml#compatibility).
You can see how it works for some edge cases in
[this go.dev/play example](https://go.dev/play/p/3vNLznwSZQe).
### Issues of current behavior
#### Unintuitive behavior on unset environment variables
When an environment variable is empty, all syntaxes return an empty
string with no warning given; this is frequently unexpected but can also
be used intentionally. This is especially unintuitive when the user did
not expect expansion to happen. Three examples where this is unexpected
are the following:
1. **Opaque values such as passwords that contain `$`** (issue
[#8215](https://github.com/open-telemetry/opentelemetry-collector/issues/8215)).
If the $ is followed by an alphanumeric character or one of the
special characters, it's going to lead to false positives.
2. **Prometheus relabel config** (issue
[`contrib#9984`](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/9984)).
Prometheus uses `${1}` in some of its configuration values. We
resolve this to the value of the environment variable with name
'`1`'.
3. **Other uses of $** (issue
[`contrib#11846`](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/11846)).
If a product requires the use of `$` in some field, we would most
likely interpret it as an environment variable. This is not
intuitive for users.
#### Unexpected type casting
When using the env syntax we parse its value as YAML. Even if you are
familiar with YAML, because of the implicit type casting rules and the
way we store intermediate values, we can get unintuitive results.
The most clear example of this is issue
[*#8565*](https://github.com/open-telemetry/opentelemetry-collector/issues/8565):
When setting a variable to value `0123` and using it in a string-typed
field, it will end up as the string `"83"` (where as the user would
expect the string to be `0123`).
#### We are less restrictive than the Configuration WG
The Configuration WG defines an [*environment variable expansion feature
for SDK
configurations*](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/configuration/data-model.md#environment-variable-substitution).
This accepts only non empty alphanumeric + underscore identifiers
starting with alphabetic or underscore. If the Configuration WG were to
expand this in the future (e.g. to include other features present in
Bash-like syntax as in [opentelemetry-specification/pull/3948](https://github.com/open-telemetry/opentelemetry-specification/pull/3948)), we would not be able to expand our braces syntax to
support new features without breaking users.
## Desired behavior
*This section is written as if the changes were already implemented.*
The Collector supports **two** different syntaxes for environment
variable resolution:
1. The *braces syntax*, `${ENV}`.
2. The *env provider syntax*, `${env:ENV}`.
These both have **the same character set and behavior**. They both use
the env provider under the hood. This means we support the exact same
syntax as the Configuration WG.
The naked syntax supported in Bash is not supported in the Collector.
Escaping is supported by using two dollar signs. Escaping is also
honored for unsupported identifiers like `${1}` (i.e. anything that
matches `\${[^$}]+}`).
### Type casting rules
The environment variable value is parsed by the yaml.v3 parser to an
any-typed variable and the original representation as a string is also stored.
The `yaml.v3` parser mostly follows the YAML v1.2 specification with [*some
exceptions*](https://github.com/go-yaml/yaml#compatibility). You can see
how it works for some edge cases in this example:
[*https://go.dev/play/p/RtPmH8aZA1X*](https://go.dev/play/p/RtPmH8aZA1X).
When unmarshalling, we use mapstructure with WeaklyTypedInput
**disabled**. We check via a hook the original string representation of the data
and use its return value when it is valid and we are mapping to a string
field. This method has default casting rules for unambiguous scalar
types but may return the original representation depending on the
construction of confmap.Conf (see the comparison table below for details).
For using this notation in inline mode (e.g.`http://endpoint/${env:PATH}`), we
use the original string representation as well (see the comparison table below for details).
### Character set
An environment variable identifier must be a nonempty ASCII alphanumeric
or underscore starting with an alphabetic or underscore character. Its
maximum length is 200 characters. Both syntaxes support recursive
resolution.
When an invalid identifier is found, an error is emitted. To use an invalid
identifier, the string must be escaped.
### Comparison table with current behavior
This is a comparison between the current and desired behavior for
loading a field with the braces syntax, `env` syntax.
| Raw value | Field type | Current behavior, `${ENV}`, single field | Current behavior, `${env:ENV}` , single field | Desired behavior, entire field | Desired behavior, inline string field |
|--------------|------------|------------------------------------------|------------------------------------------------|--------------------------------|---------------------------------------|
| `123` | integer | 123 | 123 | 123 | n/a |
| `0123` | integer | 83 | 83 | 83 | n/a |
| `0123` | string | 0123 | 83 | 0123 | 0123 |
| `0xdeadbeef` | string | 0xdeadbeef | 3735928559 | 0xdeadbeef | 0xdeadbeef |
| `"0123"` | string | "0123" | 0123 | "0123" | "0123" |
| `!!str 0123` | string | !!str 0123 | 0123 | !!str 0123 | !!str 0123 |
| `t` | boolean | true | true | Error: mapping string to bool | n/a |
| `23` | boolean | true | true | Error: mapping integer to bool | n/a |
================================================
FILE: docs/rfcs/experimental-profiling.md
================================================
# Experimental support for profiling
## Overview
The OpenTelemetry collector has traces, metrics and logs as stable signals. We
want to start experimenting with support for profiles as an experimental
signal. But we also don't want to introduce breaking changes in packages
otherwise considered stable.
This document describes:
* The approach we intend to take to introduce profiling with no breaking changes
* How the migration will happen once profiling goes stable
### Discarded approaches
#### Refactor everything into per-signal subpackages
A first approach, discussed in [issue
10207](https://github.com/open-telemetry/opentelemetry-collector/issues/10207)
has been discarded.
It aimed to refactor the current packages with per-signal subpackages, so each
subpackage could have its own stability level, like pdata does.
This has been discarded, as the Collector SIG does not want the profiling
signal to impact the road to the collector reaching 1.0.
#### Use build tags
An approach would have been to use build tags to limit the availability of
profiles within packages.
This approach would make the UX very bad though, as most packages are meant to
be imported and not used in a compiled collector. It would therefore not have
been possible to specify the appropriate build tags.
This has been discarded, as the usage would have been too difficult.
## Proposed approach
The proposed approach will consist of two main phases:
* Introduce `experimental` packages for each required module of the collector that needs to be profiles-aware.
* `consumer`, `receiver`, `connector`, `component`, `processor`
* Mark specific APIs as `experimental` in their godoc for parts that can't be a new package.
* `service`
### Introduce "experimental" subpackages
Each package that needs to be profiling signal-aware will have its public
methods and interfaces moves into an internal subpackage.
Then, the original package will get similar API methods and interfaces as the
ones currently available on the main branch.
The profiling methods and interfaces will be made available in a `profiles`
subpackage.
See [PR
#10253](https://github.com/open-telemetry/opentelemetry-collector/pull/10253)
for an example.
### Mark specific APIs as `experimental`
In order to boot a functional collector with profiles support, some stable
packages need to be aware of the experimental ones.
To support that case, we will mark new APIs as `experimental` with go docs.
Every experimental API will be documented as such:
```golang
// # Experimental
//
// Notice: This method is EXPERIMENTAL and may be changed or removed in a
// later release.
```
As documented, APIs marked as experimental may changed or removed across
releases, without it being considered as a breaking change.
There are no symbols that would need to be marked as experimental today. If
there ever are then implementers may add an experimental comment to them
#### User specified configuration
The user-specified configuration will let users specify a `profiles` pipeline:
```
service:
pipelines:
profiles:
receivers: [otlp]
exporters: [otlp]
```
When an experimental signal is being used, the collector will log a warning at
boot.
## Signal status change
If the profiling signal becomes stable, all the experimental packages will be
merged back into their stable counterpart, and the `service` module's imports
will be updated.
If the profiling signal is removed, all the experimental packages will be
removed from the repository, and support for them will be removed in the
`service` module.
================================================
FILE: docs/rfcs/logging-before-config-resolution.md
================================================
# How To Log Before Config Resolution
## Overview
The OpenTelemetry Collector supports configuring a primary logger that the collector and its components use to write logs.
This logger cannot be created until the user's configuration has been completely resolved.
There is a need to write logs during the collector start-up, before the primary logger is instantiated, such as during
configuration resolution or config validation. This document describes
- why providing logging capabilities during startup is important
- the current (as of v0.99.0) behavior of the Collector
- different solutions to the problem
- the accepted solution
## Why Logging During Startup is Important
When the collector is starting it tries to resolve user configuration as quickly as possible.
But the Collector's configuration resolution strategy is not trivial - it allows for complex interactions between
multiple, different config sources that must all resolve without error. During this process important information could
be shared with users such as:
- [Warnings about deprecated syntax](https://github.com/open-telemetry/opentelemetry-collector/issues/9162)
- [Warnings about undesired, but handled, situations](https://github.com/open-telemetry/opentelemetry-collector/issues/5615)
- Debug information
## Requirements for any solution
1. Once the primary logger is instantiated, it should be usable anywhere in the Collector that does logging.
2. Log timestamps must be accurate to when the log was written, regardless of when the log is written to the user-specified location(s).
3. The EntryCaller (what line number the log originates from) must be accurate to where the log was written.
4. If an error causes the collector to gracefully terminate before the primary logger is created any previously written logs MUST be written to the either stout or stderr if they have not already been written there.
## Current behavior
As of v0.99.0, the collector does not provide a way to log before the primary logger is instantiated.
## Solutions
### Buffer Logs in Memory and Write Them Once the Primary Logger Exists
The Collector could provide a temporary logger that, when written to, keeps the logs in memory. These logs could then
be passed to the primary logger to be written out in the properly configured format/level.
Benefits:
- Logs are written in the user-specified format/level
Downsides:
- If the primary logger is used to write any logs before the buffered logs are passed, logs may be out of order. There are no guarantees that logs will be written in order, so the log timestamps should be taken as the source of truth for ordering.
### Create a Logger Using the Primary Logger's Defaults
When the user provides no primary logger configuration the Collector creates a Logger using a set of default values.
The Collector could, very early in startup, create a logger using these exact defaults and use it until the primary
logger is instantiated.
Benefits:
- Logs order is preserved
- Logs are still written when an error occurs before the primary logger can be instantiated
Downsides:
- Logs may be written in a format/level that differs from the format/level of the primary logger
## Accepted Solution
[Buffer Logs in Memory and Write Them Once the Primary Logger Exists](#buffer-logs-in-memory-and-write-them-once-the-primary-logger-exists)
This solution, while more complex, allows the collector to write out the logs in the user-specified format whenever possible. A fallback logger must be used in situations where the primary logger could not be created and the collector is shutting down, such as when encountering an error during configuration resolution, but otherwise the primary logger will be used to write logs that occurred before the primary logger existed.
================================================
FILE: docs/rfcs/metadata.yaml
================================================
type: rfcs
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: docs
codeowners:
active:
- codeboten
- bogdandrutu
- dmitryax
- mx-psi
================================================
FILE: docs/rfcs/optional-config-type.md
================================================
# Optional[T] type for use in configuration structs
## Overview
In Go, types are by default set to a "zero-value", a supported value for the
respective type that is semantically equivalent or similar to "empty", but which
is also a valid value for the type. For many config fields, the zero value is
not a valid configuration value and can be taken to mean that the option is
disabled, but in certain cases it can indicate a default value, necessitating a
way to represent the presence of a value without using a valid value for the
type.
Using standard Go without inventing any new types, the two most straightforward
ways to accomplish this are:
1. Using a separate boolean field to indicate whether the field is enabled or
disabled.
2. Making the type a pointer, which makes a `nil` pointer represent that a
value is not present, and a valid pointer represent the desired value.
Each of these approaches has deficiencies: Using a separate boolean field
requires the user to set the boolean field to `true` in addition to setting the
actual config option, leading to suboptimal UX. Using a pointer value has a few
drawbacks:
1. It may not be immediately obvious to a new user that a pointer type indicates
a field is optional.
2. The distinction between values that are conventionally pointers (e.g. gRPC
configs) and optional values is lost.
3. Setting a default value for a pointer field when decoding will set the field
on the resulting config struct, and additional logic must be done to unset
the default if the user has not specified a value.
4. The potential for null pointer exceptions is created.
5. Config structs are generally intended to be immutable and may be passed
around a lot, which makes the mutability property of pointer fields
an undesirable property.
## Optional types
Go does not include any form of Optional type in the standard library, but other
popular languages like Rust and Java do. We could implement something similar in
our config packages that allows us to address the downsides of using pointers
for optional config fields.
## Basic definition
A production-grade implementation will not be discussed or shown here, but the
basic implementation for an Optional type in Go could look something like:
```golang
type Optional[T any] struct {
hasValue bool
value T
defaultVal T
}
func Some[T any](value T) Optional[T] {
return Optional[T]{value: value, hasValue: true}
}
func None[T any]() Optional[T] {
return Optional[T]{}
}
func WithDefault[T any](defaultVal T) Optional[T] {
return Optional[T]{defaultVal: defaultVal}
}
func (o Optional[T]) HasValue() bool {
return o.hasValue
}
func (o Optional[T]) Value() T {
return o.value
}
func (o Optional[T]) Default() T {
return o.defaultVal
}
```
## Use cases
Optional types can fulfill the following use cases we have when decoding config.
### Representing optional config fields
To use the optional type to mark a config field as optional, the type can simply be used as a type
parameter to `Optional[T]`. The YAML representation of `Optional[T]` is the same as that of `T`,
except that the type records whether the value is present. The following config struct shows how
this may look, both in definition and in usage:
```golang
type Protocols struct {
GRPC Optional[configgrpc.ServerConfig] `mapstructure:"grpc"`
HTTP Optional[HTTPConfig] `mapstructure:"http"`
}
func (cfg *Config) Validate() error {
if !cfg.GRPC.HasValue() && !cfg.HTTP.HasValue() {
return errors.New("must specify at least one protocol when using the OTLP receiver")
}
return nil
}
func createDefaultConfig() component.Config {
return &Config{
Protocols: Protocols{
GRPC: WithDefault(configgrpc.ServerConfig{
// ...
}),
HTTP: WithDefault(HTTPConfig{
// ...
}),
},
}
}
```
For something like `confighttp.ServerConfig`, using an `Optional[T]` type for
optional fields would look like this:
```golang
type ServerConfig struct {
TLSSetting Optional[configtls.ServerConfig] `mapstructure:"tls"`
CORS Optional[CORSConfig] `mapstructure:"cors"`
Auth Optional[AuthConfig] `mapstructure:"auth,omitempty"`
ResponseHeaders Optional[map[string]configopaque.String] `mapstructure:"response_headers"`
}
func NewDefaultServerConfig() ServerConfig {
return ServerConfig{
TLSSetting: WithDefault(configtls.NewDefaultServerConfig()),
CORS: WithDefault(NewDefaultCORSConfig()),
WriteTimeout: 30 * time.Second,
ReadHeaderTimeout: 1 * time.Minute,
IdleTimeout: 1 * time.Minute,
}
}
func (sc *ServerConfig) ToListener(ctx context.Context) (net.Listener, error) {
listener, err := net.Listen("tcp", sc.Endpoint)
if err != nil {
return nil, err
}
if sc.TLSSetting.HasValue() {
var tlsCfg *tls.Config
tlsCfg, err = sc.TLSSetting.Value().LoadTLSConfig(ctx)
if err != nil {
return nil, err
}
tlsCfg.NextProtos = []string{http2.NextProtoTLS, "http/1.1"}
listener = tls.NewListener(listener, tlsCfg)
}
return listener, nil
}
func (sc *ServerConfig) ToServer(_ context.Context, host component.Host, settings component.TelemetrySettings, handler http.Handler, opts ...ToServerOption) (*http.Server, error) {
// ...
handler = httpContentDecompressor(
handler,
sc.MaxRequestBodySize,
serverOpts.ErrHandler,
sc.CompressionAlgorithms,
serverOpts.Decoders,
)
// ...
if sc.Auth.HasValue() {
server, err := sc.Auth.Value().GetServerAuthenticator(context.Background(), host.GetExtensions())
if err != nil {
return nil, err
}
handler = authInterceptor(handler, server, sc.Auth.Value().RequestParameters)
}
corsValue := sc.CORS.Value()
if sc.CORS.HasValue() && len(sc.CORS.AllowedOrigins) > 0 {
co := cors.Options{
AllowedOrigins: corsValue.AllowedOrigins,
AllowCredentials: true,
AllowedHeaders: corsValue.AllowedHeaders,
MaxAge: corsValue.MaxAge,
}
handler = cors.New(co).Handler(handler)
}
if sc.CORS.HasValue() && len(sc.CORS.AllowedOrigins) == 0 && len(sc.CORS.AllowedHeaders) > 0 {
settings.Logger.Warn("The CORS configuration specifies allowed headers but no allowed origins, and is therefore ignored.")
}
if sc.ResponseHeaders.HasValue() {
handler = responseHeadersHandler(handler, sc.ResponseHeaders.Value())
}
// ...
}
```
### Proper unmarshaling of empty values when a default is set
Currently, the OTLP receiver requires a workaround to make enabling each
protocol optional while providing defaults if a key for the protocol has been
set:
```golang
type Protocols struct {
GRPC *configgrpc.ServerConfig `mapstructure:"grpc"`
HTTP *HTTPConfig `mapstructure:"http"`
}
// Config defines configuration for OTLP receiver.
type Config struct {
// Protocols is the configuration for the supported protocols, currently gRPC and HTTP (Proto and JSON).
Protocols `mapstructure:"protocols"`
}
func createDefaultConfig() component.Config {
return &Config{
Protocols: Protocols{
GRPC: configgrpc.NewDefaultServerConfig(),
HTTP: &HTTPConfig{
// ...
},
},
}
}
func (cfg *Config) Unmarshal(conf *confmap.Conf) error {
// first load the config normally
err := conf.Unmarshal(cfg)
if err != nil {
return err
}
// gRPC will be enabled if this line is not run
if !conf.IsSet(protoGRPC) {
cfg.GRPC = nil
}
// HTTP will be enabled if this line is not run
if !conf.IsSet(protoHTTP) {
cfg.HTTP = nil
}
return nil
}
```
With an Optional type, the checks in `Unmarshal` become unnecessary, and it's
possible the entire `Unmarshal` function may no longer be needed. Instead, when
the config is unmarshaled, no value would be put into the default Optional
values and `HasValue` would return false when using this config object.
This situation is something of an edge case with our current unmarshaling
facilities and while not common, could be a rough edge for users looking to
implement similar config structures.
## Disadvantages of an Optional type
There is one noteworthy disadvantage of introducing an Optional type:
1. Since the type isn't standard, external packages working with config may
require additional adaptations to work with our config structs. For example,
if we wanted to generate our types from a JSON schema using a package like
[github.com/atombender/go-jsonschema][go-jsonschema], we would need some way
to ensure compatibility with an Optional type.
[go-jsonschema]: https://github.com/omissis/go-jsonschema
================================================
FILE: docs/rfcs/processing.md
================================================
# OpenTelemetry Collector Processor Exploration
**Status:** *Draft*
## Objective
To describe a user experience and strategies for configuring processors in the OpenTelemetry collector.
This work is being prototyped in opentelemetry-collector-contrib, the design doc is here for broader discussion.
## Summary
The OpenTelemetry (OTel) collector is a tool to set up pipelines to receive telemetry from an application and export it
to an observability backend. Part of the pipeline can include processing stages, which executes various business logic
on incoming telemetry before it is exported.
Over time, the collector has added various processors to satisfy different use cases, generally in an ad-hoc way to
support each feature independently. We can improve the experience for users of the collector by consolidating processing
patterns in terms of user experience, and this can be supported by defining a querying model for processors
within the collector core, and likely also for use in SDKs, to simplify implementation and promote the consistent user
experience and best practices.
## Goals and non-goals
Goals:
- List out use cases for processing within the collector
- Consider what could be an ideal configuration experience for users
Non-Goals:
- Merge every processor into one. Many use cases overlap and generalize, but not all of them
- Technical design or implementation of configuration experience. Currently focused on user experience.
## Use cases for processing
### Telemetry mutation
Processors can be used to mutate the telemetry in the collector pipeline. OpenTelemetry SDKs collect detailed telemetry
from applications, and it is common to have to mutate this into a way that is appropriate for an individual use case.
Some types of mutation include
- Remove a forbidden attribute such as `http.request.header.authorization`
- Reduce cardinality of an attribute such as translating `http.target` value of `/user/123451/profile` to `/user/{userId}/profile`
- Decrease the size of the telemetry payload by removing large resource attributes such as `process.command_line`
- Filtering out signals such as by removing all telemetry with a `http.target` of `/health`
- Attach information from resource into telemetry, for example adding certain resource fields as metric dimensions
The processors implementing this use case are `attributesprocessor`, `filterprocessor`, `metricstransformprocessor`,
`resourceprocessor`, `spanprocessor`.
### Metric generation
The collector may generate new metrics based on incoming telemetry. This can be for covering gaps in SDK coverage of
metrics vs spans, or to create new metrics based on existing ones to model the data better for backend-specific
expectations.
- Create new metrics based on information in spans, for example to create a duration metric that is not implemented in the SDK yet
- Apply arithmetic between multiple incoming metrics to produce an output one, for example divide an `amount` and a `capacity` to create a `utilization` metric
The components implementing this use case are `metricsgenerationprocessor` and the former `spanmetricsprocessor` (now `spanmetricsconnector`).
### Grouping
Some processors are stateful, grouping telemetry over a window of time based on either a trace ID or an attribute value,
or just general batching.
- Batch incoming telemetry before sending to exporters to reduce export requests
- Group spans by trace ID to allow doing tail sampling
- Group telemetry for the same path
The processors implementing this use case are `batchprocessor`, `groupbyattrprocessor`, `groupbytraceprocessor`.
### Metric temporality
Two processors convert between the two types of temporality, cumulative and delta. The conversion is generally expected
to happen as close to the source data as possible, for example within receivers themselves. The same configuration
mechanism could be used for selecting metrics for temporality conversion as other cases, but it is expected that in
practice configuration will be limited.
The processors implementing this use case are `cumulativetodeltaprocessor`.
### Telemetry enrichment
OpenTelemetry SDKs focus on collecting application specific data. They also may include resource detectors to populate
environment specific data but the collector is commonly used to fill gaps in coverage of environment specific data.
- Add environment about a cloud provider to `Resource` of all incoming telemetry
The processors implementing this use case are `k8sattributesprocessor`, `resourcedetectionprocessor`.
## OpenTelemetry Transformation Language
When looking at the use cases, there are certain common features for telemetry mutation and metric generation.
- Identify the type of signal (`span`, `metric`, `log`).
- Navigate to a path within the telemetry to operate on it
- Define an operation, and possibly operation arguments
We can try to model these into a transformation language, in particular allowing the first two points to be shared among all
processing operations, and only have implementation of individual types of processing need to implement operators that
the user can use within an expression.
Telemetry is modeled in the collector as [`pdata`](https://github.com/open-telemetry/opentelemetry-collector/tree/main/pdata)
which is roughly a 1:1 mapping of the [OTLP protocol](https://github.com/open-telemetry/opentelemetry-proto/tree/main/opentelemetry/proto).
This data can be navigated using field expressions, which are fields within the protocol separated by dots. For example,
the status message of a span is `status.message`. A map lookup can include the key as a string, for example `attributes["http.status_code"]`.
Operations are scoped to the type of a signal (`span`, `metric`, `log`), with all of the flattened points of that
signal being part of a transformation space. Virtual fields are added to access data from a higher level before flattening, for
`resource`, `library_info`. For metrics, the structure presented for processing is actual data points, e.g. `NumberDataPoint`,
`HistogramDataPoint`, with the information from higher levels like `Metric` or the data type available as virtual fields.
Virtual fields for all signals: `resource`, `library_info`.
Virtual fields for metrics: `metric`, which contains `name`, `description`, `unit`, `type`, `aggregation_temporality`, and `is_monotonic`.
Navigation can then be used with a simple expression language for identifying telemetry to operate on.
```
... where name = "GET /cats"
```
```
... from span where attributes["http.target"] = "/health"
```
```
... where resource.attributes["deployment"] = "canary"
```
```
... from metric where metric.type = gauge
```
```
... from metric where metric.name = "http.active_requests"
```
Fields should always be fully specified - for example `attributes` refers to the `attributes` field in the telemetry, not
the `resource`. In the future, we may allow shorthand for accessing scoped information that is not ambiguous.
Having selected telemetry to operate on, any needed operations can be defined as functions. Known useful functions should
be implemented within the collector itself, provide registration from extension modules to allow customization with
contrib components, and in the future can even allow user plugins possibly through WASM, similar to work in
[HTTP proxies](https://github.com/proxy-wasm/spec). The arguments to operations will primarily be field expressions,
allowing the operation to mutate telemetry as needed.
There are times when the transformation language input and the underlying telemetry model do not translate cleanly. For example, a span ID is represented in pdata as a SpanID struct, but in the transformation language it is more natural to represent the span ID as a string or a byte array. The solution to this problem is Factories. Factories are functions that help translate between the transformation language input into the underlying pdata structure. These types of functions do not change the telemetry in any way. Instead, they manipulate the transformation language input into a form that will make working with the telemetry easier or more efficient.
### Examples
These examples contain a SQL-like declarative language. Applied statements interact with only one signal, but statements can be declared across multiple signals.
Remove a forbidden attribute such as `http.request.header.authorization` from spans only
```
traces:
delete(attributes["http.request.header.authorization"])
metrics:
delete(attributes["http.request.header.authorization"])
logs:
delete(attributes["http.request.header.authorization"])
```
Remove all attributes except for some
```
traces:
keep_keys(attributes, "http.method", "http.status_code")
metrics:
keep_keys(attributes, "http.method", "http.status_code")
logs:
keep_keys(attributes, "http.method", "http.status_code")
```
Reduce cardinality of an attribute
```
traces:
replace_match(attributes["http.target"], "/user/*/list/*", "/user/{userId}/list/{listId}")
```
Reduce cardinality of a span name
```
traces:
replace_match(name, "GET /user/*/list/*", "GET /user/{userId}/list/{listId}")
```
Reduce cardinality of any matching attribute
```
traces:
replace_all_matches(attributes, "/user/*/list/*", "/user/{userId}/list/{listId}")
```
Decrease the size of the telemetry payload by removing large resource attributes
```
traces:
delete(resource.attributes["process.command_line"])
metrics:
delete(resource.attributes["process.command_line"])
logs:
delete(resource.attributes["process.command_line"])
```
Filtering out signals such as by removing all metrics with a `http.target` of `/health`
```
metrics:
drop() where attributes["http.target"] = "/health"
```
Attach information from resource into telemetry, for example adding certain resource fields as metric attributes
```
metrics:
set(attributes["k8s_pod"], resource.attributes["k8s.pod.name"])
```
Group spans by trace ID
```
traces:
group_by(trace_id, 2m)
```
Update a spans ID
```
logs:
set(span_id, SpanID(0x0000000000000000))
traces:
set(span_id, SpanID(0x0000000000000000))
```
Create utilization metric from base metrics. Because navigation expressions only operate on a single piece of telemetry,
helper functions for reading values from other metrics need to be provided.
```
metrics:
create_gauge("pod.cpu.utilized", read_gauge("pod.cpu.usage") / read_gauge("node.cpu.limit")
```
A lot of processing. Queries are executed in order. While initially performance may degrade compared to more specialized
processors, the expectation is that over time, the transform processor's engine would improve to be able to apply optimizations
across queries, compile into machine code, etc.
```yaml
receivers:
otlp:
exporters:
otlp_grpc:
processors:
transform:
# Assuming group_by is defined in a contrib extension module, not baked into the "transform" processor
extensions: [group_by]
traces:
queries:
- drop() where attributes["http.target"] = "/health"
- delete(attributes["http.request.header.authorization"])
- replace_wildcards("/user/*/list/*", "/user/{userId}/list/{listId}", attributes["http.target"])
- group_by(trace_id, 2m)
metrics:
queries:
- drop() where attributes["http.target"] = "/health"
- delete(attributes["http.request.header.authorization"])
- replace_wildcards("/user/*/list/*", "/user/{userId}/list/{listId}", attributes["http.target"])
- set(attributes["k8s_pod"], resource.attributes["k8s.pod.name"])
logs:
queries:
- drop() where attributes["http.target"] = "/health"
- delete(attributes["http.request.header.authorization"])
- replace_wildcards("/user/*/list/*", "/user/{userId}/list/{listId}", attributes["http.target"])
pipelines:
- receivers: [otlp]
exporters: [otlp]
processors: [transform]
```
The expressions would be executed in order, with each expression either mutating an input telemetry, dropping input
telemetry, or adding additional telemetry. One caveat to note is that we would like to implement optimizations
in the transform engine, for example to only apply filtering once for multiple operations with a shared filter. Functions
with unknown side effects may cause issues with optimization we will need to explore.
## Declarative configuration
The telemetry transformation language presents an SQL-like experience for defining telemetry transformations - it is made up of
the three primary components described above, however, and can be presented declaratively instead depending on what makes
sense as a user experience.
```yaml
- type: span
filter:
match:
path: status.code
value: OK
operation:
name: drop
- type: all
operation:
name: delete
args:
- attributes["http.request.header.authorization"]
```
An implementation of the transformation language would likely parse expressions into this sort of structure so given an SQL-like
implementation, it would likely be little overhead to support a YAML approach in addition.
## Function syntax
Functions should be named and formatted according to the following standards.
- Function names MUST start with a verb unless it is a Factory.
- Factory functions MUST be UpperCamelCase and named based on the object being created.
- Function names that contain multiple words MUST separate those words with `_`.
- Functions that interact with multiple items MUST have plurality in the name. Ex: `truncate_all`, `keep_keys`, `replace_all_matches`.
- Functions that interact with a single item MUST NOT have plurality in the name. If a function would interact with multiple items due to a condition, like `where`, it is still considered singular. Ex: `set`, `delete`, `drop`, `replace_match`.
- Functions that change a specific target MUST set the target as the first parameter.
- Functions that take a list MUST set the list as the last parameter.
## Implementing a processor function
The `replace_match` function may look like this.
```go
package replaceMatch
import "regexp"
import "github.com/open-telemetry/opentelemetry/processors"
// Assuming this is not in "core"
processors.register("replace_match", replace_match)
func replace_match(path processors.TelemetryPath, pattern regexp.Regexp, replacement string) processors.Result {
val := path.Get()
if val == nil {
return processors.CONTINUE
}
// replace finds placeholders in "replacement" and swaps them in for regex matched substrings.
replaced := replace(val, pattern, replacement)
path.Set(replaced)
return processors.CONTINUE
}
```
Here, the processor framework recognizes the second parameter of the function is `regexp.Regexp` so will compile the string
provided by the user in the config when processing it. Similarly for `path`, it recognizes properties of type `TelemetryPath`
and will resolve it to the path within a matched telemetry during execution and pass it to the function. The path allows
scalar operations on the field within the telemetry. The processor does not need to be aware of telemetry filtering,
the `where ...` clause, as that will be handled by the framework before passing to the function.
## Embedded processors
The above describes a transformation language for configuring processing logic in the OpenTelemetry collector. There will be a
single processor that exposes the processing logic into the collector config; however, the logic will be implemented
within core packages rather than directly inside a processor. This is to ensure that where appropriate, processing
can be embedded into other components, for example metric processing is often most appropriate to execute within a
receiver based on receiver-specific requirements.
## Limitations
There are some known issues and limitations that we hope to address while iterating on this idea.
- Handling array-typed attributes
- Working on a array of points, rather than a single point
- Metric alignment - for example defining an expression on two metrics, that may not be at the same timestamp
- The collector has separate pipelines per signal - while the transformation language could apply cross-signal, we will need to remain single-signal for now
================================================
FILE: docs/rfcs/release-approvers.md
================================================
# OpenTelemetry Collector Releases approvers
## Problem statement
Release engineering requires a different set of skills and interests than developing the Collector
codebase. As such, the set of contributors for the Collector releases has overlap with but is
different from the set of contributors for the Collector codebase. We are missing out on retaining
people who are more interested in these aspects. We can see this with the examples of the
OpenTelemetry Operator repository and the OpenTelemetry Collector Helm Chart repository; they are
able to work independently from the other Collector repositories and have been able to create an
independent community.
We also have a [growing backlog][1] of issues related to the release process that would benefit from
a dedicated set of people.
## Overview
I propose we create a new `collector-releases-approvers` Github team that are [approvers][2] on the
[opentelemetry-collector-releases][3] repository and code owners for the Builder and release
workflows. The existing approvers teams will focus on the Collector and Collector components
codebases. This opens future possibilities for creating a separate WG/SIG for this and further
improving our release process.
## Team scope and responsibilities
The `collector-releases-approvers` team will be [approvers][2] for the
opentelemetry-collector-releases repository. They will also be listed as [code owners][4] for the
release workflows on the opentelemetry-collector and opentelemetry-collector-contrib repositories.
The new team will not acquire any responsibilities related to the release; there will be no changes
in the release rotation or release duties after this change.
## Initial team members
Initially, all members of the `collector-contrib-approvers` team will be part of the
`collector-releases-approvers` team. The Collector maintainers will reach out to existing
contributors to the Collector releases to invite them to join the team.
## Prior art
This introduces an approver group without a maintainer. It also introduces this team as a code owner for files in other repositories. There are prior instances of both of these patterns within the OpenTelemetry project:
1. There are currently two SIGs that have approver teams without corresponding maintainer teams:
- The Docs SIG has multiple localization approver teams that have approver duties for the translations of the OpenTelemetry documentation.
- The Semantic Conventions SIG has multiple approver teams corresponding to different Working Groups and/or semantic conventions areas.
2. There are multiple instances of teams being code owners:
- [opentelemetry-collector approvers are code owners for `examples/demo` in `opentelemetry-collector-contrib`][5]
- [Helm chart and Operator approvers are code owners for the k8s distro in `opentelemetry-collector-releases`][6]
- [Go instrumentation approvers are code owners of instrgen in `opentelemetry-go-contrib`][7]
## Future work
If we are able to grow a healthy community around the releases repository, in the future we can
consider the following:
- Having a dedicated SIG/WG for the opentelemetry-collector-releases repository.
- Making the `collector-releases-approvers` code owners of the OpenTelemetry Collector Builder.
- Having dedicated meetings for release retros that allow us to iteratively improve the Collector
release process.
- Splitting off the artifact and container release process to be independent from the source code
release from opentelemetry-collector and opentelemetry-collector-contrib.
This proposal does not require us to do any of the above, but they are interesting possibilities for
the future.
[1]: https://github.com/search?q=org%3Aopen-telemetry+label%3Arelease-retro++&type=issues&state=open
[2]: https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#approver
[3]: https://github.com/open-telemetry/opentelemetry-collector-releases
[4]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/CONTRIBUTING.md#membership-roles-and-responsibilities
[5]: https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/40fa8b8a925cadf569e785cbc85d6dfca152bde2/.github/CODEOWNERS#L40
[6]: https://github.com/open-telemetry/opentelemetry-collector-releases/blob/3ba7931410d1696d9df7bef424b634a5d64cffbd/.github/CODEOWNERS#L17
[7]: https://github.com/open-telemetry/opentelemetry-go-contrib/blob/c0cc77f10a2dae774161d6a03441b12c0e8b0816/CODEOWNERS#L73
================================================
FILE: docs/rfcs/semconv-feature-gates.md
================================================
# Semantic conventions migrations in the Collector
## Overview
The OpenTelemetry Collector components emit telemetry that often conforms to semantic conventions.
Semantic conventions have [varying levels of stability][1] and often have an SDK-focused migration
guide.
This RFC defines how migration should be handled in Collector components that have
semantic conventions that migrate to a stable version, in a Collector-native way.
## Scope and goals
This RFC provides general guidelines for semantic convention-mandated migrations of telemetry created by Collector components (usually receivers) and output into the Collector's pipeline. It explicitly does not attempt to cover:
- telemetry created by an application and forwarded by a Collector receiver;
- internal telemetry of Collector components;
- guidelines for the migration of specific semantic conventions.
The migration mechanism should have the following characteristics:
1. **Collector native**: the mechanism should work in a similar way to other Collector migrations
and should feel natural and intuitive to users.
2. **Simple**: a user should have to make a small number of changes to their Collector deployment to
migrate to a new set of conventions.
3. **Easy to understand**: It should be easy to understand how to migrate a particular set of
conventions.
5. **Flexible (double publish)**: The mechanism should allow you to 'double publish' v0 and v1
conventions
6. **Flexible (other conventions)**: The mechanism should still allow for evolution of other
semantic conventions that are not being migrated.
## Background
### Setup
We want to write guidance for when we have a component that emits telemetry from a common
`area` that is undergoing a migration mandated by the Semantic Conventions SIG. In the rest of this
document we refer to the **v0** conventions and the **v1** conventions, which are the conventions
in this area before and after the migration.
When the semantic conventions are specific to a component we use
- `kind` to refer to the component kind (receiver, exporter...)
- `id` for the component id (e.g. `hostmetrics`)
### What does the semconv spec say?
The semantic conventions specification defines an environment variable named
`OTEL_SEMCONV_STABILITY_OPT_IN` that, for each area, takes two possible values:
1. One value representing the new semantic conventions (e.g. `http`, `gen_ai_latest_experimental`)
2. Once mature enough, a second value ending in `/dup` that emits both the old conventions and the
new ones.
This is not specified in a generic way, but it is a consistent pattern across all semantic
conventions areas that are being actively worked on:
Example 1: HTTP compatibility warning
Taken from [semconv v1.38.0][2]:
> **Warning**
> Existing HTTP instrumentations that are using
> [v1.20.0 of this document](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/trace/semantic_conventions/http.md)
> (or prior):
>
> * SHOULD NOT change the version of the HTTP or networking conventions that they emit
> until the HTTP semantic conventions are marked stable (HTTP stabilization will
> include stabilization of a core set of networking conventions which are also used
> in HTTP instrumentations). Conventions include, but are not limited to, attributes,
> metric and span names, and unit of measure.
> * SHOULD introduce an environment variable `OTEL_SEMCONV_STABILITY_OPT_IN`
> in the existing major version which is a comma-separated list of values.
> The only values defined so far are:
> * `http` - emit the new, stable HTTP and networking conventions,
> and stop emitting the old experimental HTTP and networking conventions
> that the instrumentation emitted previously.
> * `http/dup` - emit both the old and the stable HTTP and networking conventions,
> allowing for a seamless transition.
> * The default behavior (in the absence of one of these values) is to continue
> emitting whatever version of the old experimental HTTP and networking conventions
> the instrumentation was emitting previously.
> * Note: `http/dup` has higher precedence than `http` in case both values are present
> * SHOULD maintain (security patching at a minimum) the existing major version
> for at least six months after it starts emitting both sets of conventions.
> * SHOULD drop the environment variable in the next major version (stable
> next major version SHOULD NOT be released prior to October 1, 2023).
Example 2: GenAI compatibility warning
From [semconv v1.38.0][3]:
> [!Warning]
>
> Existing GenAI instrumentations that are using
> [v1.36.0 of this document](https://github.com/open-telemetry/semantic-conventions/blob/v1.36.0/docs/gen-ai/README.md)
> (or prior):
>
> * SHOULD NOT change the version of the GenAI conventions that they emit by default.
> Conventions include, but are not limited to, attributes, metric, span and event names,
> span kind and unit of measure.
> * SHOULD introduce an environment variable `OTEL_SEMCONV_STABILITY_OPT_IN`
> as a comma-separated list of category-specific values. The list of values
> includes:
> * `gen_ai_latest_experimental` - emit the latest experimental version of
> GenAI conventions (supported by the instrumentation) and do not emit the
> old one (v1.36.0 or prior).
> * The default behavior is to continue emitting whatever version of the GenAI
> conventions the instrumentation was emitting (1.36.0 or prior).
>
> This transition plan will be updated to include stable version before the
> GenAI conventions are marked as stable.
Example 3: K8s compatibility warning
> From [semconv v1.38.0][3]:
> When existing K8s instrumentations published by OpenTelemetry are
> updated to the stable K8s semantic conventions, they:
>
> - SHOULD introduce an environment variable `OTEL_SEMCONV_STABILITY_OPT_IN` in
> their existing major version, which accepts:
> - `k8s` - emit the stable k8s conventions, and stop emitting
> the old k8s conventions that the instrumentation emitted previously.
> - `k8s/dup` - emit both the old and the stable k8s conventions,
> allowing for a phased rollout of the stable semantic conventions.
> - The default behavior (in the absence of one of these values) is to continue
> emitting whatever version of the old k8s conventions the
> instrumentation was emitting previously.
> - Need to maintain (security patching at a minimum) their existing major version
> for at least six months after it starts emitting both sets of conventions.
> - May drop the environment variable in their next major version and emit only
> the stable k8s conventions.
> Specifically for the Opentelemetry Collector:
> The transition will happen through two different feature gates.
> One for enabling the new schema called `semconv.k8s.enableStable`,
> and one for disabling the old schema called `semconv.k8s.disableLegacy`. Then:
> - On alpha the old schema is enabled by default (`semconv.k8s.disableLegacy` defaults to false),
> while the new schema is disabled by default (`semconv.k8s.enableStable` defaults to false).
> - On beta/stable the old schema is disabled by default (`semconv.k8s.disableLegacy` defaults to true),
> while the new is enabled by default (`semconv.k8s.enableStable` defaults to true).
> - It is an error to disable both schemas
> - Both schemas can be enabled with `--feature-gates=-semconv.k8s.disableLegacy,+semconv.k8s.enableStable`.
## Proposed mechanism
Suppose the `` (e.g. `hostmetrics`) `kind` (e.g. `receiver`) component is migrating from v0 to
v1 semantic conventions on the area `area` (e.g. `process`). The semantic conventions specification
defines the set of conventions that are in scope for a particular migration.
To support this migration, the component defines two feature gates: `..EmitV1Conventions` (e.g.
`receiver.hostmetrics.EmitV1ProcessConventions`) and `..DontEmitV0Conventions`
(e.g. `receiver.hostmetrics.DontEmitV0ProcessConventions`). These feature gates work as follows:
| `..EmitV1Conventions` status | `..DontEmitV0Conventions` status | Resulting behavior |
|-----------------------------------------------|-------------------------------------------------------|-----------------------------------------------------------|
| Disabled | Disabled | Emit telemetry under the 'v0' conventions |
| Disabled | Enabled | Error at startup since this would not emit any telemetry |
| Enabled | Disabled | Emit telemetry under both the v0 and the v1 conventions |
| Enabled | Enabled | Emit telemetry under the v1 conventions |
Both feature gates evolve at the same pace through the feature gate stages, so that the progression
is as follows:
1. Initially both are at **alpha** stage (disabled by default). This means that the default behavior
is to emit only the 'v0' conventions. Users can opt-in to emit the v1 conventions alongside the
v0 conventions or to emit only the v1 conventions. A warning message must be logged by the component at startup indicating the upcoming change.
2. Whenever there is a semantic conventions release that marks these as stable, the feature gates are promoted to the
**beta** stage on the same Collector release. The new default behavior is therefore to emit only the
'v1' conventions. Users can opt-out to emit the v1 conventions alongside the v0 conventions or
to emit only the v0 conventions.
3. After 4 minor releases, the feature gates are promoted to the **stable** stage. At this point users
can only use the v1 conventions.
4. After additional 4 minor releases, the feature gates are removed.
This mechanism does not cover any sort of transition for experimental semantic conventions. These
presumably would be covered by separate feature gates or some other mechanism.
## Handling conflicts during double-publishing
During the double-publishing phase (when both `..EmitV1Conventions` is enabled and `..DontEmitV0Conventions` is disabled), components typically emit both v0 and v1 telemetry. For metrics, this usually means emitting two separate metrics with different names (e.g., `http.server.duration` for v0 and `http.server.request.duration` for v1).
However, in some cases v0 and v1 conventions may use the same metric name but with different characteristics (e.g., different attributes or metric types). Two metrics with identical names must not be emitted, as this would produce an invalid OpenTelemetry dataset and cause issues on backends. Below are examples illustrating how such conflicts can be resolved:
**Different attributes:** If a metric name stays the same but an attribute is renamed, emit a single metric with both the v0 and v1 attributes present. For instance, if `process.cpu.time` uses `process.owner` in v0 and `process.owner.name` in v1, emit one metric with both attributes.
**Different metric type:** If a metric name stays the same but the type changes (e.g., Gauge to UpDownCounter), emit a single metric with the v1 type, effectively prioritizing the new convention. For instance, if `system.memory.usage` changes from Gauge to UpDownCounter, emit it as an UpDownCounter.
## Alternative mechanisms
There are some other possibilities:
### Environment variable
We could just use the `OTEL_SEMCONV_STABILITY_OPT_IN` mechanism. However, this does not feel
"Collector native": Collector users expect experimental features to be controlled via feature gates
and as such this could be a surprising mechanism. In particular, users would expect that they are
able to 'roll back' to the previous behavior even after a Collector upgrade, something that the
environment variable mechanism explicitly does not support.
### More granular feature gate pairs
The granularity of the feature gates described could be changed: we could have a pair per convention
or even a pair for the whole Collector. I argue 'per component' strikes the right balance between
simplicity and flexibility:
- per convention would lead to dozens of feature gates on some of the areas we want to stabilize. It
would also be unclear how these interact on edge cases (semantic conventions may only make sense
holistically)
- a single pair of feature gates would effectively be forever unstable and would not be flexible
enough to allow people to migrate on a per dashboard basis
### Meta feature gate
We could have both a feature gate pair per component and a meta target feature gate pair that allows
you to enable/disable all v1 conventions at the same time. This is effectively a superset of the
proposed mechanism, so I argue we can postpone this for later: if users ask for it, we can always
add it in the future.
## Open questions and future possibilities
This document does not cover how to deal with experimental semantic conventions after the 'big'
migration has been completed in one particular area. What to do here in part depends on the
[stabilization changes][4]. Quoting the blogpost:
> Instrumentation stability should be decoupled from semantic convention stability. We have a lot of
> stable instrumentation that is safe to run in production, but has data that may change in the
> future. Users have told us that conflating these two levels of stability is confusing and limits
> their options.
How to deal with these remains an open question that should be tackled in OTEPs first.
As mentioned above, the 'Meta feature gate' remains a possibility even when adopting this mechanism.
[1]: https://opentelemetry.io/docs/specs/semconv/general/semantic-convention-groups/#group-stability
[2]: https://github.com/open-telemetry/semantic-conventions/blob/v1.38.0/docs/http/README.md
[3]: https://github.com/open-telemetry/semantic-conventions/blob/v1.38.0/docs/gen-ai/README.md
[4]: https://opentelemetry.io/blog/2025/stability-proposal-announcement/
================================================
FILE: docs/scraping-receivers.md
================================================
# Scraping Metrics Receivers
Scraping metrics receivers are receivers that pull data from external sources at regular intervals and translate it
into [pdata](../pdata/README.md) which is sent further in the pipeline. The external source of metrics usually is a
monitored system providing data about itself in some arbitrary format. There are two types of scraping metrics
receivers:
- **Generic scraping metrics receivers:** The set of metrics emitted by this type of receiver fully depends on the
state of the external source and/or the user settings. Examples:
- [Prometheus Receiver](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/prometheusreceiver)
- [SQL Query Receiver](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/sqlqueryreceiver)
- **Built-in scraping metrics receivers:** Receivers of this type emit a predefined set of metrics. However, the
metrics themselves are configurable via user settings. Examples of scrapings metrics receivers:
- [Redis Receiver](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/redisreceiver)
- [Zookeeper Receiver](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/zookeeperreceiver)
This document covers built-in scraping metrics receivers. It defines which metrics these receivers can emit,
defines stability guarantees and provides guidelines for metric updates.
## Defining emitted metrics
Each built-in scraping metrics receiver has a `metadata.yaml` file that MUST define all the metrics emitted by the
receiver. The file is being used to generate an API for metrics recording, user settings to customize the emitted
metrics and user documentation. The file schema is defined in
https://github.com/open-telemetry/opentelemetry-collector/blob/main/cmd/mdatagen/metadata-schema.yaml
Defining a metric in `metadata.yaml` DOES NOT guarantee that the metric will always be produced by the receiver. In
some cases it may be impossible to fetch particular metrics from a system in a particular state.
There are two categories of the metrics emitted by scraping receivers:
- **Default metrics**: emitted by default, but can be disabled in user settings.
- **Optional metrics**: not emitted by default, but can be enabled in user settings.
See also [Semantic Convention compatibility](./coding-guidelines.md#semantic-conventions-compatibility) guidance.
### How to identify if new metric should be default or optional?
There is no strict rule to differentiate default metrics from optional. As a rule of thumb, default metrics SHOULD be
treated as metrics essential to determine healthiness of a particular monitored system. Optional metrics on the
other hand SHOULD be treated as a source of additional information about the monitored system.
Additionally, if any of the following conditions can be applied to a metric, it MUST be marked as optional:
- **It is redundant with another metric.** For example system CPU usage can be emitted as two different metrics:
`system.cpu.time` (CPU time reported as cumulative sum in seconds) or `system.cpu.utilization` (fraction of CPU
time spent in different states reported as gauge), one of them has to be marked as option.
- **It creates a disproportionately high cardinality of resources and/or data points.**
- **There is a notable expected performance impact.**
- **The source must be configured in an unusual way.**
- **It requires dedicated configuration in the receiver.**
## Stability levels of scraping receivers
All the requirements defined for components in [the Collector's README](../README.md#stability-levels) are
applicable to the scraping receivers as well. In addition, the following rules applied specifically to scraping
metrics receivers:
### Development
The receiver is not ready for use. All the metrics emitted by the receiver are not finalized and can change in any way.
### Alpha
The receiver is ready for limited non-critical workloads. The list of emitted default metrics SHOULD be
considered as complete, but any changes to the `metadata.yaml` still MAY be applied.
### Beta
The receiver is ready for non-critical production workloads. The list of emitted default metrics MUST be
considered as complete. Breaking changes to the emitted metrics SHOULD be applied following [the deprecation
process](#changing-the-emitted-metrics).
### Stable
The receiver is ready for production workloads. Breaking changes to the emitted metrics SHOULD be avoided.
Nevertheless, metrics that are emitted by default MUST be always kept up-to-date with the latest stable version of the
monitored system. Given that, occasional breaking changes in the emitted metrics are expected even in the stable
receivers. Any breaking change MUST be applied following [the deprecation process](#changing-the-emitted-metrics).
## Stability levels for metrics and attributes
See [Collector's Telemetry Stability levels](./coding-guidelines.md#telemetry-stability-levels)
## Changing the emitted metrics
Some changes are not considered breaking and can be applied to metrics emitted by scraping receivers of any
stability level:
- Adding a new optional metric.
Most of other changes to the emitted metrics are considered breaking and MUST be handled according to the stability
level of the receiver. Each type of breaking change defines a set of steps that MUST (or SHOULD) be applied across
several releases for a Stable (or Beta) components. At least 3 versions SHOULD be kept between the steps to give
users time to prepare, e.g. if the first step is released in v0.62.0, the second step SHOULD be released not earlier
than 0.65.0. Any warnings SHOULD include the version starting from which the next step will take effect. If a
breaking change is more complicated and many metrics are involved in the change, feature gates SHOULD be used instead.
### Removing an optional metric
Steps to remove an optional metric:
1. Mark the metric as deprecated in `metadata.yaml` by adding "[DEPRECATED]" in its description. Show a warning that
the metric will be removed if the `enabled` option is set explicitly to `true` in user settings.
2. Remove the metric.
### Removing a default metric
Steps to remove a default metric:
1. Mark the metric as deprecated in `metadata.yaml` by adding "[DEPRECATED]" in its description. Show a warning that
the metric will be removed if the `enabled` option is not explicitly set to `false` in user settings.
2. Make the metric optional. Show a warning that the metric will be removed if the `enabled` option is set to `true`
in user settings.
3. Remove the metric.
### Making a default metric optional
Steps to turn a metric from default to optional:
1. Add a warning that the metric will be turned into optional if `enabled` field is not set explicitly to any value in
user settings. Warning example: "WARNING: Metric `foo.bar` will be disabled by default in v0.65.0. If you want to
keep it, please enable it explicitly in the receiver settings."
2. Remove the warning and update `metadata.yaml` to make the metric optional.
### Adding a new default metric or turning an existing optional metric into default
Adding a new default metric is a breaking change for a scraping receiver because it introduces an unexpected output
for users and additional load on metric backends. Steps to apply such a change:
1. If the metric doesn't exist yet, add one as an optional metric. Add a warning that the metric will be turned into
default if the `enabled` option is not set explicitly to any value in user settings. A warning example: "WARNING:
Metric `foo.bar` will be enabled by default in v0.65.0. If you don't want the metric to be emitted, please
disable it in the receiver settings."
2. Remove the warning and update `metadata.yaml` to make the metric default.
### Other changes
Other breaking changes SHOULD follow similar strategies inspecting presence of `enabled` field in user settings. For
example, if a metric has to be renamed for any reason, the guidelines for "Removing an optional metric" and "Adding a
new default metric" SHOULD be followed simultaneously.
Breaking changes that cannot be done through enabling/disabling metrics (e.g. removing or adding an extra attribute)
SHOULD be applied using a feature gate with the following steps:
1. Add a feature gate that is disabled by default. Enabling the feature gate changes the metrics behavior in a desired
way. For example, if several metrics emitted by host metrics receiver need to be updated to have an additional
`direction` attribute, the following feature gate can be used:
`receiver.hostmetricsreceiver.emitMetricsWithDirectionAttribute`. Show user a warning if the feature gate is not
enabled explicitly, for example: "[WARNING] Metrics `system.network.packets` and `system.network.errors` will be
changed in v0.65.0 to emit an additional `direction` attribute, enable a feature gate
`receiver.hostmetricsreceiver.emitMetricsWithDirectionAttribute` to apply and test the upcoming changes earlier".
2. Enable the feature gate by default and update the warning thrown if user disables the feature gate explicitly.
3. Remove the feature gate along with the old behavior.
================================================
FILE: docs/security-best-practices.md
================================================
# Security
The OpenTelemetry Collector defaults to operating in a secure manner but is
configuration driven. This document captures important security aspects and
considerations for the Collector. This document is intended for component
developers. It assumes at least a basic understanding of the Collector
architecture and functionality.
> Note: Please review the
> [configuration documentation](https://opentelemetry.io/docs/collector/configuration/)
> prior to this security document.
Security documentation for end users can be found on the OpenTelemetry
documentation website:
- [Collector configuration best practices](https://opentelemetry.io/docs/security/config-best-practices/)
- [Collector hosting best practices](https://opentelemetry.io/docs/security/hosting-best-practices/)
## TL;DR
- Configuration
- MUST come from the central configuration file
- SHOULD use configuration helpers
- Permissions
- SHOULD minimize privileged access
- MUST document what requires privileged access and why
- Receivers/Exporters
- MUST default to encrypted connections
- SHOULD leverage helper functions
- Extensions
- SHOULD NOT expose sensitive health or telemetry data by default
> For more information about securing the OpenTelemetry Collector, see
> [this blog post](https://medium.com/opentelemetry/securing-your-opentelemetry-collector-1a4f9fa5bd6f).
## Configuration
The Collector binary does not contain an embedded or default configuration and
MUST NOT start without a configuration file being specified. The configuration
file passed to the Collector MUST be validated prior to being loaded. If an
invalid configuration is detected, the Collector MUST fail to start as a
protective mechanism.
Component developers MUST get configuration information from the Collector's
configuration file. Component developers SHOULD leverage
[configuration helper functions](https://github.com/open-telemetry/opentelemetry-collector/tree/main/config).
When defining Go structs for configuration data that may contain sensitive
information, use the `configopaque` package to define fields with the
`configopaque.String` type. This ensures that the data is masked when serialized
to prevent accidental exposure.
> For more information, see the
> [configopaque](https://pkg.go.dev/go.opentelemetry.io/collector/config/configopaque)
> documentation.
## Permissions
The Collector supports running as a custom user and SHOULD NOT be run as a
root/admin user. For the majority of use-cases, the Collector SHOULD NOT require
privileged access to function. Some components MAY require privileged access or
external permissions, including network access or RBAC.
Component developers SHOULD minimize privileged access requirements and MUST
document what requires privileged access and why.
## Receivers and Exporters
Receivers and Exporters can be either push or pull-based. In either case, the
connection established SHOULD be over a secure and authenticated channel.
Component developers MUST default to encrypted connections (using the
`insecure: false` configuration setting) and SHOULD leverage
[gRPC](https://github.com/open-telemetry/opentelemetry-collector/tree/main/config/configgrpc)
and
[http](https://github.com/open-telemetry/opentelemetry-collector/tree/main/config/confighttp)
helper functions.
## Safeguards against denial of service attacks
See the [Collector configuration security documentation](https://opentelemetry.io/docs/security/config-best-practices/#protect-against-denial-of-service-attacks) to learn how to safeguard against denial of service attacks.
## Extensions
Component developers SHOULD NOT expose health or telemetry data outside the
Collector by default.
================================================
FILE: docs/standard-warnings.md
================================================
# Standard Warnings
Some components have scenarios that could cause issues. Some components require the collector be interacted with in a specific way in order to ensure the component works as intended. This document describes common warnings that may affect a component in the collector.
Visit a component's README to see if it is affected by any of these standard warnings.
## Unsound Transformations
Incorrect usage of the component may lead to telemetry data that is unsound i.e. not spec-compliant/meaningless. This would most likely be caused by converting metric data types or creating new metrics from existing metrics.
## Statefulness
The component keeps state related to telemetry data and therefore needs all data from a producer to be sent to the same Collector instance to ensure a correct behavior. Examples of scenarios that require state would be computing/exporting delta metrics, tail-based sampling and grouping telemetry.
## Identity Conflict
The component may change the [identity of a metric](https://github.com/open-telemetry/opentelemetry-specification/blob/main//specification/metrics/data-model.md#opentelemetry-protocol-data-model-producer-recommendations) or the [identity of a timeseries](https://github.com/open-telemetry/opentelemetry-specification/blob/main//specification/metrics/data-model.md#timeseries-model). This could be done by modifying the metric/timeseries's name, attributes, or instrumentation scope. Modifying a metric/timeseries's identity could result in a metric/timeseries identity conflict, which caused by two metrics/timeseries sharing the same name, attributes, and instrumentation scope.
## Orphaned Telemetry
The component modifies the incoming telemetry in such a way that a span becomes orphaned, that is, it contains a `trace_id` or `parent_span_id` that does not exist. This may occur because the component can modify `span_id`, `trace_id`, or `parent_span_id` or because the component can delete telemetry.
================================================
FILE: docs/vision.md
================================================
# 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](https://opentelemetry.io/docs/collector/internal-telemetry/).
## 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).
================================================
FILE: examples/README.md
================================================
# Examples
Information on how the examples can be used can be found in the [Getting
Started
documentation](https://opentelemetry.io/docs/collector/getting-started/).
================================================
FILE: examples/k8s/otel-config.yaml
================================================
---
apiVersion: v1
kind: ConfigMap
metadata:
name: otel-agent-conf
labels:
app: opentelemetry
component: otel-agent-conf
data:
otel-agent-config: |
receivers:
otlp:
protocols:
grpc:
endpoint: ${env:MY_POD_IP}:4317
http:
endpoint: ${env:MY_POD_IP}:4318
exporters:
otlp_grpc:
endpoint: "otel-collector.default:4317"
tls:
insecure: true
sending_queue:
num_consumers: 4
queue_size: 100
retry_on_failure:
enabled: true
processors:
memory_limiter:
# 80% of maximum memory up to 2G
limit_mib: 400
# 25% of limit up to 2G
spike_limit_mib: 100
check_interval: 5s
extensions:
zpages: {}
service:
extensions: [zpages]
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter]
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"
image: otel/opentelemetry-collector:latest
name: otel-agent
resources:
limits:
cpu: 500m
memory: 500Mi
requests:
cpu: 100m
memory: 100Mi
ports:
- containerPort: 55679 # ZPages endpoint.
- containerPort: 4317 # Default OpenTelemetry receiver port.
- containerPort: 8888 # Metrics.
env:
- name: MY_POD_IP
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: status.podIP
- name: GOMEMLIMIT
value: 400MiB
volumeMounts:
- name: otel-agent-config-vol
mountPath: /conf
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:
endpoint: ${env:MY_POD_IP}:4317
http:
endpoint: ${env:MY_POD_IP}:4318
processors:
memory_limiter:
# 80% of maximum memory up to 2G
limit_mib: 1500
# 25% of limit up to 2G
spike_limit_mib: 512
check_interval: 5s
extensions:
zpages: {}
exporters:
otlp_grpc:
endpoint: "http://someotlp.target.com:4317" # Replace with a real endpoint.
tls:
insecure: true
service:
extensions: [zpages]
pipelines:
traces/1:
receivers: [otlp]
processors: [memory_limiter]
exporters: [otlp]
---
apiVersion: v1
kind: Service
metadata:
name: otel-collector
labels:
app: opentelemetry
component: otel-collector
spec:
ports:
- name: otlp-grpc # Default endpoint for OpenTelemetry gRPC receiver.
port: 4317
protocol: TCP
targetPort: 4317
- name: otlp-http # Default endpoint for OpenTelemetry HTTP receiver.
port: 4318
protocol: TCP
targetPort: 4318
- 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"
image: otel/opentelemetry-collector:latest
name: otel-collector
resources:
limits:
cpu: 1
memory: 2Gi
requests:
cpu: 200m
memory: 400Mi
ports:
- containerPort: 55679 # Default endpoint for ZPages.
- containerPort: 4317 # Default endpoint for OpenTelemetry receiver.
- containerPort: 14250 # Default endpoint for Jaeger gRPC receiver.
- containerPort: 14268 # Default endpoint for Jaeger HTTP receiver.
- containerPort: 9411 # Default endpoint for Zipkin receiver.
- containerPort: 8888 # Default endpoint for querying metrics.
env:
- name: MY_POD_IP
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: status.podIP
- name: GOMEMLIMIT
value: 1600MiB
volumeMounts:
- name: otel-collector-config-vol
mountPath: /conf
# - name: otel-collector-secrets
# mountPath: /secrets
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
================================================
FILE: examples/local/otel-config.yaml
================================================
extensions:
zpages:
endpoint: localhost:55679
receivers:
otlp:
protocols:
grpc:
endpoint: localhost:4317
http:
endpoint: localhost:4318
processors:
memory_limiter:
# 75% of maximum memory up to 2G
limit_mib: 1536
# 25% of limit up to 2G
spike_limit_mib: 512
check_interval: 5s
exporters:
debug:
verbosity: detailed
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter]
exporters: [debug]
metrics:
receivers: [otlp]
processors: [memory_limiter]
exporters: [debug]
logs:
receivers: [otlp]
processors: [memory_limiter]
exporters: [debug]
extensions: [zpages]
================================================
FILE: exporter/Makefile
================================================
include ../Makefile.Common
================================================
FILE: exporter/README.md
================================================
# General Information
An exporter defines how the pipeline data leaves the collector.
This repository hosts the following exporters available in
traces, metrics and logs pipelines (sorted alphabetically):
- [Debug](debugexporter/README.md)
- [OTLP gRPC](otlpexporter/README.md)
- [OTLP HTTP](otlphttpexporter/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):
* exporters *not* configured to mutate the data will have shared access to the data
* exporters with the Capabilities to mutate the data will receive a copy of the data
Exporters access export data when `ConsumeTraces`/`ConsumeMetrics`/`ConsumeLogs`
function is called. Unless exporter's capabilities include mutation, the exporter MUST NOT modify the `pdata.Traces`/`pdata.Metrics`/`pdata.Logs` argument of
these functions. Any approach that does not mutate the original `pdata.Traces`/`pdata.Metrics`/`pdata.Logs` is allowed without the mutation capability.
## 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.
================================================
FILE: exporter/debugexporter/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: exporter/debugexporter/README.md
================================================
# Debug Exporter
| Status | |
| ------------- |-----------|
| Stability | [alpha]: traces, metrics, logs, profiles |
| Distributions | [core], [contrib], [k8s] |
| Warnings | [Unstable Output Format](#warnings) |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Aexporter%2Fdebug) [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aclosed+is%3Aissue+label%3Aexporter%2Fdebug) |
| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@andrzej-stencel](https://www.github.com/andrzej-stencel) |
[alpha]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#alpha
[core]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
[k8s]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-k8s
Outputs telemetry data to the console for debugging purposes.
See also the [Troubleshooting][troubleshooting_docs] document for examples on using this exporter.
[troubleshooting_docs]: https://opentelemetry.io/docs/collector/troubleshooting/#local-exporters
## Getting Started
The following settings are optional:
- `verbosity` (default = `basic`): the verbosity of the debug exporter: `basic`, `normal` or `detailed`.
See [Verbosity levels](#verbosity-levels) below for more information.
- `sampling_initial` (default = `2`): number of messages initially logged each
second.
- `sampling_thereafter` (default = `1`): sampling rate after the initial
messages are logged (every Mth message is logged).
The default value of `1` means that sampling is disabled.
To enable sampling, change `sampling_thereafter` to a value higher than `1`.
Refer to [Zap docs](https://godoc.org/go.uber.org/zap/zapcore#NewSampler) for more details
on how sampling parameters impact number of messages.
- `use_internal_logger` (default = `true`): uses the collector's internal logger for output. See [below](#using-the-collectors-internal-logger) for description.
- `output_paths` (default = `["stdout"]`): a list of file paths to write output to. This option can only be used when `use_internal_logger` is `false`. Special strings "stdout" and "stderr" are interpreted as [standard output](https://en.wikipedia.org/wiki/Standard_streams#Standard_output_(stdout)) and [standard error](https://en.wikipedia.org/wiki/Standard_streams#Standard_error_(stderr)) respectively. All other values are treated as file paths. Setting `output_paths` when `use_internal_logger` is `true` results in a configuration error.
- `sending_queue` (disabled by default): see [Sending Queue](../exporterhelper/README.md#sending-queue) for the full set of available options.
Example configuration:
```yaml
exporters:
debug:
verbosity: detailed
sampling_initial: 5
sampling_thereafter: 200
```
Example configuration with custom output path:
```yaml
exporters:
debug:
use_internal_logger: false
output_paths:
- stderr
```
## Verbosity levels
The following subsections describe the output from the exporter depending on the configured verbosity level - `basic`, `normal` and `detailed`.
The default verbosity level is `basic`.
To understand how the below example output was generated, see [Generating example output](./generating-example-output.md).
### Basic verbosity
With `verbosity: basic`, the exporter outputs a single-line summary of received data with a total count of telemetry records for every batch of received logs, metrics or traces.
Here's an example output:
```console
2025-04-17T10:40:44.559+0200 info Traces {"otelcol.component.id": "debug/basic", "otelcol.component.kind": "Exporter", "otelcol.signal": "traces", "resource spans": 1, "spans": 2}
```
### Normal verbosity
With `verbosity: normal`, the exporter outputs about one line for each telemetry record.
The "one line per telemetry record" is not a strict rule.
For example, logs with multiline body will be output as multiple lines.
Here's an example output:
```console
2025-05-09T19:57:16.332+0200 info Traces {"resource": {}, "otelcol.component.id": "debug/normal", "otelcol.component.kind": "exporter", "otelcol.signal": "traces", "resource spans": 1, "spans": 2}
2025-05-09T19:57:16.332+0200 info ResourceTraces #0 [https://opentelemetry.io/schemas/1.25.0] service.name=telemetrygen
ScopeTraces #0 telemetrygen
okey-dokey-0 ab1030bd4ee554af936542b01d7b4807 1d8c93663d043aa8 net.sock.peer.addr=1.2.3.4 peer.service=telemetrygen-client
lets-go ab1030bd4ee554af936542b01d7b4807 0d238e8a2f97733f net.sock.peer.addr=1.2.3.4 peer.service=telemetrygen-server
{"resource": {}, "otelcol.component.id": "debug/normal", "otelcol.component.kind": "exporter", "otelcol.signal": "traces"}
```
### Detailed verbosity
With `verbosity: detailed`, the exporter outputs all details of every telemetry record, typically writing multiple lines for every telemetry record.
Here's an example output:
```console
2025-04-17T10:40:44.560+0200 info Traces {"otelcol.component.id": "debug/detailed", "otelcol.component.kind": "Exporter", "otelcol.signal": "traces", "resource spans": 1, "spans": 2}
2025-04-17T10:40:44.560+0200 info ResourceSpans #0
Resource SchemaURL: https://opentelemetry.io/schemas/1.25.0
Resource attributes:
-> service.name: Str(telemetrygen)
ScopeSpans #0
ScopeSpans SchemaURL:
InstrumentationScope telemetrygen
Span #0
Trace ID : fafdac970271dd2ce89de2442c0518c7
Parent ID : d98de4cb8e2a0ad6
ID : 3875f436d989d0e5
Name : okey-dokey-0
Kind : Server
Start time : 2025-04-17 08:40:44.555461596 +0000 UTC
End time : 2025-04-17 08:40:44.555584596 +0000 UTC
Status code : Unset
Status message :
Attributes:
-> net.sock.peer.addr: Str(1.2.3.4)
-> peer.service: Str(telemetrygen-client)
Span #1
Trace ID : fafdac970271dd2ce89de2442c0518c7
Parent ID :
ID : d98de4cb8e2a0ad6
Name : lets-go
Kind : Client
Start time : 2025-04-17 08:40:44.555461596 +0000 UTC
End time : 2025-04-17 08:40:44.555584596 +0000 UTC
Status code : Unset
Status message :
Attributes:
-> net.sock.peer.addr: Str(1.2.3.4)
-> peer.service: Str(telemetrygen-server)
{"otelcol.component.id": "debug/detailed", "otelcol.component.kind": "Exporter", "otelcol.signal": "traces"}
```
## Using the collector's internal logger
When `use_internal_logger` is set to `true` (the default), the exporter uses the collector's [internal logger][internal_telemetry] for output.
This comes with the following consequences:
- The output from the exporter may be annotated by additional output from the collector's logger.
- The output from the exporter is affected by the collector's [logging configuration][internal_logs_config] specified in `service::telemetry::logs`.
When `use_internal_logger` is set to `false`, the exporter does not use the collector's internal logger.
Changing the values in `service::telemetry::logs` has no effect on the exporter's output.
The exporter's output is sent to the paths specified in `output_paths` (default: `["stdout"]`).
You can configure `output_paths` to send output to `stderr`, a file, or multiple destinations.
[internal_telemetry]: https://opentelemetry.io/docs/collector/internal-telemetry/
[internal_logs_config]: https://opentelemetry.io/docs/collector/internal-telemetry/#configure-internal-logs
## Warnings
- Unstable Output Format: The output formats for all verbosity levels is not guaranteed and may be changed at any time without a breaking change.
================================================
FILE: exporter/debugexporter/config.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package debugexporter // import "go.opentelemetry.io/collector/exporter/debugexporter"
import (
"errors"
"fmt"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/config/configtelemetry"
"go.opentelemetry.io/collector/exporter/exporterhelper"
)
// supportedLevels in this exporter's configuration.
// configtelemetry.LevelNone and other future values are not supported.
var supportedLevels map[configtelemetry.Level]struct{} = map[configtelemetry.Level]struct{}{
configtelemetry.LevelBasic: {},
configtelemetry.LevelNormal: {},
configtelemetry.LevelDetailed: {},
}
// Config defines configuration for debug exporter.
type Config struct {
// Verbosity defines the debug exporter verbosity.
Verbosity configtelemetry.Level `mapstructure:"verbosity,omitempty"`
// 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"`
// UseInternalLogger defines whether the exporter sends the output to the collector's internal logger.
UseInternalLogger bool `mapstructure:"use_internal_logger"`
// OutputPaths is a list of file paths to write logging output to.
// This option can only be used when use_internal_logger is false.
// Special strings "stdout" and "stderr" are interpreted as os.Stdout and os.Stderr respectively.
// All other values are treated as file paths.
// If not set, defaults to ["stdout"].
OutputPaths []string `mapstructure:"output_paths"`
QueueConfig configoptional.Optional[exporterhelper.QueueBatchConfig] `mapstructure:"sending_queue"`
// prevent unkeyed literal initialization
_ struct{}
}
var _ component.Config = (*Config)(nil)
// Validate checks if the exporter configuration is valid
func (cfg *Config) Validate() error {
if _, ok := supportedLevels[cfg.Verbosity]; !ok {
return fmt.Errorf("verbosity level %q is not supported", cfg.Verbosity)
}
// output_paths is only used when use_internal_logger is false
// If set when use_internal_logger is true, it would be ignored, which is confusing
if cfg.UseInternalLogger && cfg.OutputPaths != nil {
return errors.New("output_paths is not supported when use_internal_logger is true")
}
// If use_internal_logger is false and output_paths is explicitly set to empty, error
// (nil output_paths will default to ["stdout"] in createCustomLogger)
if !cfg.UseInternalLogger && cfg.OutputPaths != nil && len(cfg.OutputPaths) == 0 {
return errors.New("output_paths must not be empty when use_internal_logger is false")
}
return nil
}
================================================
FILE: exporter/debugexporter/config_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package debugexporter
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/config/configtelemetry"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/confmaptest"
"go.opentelemetry.io/collector/exporter/exporterhelper"
)
func TestUnmarshalDefaultConfig(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
require.NoError(t, confmap.New().Unmarshal(&cfg))
assert.Equal(t, factory.CreateDefaultConfig(), cfg)
}
func TestUnmarshalConfig(t *testing.T) {
tests := []struct {
filename string
cfg *Config
expectedUnmarshalErr string
expectedValidateErr string
}{
{
filename: "config_verbosity.yaml",
cfg: &Config{
Verbosity: configtelemetry.LevelDetailed,
SamplingInitial: 10,
SamplingThereafter: 50,
UseInternalLogger: false,
OutputPaths: []string{"stdout"},
QueueConfig: configoptional.Default(exporterhelper.NewDefaultQueueConfig()),
},
},
{
filename: "config_output_paths.yaml",
cfg: &Config{
Verbosity: configtelemetry.LevelBasic,
SamplingInitial: 2,
SamplingThereafter: 1,
UseInternalLogger: false,
OutputPaths: []string{"stderr"},
QueueConfig: configoptional.Default(exporterhelper.NewDefaultQueueConfig()),
},
},
{
filename: "config_output_paths_empty.yaml",
expectedValidateErr: "output_paths must not be empty when use_internal_logger is false",
},
{
filename: "config_verbosity_typo.yaml",
expectedUnmarshalErr: "has invalid keys: verBosity",
},
}
for _, tt := range tests {
t.Run(tt.filename, func(t *testing.T) {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", tt.filename))
require.NoError(t, err)
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
err = cm.Unmarshal(&cfg)
if tt.expectedUnmarshalErr != "" {
require.ErrorContains(t, err, tt.expectedUnmarshalErr)
return
}
require.NoError(t, err)
cfgCasted := cfg.(*Config)
err = cfgCasted.Validate()
if tt.expectedValidateErr != "" {
require.ErrorContains(t, err, tt.expectedValidateErr)
return
}
require.NoError(t, err)
assert.Equal(t, tt.cfg, cfg)
})
}
}
func Test_UnmarshalMarshalled(t *testing.T) {
for name, tc := range map[string]struct {
inCfg *Config
expectedConfig *Config
expectedErr string
}{
"Base": {
inCfg: &Config{},
expectedConfig: &Config{},
},
"VerbositySpecified": {
inCfg: &Config{
Verbosity: configtelemetry.LevelDetailed,
},
expectedConfig: &Config{
Verbosity: configtelemetry.LevelDetailed,
},
},
} {
t.Run(name, func(t *testing.T) {
conf := confmap.New()
err := conf.Marshal(tc.inCfg)
require.NoError(t, err)
raw := conf.ToStringMap()
conf = confmap.NewFromStringMap(raw)
outCfg := &Config{}
err = conf.Unmarshal(outCfg)
if tc.expectedErr == "" {
require.NoError(t, err)
assert.Equal(t, tc.expectedConfig, outCfg)
return
}
require.Error(t, err)
assert.EqualError(t, err, tc.expectedErr)
})
}
}
func TestValidate(t *testing.T) {
tests := []struct {
name string
cfg *Config
expectedErr string
}{
{
name: "verbosity none",
cfg: &Config{
Verbosity: configtelemetry.LevelNone,
},
expectedErr: "verbosity level \"None\" is not supported",
},
{
name: "verbosity detailed",
cfg: &Config{
Verbosity: configtelemetry.LevelDetailed,
UseInternalLogger: true, // Default behavior
},
},
{
name: "empty output_paths when use_internal_logger is false",
cfg: &Config{
UseInternalLogger: false,
OutputPaths: []string{},
},
expectedErr: "output_paths must not be empty when use_internal_logger is false",
},
{
name: "valid output_paths when use_internal_logger is false",
cfg: &Config{
UseInternalLogger: false,
OutputPaths: []string{"stdout"},
},
},
{
name: "nil output_paths when use_internal_logger is false (defaults to stdout)",
cfg: &Config{
UseInternalLogger: false,
OutputPaths: nil,
},
},
{
name: "output_paths set when use_internal_logger is true (not allowed)",
cfg: &Config{
UseInternalLogger: true,
OutputPaths: []string{"stderr"},
},
expectedErr: "output_paths is not supported when use_internal_logger is true",
},
{
name: "nil output_paths when use_internal_logger is true (allowed)",
cfg: &Config{
UseInternalLogger: true,
OutputPaths: nil,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.cfg.Validate()
if tt.expectedErr != "" {
assert.ErrorContains(t, err, tt.expectedErr)
} else {
assert.NoError(t, err)
}
})
}
}
================================================
FILE: exporter/debugexporter/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:generate mdatagen metadata.yaml
// Package debugexporter exports data to console as logs.
package debugexporter // import "go.opentelemetry.io/collector/exporter/debugexporter"
================================================
FILE: exporter/debugexporter/exporter.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package debugexporter // import "go.opentelemetry.io/collector/exporter/debugexporter"
import (
"context"
"go.uber.org/zap"
"go.opentelemetry.io/collector/config/configtelemetry"
"go.opentelemetry.io/collector/exporter/debugexporter/internal/normal"
"go.opentelemetry.io/collector/exporter/debugexporter/internal/otlptext"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/ptrace"
)
type debugExporter struct {
verbosity configtelemetry.Level
logger *zap.Logger
logsMarshaler plog.Marshaler
metricsMarshaler pmetric.Marshaler
tracesMarshaler ptrace.Marshaler
profilesMarshaler pprofile.Marshaler
}
func newDebugExporter(logger *zap.Logger, verbosity configtelemetry.Level) *debugExporter {
var logsMarshaler plog.Marshaler
var metricsMarshaler pmetric.Marshaler
var tracesMarshaler ptrace.Marshaler
var profilesMarshaler pprofile.Marshaler
if verbosity == configtelemetry.LevelDetailed {
logsMarshaler = otlptext.NewTextLogsMarshaler()
metricsMarshaler = otlptext.NewTextMetricsMarshaler()
tracesMarshaler = otlptext.NewTextTracesMarshaler()
profilesMarshaler = otlptext.NewTextProfilesMarshaler()
} else {
logsMarshaler = normal.NewNormalLogsMarshaler()
metricsMarshaler = normal.NewNormalMetricsMarshaler()
tracesMarshaler = normal.NewNormalTracesMarshaler()
profilesMarshaler = normal.NewNormalProfilesMarshaler()
}
return &debugExporter{
verbosity: verbosity,
logger: logger,
logsMarshaler: logsMarshaler,
metricsMarshaler: metricsMarshaler,
tracesMarshaler: tracesMarshaler,
profilesMarshaler: profilesMarshaler,
}
}
func (s *debugExporter) pushTraces(_ context.Context, td ptrace.Traces) error {
s.logger.Info("Traces",
zap.Int("resource spans", td.ResourceSpans().Len()),
zap.Int("spans", td.SpanCount()))
if s.verbosity == configtelemetry.LevelBasic {
return nil
}
buf, err := s.tracesMarshaler.MarshalTraces(td)
if err != nil {
return err
}
s.logger.Info(string(buf))
return nil
}
func (s *debugExporter) pushMetrics(_ context.Context, md pmetric.Metrics) error {
s.logger.Info("Metrics",
zap.Int("resource metrics", md.ResourceMetrics().Len()),
zap.Int("metrics", md.MetricCount()),
zap.Int("data points", md.DataPointCount()))
if s.verbosity == configtelemetry.LevelBasic {
return nil
}
buf, err := s.metricsMarshaler.MarshalMetrics(md)
if err != nil {
return err
}
s.logger.Info(string(buf))
return nil
}
func (s *debugExporter) pushLogs(_ context.Context, ld plog.Logs) error {
s.logger.Info("Logs",
zap.Int("resource logs", ld.ResourceLogs().Len()),
zap.Int("log records", ld.LogRecordCount()))
if s.verbosity == configtelemetry.LevelBasic {
return nil
}
buf, err := s.logsMarshaler.MarshalLogs(ld)
if err != nil {
return err
}
s.logger.Info(string(buf))
return nil
}
func (s *debugExporter) pushProfiles(_ context.Context, pd pprofile.Profiles) error {
s.logger.Info("Profiles",
zap.Int("resource profiles", pd.ResourceProfiles().Len()),
zap.Int("sample records", pd.SampleCount()))
if s.verbosity == configtelemetry.LevelBasic {
return nil
}
buf, err := s.profilesMarshaler.MarshalProfiles(pd)
if err != nil {
return err
}
s.logger.Info(string(buf))
return nil
}
================================================
FILE: exporter/debugexporter/exporter_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package debugexporter // import "go.opentelemetry.io/collector/exporter/debugexporter"
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/config/configtelemetry"
"go.opentelemetry.io/collector/exporter/debugexporter/internal/metadata"
"go.opentelemetry.io/collector/exporter/exporterhelper"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/pdata/testdata"
)
func TestTracesNoErrors(t *testing.T) {
for _, tc := range createTestCases() {
t.Run(tc.name, func(t *testing.T) {
lte, err := createTraces(context.Background(), exportertest.NewNopSettings(metadata.Type), tc.config)
require.NotNil(t, lte)
assert.NoError(t, err)
assert.NoError(t, lte.ConsumeTraces(context.Background(), ptrace.NewTraces()))
assert.NoError(t, lte.ConsumeTraces(context.Background(), testdata.GenerateTraces(10)))
assert.NoError(t, lte.Shutdown(context.Background()))
})
}
}
func TestMetricsNoErrors(t *testing.T) {
for _, tc := range createTestCases() {
t.Run(tc.name, func(t *testing.T) {
lme, err := createMetrics(context.Background(), exportertest.NewNopSettings(metadata.Type), tc.config)
require.NotNil(t, lme)
assert.NoError(t, err)
assert.NoError(t, lme.ConsumeMetrics(context.Background(), pmetric.NewMetrics()))
assert.NoError(t, lme.ConsumeMetrics(context.Background(), testdata.GenerateMetricsAllTypes()))
assert.NoError(t, lme.ConsumeMetrics(context.Background(), testdata.GenerateMetricsAllTypesEmpty()))
assert.NoError(t, lme.ConsumeMetrics(context.Background(), testdata.GenerateMetricsMetricTypeInvalid()))
assert.NoError(t, lme.ConsumeMetrics(context.Background(), testdata.GenerateMetrics(10)))
assert.NoError(t, lme.Shutdown(context.Background()))
})
}
}
func TestLogsNoErrors(t *testing.T) {
for _, tc := range createTestCases() {
t.Run(tc.name, func(t *testing.T) {
lle, err := createLogs(context.Background(), exportertest.NewNopSettings(metadata.Type), tc.config)
require.NotNil(t, lle)
assert.NoError(t, err)
assert.NoError(t, lle.ConsumeLogs(context.Background(), plog.NewLogs()))
assert.NoError(t, lle.ConsumeLogs(context.Background(), testdata.GenerateLogs(10)))
assert.NoError(t, lle.Shutdown(context.Background()))
})
}
}
func TestProfilesNoErrors(t *testing.T) {
for _, tc := range createTestCases() {
t.Run(tc.name, func(t *testing.T) {
lle, err := createProfiles(context.Background(), exportertest.NewNopSettings(metadata.Type), tc.config)
require.NotNil(t, lle)
assert.NoError(t, err)
assert.NoError(t, lle.ConsumeProfiles(context.Background(), pprofile.NewProfiles()))
assert.NoError(t, lle.ConsumeProfiles(context.Background(), testdata.GenerateProfiles(10)))
assert.NoError(t, lle.Shutdown(context.Background()))
})
}
}
func TestErrors(t *testing.T) {
le := newDebugExporter(zaptest.NewLogger(t), configtelemetry.LevelDetailed)
require.NotNil(t, le)
errWant := errors.New("my error")
le.tracesMarshaler = &errMarshaler{err: errWant}
le.metricsMarshaler = &errMarshaler{err: errWant}
le.logsMarshaler = &errMarshaler{err: errWant}
le.profilesMarshaler = &errMarshaler{err: errWant}
assert.Equal(t, errWant, le.pushTraces(context.Background(), ptrace.NewTraces()))
assert.Equal(t, errWant, le.pushMetrics(context.Background(), pmetric.NewMetrics()))
assert.Equal(t, errWant, le.pushLogs(context.Background(), plog.NewLogs()))
assert.Equal(t, errWant, le.pushProfiles(context.Background(), pprofile.NewProfiles()))
}
type testCase struct {
name string
config *Config
}
func createTestCases() []testCase {
return []testCase{
{
name: "default config",
config: func() *Config {
c := createDefaultConfig().(*Config)
c.QueueConfig = configoptional.Some(exporterhelper.NewDefaultQueueConfig())
c.QueueConfig.Get().QueueSize = 10
return c
}(),
},
{
name: "don't use internal logger",
config: func() *Config {
cfg := createDefaultConfig().(*Config)
cfg.QueueConfig = configoptional.Some(exporterhelper.NewDefaultQueueConfig())
cfg.QueueConfig.Get().QueueSize = 10
cfg.UseInternalLogger = false
return cfg
}(),
},
{
name: "custom output paths",
config: func() *Config {
cfg := createDefaultConfig().(*Config)
queueCfg := exporterhelper.NewDefaultQueueConfig()
queueCfg.QueueSize = 10
cfg.QueueConfig = configoptional.Some(queueCfg)
cfg.UseInternalLogger = false
cfg.OutputPaths = []string{"stderr"}
return cfg
}(),
},
}
}
type errMarshaler struct {
err error
}
func (e errMarshaler) MarshalLogs(plog.Logs) ([]byte, error) {
return nil, e.err
}
func (e errMarshaler) MarshalMetrics(pmetric.Metrics) ([]byte, error) {
return nil, e.err
}
func (e errMarshaler) MarshalTraces(ptrace.Traces) ([]byte, error) {
return nil, e.err
}
func (e errMarshaler) MarshalProfiles(pprofile.Profiles) ([]byte, error) {
return nil, e.err
}
================================================
FILE: exporter/debugexporter/factory.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package debugexporter // import "go.opentelemetry.io/collector/exporter/debugexporter"
import (
"context"
"time"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/config/configtelemetry"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/debugexporter/internal/metadata"
"go.opentelemetry.io/collector/exporter/debugexporter/internal/otlptext"
"go.opentelemetry.io/collector/exporter/exporterhelper"
"go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper"
"go.opentelemetry.io/collector/exporter/xexporter"
)
// The value of "type" key in configuration.
var componentType = component.MustNewType("debug")
const (
defaultSamplingInitial = 2
defaultSamplingThereafter = 1
)
// NewFactory creates and returns a new factory for the Debug exporter.
func NewFactory() exporter.Factory {
return xexporter.NewFactory(
componentType,
createDefaultConfig,
xexporter.WithTraces(createTraces, metadata.TracesStability),
xexporter.WithMetrics(createMetrics, metadata.MetricsStability),
xexporter.WithLogs(createLogs, metadata.LogsStability),
xexporter.WithProfiles(createProfiles, metadata.ProfilesStability),
)
}
func createDefaultConfig() component.Config {
return &Config{
Verbosity: configtelemetry.LevelBasic,
SamplingInitial: defaultSamplingInitial,
SamplingThereafter: defaultSamplingThereafter,
UseInternalLogger: true,
QueueConfig: configoptional.Default(exporterhelper.NewDefaultQueueConfig()),
}
}
func createTraces(ctx context.Context, set exporter.Settings, config component.Config) (exporter.Traces, error) {
cfg := config.(*Config)
exporterLogger := createLogger(cfg, set.Logger)
debug := newDebugExporter(exporterLogger, cfg.Verbosity)
return exporterhelper.NewTraces(ctx, set, config,
debug.pushTraces,
exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}),
exporterhelper.WithQueue(cfg.QueueConfig),
exporterhelper.WithTimeout(exporterhelper.TimeoutConfig{Timeout: 0}),
exporterhelper.WithShutdown(otlptext.LoggerSync(exporterLogger)),
)
}
func createMetrics(ctx context.Context, set exporter.Settings, config component.Config) (exporter.Metrics, error) {
cfg := config.(*Config)
exporterLogger := createLogger(cfg, set.Logger)
debug := newDebugExporter(exporterLogger, cfg.Verbosity)
return exporterhelper.NewMetrics(ctx, set, config,
debug.pushMetrics,
exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}),
exporterhelper.WithQueue(cfg.QueueConfig),
exporterhelper.WithTimeout(exporterhelper.TimeoutConfig{Timeout: 0}),
exporterhelper.WithShutdown(otlptext.LoggerSync(exporterLogger)),
)
}
func createLogs(ctx context.Context, set exporter.Settings, config component.Config) (exporter.Logs, error) {
cfg := config.(*Config)
exporterLogger := createLogger(cfg, set.Logger)
debug := newDebugExporter(exporterLogger, cfg.Verbosity)
return exporterhelper.NewLogs(ctx, set, config,
debug.pushLogs,
exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}),
exporterhelper.WithQueue(cfg.QueueConfig),
exporterhelper.WithTimeout(exporterhelper.TimeoutConfig{Timeout: 0}),
exporterhelper.WithShutdown(otlptext.LoggerSync(exporterLogger)),
)
}
func createProfiles(ctx context.Context, set exporter.Settings, config component.Config) (xexporter.Profiles, error) {
cfg := config.(*Config)
exporterLogger := createLogger(cfg, set.Logger)
debug := newDebugExporter(exporterLogger, cfg.Verbosity)
return xexporterhelper.NewProfiles(ctx, set, config,
debug.pushProfiles,
exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}),
exporterhelper.WithQueue(cfg.QueueConfig),
exporterhelper.WithTimeout(exporterhelper.TimeoutConfig{Timeout: 0}),
exporterhelper.WithShutdown(otlptext.LoggerSync(exporterLogger)),
)
}
func createLogger(cfg *Config, logger *zap.Logger) *zap.Logger {
var exporterLogger *zap.Logger
if cfg.UseInternalLogger {
core := zapcore.NewSamplerWithOptions(
logger.Core(),
1*time.Second,
cfg.SamplingInitial,
cfg.SamplingThereafter,
)
exporterLogger = zap.New(core)
} else {
exporterLogger = createCustomLogger(cfg)
}
return exporterLogger
}
func createCustomLogger(exporterConfig *Config) *zap.Logger {
encoderConfig := zap.NewDevelopmentEncoderConfig()
// Do not prefix the output with log level (`info`)
encoderConfig.LevelKey = ""
// Do not prefix the output with current timestamp.
encoderConfig.TimeKey = ""
outputPaths := exporterConfig.OutputPaths
if outputPaths == nil {
outputPaths = []string{"stdout"}
}
zapConfig := zap.Config{
Level: zap.NewAtomicLevelAt(zap.InfoLevel),
DisableCaller: true,
Sampling: &zap.SamplingConfig{
Initial: exporterConfig.SamplingInitial,
Thereafter: exporterConfig.SamplingThereafter,
},
Encoding: "console",
EncoderConfig: encoderConfig,
OutputPaths: outputPaths,
}
return zap.Must(zapConfig.Build())
}
================================================
FILE: exporter/debugexporter/factory_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package debugexporter
import (
"context"
"os"
"path/filepath"
"runtime"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/config/configtelemetry"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/exporter/xexporter"
)
func TestCreateDefaultConfig(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
assert.NotNil(t, cfg, "failed to create default config")
require.NoError(t, componenttest.CheckConfigStruct(cfg))
config := cfg.(*Config)
assert.True(t, config.UseInternalLogger)
// OutputPaths is nil by default (only used when UseInternalLogger is false,
// where it defaults to ["stdout"] in createCustomLogger if not set)
assert.Nil(t, config.OutputPaths)
}
func TestCreateMetrics(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
me, err := factory.CreateMetrics(context.Background(), exportertest.NewNopSettings(factory.Type()), cfg)
require.NoError(t, err)
assert.NotNil(t, me)
}
func TestCreateTraces(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
te, err := factory.CreateTraces(context.Background(), exportertest.NewNopSettings(factory.Type()), cfg)
require.NoError(t, err)
assert.NotNil(t, te)
}
func TestCreateLogs(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
te, err := factory.CreateLogs(context.Background(), exportertest.NewNopSettings(factory.Type()), cfg)
require.NoError(t, err)
assert.NotNil(t, te)
}
func TestCreateFactoryProfiles(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
te, err := factory.(xexporter.Factory).CreateProfiles(context.Background(), exportertest.NewNopSettings(factory.Type()), cfg)
require.NoError(t, err)
assert.NotNil(t, te)
}
func TestCreateCustomLogger(t *testing.T) {
tests := []struct {
name string
outputPaths []string
}{
{
name: "stdout",
outputPaths: []string{"stdout"},
},
{
name: "stderr",
outputPaths: []string{"stderr"},
},
{
name: "multiple paths",
outputPaths: []string{"stdout", "stderr"},
},
{
name: "nil defaults to stdout",
outputPaths: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
config := &Config{
OutputPaths: tt.outputPaths,
SamplingInitial: 2,
SamplingThereafter: 1,
}
logger := createCustomLogger(config)
require.NotNil(t, logger)
logger.Info("test message")
// Sync may return an error for stdout/stderr in test environments
_ = logger.Sync()
})
}
}
func TestCreateCustomLoggerWithFileOutput(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("Skipping on Windows due to file locking issues with t.TempDir() cleanup")
}
tmpDir := t.TempDir()
filePath := filepath.Clean(filepath.Join(tmpDir, "debug.log"))
config := &Config{
OutputPaths: []string{filePath},
SamplingInitial: 2,
SamplingThereafter: 1,
}
logger := createCustomLogger(config)
require.NotNil(t, logger)
logger.Info("test message to file")
require.NoError(t, logger.Sync())
// Verify file was created and contains content
content, err := os.ReadFile(filePath)
require.NoError(t, err)
assert.Contains(t, string(content), "test message to file")
}
func TestCreateLogger(t *testing.T) {
tests := []struct {
name string
config *Config
}{
{
name: "use internal logger",
config: &Config{
UseInternalLogger: true,
SamplingInitial: 2,
SamplingThereafter: 1,
},
},
{
name: "use custom logger with stdout",
config: &Config{
UseInternalLogger: false,
OutputPaths: []string{"stdout"},
SamplingInitial: 2,
SamplingThereafter: 1,
},
},
{
name: "use custom logger with nil output paths defaults to stdout",
config: &Config{
UseInternalLogger: false,
OutputPaths: nil,
SamplingInitial: 2,
SamplingThereafter: 1,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
baseLogger := zap.NewNop()
logger := createLogger(tt.config, baseLogger)
require.NotNil(t, logger)
logger.Info("test message")
_ = logger.Sync()
})
}
}
func TestCreateLoggerWithInternalLogger(t *testing.T) {
config := &Config{
UseInternalLogger: true,
SamplingInitial: 10,
SamplingThereafter: 50,
Verbosity: configtelemetry.LevelDetailed,
}
baseLogger := zap.NewNop()
logger := createLogger(config, baseLogger)
require.NotNil(t, logger)
logger.Info("test message")
_ = logger.Sync()
}
================================================
FILE: exporter/debugexporter/generated_component_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package debugexporter
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/confmap/confmaptest"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
)
var typ = component.MustNewType("debug")
func TestComponentFactoryType(t *testing.T) {
require.Equal(t, typ, NewFactory().Type())
}
func TestComponentConfigStruct(t *testing.T) {
require.NoError(t, componenttest.CheckConfigStruct(NewFactory().CreateDefaultConfig()))
}
func TestComponentLifecycle(t *testing.T) {
factory := NewFactory()
tests := []struct {
createFn func(ctx context.Context, set exporter.Settings, cfg component.Config) (component.Component, error)
name string
}{
{
name: "logs",
createFn: func(ctx context.Context, set exporter.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateLogs(ctx, set, cfg)
},
},
{
name: "metrics",
createFn: func(ctx context.Context, set exporter.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateMetrics(ctx, set, cfg)
},
},
{
name: "traces",
createFn: func(ctx context.Context, set exporter.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateTraces(ctx, set, cfg)
},
},
}
cm, err := confmaptest.LoadConf("metadata.yaml")
require.NoError(t, err)
cfg := factory.CreateDefaultConfig()
sub, err := cm.Sub("tests::config")
require.NoError(t, err)
require.NoError(t, sub.Unmarshal(&cfg))
for _, tt := range tests {
t.Run(tt.name+"-shutdown", func(t *testing.T) {
c, err := tt.createFn(context.Background(), exportertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
err = c.Shutdown(context.Background())
require.NoError(t, err)
})
t.Run(tt.name+"-lifecycle", func(t *testing.T) {
c, err := tt.createFn(context.Background(), exportertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
host := newMdatagenNopHost()
err = c.Start(context.Background(), host)
require.NoError(t, err)
require.NotPanics(t, func() {
switch tt.name {
case "logs":
e, ok := c.(exporter.Logs)
require.True(t, ok)
logs := generateLifecycleTestLogs()
if !e.Capabilities().MutatesData {
logs.MarkReadOnly()
}
err = e.ConsumeLogs(context.Background(), logs)
case "metrics":
e, ok := c.(exporter.Metrics)
require.True(t, ok)
metrics := generateLifecycleTestMetrics()
if !e.Capabilities().MutatesData {
metrics.MarkReadOnly()
}
err = e.ConsumeMetrics(context.Background(), metrics)
case "traces":
e, ok := c.(exporter.Traces)
require.True(t, ok)
traces := generateLifecycleTestTraces()
if !e.Capabilities().MutatesData {
traces.MarkReadOnly()
}
err = e.ConsumeTraces(context.Background(), traces)
}
})
require.NoError(t, err)
err = c.Shutdown(context.Background())
require.NoError(t, err)
})
}
}
func generateLifecycleTestLogs() plog.Logs {
logs := plog.NewLogs()
rl := logs.ResourceLogs().AppendEmpty()
rl.Resource().Attributes().PutStr("resource", "R1")
l := rl.ScopeLogs().AppendEmpty().LogRecords().AppendEmpty()
l.Body().SetStr("test log message")
l.SetTimestamp(pcommon.NewTimestampFromTime(time.Now()))
return logs
}
func generateLifecycleTestMetrics() pmetric.Metrics {
metrics := pmetric.NewMetrics()
rm := metrics.ResourceMetrics().AppendEmpty()
rm.Resource().Attributes().PutStr("resource", "R1")
m := rm.ScopeMetrics().AppendEmpty().Metrics().AppendEmpty()
m.SetName("test_metric")
dp := m.SetEmptyGauge().DataPoints().AppendEmpty()
dp.Attributes().PutStr("test_attr", "value_1")
dp.SetIntValue(123)
dp.SetTimestamp(pcommon.NewTimestampFromTime(time.Now()))
return metrics
}
func generateLifecycleTestTraces() ptrace.Traces {
traces := ptrace.NewTraces()
rs := traces.ResourceSpans().AppendEmpty()
rs.Resource().Attributes().PutStr("resource", "R1")
span := rs.ScopeSpans().AppendEmpty().Spans().AppendEmpty()
span.Attributes().PutStr("test_attr", "value_1")
span.SetName("test_span")
span.SetStartTimestamp(pcommon.NewTimestampFromTime(time.Now().Add(-1 * time.Second)))
span.SetEndTimestamp(pcommon.NewTimestampFromTime(time.Now()))
return traces
}
var _ component.Host = (*mdatagenNopHost)(nil)
type mdatagenNopHost struct{}
func newMdatagenNopHost() component.Host {
return &mdatagenNopHost{}
}
func (mnh *mdatagenNopHost) GetExtensions() map[component.ID]component.Component {
return nil
}
func (mnh *mdatagenNopHost) GetFactory(_ component.Kind, _ component.Type) component.Factory {
return nil
}
================================================
FILE: exporter/debugexporter/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package debugexporter
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: exporter/debugexporter/generating-example-output.md
================================================
# Generating example output
This document describes how to generate the example output used in the [README](./README.md)'s [Verbosity levels](./README.md#verbosity-levels) section.
1. Prepare the configuration of the Collector.
```yaml
exporters:
debug/basic:
verbosity: basic
debug/normal:
verbosity: normal
debug/detailed:
verbosity: detailed
receivers:
otlp:
protocols:
grpc:
service:
pipelines:
traces:
exporters:
- debug/basic
- debug/normal
- debug/detailed
receivers:
- otlp
```
2. Run the Collector (download latest version from ).
```console
otelcol --config config.yaml
```
3. Run the `telemetrygen` tool (install latest version with `go install github.com/open-telemetry/opentelemetry-collector-contrib/cmd/telemetrygen@latest`).
```console
telemetrygen traces --otlp-insecure
```
================================================
FILE: exporter/debugexporter/go.mod
================================================
module go.opentelemetry.io/collector/exporter/debugexporter
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.opentelemetry.io/collector/config/configoptional v1.54.0
go.opentelemetry.io/collector/config/configtelemetry v0.148.0
go.opentelemetry.io/collector/confmap v1.54.0
go.opentelemetry.io/collector/consumer v1.54.0
go.opentelemetry.io/collector/exporter v1.54.0
go.opentelemetry.io/collector/exporter/exporterhelper v0.148.0
go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper v0.148.0
go.opentelemetry.io/collector/exporter/exportertest v0.148.0
go.opentelemetry.io/collector/exporter/xexporter v0.148.0
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/collector/pdata/pprofile v0.148.0
go.opentelemetry.io/collector/pdata/testdata v0.148.0
go.opentelemetry.io/collector/pdata/xpdata v0.148.0
go.uber.org/goleak v1.3.0
go.uber.org/zap v1.27.1
golang.org/x/sys v0.42.0
)
require (
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/client v1.54.0 // indirect
go.opentelemetry.io/collector/config/configretry v1.54.0 // indirect
go.opentelemetry.io/collector/confmap/xconfmap v0.148.0 // indirect
go.opentelemetry.io/collector/consumer/consumererror v0.148.0 // indirect
go.opentelemetry.io/collector/consumer/consumererror/xconsumererror v0.148.0 // indirect
go.opentelemetry.io/collector/consumer/consumertest v0.148.0 // indirect
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0 // indirect
go.opentelemetry.io/collector/extension v1.54.0 // indirect
go.opentelemetry.io/collector/extension/xextension v0.148.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
go.opentelemetry.io/collector/pipeline v1.54.0 // indirect
go.opentelemetry.io/collector/pipeline/xpipeline v0.148.0 // indirect
go.opentelemetry.io/collector/receiver v1.54.0 // indirect
go.opentelemetry.io/collector/receiver/receivertest v0.148.0 // indirect
go.opentelemetry.io/collector/receiver/xreceiver v0.148.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest
replace go.opentelemetry.io/collector/confmap => ../../confmap
replace go.opentelemetry.io/collector/consumer => ../../consumer
replace go.opentelemetry.io/collector/exporter => ../
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/pdata/testdata => ../../pdata/testdata
replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile
replace go.opentelemetry.io/collector/receiver => ../../receiver
replace go.opentelemetry.io/collector/receiver/receivertest => ../../receiver/receivertest
replace go.opentelemetry.io/collector/extension => ../../extension
replace go.opentelemetry.io/collector/config/configtelemetry => ../../config/configtelemetry
replace go.opentelemetry.io/collector/config/configretry => ../../config/configretry
replace go.opentelemetry.io/collector/consumer/consumererror/xconsumererror => ../../consumer/consumererror/xconsumererror
replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer
replace go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest
replace go.opentelemetry.io/collector/receiver/xreceiver => ../../receiver/xreceiver
replace go.opentelemetry.io/collector/exporter/xexporter => ../xexporter
replace go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper => ../exporterhelper/xexporterhelper
replace go.opentelemetry.io/collector/pipeline => ../../pipeline
replace go.opentelemetry.io/collector/pipeline/xpipeline => ../../pipeline/xpipeline
replace go.opentelemetry.io/collector/exporter/exportertest => ../exportertest
replace go.opentelemetry.io/collector/consumer/consumererror => ../../consumer/consumererror
replace go.opentelemetry.io/collector/extension/extensiontest => ../../extension/extensiontest
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/extension/xextension => ../../extension/xextension
replace go.opentelemetry.io/collector/client => ../../client
replace go.opentelemetry.io/collector/pdata/xpdata => ../../pdata/xpdata
replace go.opentelemetry.io/collector/config/configoptional => ../../config/configoptional
replace go.opentelemetry.io/collector/confmap/xconfmap => ../../confmap/xconfmap
replace go.opentelemetry.io/collector/exporter/exporterhelper => ../exporterhelper
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
================================================
FILE: exporter/debugexporter/go.sum
================================================
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: exporter/debugexporter/internal/metadata/generated_status.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/component"
)
var (
Type = component.MustNewType("debug")
ScopeName = "go.opentelemetry.io/collector/exporter/debugexporter"
)
const (
TracesStability = component.StabilityLevelAlpha
MetricsStability = component.StabilityLevelAlpha
LogsStability = component.StabilityLevelAlpha
ProfilesStability = component.StabilityLevelAlpha
)
================================================
FILE: exporter/debugexporter/internal/normal/common.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package normal // import "go.opentelemetry.io/collector/exporter/debugexporter/internal/normal"
import (
"fmt"
"strings"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// writeAttributes returns a slice of strings in the form "attrKey=attrValue"
func writeAttributes(attributes pcommon.Map) (attributeStrings []string) {
for k, v := range attributes.All() {
attribute := fmt.Sprintf("%s=%s", k, v.AsString())
attributeStrings = append(attributeStrings, attribute)
}
return attributeStrings
}
// writeAttributesString returns a string in the form " attrKey=attrValue attr2=value2"
func writeAttributesString(attributesMap pcommon.Map) (attributesString string) {
attributes := writeAttributes(attributesMap)
if len(attributes) > 0 {
attributesString = " " + strings.Join(attributes, " ")
}
return attributesString
}
func writeResourceDetails(schemaURL string) (resourceDetails string) {
if schemaURL != "" {
resourceDetails = " [" + schemaURL + "]"
}
return resourceDetails
}
func writeScopeDetails(name, version, schemaURL string) (scopeDetails string) {
if name != "" {
scopeDetails += name
}
if version != "" {
scopeDetails += "@" + version
}
if schemaURL != "" {
if scopeDetails != "" {
scopeDetails += " "
}
scopeDetails += "[" + schemaURL + "]"
}
if scopeDetails != "" {
scopeDetails = " " + scopeDetails
}
return scopeDetails
}
================================================
FILE: exporter/debugexporter/internal/normal/logs.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package normal // import "go.opentelemetry.io/collector/exporter/debugexporter/internal/normal"
import (
"bytes"
"fmt"
"strings"
"go.opentelemetry.io/collector/pdata/plog"
)
type normalLogsMarshaler struct{}
// Ensure normalLogsMarshaller implements interface plog.Marshaler
var _ plog.Marshaler = normalLogsMarshaler{}
// NewNormalLogsMarshaler returns a plog.Marshaler for normal verbosity. It writes one line of text per log record
func NewNormalLogsMarshaler() plog.Marshaler {
return normalLogsMarshaler{}
}
func (normalLogsMarshaler) MarshalLogs(ld plog.Logs) ([]byte, error) {
var buffer bytes.Buffer
for i := 0; i < ld.ResourceLogs().Len(); i++ {
resourceLog := ld.ResourceLogs().At(i)
buffer.WriteString(fmt.Sprintf("ResourceLog #%d%s%s\n", i, writeResourceDetails(resourceLog.SchemaUrl()), writeAttributesString(resourceLog.Resource().Attributes())))
for j := 0; j < resourceLog.ScopeLogs().Len(); j++ {
scopeLog := resourceLog.ScopeLogs().At(j)
buffer.WriteString(fmt.Sprintf("ScopeLog #%d%s%s\n", i, writeScopeDetails(scopeLog.Scope().Name(), scopeLog.Scope().Version(), scopeLog.SchemaUrl()), writeAttributesString(scopeLog.Scope().Attributes())))
for k := 0; k < scopeLog.LogRecords().Len(); k++ {
logRecord := scopeLog.LogRecords().At(k)
logAttributes := writeAttributes(logRecord.Attributes())
logString := fmt.Sprintf("%s %s", logRecord.Body().AsString(), strings.Join(logAttributes, " "))
buffer.WriteString(logString)
buffer.WriteString("\n")
}
}
}
return buffer.Bytes(), nil
}
================================================
FILE: exporter/debugexporter/internal/normal/logs_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package normal
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
)
func TestMarshalLogs(t *testing.T) {
tests := []struct {
name string
input plog.Logs
expected string
}{
{
name: "empty logs",
input: plog.NewLogs(),
expected: "",
},
{
name: "one log record",
input: func() plog.Logs {
logs := plog.NewLogs()
logRecord := logs.ResourceLogs().AppendEmpty().ScopeLogs().AppendEmpty().LogRecords().AppendEmpty()
logRecord.SetTimestamp(pcommon.NewTimestampFromTime(time.Date(2024, 1, 23, 17, 54, 41, 153, time.UTC)))
logRecord.SetSeverityNumber(plog.SeverityNumberInfo)
logRecord.SetSeverityText("INFO")
logRecord.Body().SetStr("Single line log message")
logRecord.Attributes().PutStr("key1", "value1")
logRecord.Attributes().PutStr("key2", "value2")
return logs
}(),
expected: `ResourceLog #0
ScopeLog #0
Single line log message key1=value1 key2=value2
`,
},
{
name: "one log record with resource and scope attributes",
input: func() plog.Logs {
logs := plog.NewLogs()
resourceLogs := logs.ResourceLogs().AppendEmpty()
resourceLogs.SetSchemaUrl("https://opentelemetry.io/resource-schema-url")
resourceLogs.Resource().Attributes().PutStr("resourceKey1", "resourceValue1")
resourceLogs.Resource().Attributes().PutBool("resourceKey2", false)
scopeLogs := resourceLogs.ScopeLogs().AppendEmpty()
scopeLogs.SetSchemaUrl("http://opentelemetry.io/scope-schema-url")
scopeLogs.Scope().SetName("scope-name")
scopeLogs.Scope().SetVersion("1.2.3")
scopeLogs.Scope().Attributes().PutStr("scopeKey1", "scopeValue1")
scopeLogs.Scope().Attributes().PutBool("scopeKey2", true)
logRecord := scopeLogs.LogRecords().AppendEmpty()
logRecord.SetTimestamp(pcommon.NewTimestampFromTime(time.Date(2024, 1, 23, 17, 54, 41, 153, time.UTC)))
logRecord.SetSeverityNumber(plog.SeverityNumberInfo)
logRecord.SetSeverityText("INFO")
logRecord.Body().SetStr("Single line log message")
logRecord.Attributes().PutStr("key1", "value1")
logRecord.Attributes().PutStr("key2", "value2")
return logs
}(),
expected: `ResourceLog #0 [https://opentelemetry.io/resource-schema-url] resourceKey1=resourceValue1 resourceKey2=false
ScopeLog #0 scope-name@1.2.3 [http://opentelemetry.io/scope-schema-url] scopeKey1=scopeValue1 scopeKey2=true
Single line log message key1=value1 key2=value2
`,
},
{
name: "multiline log",
input: func() plog.Logs {
logs := plog.NewLogs()
logRecord := logs.ResourceLogs().AppendEmpty().ScopeLogs().AppendEmpty().LogRecords().AppendEmpty()
logRecord.SetTimestamp(pcommon.NewTimestampFromTime(time.Date(2024, 1, 23, 17, 54, 41, 153, time.UTC)))
logRecord.SetSeverityNumber(plog.SeverityNumberInfo)
logRecord.SetSeverityText("INFO")
logRecord.Body().SetStr("First line of the log message\n second line of the log message")
logRecord.Attributes().PutStr("key1", "value1")
logRecord.Attributes().PutStr("key2", "value2")
return logs
}(),
expected: `ResourceLog #0
ScopeLog #0
First line of the log message
second line of the log message key1=value1 key2=value2
`,
},
{
name: "two log records",
input: func() plog.Logs {
logs := plog.NewLogs()
logRecords := logs.ResourceLogs().AppendEmpty().ScopeLogs().AppendEmpty().LogRecords()
logRecord := logRecords.AppendEmpty()
logRecord.SetTimestamp(pcommon.NewTimestampFromTime(time.Date(2024, 1, 23, 17, 54, 41, 153, time.UTC)))
logRecord.SetSeverityNumber(plog.SeverityNumberInfo)
logRecord.SetSeverityText("INFO")
logRecord.Body().SetStr("Single line log message")
logRecord.Attributes().PutStr("key1", "value1")
logRecord.Attributes().PutStr("key2", "value2")
logRecord = logRecords.AppendEmpty()
logRecord.Body().SetStr("Multi-line\nlog message")
logRecord.Attributes().PutStr("mykey2", "myvalue2")
logRecord.Attributes().PutStr("mykey1", "myvalue1")
return logs
}(),
expected: `ResourceLog #0
ScopeLog #0
Single line log message key1=value1 key2=value2
Multi-line
log message mykey2=myvalue2 mykey1=myvalue1
`,
},
{
name: "log with maps in body and attributes",
input: func() plog.Logs {
logs := plog.NewLogs()
logRecord := logs.ResourceLogs().AppendEmpty().ScopeLogs().AppendEmpty().LogRecords().AppendEmpty()
logRecord.SetTimestamp(pcommon.NewTimestampFromTime(time.Date(2020, 2, 11, 20, 26, 13, 789, time.UTC)))
logRecord.SetSeverityNumber(plog.SeverityNumberInfo)
logRecord.SetSeverityText("INFO")
body := logRecord.Body().SetEmptyMap()
body.PutStr("app", "CurrencyConverter")
bodyEvent := body.PutEmptyMap("event")
bodyEvent.PutStr("operation", "convert")
bodyEvent.PutStr("result", "success")
conversionAttr := logRecord.Attributes().PutEmptyMap("conversion")
conversionSourceAttr := conversionAttr.PutEmptyMap("source")
conversionSourceAttr.PutStr("currency", "USD")
conversionSourceAttr.PutDouble("amount", 34.22)
conversionDestinationAttr := conversionAttr.PutEmptyMap("destination")
conversionDestinationAttr.PutStr("currency", "EUR")
logRecord.Attributes().PutStr("service", "payments")
return logs
}(),
expected: `ResourceLog #0
ScopeLog #0
{"app":"CurrencyConverter","event":{"operation":"convert","result":"success"}} conversion={"destination":{"currency":"EUR"},"source":{"amount":34.22,"currency":"USD"}} service=payments
`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
output, err := NewNormalLogsMarshaler().MarshalLogs(tt.input)
require.NoError(t, err)
assert.Equal(t, tt.expected, string(output))
})
}
}
================================================
FILE: exporter/debugexporter/internal/normal/metrics.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package normal // import "go.opentelemetry.io/collector/exporter/debugexporter/internal/normal"
import (
"bytes"
"fmt"
"math"
"strconv"
"strings"
"go.opentelemetry.io/collector/pdata/pmetric"
)
type normalMetricsMarshaler struct{}
// Ensure normalMetricsMarshaller implements interface pmetric.Marshaler
var _ pmetric.Marshaler = normalMetricsMarshaler{}
// NewNormalMetricsMarshaler returns a pmetric.Marshaler for normal verbosity. It writes one line of text per log record
func NewNormalMetricsMarshaler() pmetric.Marshaler {
return normalMetricsMarshaler{}
}
func (normalMetricsMarshaler) MarshalMetrics(md pmetric.Metrics) ([]byte, error) {
var buffer bytes.Buffer
for i := 0; i < md.ResourceMetrics().Len(); i++ {
resourceMetrics := md.ResourceMetrics().At(i)
buffer.WriteString(fmt.Sprintf("ResourceMetrics #%d%s%s\n", i, writeResourceDetails(resourceMetrics.SchemaUrl()), writeAttributesString(resourceMetrics.Resource().Attributes())))
for j := 0; j < resourceMetrics.ScopeMetrics().Len(); j++ {
scopeMetrics := resourceMetrics.ScopeMetrics().At(j)
buffer.WriteString(fmt.Sprintf("ScopeMetrics #%d%s%s\n", i, writeScopeDetails(scopeMetrics.Scope().Name(), scopeMetrics.Scope().Version(), scopeMetrics.SchemaUrl()), writeAttributesString(scopeMetrics.Scope().Attributes())))
for k := 0; k < scopeMetrics.Metrics().Len(); k++ {
metric := scopeMetrics.Metrics().At(k)
var dataPointLines []string
switch metric.Type() {
case pmetric.MetricTypeGauge:
dataPointLines = writeNumberDataPoints(metric, metric.Gauge().DataPoints())
case pmetric.MetricTypeSum:
dataPointLines = writeNumberDataPoints(metric, metric.Sum().DataPoints())
case pmetric.MetricTypeHistogram:
dataPointLines = writeHistogramDataPoints(metric)
case pmetric.MetricTypeExponentialHistogram:
dataPointLines = writeExponentialHistogramDataPoints(metric)
case pmetric.MetricTypeSummary:
dataPointLines = writeSummaryDataPoints(metric)
}
for _, line := range dataPointLines {
buffer.WriteString(line)
}
}
}
}
return buffer.Bytes(), nil
}
func writeNumberDataPoints(metric pmetric.Metric, dataPoints pmetric.NumberDataPointSlice) (lines []string) {
for i := 0; i < dataPoints.Len(); i++ {
dataPoint := dataPoints.At(i)
dataPointAttributes := writeAttributes(dataPoint.Attributes())
var value string
switch dataPoint.ValueType() {
case pmetric.NumberDataPointValueTypeInt:
value = strconv.FormatInt(dataPoint.IntValue(), 10)
case pmetric.NumberDataPointValueTypeDouble:
value = fmt.Sprintf("%v", dataPoint.DoubleValue())
}
dataPointLine := fmt.Sprintf("%s{%s} %s\n", metric.Name(), strings.Join(dataPointAttributes, ","), value)
lines = append(lines, dataPointLine)
}
return lines
}
func writeHistogramDataPoints(metric pmetric.Metric) (lines []string) {
for i := 0; i < metric.Histogram().DataPoints().Len(); i++ {
dataPoint := metric.Histogram().DataPoints().At(i)
dataPointAttributes := writeAttributes(dataPoint.Attributes())
var value strings.Builder
fmt.Fprintf(&value, "count=%d", dataPoint.Count())
if dataPoint.HasSum() {
fmt.Fprintf(&value, " sum=%v", dataPoint.Sum())
}
if dataPoint.HasMin() {
fmt.Fprintf(&value, " min=%v", dataPoint.Min())
}
if dataPoint.HasMax() {
fmt.Fprintf(&value, " max=%v", dataPoint.Max())
}
for bucketIndex := 0; bucketIndex < dataPoint.BucketCounts().Len(); bucketIndex++ {
bucketBound := ""
if bucketIndex < dataPoint.ExplicitBounds().Len() {
bucketBound = fmt.Sprintf("le%v=", dataPoint.ExplicitBounds().At(bucketIndex))
}
bucketCount := dataPoint.BucketCounts().At(bucketIndex)
fmt.Fprintf(&value, " %s%d", bucketBound, bucketCount)
}
dataPointLine := fmt.Sprintf("%s{%s} %s\n", metric.Name(), strings.Join(dataPointAttributes, ","), value.String())
lines = append(lines, dataPointLine)
}
return lines
}
func writeExponentialHistogramDataPoints(metric pmetric.Metric) (lines []string) {
for i := 0; i < metric.ExponentialHistogram().DataPoints().Len(); i++ {
dataPoint := metric.ExponentialHistogram().DataPoints().At(i)
dataPointAttributes := writeAttributes(dataPoint.Attributes())
var value strings.Builder
fmt.Fprintf(&value, "count=%d", dataPoint.Count())
if dataPoint.HasSum() {
fmt.Fprintf(&value, " sum=%v", dataPoint.Sum())
}
if dataPoint.HasMin() {
fmt.Fprintf(&value, " min=%v", dataPoint.Min())
}
if dataPoint.HasMax() {
fmt.Fprintf(&value, " max=%v", dataPoint.Max())
}
factor := math.Ldexp(math.Ln2, -int(dataPoint.Scale()))
negB := dataPoint.Negative()
for j := negB.BucketCounts().Len() - 1; j >= 0; j-- {
index := float64(negB.Offset()) + float64(j)
upperBound := -math.Exp(index * factor)
fmt.Fprintf(&value, " le%v=%d", upperBound, negB.BucketCounts().At(j))
}
if dataPoint.ZeroCount() != 0 {
fmt.Fprintf(&value, " zero=%d", dataPoint.ZeroCount())
}
posB := dataPoint.Positive()
for j := 0; j < posB.BucketCounts().Len(); j++ {
index := float64(posB.Offset()) + float64(j)
upperBound := math.Exp((index + 1) * factor)
fmt.Fprintf(&value, " le%v=%d", upperBound, posB.BucketCounts().At(j))
}
dataPointLine := fmt.Sprintf("%s{%s} %s\n", metric.Name(), strings.Join(dataPointAttributes, ","), value.String())
lines = append(lines, dataPointLine)
}
return lines
}
func writeSummaryDataPoints(metric pmetric.Metric) (lines []string) {
for i := 0; i < metric.Summary().DataPoints().Len(); i++ {
dataPoint := metric.Summary().DataPoints().At(i)
dataPointAttributes := writeAttributes(dataPoint.Attributes())
var value strings.Builder
fmt.Fprintf(&value, "count=%d", dataPoint.Count())
fmt.Fprintf(&value, " sum=%f", dataPoint.Sum())
for quantileIndex := 0; quantileIndex < dataPoint.QuantileValues().Len(); quantileIndex++ {
quantile := dataPoint.QuantileValues().At(quantileIndex)
fmt.Fprintf(&value, " q%v=%v", quantile.Quantile(), quantile.Value())
}
dataPointLine := fmt.Sprintf("%s{%s} %s\n", metric.Name(), strings.Join(dataPointAttributes, ","), value.String())
lines = append(lines, dataPointLine)
}
return lines
}
================================================
FILE: exporter/debugexporter/internal/normal/metrics_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package normal
import (
"fmt"
"math"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/pmetric"
)
func TestMarshalMetrics(t *testing.T) {
tests := []struct {
name string
input pmetric.Metrics
expected string
}{
{
name: "empty metrics",
input: pmetric.NewMetrics(),
expected: "",
},
{
name: "sum data point",
input: func() pmetric.Metrics {
metrics := pmetric.NewMetrics()
metric := metrics.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty()
metric.SetName("system.cpu.time")
dataPoint := metric.SetEmptySum().DataPoints().AppendEmpty()
dataPoint.SetDoubleValue(123.456)
dataPoint.Attributes().PutStr("state", "user")
dataPoint.Attributes().PutStr("cpu", "0")
return metrics
}(),
expected: `ResourceMetrics #0
ScopeMetrics #0
system.cpu.time{state=user,cpu=0} 123.456
`,
},
{
name: "data point with resource and scope attributes",
input: func() pmetric.Metrics {
metrics := pmetric.NewMetrics()
resourceMetrics := metrics.ResourceMetrics().AppendEmpty()
resourceMetrics.SetSchemaUrl("https://opentelemetry.io/resource-schema-url")
resourceMetrics.Resource().Attributes().PutStr("resourceKey1", "resourceValue1")
resourceMetrics.Resource().Attributes().PutBool("resourceKey2", false)
scopeMetrics := resourceMetrics.ScopeMetrics().AppendEmpty()
scopeMetrics.SetSchemaUrl("http://opentelemetry.io/scope-schema-url")
scopeMetrics.Scope().SetName("scope-name")
scopeMetrics.Scope().SetVersion("1.2.3")
scopeMetrics.Scope().Attributes().PutStr("scopeKey1", "scopeValue1")
scopeMetrics.Scope().Attributes().PutBool("scopeKey2", true)
metric := scopeMetrics.Metrics().AppendEmpty()
metric.SetName("system.cpu.time")
dataPoint := metric.SetEmptySum().DataPoints().AppendEmpty()
dataPoint.SetDoubleValue(123.456)
dataPoint.Attributes().PutStr("state", "user")
dataPoint.Attributes().PutStr("cpu", "0")
return metrics
}(),
expected: `ResourceMetrics #0 [https://opentelemetry.io/resource-schema-url] resourceKey1=resourceValue1 resourceKey2=false
ScopeMetrics #0 scope-name@1.2.3 [http://opentelemetry.io/scope-schema-url] scopeKey1=scopeValue1 scopeKey2=true
system.cpu.time{state=user,cpu=0} 123.456
`,
},
{
name: "gauge data point",
input: func() pmetric.Metrics {
metrics := pmetric.NewMetrics()
metric := metrics.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty()
metric.SetName("system.cpu.utilization")
dataPoint := metric.SetEmptyGauge().DataPoints().AppendEmpty()
dataPoint.SetDoubleValue(78.901234567)
dataPoint.Attributes().PutStr("state", "free")
dataPoint.Attributes().PutStr("cpu", "8")
return metrics
}(),
expected: `ResourceMetrics #0
ScopeMetrics #0
system.cpu.utilization{state=free,cpu=8} 78.901234567
`,
},
{
name: "histogram",
input: func() pmetric.Metrics {
metrics := pmetric.NewMetrics()
metric := metrics.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty()
metric.SetName("http.server.request.duration")
dataPoint := metric.SetEmptyHistogram().DataPoints().AppendEmpty()
dataPoint.Attributes().PutInt("http.response.status_code", 200)
dataPoint.Attributes().PutStr("http.request.method", "GET")
dataPoint.ExplicitBounds().FromRaw([]float64{0.125, 0.5, 1, 3})
dataPoint.BucketCounts().FromRaw([]uint64{1324, 13, 0, 2, 1})
dataPoint.SetCount(1340)
dataPoint.SetSum(99.573)
dataPoint.SetMin(0.017)
dataPoint.SetMax(8.13)
return metrics
}(),
expected: `ResourceMetrics #0
ScopeMetrics #0
http.server.request.duration{http.response.status_code=200,http.request.method=GET} count=1340 sum=99.573 min=0.017 max=8.13 le0.125=1324 le0.5=13 le1=0 le3=2 1
`,
},
{
name: "exponential histogram without buckets",
input: func() pmetric.Metrics {
metrics := pmetric.NewMetrics()
metric := metrics.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty()
metric.SetName("http.server.request.duration")
dataPoint := metric.SetEmptyExponentialHistogram().DataPoints().AppendEmpty()
dataPoint.Attributes().PutInt("http.response.status_code", 200)
dataPoint.Attributes().PutStr("http.request.method", "GET")
dataPoint.SetCount(1340)
dataPoint.SetSum(99.573)
dataPoint.SetMin(0.017)
dataPoint.SetMax(8.13)
return metrics
}(),
expected: `ResourceMetrics #0
ScopeMetrics #0
http.server.request.duration{http.response.status_code=200,http.request.method=GET} count=1340 sum=99.573 min=0.017 max=8.13
`,
},
{
name: "exponential histogram with buckets",
input: func() pmetric.Metrics {
metrics := pmetric.NewMetrics()
metric := metrics.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty()
metric.SetName("http.server.request.duration")
dataPoint := metric.SetEmptyExponentialHistogram().DataPoints().AppendEmpty()
dataPoint.Attributes().PutInt("http.response.status_code", 200)
dataPoint.Attributes().PutStr("http.request.method", "GET")
dataPoint.SetCount(1340)
dataPoint.SetSum(99.573)
dataPoint.SetMin(0.017)
dataPoint.SetMax(8.13)
dataPoint.SetScale(3)
dataPoint.SetZeroCount(3)
dataPoint.Negative().SetOffset(-2)
dataPoint.Negative().BucketCounts().FromRaw([]uint64{10, 20})
dataPoint.Positive().SetOffset(1)
dataPoint.Positive().BucketCounts().FromRaw([]uint64{40, 50, 60})
return metrics
}(),
expected: func() string {
f := math.Ldexp(math.Ln2, -3)
return fmt.Sprintf("ResourceMetrics #0\nScopeMetrics #0\n"+
"http.server.request.duration{http.response.status_code=200,http.request.method=GET}"+
" count=1340 sum=99.573 min=0.017 max=8.13"+
" le%v=20 le%v=10 zero=3 le%v=40 le%v=50 le%v=60\n",
-math.Exp(-1*f), -math.Exp(-2*f),
math.Exp(2*f), math.Exp(3*f), math.Exp(4*f))
}(),
},
{
name: "exponential histogram with positive buckets only",
input: func() pmetric.Metrics {
metrics := pmetric.NewMetrics()
metric := metrics.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty()
metric.SetName("latency")
dataPoint := metric.SetEmptyExponentialHistogram().DataPoints().AppendEmpty()
dataPoint.SetCount(2)
dataPoint.SetScale(1)
dataPoint.Positive().SetOffset(1)
dataPoint.Positive().BucketCounts().FromRaw([]uint64{1, 1})
return metrics
}(),
expected: func() string {
f := math.Ldexp(math.Ln2, -1)
return fmt.Sprintf("ResourceMetrics #0\nScopeMetrics #0\n"+
"latency{} count=2 le%v=1 le%v=1\n",
math.Exp(2*f), math.Exp(3*f))
}(),
},
{
name: "summary",
input: func() pmetric.Metrics {
metrics := pmetric.NewMetrics()
metric := metrics.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty()
metric.SetName("summary")
dataPoint := metric.SetEmptySummary().DataPoints().AppendEmpty()
dataPoint.Attributes().PutInt("http.response.status_code", 200)
dataPoint.Attributes().PutStr("http.request.method", "GET")
dataPoint.SetCount(1340)
dataPoint.SetSum(99.573)
quantile := dataPoint.QuantileValues().AppendEmpty()
quantile.SetQuantile(0.01)
quantile.SetValue(15)
return metrics
}(),
expected: `ResourceMetrics #0
ScopeMetrics #0
summary{http.response.status_code=200,http.request.method=GET} count=1340 sum=99.573000 q0.01=15
`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
output, err := NewNormalMetricsMarshaler().MarshalMetrics(tt.input)
require.NoError(t, err)
assert.Equal(t, tt.expected, string(output))
})
}
}
================================================
FILE: exporter/debugexporter/internal/normal/profiles.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package normal // import "go.opentelemetry.io/collector/exporter/debugexporter/internal/normal"
import (
"bytes"
"fmt"
"strconv"
"strings"
"go.opentelemetry.io/collector/pdata/pprofile"
)
type normalProfilesMarshaler struct{}
// Ensure normalProfilesMarshaller implements interface pprofile.Marshaler
var _ pprofile.Marshaler = normalProfilesMarshaler{}
// NewNormalProfilesMarshaler returns a pprofile.Marshaler for normal verbosity. It writes one line of text per log record
func NewNormalProfilesMarshaler() pprofile.Marshaler {
return normalProfilesMarshaler{}
}
func (normalProfilesMarshaler) MarshalProfiles(pd pprofile.Profiles) ([]byte, error) {
var buffer bytes.Buffer
dic := pd.Dictionary()
for i := 0; i < pd.ResourceProfiles().Len(); i++ {
resourceProfiles := pd.ResourceProfiles().At(i)
buffer.WriteString(fmt.Sprintf("ResourceProfiles #%d%s%s\n", i, writeResourceDetails(resourceProfiles.SchemaUrl()), writeAttributesString(resourceProfiles.Resource().Attributes())))
for j := 0; j < resourceProfiles.ScopeProfiles().Len(); j++ {
scopeProfiles := resourceProfiles.ScopeProfiles().At(j)
buffer.WriteString(fmt.Sprintf("ScopeProfiles #%d%s%s\n", i, writeScopeDetails(scopeProfiles.Scope().Name(), scopeProfiles.Scope().Version(), scopeProfiles.SchemaUrl()), writeAttributesString(scopeProfiles.Scope().Attributes())))
for k := 0; k < scopeProfiles.Profiles().Len(); k++ {
profile := scopeProfiles.Profiles().At(k)
buffer.WriteString(profile.ProfileID().String())
buffer.WriteString(" samples=")
buffer.WriteString(strconv.Itoa(profile.Samples().Len()))
if profile.AttributeIndices().Len() > 0 {
attrs := []string{}
for _, i := range profile.AttributeIndices().AsRaw() {
a := dic.AttributeTable().At(int(i))
attrs = append(attrs, fmt.Sprintf("%s=%s", dic.StringTable().At(int(a.KeyStrindex())), a.Value().AsString()))
}
buffer.WriteString(" ")
buffer.WriteString(strings.Join(attrs, " "))
}
buffer.WriteString("\n")
}
}
}
return buffer.Bytes(), nil
}
================================================
FILE: exporter/debugexporter/internal/normal/profiles_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package normal
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/pprofile"
)
func TestMarshalProfiles(t *testing.T) {
tests := []struct {
name string
input pprofile.Profiles
expected string
}{
{
name: "empty profile",
input: pprofile.NewProfiles(),
expected: "",
},
{
name: "one profile",
input: func() pprofile.Profiles {
profiles := pprofile.NewProfiles()
dic := profiles.Dictionary()
dic.StringTable().Append("")
dic.StringTable().Append("key1")
a := dic.AttributeTable().AppendEmpty()
a.SetKeyStrindex(1)
a.Value().SetStr("value1")
profile := profiles.ResourceProfiles().AppendEmpty().ScopeProfiles().AppendEmpty().Profiles().AppendEmpty()
profiles.ResourceProfiles().At(0).SetSchemaUrl("https://example.com/resource")
profiles.ResourceProfiles().At(0).Resource().Attributes().PutStr("resourceKey", "resourceValue")
profiles.ResourceProfiles().At(0).ScopeProfiles().At(0).SetSchemaUrl("https://example.com/scope")
profiles.ResourceProfiles().At(0).ScopeProfiles().At(0).Scope().SetName("scope-name")
profiles.ResourceProfiles().At(0).ScopeProfiles().At(0).Scope().SetVersion("1.2.3")
profiles.ResourceProfiles().At(0).ScopeProfiles().At(0).Scope().Attributes().PutStr("scopeKey", "scopeValue")
profile.SetProfileID([16]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10})
profile.Samples().AppendEmpty()
profile.Samples().AppendEmpty()
profile.AttributeIndices().Append(0)
return profiles
}(),
expected: `ResourceProfiles #0 [https://example.com/resource] resourceKey=resourceValue
ScopeProfiles #0 scope-name@1.2.3 [https://example.com/scope] scopeKey=scopeValue
0102030405060708090a0b0c0d0e0f10 samples=2 key1=value1
`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
output, err := NewNormalProfilesMarshaler().MarshalProfiles(tt.input)
require.NoError(t, err)
assert.Equal(t, tt.expected, string(output))
})
}
}
================================================
FILE: exporter/debugexporter/internal/normal/traces.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package normal // import "go.opentelemetry.io/collector/exporter/debugexporter/internal/normal"
import (
"bytes"
"fmt"
"strings"
"go.opentelemetry.io/collector/pdata/ptrace"
)
type normalTracesMarshaler struct{}
// Ensure normalTracesMarshaller implements interface ptrace.Marshaler
var _ ptrace.Marshaler = normalTracesMarshaler{}
// NewNormalTracesMarshaler returns a ptrace.Marshaler for normal verbosity. It writes one line of text per log record
func NewNormalTracesMarshaler() ptrace.Marshaler {
return normalTracesMarshaler{}
}
func (normalTracesMarshaler) MarshalTraces(md ptrace.Traces) ([]byte, error) {
var buffer bytes.Buffer
for i := 0; i < md.ResourceSpans().Len(); i++ {
resourceTraces := md.ResourceSpans().At(i)
buffer.WriteString(fmt.Sprintf("ResourceTraces #%d%s%s\n", i, writeResourceDetails(resourceTraces.SchemaUrl()), writeAttributesString(resourceTraces.Resource().Attributes())))
for j := 0; j < resourceTraces.ScopeSpans().Len(); j++ {
scopeTraces := resourceTraces.ScopeSpans().At(j)
buffer.WriteString(fmt.Sprintf("ScopeTraces #%d%s%s\n", i, writeScopeDetails(scopeTraces.Scope().Name(), scopeTraces.Scope().Version(), scopeTraces.SchemaUrl()), writeAttributesString(scopeTraces.Scope().Attributes())))
for k := 0; k < scopeTraces.Spans().Len(); k++ {
span := scopeTraces.Spans().At(k)
buffer.WriteString(span.Name())
buffer.WriteString(" ")
buffer.WriteString(span.TraceID().String())
buffer.WriteString(" ")
buffer.WriteString(span.SpanID().String())
if span.Attributes().Len() > 0 {
spanAttributes := writeAttributes(span.Attributes())
buffer.WriteString(" ")
buffer.WriteString(strings.Join(spanAttributes, " "))
}
buffer.WriteString("\n")
}
}
}
return buffer.Bytes(), nil
}
================================================
FILE: exporter/debugexporter/internal/normal/traces_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package normal
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/ptrace"
)
func TestMarshalTraces(t *testing.T) {
tests := []struct {
name string
input ptrace.Traces
expected string
}{
{
name: "empty traces",
input: ptrace.NewTraces(),
expected: "",
},
{
name: "one span with resource and scope attributes",
input: func() ptrace.Traces {
traces := ptrace.NewTraces()
resourceSpans := traces.ResourceSpans().AppendEmpty()
resourceSpans.SetSchemaUrl("https://opentelemetry.io/resource-schema-url")
resourceSpans.Resource().Attributes().PutStr("resourceKey1", "resourceValue1")
resourceSpans.Resource().Attributes().PutBool("resourceKey2", false)
scopeSpans := resourceSpans.ScopeSpans().AppendEmpty()
scopeSpans.SetSchemaUrl("http://opentelemetry.io/scope-schema-url")
scopeSpans.Scope().SetName("scope-name")
scopeSpans.Scope().SetVersion("1.2.3")
scopeSpans.Scope().Attributes().PutStr("scopeKey1", "scopeValue1")
scopeSpans.Scope().Attributes().PutBool("scopeKey2", true)
span := scopeSpans.Spans().AppendEmpty()
span.SetName("span-name")
span.SetTraceID([16]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10})
span.SetSpanID([8]byte{0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18})
span.Attributes().PutStr("key1", "value1")
span.Attributes().PutStr("key2", "value2")
return traces
}(),
expected: `ResourceTraces #0 [https://opentelemetry.io/resource-schema-url] resourceKey1=resourceValue1 resourceKey2=false
ScopeTraces #0 scope-name@1.2.3 [http://opentelemetry.io/scope-schema-url] scopeKey1=scopeValue1 scopeKey2=true
span-name 0102030405060708090a0b0c0d0e0f10 1112131415161718 key1=value1 key2=value2
`,
},
{
name: "one span",
input: func() ptrace.Traces {
traces := ptrace.NewTraces()
span := traces.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans().AppendEmpty()
span.SetName("span-name")
span.SetTraceID([16]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10})
span.SetSpanID([8]byte{0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18})
span.Attributes().PutStr("key1", "value1")
span.Attributes().PutStr("key2", "value2")
return traces
}(),
expected: `ResourceTraces #0
ScopeTraces #0
span-name 0102030405060708090a0b0c0d0e0f10 1112131415161718 key1=value1 key2=value2
`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
output, err := NewNormalTracesMarshaler().MarshalTraces(tt.input)
require.NoError(t, err)
assert.Equal(t, tt.expected, string(output))
})
}
}
================================================
FILE: exporter/debugexporter/internal/otlptext/databuffer.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlptext // import "go.opentelemetry.io/collector/exporter/debugexporter/internal/otlptext"
import (
"bytes"
"fmt"
"math"
"strings"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/pdata/xpdata/entity"
)
type dataBuffer struct {
buf bytes.Buffer
}
func (b *dataBuffer) logEntry(format string, a ...any) {
b.buf.WriteString(fmt.Sprintf(format, a...))
b.buf.WriteString("\n")
}
func (b *dataBuffer) logAttr(attr string, value any) {
b.logEntry(" %-15s: %s", attr, value)
}
func (b *dataBuffer) logAttributes(header string, m pcommon.Map) {
if m.Len() == 0 {
return
}
b.logEntry("%s:", header)
attrPrefix := " ->"
// Add offset to attributes if needed.
headerParts := strings.Split(header, "->")
if len(headerParts) > 1 {
attrPrefix = headerParts[0] + attrPrefix
}
for k, v := range m.All() {
b.logEntry("%s %s: %s", attrPrefix, k, valueToString(v))
}
}
func (b *dataBuffer) logEntityRefs(resource pcommon.Resource) {
entityRefs := entity.ResourceEntityRefs(resource)
if entityRefs.Len() == 0 {
return
}
b.logEntry("Resource entity refs:")
for i := 0; i < entityRefs.Len(); i++ {
entityRef := entityRefs.At(i)
b.logEntry(" -> Entity ref #%d:", i)
b.logEntry(" -> Type: %s", entityRef.Type())
if entityRef.SchemaUrl() != "" {
b.logEntry(" -> Schema URL: %s", entityRef.SchemaUrl())
}
idKeys := entityRef.IdKeys()
if idKeys.Len() > 0 {
b.logEntry(" -> ID keys:")
for j := 0; j < idKeys.Len(); j++ {
b.logEntry(" -> %s", idKeys.At(j))
}
}
descKeys := entityRef.DescriptionKeys()
if descKeys.Len() > 0 {
b.logEntry(" -> Description keys:")
for j := 0; j < descKeys.Len(); j++ {
b.logEntry(" -> %s", descKeys.At(j))
}
}
}
}
func (b *dataBuffer) logAttributesWithIndentation(header string, m pcommon.Map, indentVal int) {
if m.Len() == 0 {
return
}
indent := strings.Repeat(" ", indentVal)
b.logEntry("%s%s:", indent, header)
attrPrefix := indent + " ->"
// Add offset to attributes if needed.
headerParts := strings.Split(header, "->")
if len(headerParts) > 1 {
attrPrefix = headerParts[0] + attrPrefix
}
for k, v := range m.All() {
b.logEntry("%s %s: %s", attrPrefix, k, valueToString(v))
}
}
func (b *dataBuffer) logInstrumentationScope(il pcommon.InstrumentationScope) {
b.logEntry(
"InstrumentationScope %s %s",
il.Name(),
il.Version())
b.logAttributes("InstrumentationScope attributes", il.Attributes())
}
func (b *dataBuffer) logMetricDescriptor(md pmetric.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.Type().String())
b.logAttributes(" -> Metadata", md.Metadata())
}
func (b *dataBuffer) logMetricDataPoints(m pmetric.Metric) {
switch m.Type() {
case pmetric.MetricTypeEmpty:
return
case pmetric.MetricTypeGauge:
b.logNumberDataPoints(m.Gauge().DataPoints())
case pmetric.MetricTypeSum:
data := m.Sum()
b.logEntry(" -> IsMonotonic: %t", data.IsMonotonic())
b.logEntry(" -> AggregationTemporality: %s", data.AggregationTemporality().String())
b.logNumberDataPoints(data.DataPoints())
case pmetric.MetricTypeHistogram:
data := m.Histogram()
b.logEntry(" -> AggregationTemporality: %s", data.AggregationTemporality().String())
b.logHistogramDataPoints(data.DataPoints())
case pmetric.MetricTypeExponentialHistogram:
data := m.ExponentialHistogram()
b.logEntry(" -> AggregationTemporality: %s", data.AggregationTemporality().String())
b.logExponentialHistogramDataPoints(data.DataPoints())
case pmetric.MetricTypeSummary:
data := m.Summary()
b.logDoubleSummaryDataPoints(data.DataPoints())
}
}
func (b *dataBuffer) logNumberDataPoints(ps pmetric.NumberDataPointSlice) {
for i := 0; i < ps.Len(); i++ {
p := ps.At(i)
b.logEntry("NumberDataPoints #%d", i)
b.logDataPointAttributes(p.Attributes())
b.logEntry("StartTimestamp: %s", p.StartTimestamp())
b.logEntry("Timestamp: %s", p.Timestamp())
switch p.ValueType() {
case pmetric.NumberDataPointValueTypeInt:
b.logEntry("Value: %d", p.IntValue())
case pmetric.NumberDataPointValueTypeDouble:
b.logEntry("Value: %f", p.DoubleValue())
}
b.logExemplars("Exemplars", p.Exemplars())
}
}
func (b *dataBuffer) logHistogramDataPoints(ps pmetric.HistogramDataPointSlice) {
for i := 0; i < ps.Len(); i++ {
p := ps.At(i)
b.logEntry("HistogramDataPoints #%d", i)
b.logDataPointAttributes(p.Attributes())
b.logEntry("StartTimestamp: %s", p.StartTimestamp())
b.logEntry("Timestamp: %s", p.Timestamp())
b.logEntry("Count: %d", p.Count())
if p.HasSum() {
b.logEntry("Sum: %f", p.Sum())
}
if p.HasMin() {
b.logEntry("Min: %f", p.Min())
}
if p.HasMax() {
b.logEntry("Max: %f", p.Max())
}
for i := 0; i < p.ExplicitBounds().Len(); i++ {
b.logEntry("ExplicitBounds #%d: %f", i, p.ExplicitBounds().At(i))
}
for j := 0; j < p.BucketCounts().Len(); j++ {
b.logEntry("Buckets #%d, Count: %d", j, p.BucketCounts().At(j))
}
b.logExemplars("Exemplars", p.Exemplars())
}
}
func (b *dataBuffer) logExponentialHistogramDataPoints(ps pmetric.ExponentialHistogramDataPointSlice) {
for i := 0; i < ps.Len(); i++ {
p := ps.At(i)
b.logEntry("ExponentialHistogramDataPoints #%d", i)
b.logDataPointAttributes(p.Attributes())
b.logEntry("StartTimestamp: %s", p.StartTimestamp())
b.logEntry("Timestamp: %s", p.Timestamp())
b.logEntry("Count: %d", p.Count())
if p.HasSum() {
b.logEntry("Sum: %f", p.Sum())
}
if p.HasMin() {
b.logEntry("Min: %f", p.Min())
}
if p.HasMax() {
b.logEntry("Max: %f", p.Max())
}
scale := int(p.Scale())
factor := math.Ldexp(math.Ln2, -scale)
// Note: the equation used here, which is
// math.Exp(index * factor)
// reports +Inf as the _lower_ boundary of the bucket nearest
// infinity, which is incorrect and can be addressed in various
// ways. The OTel-Go implementation of this histogram pending
// in https://github.com/open-telemetry/opentelemetry-go/pull/2393
// uses a lookup table for the last finite boundary, which can be
// easily computed using `math/big` (for scales up to 20).
negB := p.Negative().BucketCounts()
posB := p.Positive().BucketCounts()
for i := 0; i < negB.Len(); i++ {
pos := negB.Len() - i - 1
index := float64(p.Negative().Offset()) + float64(pos)
lower := math.Exp(index * factor)
upper := math.Exp((index + 1) * factor)
b.logEntry("Bucket [%f, %f), Count: %d", -upper, -lower, negB.At(pos))
}
if p.ZeroCount() != 0 {
b.logEntry("Bucket [0, 0], Count: %d", p.ZeroCount())
}
for pos := 0; pos < posB.Len(); pos++ {
index := float64(p.Positive().Offset()) + float64(pos)
lower := math.Exp(index * factor)
upper := math.Exp((index + 1) * factor)
b.logEntry("Bucket (%f, %f], Count: %d", lower, upper, posB.At(pos))
}
b.logExemplars("Exemplars", p.Exemplars())
}
}
func (b *dataBuffer) logDoubleSummaryDataPoints(ps pmetric.SummaryDataPointSlice) {
for i := 0; i < ps.Len(); i++ {
p := ps.At(i)
b.logEntry("SummaryDataPoints #%d", i)
b.logDataPointAttributes(p.Attributes())
b.logEntry("StartTimestamp: %s", p.StartTimestamp())
b.logEntry("Timestamp: %s", 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 *dataBuffer) logDataPointAttributes(attributes pcommon.Map) {
b.logAttributes("Data point attributes", attributes)
}
func (b *dataBuffer) logEvents(description string, se ptrace.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: %s", e.Timestamp())
b.logEntry(" -> DroppedAttributesCount: %d", e.DroppedAttributesCount())
b.logAttributes(" -> Attributes:", e.Attributes())
}
}
func (b *dataBuffer) logLinks(description string, sl ptrace.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())
b.logEntry(" -> ID: %s", l.SpanID())
b.logEntry(" -> TraceState: %s", l.TraceState().AsRaw())
b.logEntry(" -> DroppedAttributesCount: %d", l.DroppedAttributesCount())
b.logAttributes(" -> Attributes:", l.Attributes())
}
}
func (b *dataBuffer) logExemplars(description string, se pmetric.ExemplarSlice) {
if se.Len() == 0 {
return
}
b.logEntry("%s:", description)
for i := 0; i < se.Len(); i++ {
e := se.At(i)
b.logEntry("Exemplar #%d", i)
b.logEntry(" -> Trace ID: %s", e.TraceID())
b.logEntry(" -> Span ID: %s", e.SpanID())
b.logEntry(" -> Timestamp: %s", e.Timestamp())
switch e.ValueType() {
case pmetric.ExemplarValueTypeInt:
b.logEntry(" -> Value: %d", e.IntValue())
case pmetric.ExemplarValueTypeDouble:
b.logEntry(" -> Value: %f", e.DoubleValue())
}
b.logAttributes(" -> FilteredAttributes", e.FilteredAttributes())
}
}
func (b *dataBuffer) logProfileSamples(ss pprofile.SampleSlice, dic pprofile.ProfilesDictionary) {
if ss.Len() == 0 {
return
}
for i := 0; i < ss.Len(); i++ {
b.logEntry(" Sample #%d", i)
sample := ss.At(i)
b.logEntry(" Values: %d", sample.Values().AsRaw())
if lai := sample.AttributeIndices().Len(); lai > 0 {
b.logEntry(" Attributes:")
for j := range lai {
attr := dic.AttributeTable().At(int(sample.AttributeIndices().At(j)))
b.logEntry(" -> %s: %s", dic.StringTable().At(int(attr.KeyStrindex())), attr.Value().AsRaw())
}
}
}
}
func (b *dataBuffer) logProfileMappings(ms pprofile.MappingSlice) {
if ms.Len() == 0 {
return
}
for i := 0; i < ms.Len(); i++ {
b.logEntry("Mapping #%d", i)
mapping := ms.At(i)
b.logEntry(" Memory start: %d", mapping.MemoryStart())
b.logEntry(" Memory limit: %d", mapping.MemoryLimit())
b.logEntry(" File offset: %d", mapping.FileOffset())
b.logEntry(" File name: %d", mapping.FilenameStrindex())
b.logEntry(" Attributes: %d", mapping.AttributeIndices().AsRaw())
}
}
func (b *dataBuffer) logProfileLocations(ls pprofile.LocationSlice) {
if ls.Len() == 0 {
return
}
for i := 0; i < ls.Len(); i++ {
b.logEntry("Location #%d", i)
location := ls.At(i)
b.logEntry(" Mapping index: %d", location.MappingIndex())
b.logEntry(" Address: %d", location.Address())
if ll := location.Lines().Len(); ll > 0 {
for j := range ll {
b.logEntry(" Line #%d", j)
line := location.Lines().At(j)
b.logEntry(" Function index: %d", line.FunctionIndex())
b.logEntry(" Line: %d", line.Line())
b.logEntry(" Column: %d", line.Column())
}
}
b.logEntry(" Attributes: %d", location.AttributeIndices().AsRaw())
}
}
func (b *dataBuffer) logProfileFunctions(fs pprofile.FunctionSlice) {
if fs.Len() == 0 {
return
}
for i := 0; i < fs.Len(); i++ {
b.logEntry("Function #%d", i)
function := fs.At(i)
b.logEntry(" Name: %d", function.NameStrindex())
b.logEntry(" System name: %d", function.SystemNameStrindex())
b.logEntry(" Filename: %d", function.FilenameStrindex())
b.logEntry(" Start line: %d", function.StartLine())
}
}
func (b *dataBuffer) logStringTable(ss pcommon.StringSlice) {
if ss.Len() == 0 {
return
}
b.logEntry("String table:")
for i := 0; i < ss.Len(); i++ {
b.logEntry(" %s", ss.At(i))
}
}
func keyValueAndUnitsToMap(aus pprofile.KeyValueAndUnitSlice) pcommon.Map {
m := pcommon.NewMap()
for i := 0; i < aus.Len(); i++ {
au := aus.At(i)
m.PutInt("Key", int64(au.KeyStrindex()))
m.PutStr("Value", au.Value().AsString())
m.PutInt("unit", int64(au.UnitStrindex()))
}
return m
}
func linkTableToMap(ls pprofile.LinkSlice) pcommon.Map {
m := pcommon.NewMap()
for i := 0; i < ls.Len(); i++ {
l := ls.At(i)
m.PutStr("Trace ID", l.TraceID().String())
m.PutStr("Span ID", l.SpanID().String())
}
return m
}
func valueToString(v pcommon.Value) string {
return fmt.Sprintf("%s(%s)", v.Type().String(), v.AsString())
}
================================================
FILE: exporter/debugexporter/internal/otlptext/databuffer_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlptext
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestNestedArraySerializesCorrectly(t *testing.T) {
ava := pcommon.NewValueSlice()
ava.Slice().AppendEmpty().SetStr("foo")
ava.Slice().AppendEmpty().SetInt(42)
ava.Slice().AppendEmpty().SetEmptySlice().AppendEmpty().SetStr("bar")
ava.Slice().AppendEmpty().SetBool(true)
ava.Slice().AppendEmpty().SetDouble(5.5)
assert.Equal(t, `Slice(["foo",42,["bar"],true,5.5])`, valueToString(ava))
}
func TestNestedMapSerializesCorrectly(t *testing.T) {
ava := pcommon.NewValueMap()
av := ava.Map()
av.PutStr("foo", "test")
av.PutEmptyMap("zoo").PutInt("bar", 13)
assert.Equal(t, `Map({"foo":"test","zoo":{"bar":13}})`, valueToString(ava))
}
================================================
FILE: exporter/debugexporter/internal/otlptext/known_sync_error.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:build linux || darwin
package otlptext // import "go.opentelemetry.io/collector/exporter/debugexporter/internal/otlptext"
import (
"errors"
"syscall"
)
var knownSyncErrors = []error{
// sync /dev/stdout: invalid argument
syscall.EINVAL,
// sync /dev/stdout: not supported
syscall.ENOTSUP,
// sync /dev/stdout: inappropriate ioctl for device
syscall.ENOTTY,
// sync /dev/stdout: bad file descriptor
syscall.EBADF,
}
// knownSyncError returns true if the given error is one of the known
// non-actionable errors returned by Sync on Linux and macOS.
func knownSyncError(err error) bool {
for _, syncError := range knownSyncErrors {
if errors.Is(err, syncError) {
return true
}
}
return false
}
================================================
FILE: exporter/debugexporter/internal/otlptext/known_sync_error_other.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:build !linux && !darwin && !windows
package otlptext // import "go.opentelemetry.io/collector/exporter/debugexporter/internal/otlptext"
// knownSyncError returns true if the given error is one of the known
// non-actionable errors returned by Sync on Plan 9.
func knownSyncError(err error) bool {
return false
}
================================================
FILE: exporter/debugexporter/internal/otlptext/known_sync_error_windows.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:build windows
package otlptext // import "go.opentelemetry.io/collector/exporter/debugexporter/internal/otlptext"
import (
"errors"
"golang.org/x/sys/windows"
)
// knownSyncError returns true if the given error is one of the known
// non-actionable errors returned by Sync on Windows:
//
// - sync /dev/stderr: The handle is invalid.
func knownSyncError(err error) bool {
return errors.Is(err, windows.ERROR_INVALID_HANDLE)
}
================================================
FILE: exporter/debugexporter/internal/otlptext/logs.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlptext // import "go.opentelemetry.io/collector/exporter/debugexporter/internal/otlptext"
import (
"go.opentelemetry.io/collector/pdata/plog"
)
// NewTextLogsMarshaler returns a plog.Marshaler to encode to OTLP text bytes.
func NewTextLogsMarshaler() plog.Marshaler {
return textLogsMarshaler{}
}
type textLogsMarshaler struct{}
// MarshalLogs plog.Logs to OTLP text.
func (textLogsMarshaler) MarshalLogs(ld plog.Logs) ([]byte, error) {
buf := dataBuffer{}
rls := ld.ResourceLogs()
for i := 0; i < rls.Len(); i++ {
buf.logEntry("ResourceLog #%d", i)
rl := rls.At(i)
buf.logEntry("Resource SchemaURL: %s", rl.SchemaUrl())
buf.logAttributes("Resource attributes", rl.Resource().Attributes())
buf.logEntityRefs(rl.Resource())
ills := rl.ScopeLogs()
for j := 0; j < ills.Len(); j++ {
buf.logEntry("ScopeLogs #%d", j)
ils := ills.At(j)
buf.logEntry("ScopeLogs SchemaURL: %s", ils.SchemaUrl())
buf.logInstrumentationScope(ils.Scope())
logs := ils.LogRecords()
for k := 0; k < logs.Len(); k++ {
buf.logEntry("LogRecord #%d", k)
lr := logs.At(k)
buf.logEntry("ObservedTimestamp: %s", lr.ObservedTimestamp())
buf.logEntry("Timestamp: %s", lr.Timestamp())
buf.logEntry("SeverityText: %s", lr.SeverityText())
buf.logEntry("SeverityNumber: %s(%d)", lr.SeverityNumber(), lr.SeverityNumber())
if lr.EventName() != "" {
buf.logEntry("EventName: %s", lr.EventName())
}
buf.logEntry("Body: %s", valueToString(lr.Body()))
buf.logAttributes("Attributes", lr.Attributes())
buf.logEntry("Trace ID: %s", lr.TraceID())
buf.logEntry("Span ID: %s", lr.SpanID())
buf.logEntry("Flags: %d", lr.Flags())
}
}
}
return buf.buf.Bytes(), nil
}
================================================
FILE: exporter/debugexporter/internal/otlptext/logs_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlptext
import (
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/testdata"
)
func TestLogsText(t *testing.T) {
tests := []struct {
name string
in plog.Logs
out string
}{
{
name: "empty_logs",
in: plog.NewLogs(),
out: "empty.out",
},
{
name: "logs_with_one_record",
in: testdata.GenerateLogs(1),
out: "one_record.out",
},
{
name: "logs_with_two_records",
in: testdata.GenerateLogs(2),
out: "two_records.out",
},
{
name: "log_with_event_name",
in: func() plog.Logs {
ls := plog.NewLogs()
l := ls.ResourceLogs().AppendEmpty().ScopeLogs().AppendEmpty().LogRecords().AppendEmpty()
l.SetTimestamp(pcommon.NewTimestampFromTime(time.Date(2020, 2, 11, 20, 26, 13, 789, time.UTC)))
l.SetSeverityNumber(plog.SeverityNumberInfo)
l.SetSeverityText("Info")
l.SetEventName("event_name")
l.Body().SetStr("This is a log message")
attrs := l.Attributes()
attrs.PutStr("app", "server")
attrs.PutInt("instance_num", 1)
l.SetSpanID([8]byte{0x01, 0x02, 0x04, 0x08})
l.SetTraceID([16]byte{0x08, 0x04, 0x02, 0x01})
return ls
}(),
out: "log_with_event_name.out",
},
{
name: "logs_with_embedded_maps",
in: func() plog.Logs {
ls := plog.NewLogs()
l := ls.ResourceLogs().AppendEmpty().ScopeLogs().AppendEmpty().LogRecords().AppendEmpty()
l.SetTimestamp(pcommon.NewTimestampFromTime(time.Date(2020, 2, 11, 20, 26, 13, 789, time.UTC)))
l.SetSeverityNumber(plog.SeverityNumberInfo)
l.SetSeverityText("INFO")
bm := l.Body().SetEmptyMap()
bm.PutStr("key1", "val1")
bmm := bm.PutEmptyMap("key2")
bmm.PutStr("key21", "val21")
bmm.PutStr("key22", "val22")
am := l.Attributes().PutEmptyMap("key1")
am.PutStr("key11", "val11")
am.PutStr("key12", "val12")
am.PutEmptyMap("key13").PutStr("key131", "val131")
l.Attributes().PutStr("key2", "val2")
return ls
}(),
out: "embedded_maps.out",
},
{
name: "logs_with_entity_refs",
in: generateLogsWithEntityRefs(),
out: "logs_with_entity_refs.out",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := NewTextLogsMarshaler().MarshalLogs(tt.in)
require.NoError(t, err)
out, err := os.ReadFile(filepath.Join("testdata", "logs", tt.out))
require.NoError(t, err)
expected := strings.ReplaceAll(string(out), "\r", "")
assert.Equal(t, expected, string(got))
})
}
}
func generateLogsWithEntityRefs() plog.Logs {
ld := plog.NewLogs()
rl := ld.ResourceLogs().AppendEmpty()
setupResourceWithEntityRefs(rl.Resource())
sl := rl.ScopeLogs().AppendEmpty()
sl.Scope().SetName("test-scope")
lr := sl.LogRecords().AppendEmpty()
lr.SetTimestamp(pcommon.NewTimestampFromTime(time.Date(2020, 2, 11, 20, 26, 13, 789, time.UTC)))
lr.SetSeverityNumber(plog.SeverityNumberInfo)
lr.SetSeverityText("Info")
lr.Body().SetStr("This is a test log message")
lr.Attributes().PutStr("test.attribute", "test-value")
return ld
}
================================================
FILE: exporter/debugexporter/internal/otlptext/metrics.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlptext // import "go.opentelemetry.io/collector/exporter/debugexporter/internal/otlptext"
import "go.opentelemetry.io/collector/pdata/pmetric"
// NewTextMetricsMarshaler returns a pmetric.Marshaler to encode to OTLP text bytes.
func NewTextMetricsMarshaler() pmetric.Marshaler {
return textMetricsMarshaler{}
}
type textMetricsMarshaler struct{}
// MarshalMetrics pmetric.Metrics to OTLP text.
func (textMetricsMarshaler) MarshalMetrics(md pmetric.Metrics) ([]byte, error) {
buf := dataBuffer{}
rms := md.ResourceMetrics()
for i := 0; i < rms.Len(); i++ {
buf.logEntry("ResourceMetrics #%d", i)
rm := rms.At(i)
buf.logEntry("Resource SchemaURL: %s", rm.SchemaUrl())
buf.logAttributes("Resource attributes", rm.Resource().Attributes())
buf.logEntityRefs(rm.Resource())
ilms := rm.ScopeMetrics()
for j := 0; j < ilms.Len(); j++ {
buf.logEntry("ScopeMetrics #%d", j)
ilm := ilms.At(j)
buf.logEntry("ScopeMetrics SchemaURL: %s", ilm.SchemaUrl())
buf.logInstrumentationScope(ilm.Scope())
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)
}
}
}
return buf.buf.Bytes(), nil
}
================================================
FILE: exporter/debugexporter/internal/otlptext/metrics_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlptext
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/testdata"
)
func TestMetricsText(t *testing.T) {
tests := []struct {
name string
in pmetric.Metrics
out string
}{
{
name: "empty_metrics",
in: pmetric.NewMetrics(),
out: "empty.out",
},
{
name: "metrics_with_all_types",
in: testdata.GenerateMetricsAllTypes(),
out: "metrics_with_all_types.out",
},
{
name: "two_metrics",
in: testdata.GenerateMetrics(2),
out: "two_metrics.out",
},
{
name: "invalid_metric_type",
in: testdata.GenerateMetricsMetricTypeInvalid(),
out: "invalid_metric_type.out",
},
{
name: "metrics_with_entity_refs",
in: generateMetricsWithEntityRefs(),
out: "metrics_with_entity_refs.out",
},
{
name: "metrics_with_metadata",
in: generateMetricsWithMetadata(),
out: "metrics_with_metadata.out",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := NewTextMetricsMarshaler().MarshalMetrics(tt.in)
require.NoError(t, err)
out, err := os.ReadFile(filepath.Join("testdata", "metrics", tt.out))
require.NoError(t, err)
expected := strings.ReplaceAll(string(out), "\r", "")
assert.Equal(t, expected, string(got))
})
}
}
func generateMetricsWithEntityRefs() pmetric.Metrics {
md := pmetric.NewMetrics()
rm := md.ResourceMetrics().AppendEmpty()
setupResourceWithEntityRefs(rm.Resource())
sm := rm.ScopeMetrics().AppendEmpty()
sm.Scope().SetName("test-scope")
metric := sm.Metrics().AppendEmpty()
metric.SetName("test-metric")
metric.SetDescription("A test metric")
metric.SetUnit("1")
gauge := metric.SetEmptyGauge()
dp := gauge.DataPoints().AppendEmpty()
dp.SetDoubleValue(123.45)
dp.Attributes().PutStr("test.attribute", "test-value")
return md
}
func generateMetricsWithMetadata() pmetric.Metrics {
md := pmetric.NewMetrics()
rm := md.ResourceMetrics().AppendEmpty()
rm.Resource().Attributes().PutStr("service.name", "test-service")
sm := rm.ScopeMetrics().AppendEmpty()
sm.Scope().SetName("test-scope")
metric := sm.Metrics().AppendEmpty()
metric.SetName("test-metric-metadata")
metric.SetDescription("A test metric with metadata")
metric.SetUnit("1")
metric.Metadata().PutStr("meta.key", "meta-value")
metric.Metadata().PutInt("meta.id", 101)
gauge := metric.SetEmptyGauge()
dp := gauge.DataPoints().AppendEmpty()
dp.SetIntValue(1)
return md
}
================================================
FILE: exporter/debugexporter/internal/otlptext/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlptext
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: exporter/debugexporter/internal/otlptext/profiles.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlptext // import "go.opentelemetry.io/collector/exporter/debugexporter/internal/otlptext"
import (
"strconv"
"go.opentelemetry.io/collector/pdata/pprofile"
)
// NewTextProfilesMarshaler returns a pprofile.Marshaler to encode to OTLP text bytes.
func NewTextProfilesMarshaler() pprofile.Marshaler {
return textProfilesMarshaler{}
}
type textProfilesMarshaler struct{}
// MarshalProfiles pprofile.Profiles to OTLP text.
func (textProfilesMarshaler) MarshalProfiles(pd pprofile.Profiles) ([]byte, error) {
buf := dataBuffer{}
dic := pd.Dictionary()
rps := pd.ResourceProfiles()
buf.logProfileMappings(dic.MappingTable())
buf.logProfileLocations(dic.LocationTable())
buf.logProfileFunctions(dic.FunctionTable())
buf.logAttributesWithIndentation(
"Attribute table",
keyValueAndUnitsToMap(dic.AttributeTable()),
0)
buf.logAttributesWithIndentation(
"Link table",
linkTableToMap(dic.LinkTable()),
0)
buf.logStringTable(dic.StringTable())
for i := 0; i < rps.Len(); i++ {
buf.logEntry("ResourceProfiles #%d", i)
rp := rps.At(i)
buf.logEntry("Resource SchemaURL: %s", rp.SchemaUrl())
buf.logAttributes("Resource attributes", rp.Resource().Attributes())
buf.logEntityRefs(rp.Resource())
ilps := rp.ScopeProfiles()
for j := 0; j < ilps.Len(); j++ {
buf.logEntry("ScopeProfiles #%d", j)
ilp := ilps.At(j)
buf.logEntry("ScopeProfiles SchemaURL: %s", ilp.SchemaUrl())
buf.logInstrumentationScope(ilp.Scope())
profiles := ilp.Profiles()
for k := 0; k < profiles.Len(); k++ {
buf.logEntry("Profile #%d", k)
profile := profiles.At(k)
buf.logAttr("Profile ID", profile.ProfileID())
buf.logAttr("Start time", profile.Time().String())
buf.logAttr("DurationNano", strconv.FormatUint(profile.DurationNano(), 10))
buf.logAttr("Dropped attributes count", strconv.FormatUint(uint64(profile.DroppedAttributesCount()), 10))
buf.logProfileSamples(profile.Samples(), dic)
}
}
}
return buf.buf.Bytes(), nil
}
================================================
FILE: exporter/debugexporter/internal/otlptext/profiles_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlptext
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/testdata"
)
func TestProfilesText(t *testing.T) {
tests := []struct {
name string
in pprofile.Profiles
out string
}{
{
name: "empty_profiles",
in: pprofile.NewProfiles(),
out: "empty.out",
},
{
name: "two_profiles",
in: extendProfiles(testdata.GenerateProfiles(2)),
out: "two_profiles.out",
},
{
name: "profiles_with_entity_refs",
in: generateProfilesWithEntityRefs(),
out: "profiles_with_entity_refs.out",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := NewTextProfilesMarshaler().MarshalProfiles(tt.in)
require.NoError(t, err)
out, err := os.ReadFile(filepath.Join("testdata", "profiles", tt.out))
require.NoError(t, err)
expected := strings.ReplaceAll(string(out), "\r", "")
assert.Equal(t, expected, string(got))
})
}
}
// GenerateExtendedProfiles generates dummy profiling data with extended values for tests
func extendProfiles(profiles pprofile.Profiles) pprofile.Profiles {
dic := profiles.Dictionary()
location := dic.LocationTable().AppendEmpty()
location.SetMappingIndex(3)
location.SetAddress(4)
line := location.Lines().AppendEmpty()
line.SetFunctionIndex(1)
line.SetLine(2)
line.SetColumn(3)
location.AttributeIndices().FromRaw([]int32{6, 7})
dic.StringTable().Append("intValue")
at := dic.AttributeTable()
a := at.AppendEmpty()
a.SetKeyStrindex(1)
a.Value().SetInt(42)
attributeUnits := dic.AttributeTable().AppendEmpty()
attributeUnits.SetKeyStrindex(2)
attributeUnits.SetUnitStrindex(5)
dic.StringTable().Append("foobar")
mapping := dic.MappingTable().AppendEmpty()
mapping.SetMemoryStart(2)
mapping.SetMemoryLimit(3)
mapping.SetFileOffset(4)
mapping.SetFilenameStrindex(5)
mapping.AttributeIndices().FromRaw([]int32{7, 8})
function := dic.FunctionTable().AppendEmpty()
function.SetNameStrindex(2)
function.SetSystemNameStrindex(3)
function.SetFilenameStrindex(4)
function.SetStartLine(5)
linkTable := dic.LinkTable().AppendEmpty()
linkTable.SetTraceID([16]byte{0x03, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10})
linkTable.SetSpanID([8]byte{0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18})
sc := profiles.ResourceProfiles().At(0).ScopeProfiles().At(0)
profilesCount := profiles.ResourceProfiles().At(0).ScopeProfiles().At(0).Profiles().Len()
for i := range profilesCount {
if i%2 == 0 {
profile := sc.Profiles().At(i)
profile.AttributeIndices().FromRaw([]int32{1})
}
}
return profiles
}
func generateProfilesWithEntityRefs() pprofile.Profiles {
pd := pprofile.NewProfiles()
rp := pd.ResourceProfiles().AppendEmpty()
setupResourceWithEntityRefs(rp.Resource())
sp := rp.ScopeProfiles().AppendEmpty()
sp.Scope().SetName("test-scope")
profile := sp.Profiles().AppendEmpty()
sample := profile.Samples().AppendEmpty()
sample.Values().Append(100)
dic := pd.Dictionary()
dic.StringTable().Append("")
dic.StringTable().Append("cpu")
dic.StringTable().Append("nanoseconds")
sampleType := profile.SampleType()
sampleType.SetTypeStrindex(1)
sampleType.SetUnitStrindex(2)
return pd
}
================================================
FILE: exporter/debugexporter/internal/otlptext/sync.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlptext // import "go.opentelemetry.io/collector/exporter/debugexporter/internal/otlptext"
import (
"context"
"errors"
"os"
"go.uber.org/zap"
)
func LoggerSync(logger *zap.Logger) func(context.Context) error {
return func(context.Context) error {
// Currently Sync() return a different error depending on the OS.
// Since these are not actionable ignore them.
err := logger.Sync()
osErr := &os.PathError{}
if errors.As(err, &osErr) {
wrappedErr := osErr.Unwrap()
if knownSyncError(wrappedErr) {
err = nil
}
}
return err
}
}
================================================
FILE: exporter/debugexporter/internal/otlptext/test_helpers.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlptext // import "go.opentelemetry.io/collector/exporter/debugexporter/internal/otlptext"
import (
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/xpdata/entity"
)
func addEntityRefsToResource(resource pcommon.Resource) {
entityRefs := entity.ResourceEntityRefs(resource)
serviceRef := entityRefs.AppendEmpty()
serviceRef.SetType("service")
serviceRef.SetSchemaUrl("https://opentelemetry.io/schemas/1.21.0")
serviceRef.IdKeys().Append("service.name")
serviceRef.DescriptionKeys().Append("service.version")
if _, exists := resource.Attributes().Get("host.name"); exists {
hostRef := entityRefs.AppendEmpty()
hostRef.SetType("host")
hostRef.IdKeys().Append("host.name")
}
}
func setupResourceWithEntityRefs(resource pcommon.Resource) {
resource.Attributes().PutStr("service.name", "my-service")
resource.Attributes().PutStr("service.version", "1.0.0")
resource.Attributes().PutStr("host.name", "server-01")
addEntityRefsToResource(resource)
}
================================================
FILE: exporter/debugexporter/internal/otlptext/testdata/logs/embedded_maps.out
================================================
ResourceLog #0
Resource SchemaURL:
ScopeLogs #0
ScopeLogs SchemaURL:
InstrumentationScope
LogRecord #0
ObservedTimestamp: 1970-01-01 00:00:00 +0000 UTC
Timestamp: 2020-02-11 20:26:13.000000789 +0000 UTC
SeverityText: INFO
SeverityNumber: Info(9)
Body: Map({"key1":"val1","key2":{"key21":"val21","key22":"val22"}})
Attributes:
-> key1: Map({"key11":"val11","key12":"val12","key13":{"key131":"val131"}})
-> key2: Str(val2)
Trace ID:
Span ID:
Flags: 0
================================================
FILE: exporter/debugexporter/internal/otlptext/testdata/logs/empty.out
================================================
================================================
FILE: exporter/debugexporter/internal/otlptext/testdata/logs/log_with_event_name.out
================================================
ResourceLog #0
Resource SchemaURL:
ScopeLogs #0
ScopeLogs SchemaURL:
InstrumentationScope
LogRecord #0
ObservedTimestamp: 1970-01-01 00:00:00 +0000 UTC
Timestamp: 2020-02-11 20:26:13.000000789 +0000 UTC
SeverityText: Info
SeverityNumber: Info(9)
EventName: event_name
Body: Str(This is a log message)
Attributes:
-> app: Str(server)
-> instance_num: Int(1)
Trace ID: 08040201000000000000000000000000
Span ID: 0102040800000000
Flags: 0
================================================
FILE: exporter/debugexporter/internal/otlptext/testdata/logs/logs_with_entity_refs.out
================================================
ResourceLog #0
Resource SchemaURL:
Resource attributes:
-> service.name: Str(my-service)
-> service.version: Str(1.0.0)
-> host.name: Str(server-01)
Resource entity refs:
-> Entity ref #0:
-> Type: service
-> Schema URL: https://opentelemetry.io/schemas/1.21.0
-> ID keys:
-> service.name
-> Description keys:
-> service.version
-> Entity ref #1:
-> Type: host
-> ID keys:
-> host.name
ScopeLogs #0
ScopeLogs SchemaURL:
InstrumentationScope test-scope
LogRecord #0
ObservedTimestamp: 1970-01-01 00:00:00 +0000 UTC
Timestamp: 2020-02-11 20:26:13.000000789 +0000 UTC
SeverityText: Info
SeverityNumber: Info(9)
Body: Str(This is a test log message)
Attributes:
-> test.attribute: Str(test-value)
Trace ID:
Span ID:
Flags: 0
================================================
FILE: exporter/debugexporter/internal/otlptext/testdata/logs/one_record.out
================================================
ResourceLog #0
Resource SchemaURL:
Resource attributes:
-> resource-attr: Str(resource-attr-val-1)
ScopeLogs #0
ScopeLogs SchemaURL:
InstrumentationScope
LogRecord #0
ObservedTimestamp: 1970-01-01 00:00:00 +0000 UTC
Timestamp: 2020-02-11 20:26:13.000000789 +0000 UTC
SeverityText: Info
SeverityNumber: Info(9)
Body: Str(This is a log message)
Attributes:
-> app: Str(server)
-> instance_num: Int(1)
Trace ID: 08040201000000000000000000000000
Span ID: 0102040800000000
Flags: 0
================================================
FILE: exporter/debugexporter/internal/otlptext/testdata/logs/two_records.out
================================================
ResourceLog #0
Resource SchemaURL:
Resource attributes:
-> resource-attr: Str(resource-attr-val-1)
ScopeLogs #0
ScopeLogs SchemaURL:
InstrumentationScope
LogRecord #0
ObservedTimestamp: 1970-01-01 00:00:00 +0000 UTC
Timestamp: 2020-02-11 20:26:13.000000789 +0000 UTC
SeverityText: Info
SeverityNumber: Info(9)
Body: Str(This is a log message)
Attributes:
-> app: Str(server)
-> instance_num: Int(1)
Trace ID: 08040201000000000000000000000000
Span ID: 0102040800000000
Flags: 0
LogRecord #1
ObservedTimestamp: 1970-01-01 00:00:00 +0000 UTC
Timestamp: 2020-02-11 20:26:13.000000789 +0000 UTC
SeverityText: Info
SeverityNumber: Info(9)
Body: Str(something happened)
Attributes:
-> customer: Str(acme)
-> env: Str(dev)
Trace ID:
Span ID:
Flags: 0
================================================
FILE: exporter/debugexporter/internal/otlptext/testdata/metrics/empty.out
================================================
================================================
FILE: exporter/debugexporter/internal/otlptext/testdata/metrics/invalid_metric_type.out
================================================
ResourceMetrics #0
Resource SchemaURL:
Resource attributes:
-> resource-attr: Str(resource-attr-val-1)
ScopeMetrics #0
ScopeMetrics SchemaURL:
InstrumentationScope
Metric #0
Descriptor:
-> Name: sum-int
-> Description:
-> Unit: 1
-> DataType: Empty
================================================
FILE: exporter/debugexporter/internal/otlptext/testdata/metrics/metrics_with_all_types.out
================================================
ResourceMetrics #0
Resource SchemaURL:
Resource attributes:
-> resource-attr: Str(resource-attr-val-1)
ScopeMetrics #0
ScopeMetrics SchemaURL:
InstrumentationScope
Metric #0
Descriptor:
-> Name: gauge-int
-> Description:
-> Unit: 1
-> DataType: Gauge
NumberDataPoints #0
Data point attributes:
-> label-1: Str(label-value-1)
StartTimestamp: 2020-02-11 20:26:12.000000321 +0000 UTC
Timestamp: 2020-02-11 20:26:13.000000789 +0000 UTC
Value: 123
NumberDataPoints #1
Data point attributes:
-> label-2: Str(label-value-2)
StartTimestamp: 2020-02-11 20:26:12.000000321 +0000 UTC
Timestamp: 2020-02-11 20:26:13.000000789 +0000 UTC
Value: 456
Metric #1
Descriptor:
-> Name: gauge-double
-> Description:
-> Unit: 1
-> DataType: Gauge
NumberDataPoints #0
Data point attributes:
-> label-1: Str(label-value-1)
-> label-2: Str(label-value-2)
StartTimestamp: 2020-02-11 20:26:12.000000321 +0000 UTC
Timestamp: 2020-02-11 20:26:13.000000789 +0000 UTC
Value: 1.230000
NumberDataPoints #1
Data point attributes:
-> label-1: Str(label-value-1)
-> label-3: Str(label-value-3)
StartTimestamp: 2020-02-11 20:26:12.000000321 +0000 UTC
Timestamp: 2020-02-11 20:26:13.000000789 +0000 UTC
Value: 4.560000
Metric #2
Descriptor:
-> Name: sum-int
-> Description:
-> Unit: 1
-> DataType: Sum
-> IsMonotonic: true
-> AggregationTemporality: Cumulative
NumberDataPoints #0
Data point attributes:
-> label-1: Str(label-value-1)
StartTimestamp: 2020-02-11 20:26:12.000000321 +0000 UTC
Timestamp: 2020-02-11 20:26:13.000000789 +0000 UTC
Value: 123
NumberDataPoints #1
Data point attributes:
-> label-2: Str(label-value-2)
StartTimestamp: 2020-02-11 20:26:12.000000321 +0000 UTC
Timestamp: 2020-02-11 20:26:13.000000789 +0000 UTC
Value: 456
Metric #3
Descriptor:
-> Name: sum-double
-> Description:
-> Unit: 1
-> DataType: Sum
-> IsMonotonic: true
-> AggregationTemporality: Cumulative
NumberDataPoints #0
Data point attributes:
-> label-1: Str(label-value-1)
-> label-2: Str(label-value-2)
StartTimestamp: 2020-02-11 20:26:12.000000321 +0000 UTC
Timestamp: 2020-02-11 20:26:13.000000789 +0000 UTC
Value: 1.230000
NumberDataPoints #1
Data point attributes:
-> label-1: Str(label-value-1)
-> label-3: Str(label-value-3)
StartTimestamp: 2020-02-11 20:26:12.000000321 +0000 UTC
Timestamp: 2020-02-11 20:26:13.000000789 +0000 UTC
Value: 4.560000
Metric #4
Descriptor:
-> Name: histogram
-> Description:
-> Unit: 1
-> DataType: Histogram
-> AggregationTemporality: Cumulative
HistogramDataPoints #0
Data point attributes:
-> label-1: Str(label-value-1)
-> label-3: Str(label-value-3)
StartTimestamp: 2020-02-11 20:26:12.000000321 +0000 UTC
Timestamp: 2020-02-11 20:26:13.000000789 +0000 UTC
Count: 1
Sum: 15.000000
HistogramDataPoints #1
Data point attributes:
-> label-2: Str(label-value-2)
StartTimestamp: 2020-02-11 20:26:12.000000321 +0000 UTC
Timestamp: 2020-02-11 20:26:13.000000789 +0000 UTC
Count: 1
Sum: 15.000000
Min: 15.000000
Max: 15.000000
ExplicitBounds #0: 1.000000
Buckets #0, Count: 0
Buckets #1, Count: 1
Exemplars:
Exemplar #0
-> Trace ID: 0102030405060708090a0b0c0d0e0f10
-> Span ID: 1112131415161718
-> Timestamp: 2020-02-11 20:26:13.000000123 +0000 UTC
-> Value: 15.000000
-> FilteredAttributes:
-> exemplar-attachment: Str(exemplar-attachment-value)
Metric #5
Descriptor:
-> Name: exponential-histogram
-> Description:
-> Unit: 1
-> DataType: ExponentialHistogram
-> AggregationTemporality: Delta
ExponentialHistogramDataPoints #0
Data point attributes:
-> label-1: Str(label-value-1)
-> label-3: Str(label-value-3)
StartTimestamp: 2020-02-11 20:26:12.000000321 +0000 UTC
Timestamp: 2020-02-11 20:26:13.000000789 +0000 UTC
Count: 5
Sum: 0.150000
Bucket [-1.414214, -1.000000), Count: 1
Bucket [-1.000000, -0.707107), Count: 1
Bucket [0, 0], Count: 1
Bucket (1.414214, 2.000000], Count: 1
Bucket (2.000000, 2.828427], Count: 1
ExponentialHistogramDataPoints #1
Data point attributes:
-> label-2: Str(label-value-2)
StartTimestamp: 2020-02-11 20:26:12.000000321 +0000 UTC
Timestamp: 2020-02-11 20:26:13.000000789 +0000 UTC
Count: 3
Sum: 1.250000
Min: 0.000000
Max: 1.000000
Bucket [0, 0], Count: 1
Bucket (0.250000, 1.000000], Count: 1
Bucket (1.000000, 4.000000], Count: 1
Exemplars:
Exemplar #0
-> Trace ID: 0102030405060708090a0b0c0d0e0f10
-> Span ID: 1112131415161718
-> Timestamp: 2020-02-11 20:26:13.000000123 +0000 UTC
-> Value: 15
-> FilteredAttributes:
-> exemplar-attachment: Str(exemplar-attachment-value)
Metric #6
Descriptor:
-> Name: summary
-> Description:
-> Unit: 1
-> DataType: Summary
SummaryDataPoints #0
Data point attributes:
-> label-1: Str(label-value-1)
-> label-3: Str(label-value-3)
StartTimestamp: 2020-02-11 20:26:12.000000321 +0000 UTC
Timestamp: 2020-02-11 20:26:13.000000789 +0000 UTC
Count: 1
Sum: 15.000000
SummaryDataPoints #1
Data point attributes:
-> label-2: Str(label-value-2)
StartTimestamp: 2020-02-11 20:26:12.000000321 +0000 UTC
Timestamp: 2020-02-11 20:26:13.000000789 +0000 UTC
Count: 1
Sum: 15.000000
QuantileValue #0: Quantile 0.010000, Value 15.000000
================================================
FILE: exporter/debugexporter/internal/otlptext/testdata/metrics/metrics_with_entity_refs.out
================================================
ResourceMetrics #0
Resource SchemaURL:
Resource attributes:
-> service.name: Str(my-service)
-> service.version: Str(1.0.0)
-> host.name: Str(server-01)
Resource entity refs:
-> Entity ref #0:
-> Type: service
-> Schema URL: https://opentelemetry.io/schemas/1.21.0
-> ID keys:
-> service.name
-> Description keys:
-> service.version
-> Entity ref #1:
-> Type: host
-> ID keys:
-> host.name
ScopeMetrics #0
ScopeMetrics SchemaURL:
InstrumentationScope test-scope
Metric #0
Descriptor:
-> Name: test-metric
-> Description: A test metric
-> Unit: 1
-> DataType: Gauge
NumberDataPoints #0
Data point attributes:
-> test.attribute: Str(test-value)
StartTimestamp: 1970-01-01 00:00:00 +0000 UTC
Timestamp: 1970-01-01 00:00:00 +0000 UTC
Value: 123.450000
================================================
FILE: exporter/debugexporter/internal/otlptext/testdata/metrics/metrics_with_metadata.out
================================================
ResourceMetrics #0
Resource SchemaURL:
Resource attributes:
-> service.name: Str(test-service)
ScopeMetrics #0
ScopeMetrics SchemaURL:
InstrumentationScope test-scope
Metric #0
Descriptor:
-> Name: test-metric-metadata
-> Description: A test metric with metadata
-> Unit: 1
-> DataType: Gauge
-> Metadata:
-> meta.key: Str(meta-value)
-> meta.id: Int(101)
NumberDataPoints #0
StartTimestamp: 1970-01-01 00:00:00 +0000 UTC
Timestamp: 1970-01-01 00:00:00 +0000 UTC
Value: 1
================================================
FILE: exporter/debugexporter/internal/otlptext/testdata/metrics/two_metrics.out
================================================
ResourceMetrics #0
Resource SchemaURL:
Resource attributes:
-> resource-attr: Str(resource-attr-val-1)
ScopeMetrics #0
ScopeMetrics SchemaURL:
InstrumentationScope
Metric #0
Descriptor:
-> Name: gauge-int
-> Description:
-> Unit: 1
-> DataType: Gauge
NumberDataPoints #0
Data point attributes:
-> label-1: Str(label-value-1)
StartTimestamp: 2020-02-11 20:26:12.000000321 +0000 UTC
Timestamp: 2020-02-11 20:26:13.000000789 +0000 UTC
Value: 123
NumberDataPoints #1
Data point attributes:
-> label-2: Str(label-value-2)
StartTimestamp: 2020-02-11 20:26:12.000000321 +0000 UTC
Timestamp: 2020-02-11 20:26:13.000000789 +0000 UTC
Value: 456
Metric #1
Descriptor:
-> Name: gauge-double
-> Description:
-> Unit: 1
-> DataType: Gauge
NumberDataPoints #0
Data point attributes:
-> label-1: Str(label-value-1)
-> label-2: Str(label-value-2)
StartTimestamp: 2020-02-11 20:26:12.000000321 +0000 UTC
Timestamp: 2020-02-11 20:26:13.000000789 +0000 UTC
Value: 1.230000
NumberDataPoints #1
Data point attributes:
-> label-1: Str(label-value-1)
-> label-3: Str(label-value-3)
StartTimestamp: 2020-02-11 20:26:12.000000321 +0000 UTC
Timestamp: 2020-02-11 20:26:13.000000789 +0000 UTC
Value: 4.560000
================================================
FILE: exporter/debugexporter/internal/otlptext/testdata/profiles/empty.out
================================================
================================================
FILE: exporter/debugexporter/internal/otlptext/testdata/profiles/profiles_with_entity_refs.out
================================================
String table:
cpu
nanoseconds
ResourceProfiles #0
Resource SchemaURL:
Resource attributes:
-> service.name: Str(my-service)
-> service.version: Str(1.0.0)
-> host.name: Str(server-01)
Resource entity refs:
-> Entity ref #0:
-> Type: service
-> Schema URL: https://opentelemetry.io/schemas/1.21.0
-> ID keys:
-> service.name
-> Description keys:
-> service.version
-> Entity ref #1:
-> Type: host
-> ID keys:
-> host.name
ScopeProfiles #0
ScopeProfiles SchemaURL:
InstrumentationScope test-scope
Profile #0
Profile ID :
Start time : 1970-01-01 00:00:00 +0000 UTC
DurationNano : 0
Dropped attributes count: 0
Sample #0
Values: [100]
================================================
FILE: exporter/debugexporter/internal/otlptext/testdata/profiles/two_profiles.out
================================================
Mapping #0
Memory start: 2
Memory limit: 3
File offset: 4
File name: 5
Attributes: [7 8]
Location #0
Mapping index: 0
Address: 1
Attributes: []
Location #1
Mapping index: 0
Address: 2
Attributes: []
Location #2
Mapping index: 3
Address: 4
Line #0
Function index: 1
Line: 2
Column: 3
Attributes: [6 7]
Function #0
Name: 2
System name: 3
Filename: 4
Start line: 5
Attribute table:
-> Key: Int(2)
-> Value: Str()
-> unit: Int(5)
Link table:
-> Trace ID: Str(0302030405060708090a0b0c0d0e0f10)
-> Span ID: Str(1112131415161718)
String table:
key
intValue
foobar
ResourceProfiles #0
Resource SchemaURL:
Resource attributes:
-> resource-attr: Str(resource-attr-val-1)
ScopeProfiles #0
ScopeProfiles SchemaURL:
InstrumentationScope
Profile #0
Profile ID : 0102030405060708090a0b0c0d0e0f10
Start time : 2020-02-11 20:26:12.000000321 +0000 UTC
DurationNano : 1000000000
Dropped attributes count: 1
Sample #0
Values: [4]
Attributes:
-> key: value-1
Profile #1
Profile ID : 0202030405060708090a0b0c0d0e0f10
Start time : 2020-02-11 20:26:12.000000321 +0000 UTC
DurationNano : 1000000000
Dropped attributes count: 0
Sample #0
Values: [9]
Attributes:
-> key: value-2
================================================
FILE: exporter/debugexporter/internal/otlptext/testdata/traces/empty.out
================================================
================================================
FILE: exporter/debugexporter/internal/otlptext/testdata/traces/traces_with_entity_refs.out
================================================
ResourceSpans #0
Resource SchemaURL:
Resource attributes:
-> service.name: Str(my-service)
-> service.version: Str(1.0.0)
-> host.name: Str(server-01)
Resource entity refs:
-> Entity ref #0:
-> Type: service
-> Schema URL: https://opentelemetry.io/schemas/1.21.0
-> ID keys:
-> service.name
-> Description keys:
-> service.version
-> Entity ref #1:
-> Type: host
-> ID keys:
-> host.name
ScopeSpans #0
ScopeSpans SchemaURL:
InstrumentationScope test-scope
Span #0
Trace ID : 0102030405060708090a0b0c0d0e0f10
Parent ID :
ID : 0102030405060708
Name : test-span
Kind : Unspecified
Start time : 1970-01-01 00:00:00 +0000 UTC
End time : 1970-01-01 00:00:00 +0000 UTC
Status code : Unset
Status message :
DroppedAttributesCount: 0
DroppedEventsCount: 0
DroppedLinksCount: 0
================================================
FILE: exporter/debugexporter/internal/otlptext/testdata/traces/two_spans.out
================================================
ResourceSpans #0
Resource SchemaURL:
Resource attributes:
-> resource-attr: Str(resource-attr-val-1)
ScopeSpans #0
ScopeSpans SchemaURL:
InstrumentationScope
Span #0
Trace ID : 0102030405060708090a0b0c0d0e0f10
Parent ID :
ID : 1112131415161718
Name : operationA
Kind : Unspecified
TraceState : ot=th:0
Start time : 2020-02-11 20:26:12.000000321 +0000 UTC
End time : 2020-02-11 20:26:13.000000789 +0000 UTC
Status code : Error
Status message : status-cancelled
DroppedAttributesCount: 1
DroppedEventsCount: 1
DroppedLinksCount: 0
Events:
SpanEvent #0
-> Name: event-with-attr
-> Timestamp: 2020-02-11 20:26:13.000000123 +0000 UTC
-> DroppedAttributesCount: 2
-> Attributes::
-> span-event-attr: Str(span-event-attr-val)
SpanEvent #1
-> Name: event
-> Timestamp: 2020-02-11 20:26:13.000000123 +0000 UTC
-> DroppedAttributesCount: 2
Span #1
Trace ID :
Parent ID :
ID :
Name : operationB
Kind : Unspecified
Start time : 2020-02-11 20:26:12.000000321 +0000 UTC
End time : 2020-02-11 20:26:13.000000789 +0000 UTC
Status code : Unset
Status message :
DroppedAttributesCount: 0
DroppedEventsCount: 0
DroppedLinksCount: 3
Links:
SpanLink #0
-> Trace ID:
-> ID:
-> TraceState:
-> DroppedAttributesCount: 4
-> Attributes::
-> span-link-attr: Str(span-link-attr-val)
SpanLink #1
-> Trace ID:
-> ID:
-> TraceState:
-> DroppedAttributesCount: 4
================================================
FILE: exporter/debugexporter/internal/otlptext/traces.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlptext // import "go.opentelemetry.io/collector/exporter/debugexporter/internal/otlptext"
import (
"strconv"
"go.opentelemetry.io/collector/pdata/ptrace"
)
// NewTextTracesMarshaler returns a ptrace.Marshaler to encode to OTLP text bytes.
func NewTextTracesMarshaler() ptrace.Marshaler {
return textTracesMarshaler{}
}
type textTracesMarshaler struct{}
// MarshalTraces ptrace.Traces to OTLP text.
func (textTracesMarshaler) MarshalTraces(td ptrace.Traces) ([]byte, error) {
buf := dataBuffer{}
rss := td.ResourceSpans()
for i := 0; i < rss.Len(); i++ {
buf.logEntry("ResourceSpans #%d", i)
rs := rss.At(i)
buf.logEntry("Resource SchemaURL: %s", rs.SchemaUrl())
buf.logAttributes("Resource attributes", rs.Resource().Attributes())
buf.logEntityRefs(rs.Resource())
ilss := rs.ScopeSpans()
for j := 0; j < ilss.Len(); j++ {
buf.logEntry("ScopeSpans #%d", j)
ils := ilss.At(j)
buf.logEntry("ScopeSpans SchemaURL: %s", ils.SchemaUrl())
buf.logInstrumentationScope(ils.Scope())
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())
buf.logAttr("Parent ID", span.ParentSpanID())
buf.logAttr("ID", span.SpanID())
buf.logAttr("Name", span.Name())
buf.logAttr("Kind", span.Kind().String())
if ts := span.TraceState().AsRaw(); ts != "" {
buf.logAttr("TraceState", ts)
}
buf.logAttr("Start time", span.StartTimestamp().String())
buf.logAttr("End time", span.EndTimestamp().String())
buf.logAttr("Status code", span.Status().Code().String())
buf.logAttr("Status message", span.Status().Message())
buf.logAttr("DroppedAttributesCount", strconv.FormatUint(uint64(span.DroppedAttributesCount()), 10))
buf.logAttr("DroppedEventsCount", strconv.FormatUint(uint64(span.DroppedEventsCount()), 10))
buf.logAttr("DroppedLinksCount", strconv.FormatUint(uint64(span.DroppedLinksCount()), 10))
buf.logAttributes("Attributes", span.Attributes())
buf.logEvents("Events", span.Events())
buf.logLinks("Links", span.Links())
}
}
}
return buf.buf.Bytes(), nil
}
================================================
FILE: exporter/debugexporter/internal/otlptext/traces_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlptext
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/pdata/testdata"
)
func TestTracesText(t *testing.T) {
tests := []struct {
name string
in ptrace.Traces
out string
}{
{
name: "empty_traces",
in: ptrace.NewTraces(),
out: "empty.out",
},
{
name: "two_spans",
in: testdata.GenerateTraces(2),
out: "two_spans.out",
},
{
name: "traces_with_entity_refs",
in: generateTracesWithEntityRefs(),
out: "traces_with_entity_refs.out",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := NewTextTracesMarshaler().MarshalTraces(tt.in)
require.NoError(t, err)
out, err := os.ReadFile(filepath.Join("testdata", "traces", tt.out))
require.NoError(t, err)
expected := strings.ReplaceAll(string(out), "\r", "")
assert.Equal(t, expected, string(got))
})
}
}
func generateTracesWithEntityRefs() ptrace.Traces {
td := ptrace.NewTraces()
rs := td.ResourceSpans().AppendEmpty()
setupResourceWithEntityRefs(rs.Resource())
ss := rs.ScopeSpans().AppendEmpty()
ss.Scope().SetName("test-scope")
span := ss.Spans().AppendEmpty()
span.SetName("test-span")
span.SetSpanID([8]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08})
span.SetTraceID([16]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10})
return td
}
================================================
FILE: exporter/debugexporter/metadata.yaml
================================================
display_name: Debug Exporter
type: debug
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
codeowners:
active:
- andrzej-stencel
class: exporter
stability:
alpha: [traces, metrics, logs, profiles]
distributions: [core, contrib, k8s]
warnings: [Unstable Output Format]
================================================
FILE: exporter/debugexporter/testdata/config_output_paths.yaml
================================================
use_internal_logger: false
output_paths:
- stderr
================================================
FILE: exporter/debugexporter/testdata/config_output_paths_empty.yaml
================================================
use_internal_logger: false
output_paths: []
================================================
FILE: exporter/debugexporter/testdata/config_verbosity.yaml
================================================
verbosity: detailed
sampling_initial: 10
sampling_thereafter: 50
use_internal_logger: false
output_paths:
- stdout
================================================
FILE: exporter/debugexporter/testdata/config_verbosity_typo.yaml
================================================
# Typo in the configuration that assumes that this property is camelcase
verBosity: detailed
================================================
FILE: exporter/example_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package exporter_test
import (
"context"
"fmt"
"go.uber.org/zap"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/config/configretry"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exporterhelper"
"go.opentelemetry.io/collector/pdata/pmetric"
)
// typeStr defines the unique type identifier for the exporter.
var typeStr = component.MustNewType("example")
// exampleConfig holds configuration settings for the exporter.
type exampleConfig struct {
QueueSettings configoptional.Optional[exporterhelper.QueueBatchConfig]
BackOffConfig configretry.BackOffConfig
}
// exampleExporter implements the OpenTelemetry exporter interface.
type exampleExporter struct {
cancel context.CancelFunc
config exampleConfig
client *loggerClient
}
// loggerClient wraps a Zap logger to provide logging functionality.
type loggerClient struct {
logger *zap.Logger
}
// Example demonstrates the usage of the exporter factory.
func Example() {
// Instantiate the exporter factory and print its type.
exampleExporter := NewFactory()
fmt.Println(exampleExporter.Type())
// Output:
// example
}
// NewFactory creates a new exporter factory.
func NewFactory() exporter.Factory {
return exporter.NewFactory(
typeStr,
createDefaultConfig,
exporter.WithMetrics(createExampleExporter, component.StabilityLevelAlpha),
)
}
// createDefaultConfig returns the default configuration for the exporter.
func createDefaultConfig() component.Config {
return &exampleConfig{}
}
// createExampleExporter initializes an instance of the example exporter.
func createExampleExporter(ctx context.Context, params exporter.Settings, baseCfg component.Config) (exporter.Metrics, error) {
// Convert baseCfg to the correct type.
cfg := baseCfg.(*exampleConfig)
// Create a new exporter instance.
xptr := newExampleExporter(ctx, cfg, params)
// Wrap the exporter with the helper utilities.
return exporterhelper.NewMetrics(
ctx,
params,
cfg,
xptr.consumeMetrics,
exporterhelper.WithQueue(cfg.QueueSettings),
exporterhelper.WithRetry(cfg.BackOffConfig),
exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}),
exporterhelper.WithShutdown(xptr.shutdown),
)
}
// newExampleExporter constructs a new instance of the example exporter.
func newExampleExporter(ctx context.Context, cfg *exampleConfig, params exporter.Settings) *exampleExporter {
xptr := &exampleExporter{
config: *cfg,
client: &loggerClient{logger: params.Logger},
}
// Create a cancelable context.
_, xptr.cancel = context.WithCancel(ctx)
return xptr
}
// consumeMetrics processes incoming metric data and logs it.
func (xptr *exampleExporter) consumeMetrics(_ context.Context, md pmetric.Metrics) error {
xptr.client.Push(md)
return nil
}
// Shutdown properly stops the exporter and releases resources.
func (xptr *exampleExporter) shutdown(_ context.Context) error {
xptr.cancel()
return nil
}
// Push logs the received metric data.
func (client *loggerClient) Push(md pmetric.Metrics) {
client.logger.Info("Exporting metrics", zap.Any("metrics", md))
}
================================================
FILE: exporter/exporter.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package exporter // import "go.opentelemetry.io/collector/exporter"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/internal/componentalias"
"go.opentelemetry.io/collector/pipeline"
)
// Traces is an exporter that can consume traces.
type Traces interface {
component.Component
consumer.Traces
}
// Metrics is an exporter that can consume metrics.
type Metrics interface {
component.Component
consumer.Metrics
}
// Logs is an exporter that can consume logs.
type Logs interface {
component.Component
consumer.Logs
}
// Settings configures exporter creators.
type Settings struct {
// ID returns the ID of the component that will be created.
ID component.ID
component.TelemetrySettings
// BuildInfo can be used by components for informational purposes
BuildInfo component.BuildInfo
// prevent unkeyed literal initialization
_ struct{}
}
// Factory is factory interface for exporters.
//
// This interface cannot be directly implemented. Implementations must
// use the NewFactory to implement it.
type Factory interface {
component.Factory
// CreateTraces creates a Traces exporter based on this config.
// If the exporter type does not support tracing,
// this function returns the error [pipeline.ErrSignalNotSupported].
CreateTraces(ctx context.Context, set Settings, cfg component.Config) (Traces, error)
// TracesStability gets the stability level of the Traces exporter.
TracesStability() component.StabilityLevel
// CreateMetrics creates a Metrics exporter based on this config.
// If the exporter type does not support metrics,
// this function returns the error [pipeline.ErrSignalNotSupported].
CreateMetrics(ctx context.Context, set Settings, cfg component.Config) (Metrics, error)
// MetricsStability gets the stability level of the Metrics exporter.
MetricsStability() component.StabilityLevel
// CreateLogs creates a Logs exporter based on the config.
// If the exporter type does not support logs,
// this function returns the error [pipeline.ErrSignalNotSupported].
CreateLogs(ctx context.Context, set Settings, cfg component.Config) (Logs, error)
// LogsStability gets the stability level of the Logs exporter.
LogsStability() component.StabilityLevel
unexportedFactoryFunc()
}
// FactoryOption apply changes to Factory.
type FactoryOption interface {
// applyOption applies the option.
applyOption(o *factory)
}
var _ FactoryOption = (*factoryOptionFunc)(nil)
// factoryOptionFunc is an FactoryOption created through a function.
type factoryOptionFunc func(*factory)
func (f factoryOptionFunc) applyOption(o *factory) {
f(o)
}
// CreateTracesFunc is the equivalent of Factory.CreateTraces.
type CreateTracesFunc func(context.Context, Settings, component.Config) (Traces, error)
// CreateMetricsFunc is the equivalent of Factory.CreateMetrics.
type CreateMetricsFunc func(context.Context, Settings, component.Config) (Metrics, error)
// CreateLogsFunc is the equivalent of Factory.CreateLogs.
type CreateLogsFunc func(context.Context, Settings, component.Config) (Logs, error)
type factory struct {
cfgType component.Type
component.CreateDefaultConfigFunc
componentalias.TypeAliasHolder
createTracesFunc CreateTracesFunc
tracesStabilityLevel component.StabilityLevel
createMetricsFunc CreateMetricsFunc
metricsStabilityLevel component.StabilityLevel
createLogsFunc CreateLogsFunc
logsStabilityLevel component.StabilityLevel
}
func (f *factory) Type() component.Type {
return f.cfgType
}
func (f *factory) unexportedFactoryFunc() {}
func (f *factory) TracesStability() component.StabilityLevel {
return f.tracesStabilityLevel
}
func (f *factory) MetricsStability() component.StabilityLevel {
return f.metricsStabilityLevel
}
func (f *factory) LogsStability() component.StabilityLevel {
return f.logsStabilityLevel
}
func (f *factory) CreateTraces(ctx context.Context, set Settings, cfg component.Config) (Traces, error) {
if f.createTracesFunc == nil {
return nil, pipeline.ErrSignalNotSupported
}
if err := componentalias.ValidateComponentType(f, set.ID); err != nil {
return nil, err
}
return f.createTracesFunc(ctx, set, cfg)
}
func (f *factory) CreateMetrics(ctx context.Context, set Settings, cfg component.Config) (Metrics, error) {
if f.createMetricsFunc == nil {
return nil, pipeline.ErrSignalNotSupported
}
if err := componentalias.ValidateComponentType(f, set.ID); err != nil {
return nil, err
}
return f.createMetricsFunc(ctx, set, cfg)
}
func (f *factory) CreateLogs(ctx context.Context, set Settings, cfg component.Config) (Logs, error) {
if f.createLogsFunc == nil {
return nil, pipeline.ErrSignalNotSupported
}
if err := componentalias.ValidateComponentType(f, set.ID); err != nil {
return nil, err
}
return f.createLogsFunc(ctx, set, cfg)
}
// WithTraces overrides the default "error not supported" implementation for Factory.CreateTraces and the default "undefined" stability level.
func WithTraces(createTraces CreateTracesFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.tracesStabilityLevel = sl
o.createTracesFunc = createTraces
})
}
// WithMetrics overrides the default "error not supported" implementation for Factory.CreateMetrics and the default "undefined" stability level.
func WithMetrics(createMetrics CreateMetricsFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.metricsStabilityLevel = sl
o.createMetricsFunc = createMetrics
})
}
// WithLogs overrides the default "error not supported" implementation for Factory.CreateLogs and the default "undefined" stability level.
func WithLogs(createLogs CreateLogsFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.logsStabilityLevel = sl
o.createLogsFunc = createLogs
})
}
// NewFactory returns a Factory.
func NewFactory(cfgType component.Type, createDefaultConfig component.CreateDefaultConfigFunc, options ...FactoryOption) Factory {
f := &factory{
cfgType: cfgType,
CreateDefaultConfigFunc: createDefaultConfig,
TypeAliasHolder: componentalias.NewTypeAliasHolder(),
}
for _, opt := range options {
opt.applyOption(f)
}
return f
}
================================================
FILE: exporter/exporter_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package exporter
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/exporter/internal/experr"
"go.opentelemetry.io/collector/pipeline"
)
var (
testType = component.MustNewType("test")
testID = component.NewID(testType)
)
func TestNewFactory(t *testing.T) {
defaultCfg := struct{}{}
f := NewFactory(
testType,
func() component.Config { return &defaultCfg })
assert.Equal(t, testType, f.Type())
assert.EqualValues(t, &defaultCfg, f.CreateDefaultConfig())
_, err := f.CreateTraces(context.Background(), Settings{ID: testID}, &defaultCfg)
require.ErrorIs(t, err, pipeline.ErrSignalNotSupported)
_, err = f.CreateMetrics(context.Background(), Settings{ID: testID}, &defaultCfg)
require.ErrorIs(t, err, pipeline.ErrSignalNotSupported)
_, err = f.CreateLogs(context.Background(), Settings{ID: testID}, &defaultCfg)
require.ErrorIs(t, err, pipeline.ErrSignalNotSupported)
}
func TestNewFactoryWithOptions(t *testing.T) {
defaultCfg := struct{}{}
f := NewFactory(
testType,
func() component.Config { return &defaultCfg },
WithTraces(createTraces, component.StabilityLevelDevelopment),
WithMetrics(createMetrics, component.StabilityLevelAlpha),
WithLogs(createLogs, component.StabilityLevelDeprecated))
assert.Equal(t, testType, f.Type())
assert.EqualValues(t, &defaultCfg, f.CreateDefaultConfig())
wrongID := component.MustNewID("wrong")
wrongIDErrStr := experr.ErrIDMismatch(wrongID, testType).Error()
assert.Equal(t, component.StabilityLevelDevelopment, f.TracesStability())
_, err := f.CreateTraces(context.Background(), Settings{ID: testID}, &defaultCfg)
require.NoError(t, err)
_, err = f.CreateTraces(context.Background(), Settings{ID: wrongID}, &defaultCfg)
require.EqualError(t, err, wrongIDErrStr)
assert.Equal(t, component.StabilityLevelAlpha, f.MetricsStability())
_, err = f.CreateMetrics(context.Background(), Settings{ID: testID}, &defaultCfg)
require.NoError(t, err)
_, err = f.CreateMetrics(context.Background(), Settings{ID: wrongID}, &defaultCfg)
require.EqualError(t, err, wrongIDErrStr)
assert.Equal(t, component.StabilityLevelDeprecated, f.LogsStability())
_, err = f.CreateLogs(context.Background(), Settings{ID: testID}, &defaultCfg)
require.NoError(t, err)
_, err = f.CreateLogs(context.Background(), Settings{ID: wrongID}, &defaultCfg)
require.EqualError(t, err, wrongIDErrStr)
}
var nopInstance = &nop{
Consumer: consumertest.NewNop(),
}
// nop stores consumed traces and metrics for testing purposes.
type nop struct {
component.StartFunc
component.ShutdownFunc
consumertest.Consumer
}
func createTraces(context.Context, Settings, component.Config) (Traces, error) {
return nopInstance, nil
}
func createMetrics(context.Context, Settings, component.Config) (Metrics, error) {
return nopInstance, nil
}
func createLogs(context.Context, Settings, component.Config) (Logs, error) {
return nopInstance, nil
}
================================================
FILE: exporter/exporterhelper/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: exporter/exporterhelper/README.md
================================================
# Exporter Helper
This package provides reusable implementations of common capabilities for exporters.
Currently, this includes queuing, batching, timeouts, and retries.
## Configuration
The following configuration options can be modified:
### Retry on Failure
- `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 = 300s): Is the maximum amount of time spent trying to send a batch; ignored if `enabled` is `false`. If set to 0, the retries are never stopped.
- `multiplier` (default = 1.5): Factor by which the retry interval is multiplied on each attempt; ignored if `enabled` is `false`
### Sending Queue
- `sending_queue`
- `enabled` (default = true)
- `num_consumers` (default = 10): Number of consumers that dequeue batches; ignored if `enabled` is `false`
- `wait_for_result` (default = false): determines if incoming requests are blocked until the request is processed or not.
- `block_on_overflow` (default = false): If true, blocks the request until the queue has space otherwise rejects the data immediately; ignored if `enabled` is `false`
- `sizer` (default = requests): How the queue and batching is measured. Available options:
- `requests`: number of incoming batches of metrics, logs, traces (the most performant option);
- `items`: number of the smallest parts of each signal (spans, metric data points, log records);
- `bytes`: the size of serialized data in bytes (the least performant option).
- `queue_size` (default = 1000): Maximum size the queue can accept. Measured in units defined by `sizer`
- `batch`: see below.
**Failure behavior**: If data cannot be added to the sending queue, it is typically dropped. This occurs when the queue has reached its configured capacity or, for persistent queues, when the underlying storage cannot accept additional data (for example, due to insufficient disk space or I/O errors).
When `block_on_overflow` is enabled, the caller may instead wait until space becomes available, and the request may still be enqueued if capacity frees up before the timeout.
If data is rejected before entering the queue, it does not reach the exporter retry logic. Such enqueue failures are reported by the `otelcol_exporter_enqueue_failed_*` metrics.
#### Sending queue batch settings
Batch settings are available in the sending queue. Batching is disabled, by default. To enable default
batch settings, use `batch: {}`. When `batch` is defined, the settings are:
- `flush_timeout` (default = 200 ms): time after which a batch will be sent regardless of its size. Must be a non-zero value;
- `min_size` (default = 8192): the minimum size of a batch; should be less than or equal to the `sending_queue::queue_size` if `sending_queue::batch::sizer` matches `sending_queue::sizer`.
- `max_size` (default = 0): the maximum size of a batch, enables batch splitting. The maximum size of a batch should be greater than or equal to the minimum size of a batch. If set to zero, there is no maximum size;
- `sizer`: see below.
- `partition`: see below.
The `batch::sizer` field is given special treatment because the queue itself also defines a `sizer`. This field supports using different size limits for the queue and batch-related logic.
If the `batch::sizer` field is not set, it takes its value from the parent structure.
If `sending_queue::sizer` is not set, `batch::sizer` defaults to `items`.
Available `batch::sizer` options:
- `items`: number of the smallest parts of each signal (spans, metric data points, log records);
- `bytes`: the size of serialized data in bytes (the least performant option).
The `batch::partition` configuration defines the partitioning of the batches.
Available `batch::partition` options:
- `metadata_keys`: a list of `client.Metadata` keys used to partition data into
separate batches. When empty, a single batcher instance is used. When set, one batcher will be used
per distinct combination of values for the listed metadata keys. Empty value and unset metadata are
treated as distinct cases. Entries are case-insensitive. Duplicated entries will trigger a validation error. Default is empty.
### Timeout
- `timeout` (default = 5s): Time to wait per individual attempt to send data to a backend
The `initial_interval`, `max_interval`, `max_elapsed_time`, and `timeout` options accept
[duration strings](https://pkg.go.dev/time#ParseDuration),
valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
### Persistent Queue
To use the persistent queue, the following setting needs to be set:
- `sending_queue`
- `storage` (default = none): When set, enables persistence and uses the component specified as a storage extension for the persistent queue.
There is no in-memory queue when set.
The maximum number of batches stored to disk can be controlled using `sending_queue.queue_size` parameter (which,
similarly as for in-memory buffering, defaults to 1000 batches).
When persistent queue is enabled, the batches are being buffered using the provided storage extension - [filestorage] is a popular and safe choice. If the collector instance is killed while having some items in the persistent queue, on restart the items will be picked and the exporting is continued.
**Context Propagation**: Request context (including client metadata and span context) is preserved when using persistent queues. However, context set by Auth extensions is **not** propagated through the persistent queue. Auth extension context is ignored when data is persisted to disk, which means authentication/authorization information will not be available when the persisted data is processed.
```
┌─Consumer #1─┐
│ ┌───┐ │
──────Deleted────── ┌───►│ │ 1 │ ├───► Success
Waiting in channel x x x │ │ └───┘ │
for consumer ───┐ x x x │ │ │
│ x x x │ └─────────────┘
▼ x x x │
┌─────────────────────────────────────────x─────x───┐ │ ┌─Consumer #2─┐
│ x x x │ │ │ ┌───┐ │
│ ┌───┐ ┌───┐ ┌───┐ ┌─x─┐ ┌───┐ ┌─x─┐ ┌─x─┐ │ │ │ │ 2 │ ├───► Permanent -> X
│ n+1 │ n │ ... │ 6 │ │ 5 │ │ 4 │ │ 3 │ │ 2 │ │ 1 │ ├────┼───►│ └───┘ │ failure
│ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ │ │ │ │
│ │ │ └─────────────┘
└───────────────────────────────────────────────────┘ │
▲ ▲ ▲ ▲ │ ┌─Consumer #3─┐
│ │ │ │ │ │ ┌───┐ │
│ │ │ │ │ │ │ 3 │ ├───► (in progress)
write read └─────┬─────┘ ├───►│ └───┘ │
index index │ │ │ │
│ │ └─────────────┘
│ │
currently │ ┌─Consumer #4─┐
dispatched │ │ ┌───┐ │ Temporary
└───►│ │ 4 │ ├───► failure
│ └───┘ │ │
│ │ │
└─────────────┘ │
▲ │
└── Retry ───────┤
│
│
X ◄────── Retry limit exceeded ───┘
```
Example:
```
receivers:
otlp:
protocols:
grpc:
exporters:
otlp_grpc:
endpoint:
sending_queue:
storage: file_storage/otc
extensions:
file_storage/otc:
directory: /var/lib/storage/otc
timeout: 10s
service:
extensions: [file_storage]
pipelines:
metrics:
receivers: [otlp]
exporters: [otlp]
logs:
receivers: [otlp]
exporters: [otlp]
traces:
receivers: [otlp]
exporters: [otlp]
```
[filestorage]: https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/extension/storage/filestorage
================================================
FILE: exporter/exporterhelper/common.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package exporterhelper // import "go.opentelemetry.io/collector/exporter/exporterhelper"
import (
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configretry"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal"
)
// Option apply changes to BaseExporter.
type Option = internal.Option
// WithStart overrides the default Start function for an exporter.
// The default start function does nothing and always returns nil.
func WithStart(start component.StartFunc) Option {
return internal.WithStart(start)
}
// WithShutdown overrides the default Shutdown function for an exporter.
// The default shutdown function does nothing and always returns nil.
func WithShutdown(shutdown component.ShutdownFunc) Option {
return internal.WithShutdown(shutdown)
}
// WithTimeout overrides the default TimeoutConfig for an exporter.
// The default TimeoutConfig is 5 seconds.
func WithTimeout(timeoutConfig TimeoutConfig) Option {
return internal.WithTimeout(timeoutConfig)
}
// WithRetry overrides the default configretry.BackOffConfig for an exporter.
// The default configretry.BackOffConfig is to disable retries.
func WithRetry(config configretry.BackOffConfig) Option {
return internal.WithRetry(config)
}
// WithCapabilities overrides the default Capabilities() function for a Consumer.
// The default is non-mutable data.
// TODO: Verify if we can change the default to be mutable as we do for processors.
func WithCapabilities(capabilities consumer.Capabilities) Option {
return internal.WithCapabilities(capabilities)
}
================================================
FILE: exporter/exporterhelper/config.schema.yaml
================================================
$defs:
batch_config:
description: BatchConfig defines a configuration for batching requests based on a timeout and a minimum number of items.
$ref: ./internal/queuebatch.batch_config
option:
description: Option apply changes to BaseExporter.
$ref: ./internal.option
queue_batch_config:
description: QueueBatchConfig defines configuration for queueing and batching for the exporter.
$ref: ./internal/queuebatch.config
request_sizer_type:
$ref: ./internal/request.sizer_type
timeout_config:
$ref: ./internal.timeout_config
================================================
FILE: exporter/exporterhelper/constants.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package exporterhelper // import "go.opentelemetry.io/collector/exporter/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")
// errNilPushTraces is returned when a nil PushTraces is given.
errNilPushTraces = errors.New("nil PushTraces")
// errNilPushMetrics is returned when a nil PushMetrics is given.
errNilPushMetrics = errors.New("nil PushMetrics")
// errNilPushLogs is returned when a nil PushLogs is given.
errNilPushLogs = errors.New("nil PushLogs")
)
================================================
FILE: exporter/exporterhelper/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:generate mdatagen metadata.yaml
// Package exporterhelper provides helper functions for exporters.
package exporterhelper // import "go.opentelemetry.io/collector/exporter/exporterhelper"
================================================
FILE: exporter/exporterhelper/documentation.md
================================================
[comment]: <> (Code generated by mdatagen. DO NOT EDIT.)
# exporterhelper
## Internal Telemetry
The following telemetry is emitted by this component.
### otelcol_exporter_enqueue_failed_log_records
Number of log records failed to be added to the sending queue.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {record} | Sum | Int | true | Alpha |
### otelcol_exporter_enqueue_failed_metric_points
Number of metric points failed to be added to the sending queue.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {datapoint} | Sum | Int | true | Alpha |
### otelcol_exporter_enqueue_failed_profile_samples
Number of profile samples failed to be added to the sending queue.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {sample} | Sum | Int | true | Development |
### otelcol_exporter_enqueue_failed_spans
Number of spans failed to be added to the sending queue.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {span} | Sum | Int | true | Alpha |
### otelcol_exporter_queue_batch_send_size
Number of units in the batch
| Unit | Metric Type | Value Type | Stability |
| ---- | ----------- | ---------- | --------- |
| {unit} | Histogram | Int | Development |
### otelcol_exporter_queue_batch_send_size_bytes
Number of bytes in batch that was sent. Only available on detailed level.
| Unit | Metric Type | Value Type | Stability |
| ---- | ----------- | ---------- | --------- |
| By | Histogram | Int | Development |
### otelcol_exporter_queue_capacity
Fixed capacity of the retry queue (in batches).
| Unit | Metric Type | Value Type | Stability |
| ---- | ----------- | ---------- | --------- |
| {batch} | Gauge | Int | Alpha |
### otelcol_exporter_queue_size
Current size of the retry queue (in batches).
| Unit | Metric Type | Value Type | Stability |
| ---- | ----------- | ---------- | --------- |
| {batch} | Gauge | Int | Alpha |
### otelcol_exporter_send_failed_log_records
Number of log records in failed attempts to send to destination. At detailed telemetry level, includes attributes: error.type (semantic convention), error.permanent.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {record} | Sum | Int | true | Alpha |
### otelcol_exporter_send_failed_metric_points
Number of metric points in failed attempts to send to destination. At detailed telemetry level, includes attributes: error.type (semantic convention), error.permanent.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {datapoint} | Sum | Int | true | Alpha |
### otelcol_exporter_send_failed_profile_samples
Number of profile samples in failed attempts to send to destination. At detailed telemetry level, includes attributes: error.type (semantic convention), error.permanent.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {sample} | Sum | Int | true | Development |
### otelcol_exporter_send_failed_spans
Number of spans in failed attempts to send to destination. At detailed telemetry level, includes attributes: error.type (semantic convention), error.permanent.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {span} | Sum | Int | true | Alpha |
### otelcol_exporter_sent_log_records
Number of log record successfully sent to destination.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {record} | Sum | Int | true | Alpha |
### otelcol_exporter_sent_metric_points
Number of metric points successfully sent to destination.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {datapoint} | Sum | Int | true | Alpha |
### otelcol_exporter_sent_profile_samples
Number of profile samples successfully sent to destination.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {sample} | Sum | Int | true | Development |
### otelcol_exporter_sent_spans
Number of spans successfully sent to destination.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {span} | Sum | Int | true | Alpha |
## Feature Gates
This component has the following feature gates:
| Feature Gate | Stage | Description | From Version | To Version | Reference |
| ------------ | ----- | ----------- | ------------ | ---------- | --------- |
| `exporter.PersistRequestContext` | beta | controls whether context should be stored alongside requests in the persistent queue | v0.128.0 | N/A | [Link](https://github.com/open-telemetry/opentelemetry-collector/pull/13188) |
For more information about feature gates, see the [Feature Gates](https://github.com/open-telemetry/opentelemetry-collector/blob/main/featuregate/README.md) documentation.
================================================
FILE: exporter/exporterhelper/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package exporterhelper
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: exporter/exporterhelper/go.mod
================================================
module go.opentelemetry.io/collector/exporter/exporterhelper
go 1.25.0
require (
github.com/cenkalti/backoff/v5 v5.0.3
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/client v1.54.0
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.opentelemetry.io/collector/config/configoptional v1.54.0
go.opentelemetry.io/collector/config/configretry v1.54.0
go.opentelemetry.io/collector/confmap v1.54.0
go.opentelemetry.io/collector/confmap/xconfmap v0.148.0
go.opentelemetry.io/collector/consumer v1.54.0
go.opentelemetry.io/collector/consumer/consumererror v0.148.0
go.opentelemetry.io/collector/consumer/consumertest v0.148.0
go.opentelemetry.io/collector/exporter v1.54.0
go.opentelemetry.io/collector/exporter/exportertest v0.148.0
go.opentelemetry.io/collector/extension/extensiontest v0.148.0
go.opentelemetry.io/collector/extension/xextension v0.148.0
go.opentelemetry.io/collector/featuregate v1.54.0
go.opentelemetry.io/collector/internal/testutil v0.148.0
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/collector/pdata/pprofile v0.148.0
go.opentelemetry.io/collector/pdata/testdata v0.148.0
go.opentelemetry.io/collector/pdata/xpdata v0.148.0
go.opentelemetry.io/collector/pipeline v1.54.0
go.opentelemetry.io/collector/pipeline/xpipeline v0.148.0
go.opentelemetry.io/otel v1.42.0
go.opentelemetry.io/otel/metric v1.42.0
go.opentelemetry.io/otel/sdk v1.42.0
go.opentelemetry.io/otel/sdk/metric v1.42.0
go.opentelemetry.io/otel/trace v1.42.0
go.uber.org/goleak v1.3.0
go.uber.org/multierr v1.11.0
go.uber.org/zap v1.27.1
google.golang.org/grpc v1.79.3
google.golang.org/protobuf v1.36.11
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0 // indirect
go.opentelemetry.io/collector/exporter/xexporter v0.148.0 // indirect
go.opentelemetry.io/collector/extension v1.54.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
go.opentelemetry.io/collector/receiver v1.54.0 // indirect
go.opentelemetry.io/collector/receiver/receivertest v0.148.0 // indirect
go.opentelemetry.io/collector/receiver/xreceiver v0.148.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/sys v0.41.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest
replace go.opentelemetry.io/collector/consumer => ../../consumer
replace go.opentelemetry.io/collector/extension => ../../extension
replace go.opentelemetry.io/collector/exporter => ../
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/pdata/testdata => ../../pdata/testdata
replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile
replace go.opentelemetry.io/collector/pipeline => ../../pipeline
replace go.opentelemetry.io/collector/receiver => ../../receiver
replace go.opentelemetry.io/collector/config/configretry => ../../config/configretry
replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer
replace go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest
replace go.opentelemetry.io/collector/receiver/xreceiver => ../../receiver/xreceiver
replace go.opentelemetry.io/collector/receiver/receivertest => ../../receiver/receivertest
replace go.opentelemetry.io/collector/exporter/xexporter => ../xexporter
replace go.opentelemetry.io/collector/exporter/exportertest => ../exportertest
replace go.opentelemetry.io/collector/consumer/consumererror => ../../consumer/consumererror
replace go.opentelemetry.io/collector/extension/extensiontest => ../../extension/extensiontest
replace go.opentelemetry.io/collector/extension/xextension => ../../extension/xextension
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/client => ../../client
replace go.opentelemetry.io/collector/pdata/xpdata => ../../pdata/xpdata
replace go.opentelemetry.io/collector/confmap => ../../confmap
replace go.opentelemetry.io/collector/config/configoptional => ../../config/configoptional
replace go.opentelemetry.io/collector/confmap/xconfmap => ../../confmap/xconfmap
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
replace go.opentelemetry.io/collector/pipeline/xpipeline => ../../pipeline/xpipeline
================================================
FILE: exporter/exporterhelper/go.sum
================================================
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: exporter/exporterhelper/internal/base_exporter.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal"
import (
"context"
"errors"
"go.uber.org/multierr"
"go.uber.org/zap"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/config/configretry"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sender"
"go.opentelemetry.io/collector/pipeline"
)
// Option apply changes to BaseExporter.
type Option func(*BaseExporter) error
// BaseExporter contains common fields between different exporter types.
type BaseExporter struct {
component.StartFunc
component.ShutdownFunc
Set exporter.Settings
// Message for the user to be added with an export failure message.
ExportFailureMessage string
// Chain of senders that the exporter helper applies before passing the data to the actual exporter.
// The data is handled by each sender in the respective order starting from the QueueBatch.
// Most of the senders are optional, and initialized with a no-op path-through sender.
QueueSender sender.Sender[request.Request]
RetrySender sender.Sender[request.Request]
firstSender sender.Sender[request.Request]
ConsumerOptions []consumer.Option
timeoutCfg TimeoutConfig
retryCfg configretry.BackOffConfig
queueBatchSettings queuebatch.Settings[request.Request]
queueCfg configoptional.Optional[queuebatch.Config]
}
func NewBaseExporter(set exporter.Settings, signal pipeline.Signal, pusher sender.SendFunc[request.Request], options ...Option) (*BaseExporter, error) {
be := &BaseExporter{
Set: set,
timeoutCfg: NewDefaultTimeoutConfig(),
}
for _, op := range options {
if err := op(be); err != nil {
return nil, err
}
}
// Consumer Sender is always initialized.
be.firstSender = sender.NewSender(pusher)
// Next setup the timeout Sender since we want the timeout to control only the export functionality.
// Only initialize if not explicitly disabled.
if be.timeoutCfg.Timeout != 0 {
be.firstSender = newTimeoutSender(be.timeoutCfg, be.firstSender)
}
if be.retryCfg.Enabled {
be.RetrySender = newRetrySender(be.retryCfg, set, be.firstSender)
be.firstSender = be.RetrySender
}
var err error
be.firstSender, err = newObsReportSender(set, signal, be.firstSender)
if err != nil {
return nil, err
}
if be.queueCfg.HasValue() && be.queueCfg.Get().Batch.HasValue() {
// Batcher mutates the data.
be.ConsumerOptions = append(be.ConsumerOptions, consumer.WithCapabilities(consumer.Capabilities{MutatesData: true}))
}
if be.queueCfg.HasValue() {
qSet := queuebatch.AllSettings[request.Request]{
Settings: be.queueBatchSettings,
Signal: signal,
ID: set.ID,
Telemetry: set.TelemetrySettings,
}
be.QueueSender, err = NewQueueSender(qSet, *be.queueCfg.Get(), be.ExportFailureMessage, be.firstSender)
if err != nil {
return nil, err
}
be.firstSender = be.QueueSender
}
return be, nil
}
// Send sends the request using the first sender in the chain.
func (be *BaseExporter) Send(ctx context.Context, req request.Request) error {
// Have to read the number of items before sending the request since the request can
// be modified by the downstream components like the batcher.
itemsCount := req.ItemsCount()
err := be.firstSender.Send(ctx, req)
if err != nil {
be.Set.Logger.Error("Exporting failed. Rejecting data."+be.ExportFailureMessage,
zap.Error(err), zap.Int("rejected_items", itemsCount))
}
return err
}
func (be *BaseExporter) Start(ctx context.Context, host component.Host) error {
// First start the wrapped exporter.
if err := be.StartFunc.Start(ctx, host); err != nil {
return err
}
// Last start the QueueBatch.
if be.QueueSender != nil {
return be.QueueSender.Start(ctx, host)
}
return nil
}
func (be *BaseExporter) Shutdown(ctx context.Context) error {
var err error
// First shutdown the retry sender, so the queue sender can flush the queue without retries.
if be.RetrySender != nil {
err = multierr.Append(err, be.RetrySender.Shutdown(ctx))
}
// Then shutdown the queue sender.
if be.QueueSender != nil {
err = multierr.Append(err, be.QueueSender.Shutdown(ctx))
}
// Last shutdown the wrapped exporter itself.
return multierr.Append(err, be.ShutdownFunc.Shutdown(ctx))
}
// WithStart overrides the default Start function for an exporter.
// The default start function does nothing and always returns nil.
func WithStart(start component.StartFunc) Option {
return func(o *BaseExporter) error {
o.StartFunc = start
return nil
}
}
// WithShutdown overrides the default Shutdown function for an exporter.
// The default shutdown function does nothing and always returns nil.
func WithShutdown(shutdown component.ShutdownFunc) Option {
return func(o *BaseExporter) error {
o.ShutdownFunc = shutdown
return nil
}
}
// WithTimeout overrides the default TimeoutConfig for an exporter.
// The default TimeoutConfig is 5 seconds.
func WithTimeout(timeoutConfig TimeoutConfig) Option {
return func(o *BaseExporter) error {
o.timeoutCfg = timeoutConfig
return nil
}
}
// WithRetry overrides the default configretry.BackOffConfig for an exporter.
// The default configretry.BackOffConfig is to disable retries.
func WithRetry(config configretry.BackOffConfig) Option {
return func(o *BaseExporter) error {
if !config.Enabled {
o.ExportFailureMessage += " Try enabling retry_on_failure config option to retry on retryable errors."
return nil
}
o.retryCfg = config
return nil
}
}
// WithQueue overrides the default queuebatch.Config for an exporter.
// The default queuebatch.Config is to disable queueing.
// This option cannot be used with the new exporter helpers New[Traces|Metrics|Logs]RequestExporter.
func WithQueue(cfg configoptional.Optional[queuebatch.Config]) Option {
return func(o *BaseExporter) error {
if o.queueBatchSettings.Encoding == nil {
return errors.New("WithQueue option is not available for the new request exporters, use WithQueueBatch instead")
}
return WithQueueBatch(cfg, o.queueBatchSettings)(o)
}
}
// WithQueueBatch enables queueing for an exporter.
// This option should be used with the new exporter helpers New[Traces|Metrics|Logs]RequestExporter.
// If batch.partition.MetadataKeys is set, it will automatically configure the partitioner and merge function
// to partition batches based on the specified metadata keys.
// Experimental: This API is at the early stage of development and may change without backward compatibility
// until https://github.com/open-telemetry/opentelemetry-collector/issues/8122 is resolved.
func WithQueueBatch(cfg configoptional.Optional[queuebatch.Config], set queuebatch.Settings[request.Request]) Option {
return func(o *BaseExporter) error {
if !cfg.HasValue() {
o.ExportFailureMessage += " Try enabling sending_queue to survive temporary failures."
return nil
}
if cfg.Get().StorageID != nil && set.Encoding == nil {
return errors.New("`Settings.Encoding` must not be nil when persistent queue is enabled")
}
// Automatically configure partitioner if MetadataKeys is set
if cfg.Get().Batch.HasValue() && len(cfg.Get().Batch.Get().Partition.MetadataKeys) > 0 {
if set.Partitioner != nil {
return errors.New("cannot use metadata_keys when a custom partitioner is already configured")
}
if set.MergeCtx != nil {
return errors.New("cannot use metadata_keys when a custom merge function is already configured")
}
set.Partitioner = queuebatch.NewMetadataKeysPartitioner(cfg.Get().Batch.Get().Partition.MetadataKeys)
set.MergeCtx = queuebatch.NewMetadataKeysMergeCtx(cfg.Get().Batch.Get().Partition.MetadataKeys)
}
o.queueBatchSettings = set
o.queueCfg = cfg
return nil
}
}
// WithCapabilities overrides the default Capabilities() function for a Consumer.
// The default is non-mutable data.
// TODO: Verify if we can change the default to be mutable as we do for processors.
func WithCapabilities(capabilities consumer.Capabilities) Option {
return func(o *BaseExporter) error {
o.ConsumerOptions = append(o.ConsumerOptions, consumer.WithCapabilities(capabilities))
return nil
}
}
// WithQueueBatchSettings is used to set the queuebatch.Settings for the new request based exporter helper.
// It must be provided as the first option when creating a new exporter helper.
func WithQueueBatchSettings(set queuebatch.Settings[request.Request]) Option {
return func(o *BaseExporter) error {
o.queueBatchSettings = set
return nil
}
}
================================================
FILE: exporter/exporterhelper/internal/base_exporter_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/config/configretry"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/requesttest"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/pipeline"
)
func TestBaseExporter(t *testing.T) {
be, err := NewBaseExporter(exportertest.NewNopSettings(exportertest.NopType), pipeline.SignalMetrics, noopExport)
require.NoError(t, err)
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, err := NewBaseExporter(
exportertest.NewNopSettings(exportertest.NopType), pipeline.SignalMetrics, noopExport,
WithStart(func(context.Context, component.Host) error { return want }),
WithShutdown(func(context.Context) error { return want }),
WithTimeout(NewDefaultTimeoutConfig()),
)
require.NoError(t, err)
require.Equal(t, want, be.Start(context.Background(), componenttest.NewNopHost()))
require.Equal(t, want, be.Shutdown(context.Background()))
}
func TestQueueOptionsWithRequestExporter(t *testing.T) {
bs, err := NewBaseExporter(exportertest.NewNopSettings(exportertest.NopType), pipeline.SignalMetrics, noopExport,
WithRetry(configretry.NewDefaultBackOffConfig()))
require.NoError(t, err)
require.Nil(t, bs.queueBatchSettings.Encoding)
_, err = NewBaseExporter(exportertest.NewNopSettings(exportertest.NopType), pipeline.SignalMetrics, noopExport,
WithRetry(configretry.NewDefaultBackOffConfig()), WithQueue(configoptional.Some(NewDefaultQueueConfig())))
require.Error(t, err)
qCfg := NewDefaultQueueConfig()
storageID := component.NewID(component.MustNewType("test"))
qCfg.StorageID = &storageID
_, err = NewBaseExporter(exportertest.NewNopSettings(exportertest.NopType), pipeline.SignalMetrics, noopExport,
WithQueueBatchSettings(newFakeQueueBatch()),
WithRetry(configretry.NewDefaultBackOffConfig()),
WithQueueBatch(configoptional.Some(qCfg), queuebatch.Settings[request.Request]{}))
require.Error(t, err)
}
func TestBaseExporterLogging(t *testing.T) {
set := exportertest.NewNopSettings(exportertest.NopType)
logger, observed := observer.New(zap.DebugLevel)
set.Logger = zap.New(logger)
rCfg := configretry.NewDefaultBackOffConfig()
rCfg.Enabled = false
qCfg := NewDefaultQueueConfig()
qCfg.WaitForResult = true
bs, err := NewBaseExporter(set, pipeline.SignalMetrics, errExport,
WithQueueBatchSettings(newFakeQueueBatch()),
WithQueue(configoptional.Some(qCfg)),
WithRetry(rCfg))
require.NoError(t, err)
require.NoError(t, bs.Start(context.Background(), componenttest.NewNopHost()))
sendErr := bs.Send(context.Background(), &requesttest.FakeRequest{Items: 2})
require.Error(t, sendErr)
errorLogs := observed.FilterLevelExact(zap.ErrorLevel).All()
require.Len(t, errorLogs, 2)
assert.Contains(t, errorLogs[0].Message, "Exporting failed. Dropping data.")
assert.Equal(t, "my error", errorLogs[0].ContextMap()["error"])
assert.Contains(t, errorLogs[1].Message, "Exporting failed. Rejecting data.")
assert.Equal(t, "my error", errorLogs[1].ContextMap()["error"])
require.NoError(t, bs.Shutdown(context.Background()))
}
func TestWithQueue_MetadataKeys(t *testing.T) {
t.Run("with MetadataKeys - configures partitioner and merge function", func(t *testing.T) {
qCfg := NewDefaultQueueConfig()
qCfg.Batch.GetOrInsertDefault().Partition.MetadataKeys = []string{"key1", "key2"}
be, err := NewBaseExporter(
exportertest.NewNopSettings(exportertest.NopType),
pipeline.SignalMetrics,
noopExport,
WithQueueBatchSettings(newFakeQueueBatch()),
WithQueue(configoptional.Some(qCfg)),
)
require.NoError(t, err)
assert.NotNil(t, be)
// Verify partitioner and merge function are configured
assert.NotNil(t, be.queueBatchSettings.Partitioner, "Partitioner should be set when MetadataKeys is provided")
assert.NotNil(t, be.queueBatchSettings.MergeCtx, "MergeCtx should be set when MetadataKeys is provided")
})
t.Run("without MetadataKeys - does not configure partitioner", func(t *testing.T) {
tests := []struct {
name string
metadataKeys []string
}{
{"empty slice", []string{}},
{"nil", nil},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
qCfg := NewDefaultQueueConfig()
qCfg.Batch.GetOrInsertDefault().Partition.MetadataKeys = tt.metadataKeys
be, err := NewBaseExporter(
exportertest.NewNopSettings(exportertest.NopType),
pipeline.SignalMetrics,
noopExport,
WithQueueBatchSettings(newFakeQueueBatch()),
WithQueue(configoptional.Some(qCfg)),
)
require.NoError(t, err)
assert.NotNil(t, be)
// Verify partitioner and merge function are NOT configured
assert.Nil(t, be.queueBatchSettings.Partitioner, "Partitioner should not be set when MetadataKeys is %s", tt.name)
assert.Nil(t, be.queueBatchSettings.MergeCtx, "MergeCtx should not be set when MetadataKeys is %s", tt.name)
})
}
})
t.Run("error when custom partitioner already set and metadata_keys used", func(t *testing.T) {
qCfg := NewDefaultQueueConfig()
qCfg.Batch.GetOrInsertDefault().Partition.MetadataKeys = []string{"key1", "key2"}
// Set up queue batch settings with a custom partitioner already configured
customSettings := newFakeQueueBatch()
customPartitioner := queuebatch.NewPartitioner(
func(context.Context, request.Request) string {
return "custom"
},
)
customSettings.Partitioner = customPartitioner
_, err := NewBaseExporter(
exportertest.NewNopSettings(exportertest.NopType),
pipeline.SignalMetrics,
noopExport,
WithQueueBatchSettings(customSettings),
WithQueue(configoptional.Some(qCfg)),
)
require.Error(t, err)
assert.Contains(t, err.Error(), "cannot use metadata_keys when a custom partitioner is already configured")
})
t.Run("error when custom merge function already set and metadata_keys used", func(t *testing.T) {
qCfg := NewDefaultQueueConfig()
qCfg.Batch.GetOrInsertDefault().Partition.MetadataKeys = []string{"key1", "key2"}
// Set up queue batch settings with a custom merge function already configured
customSettings := newFakeQueueBatch()
customSettings.MergeCtx = func(context.Context, context.Context) context.Context {
return context.Background()
}
_, err := NewBaseExporter(
exportertest.NewNopSettings(exportertest.NopType),
pipeline.SignalMetrics,
noopExport,
WithQueueBatchSettings(customSettings),
WithQueue(configoptional.Some(qCfg)),
)
require.Error(t, err)
assert.Contains(t, err.Error(), "cannot use metadata_keys when a custom merge function is already configured")
})
}
func TestQueueRetryWithDisabledQueue(t *testing.T) {
tests := []struct {
name string
queueOptions []Option
}{
{
name: "WithQueue",
queueOptions: []Option{
WithQueueBatchSettings(newFakeQueueBatch()),
func() Option {
return WithQueue(configoptional.None[queuebatch.Config]())
}(),
},
},
{
name: "WithRequestQueue",
queueOptions: []Option{
func() Option {
return WithQueueBatch(configoptional.None[queuebatch.Config](), newFakeQueueBatch())
}(),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
set := exportertest.NewNopSettings(exportertest.NopType)
logger, observed := observer.New(zap.ErrorLevel)
set.Logger = zap.New(logger)
be, err := NewBaseExporter(set, pipeline.SignalLogs, errExport, tt.queueOptions...)
require.NoError(t, err)
require.NoError(t, be.Start(context.Background(), componenttest.NewNopHost()))
mockR := &requesttest.FakeRequest{Items: 2}
require.Error(t, be.Send(context.Background(), mockR))
assert.Len(t, observed.All(), 1)
assert.Equal(t, "Exporting failed. Rejecting data. Try enabling sending_queue to survive temporary failures.", observed.All()[0].Message)
require.NoError(t, be.Shutdown(context.Background()))
})
}
}
func errExport(context.Context, request.Request) error {
return errors.New("my error")
}
func noopExport(context.Context, request.Request) error {
return nil
}
func newFakeQueueBatch() queuebatch.Settings[request.Request] {
return queuebatch.Settings[request.Request]{
Encoding: fakeEncoding{},
}
}
type fakeEncoding struct{}
func (f fakeEncoding) Marshal(context.Context, request.Request) ([]byte, error) {
return []byte("mockRequest"), nil
}
func (f fakeEncoding) Unmarshal([]byte) (context.Context, request.Request, error) {
return context.Background(), &requesttest.FakeRequest{}, nil
}
================================================
FILE: exporter/exporterhelper/internal/config.schema.yaml
================================================
$defs:
timeout_config:
description: TimeoutConfig for timeout. The timeout applies to individual attempts to send data to the backend.
type: object
properties:
timeout:
description: Timeout is the timeout for every attempt to send data to the backend. A zero timeout means no timeout.
type: string
x-customType: time.Duration
format: duration
================================================
FILE: exporter/exporterhelper/internal/constants.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal"
import "errors"
var (
// errNilLogger is returned when a logger is nil
errNilLogger = errors.New("nil logger")
// errNilConsumeRequest is returned when a nil PushTraces is given.
errNilConsumeRequest = errors.New("nil RequestConsumeFunc")
// errNilTracesConverter is returned when a nil RequestFromTracesFunc is given.
errNilTracesConverter = errors.New("nil RequestFromTracesFunc")
// errNilMetricsConverter is returned when a nil RequestFromMetricsFunc is given.
errNilMetricsConverter = errors.New("nil RequestFromMetricsFunc")
// errNilLogsConverter is returned when a nil RequestFromLogsFunc is given.
errNilLogsConverter = errors.New("nil RequestFromLogsFunc")
)
================================================
FILE: exporter/exporterhelper/internal/experr/err.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package experr // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/experr"
import (
"errors"
)
type shutdownErr struct {
err error
}
func NewShutdownErr(err error) error {
return shutdownErr{err: err}
}
func (s shutdownErr) Error() string {
return "interrupted due to shutdown: " + s.err.Error()
}
func (s shutdownErr) Unwrap() error {
return s.err
}
func IsShutdownErr(err error) bool {
var sdErr shutdownErr
return errors.As(err, &sdErr)
}
================================================
FILE: exporter/exporterhelper/internal/experr/err_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package experr
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewShutdownErr(t *testing.T) {
err := NewShutdownErr(errors.New("some error"))
assert.Equal(t, "interrupted due to shutdown: some error", err.Error())
}
func TestIsShutdownErr(t *testing.T) {
err := errors.New("testError")
require.False(t, IsShutdownErr(err))
err = NewShutdownErr(err)
require.True(t, IsShutdownErr(err))
}
================================================
FILE: exporter/exporterhelper/internal/hosttest/hosttest.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package hosttest // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/hosttest"
import (
"go.opentelemetry.io/collector/component"
)
func NewHost(ext map[component.ID]component.Component) component.Host {
return &mockHost{ext: ext}
}
type mockHost struct {
ext map[component.ID]component.Component
}
func (nh *mockHost) GetExtensions() map[component.ID]component.Component {
return nh.ext
}
================================================
FILE: exporter/exporterhelper/internal/hosttest/hosttest_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package hosttest
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
)
// nopExtension acts as an extension for testing purposes.
type nopExtension struct {
component.StartFunc
component.ShutdownFunc
}
func TestMockHost(t *testing.T) {
componenttest.NewNopHost()
ext := map[component.ID]component.Component{
component.MustNewID("test"): &nopExtension{},
}
host := NewHost(ext)
assert.Equal(t, ext, host.GetExtensions())
}
================================================
FILE: exporter/exporterhelper/internal/metadata/generated_feature_gates.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/featuregate"
)
var ExporterPersistRequestContextFeatureGate = featuregate.GlobalRegistry().MustRegister(
"exporter.PersistRequestContext",
featuregate.StageBeta,
featuregate.WithRegisterDescription("controls whether context should be stored alongside requests in the persistent queue"),
featuregate.WithRegisterReferenceURL("https://github.com/open-telemetry/opentelemetry-collector/pull/13188"),
featuregate.WithRegisterFromVersion("v0.128.0"),
)
================================================
FILE: exporter/exporterhelper/internal/metadata/generated_telemetry.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"context"
"errors"
"sync"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/metric/embedded"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/collector/component"
)
func Meter(settings component.TelemetrySettings) metric.Meter {
return settings.MeterProvider.Meter("go.opentelemetry.io/collector/exporter/exporterhelper")
}
func Tracer(settings component.TelemetrySettings) trace.Tracer {
return settings.TracerProvider.Tracer("go.opentelemetry.io/collector/exporter/exporterhelper")
}
// TelemetryBuilder provides an interface for components to report telemetry
// as defined in metadata and user config.
type TelemetryBuilder struct {
meter metric.Meter
mu sync.Mutex
registrations []metric.Registration
ExporterEnqueueFailedLogRecords metric.Int64Counter
ExporterEnqueueFailedMetricPoints metric.Int64Counter
ExporterEnqueueFailedProfileSamples metric.Int64Counter
ExporterEnqueueFailedSpans metric.Int64Counter
ExporterQueueBatchSendSize metric.Int64Histogram
ExporterQueueBatchSendSizeBytes metric.Int64Histogram
ExporterQueueCapacity metric.Int64ObservableGauge
ExporterQueueSize metric.Int64ObservableGauge
ExporterSendFailedLogRecords metric.Int64Counter
ExporterSendFailedMetricPoints metric.Int64Counter
ExporterSendFailedProfileSamples metric.Int64Counter
ExporterSendFailedSpans metric.Int64Counter
ExporterSentLogRecords metric.Int64Counter
ExporterSentMetricPoints metric.Int64Counter
ExporterSentProfileSamples metric.Int64Counter
ExporterSentSpans metric.Int64Counter
}
// TelemetryBuilderOption applies changes to default builder.
type TelemetryBuilderOption interface {
apply(*TelemetryBuilder)
}
type telemetryBuilderOptionFunc func(mb *TelemetryBuilder)
func (tbof telemetryBuilderOptionFunc) apply(mb *TelemetryBuilder) {
tbof(mb)
}
// RegisterExporterQueueCapacityCallback sets callback for observable ExporterQueueCapacity metric.
func (builder *TelemetryBuilder) RegisterExporterQueueCapacityCallback(cb metric.Int64Callback) error {
reg, err := builder.meter.RegisterCallback(func(ctx context.Context, o metric.Observer) error {
cb(ctx, &observerInt64{inst: builder.ExporterQueueCapacity, obs: o})
return nil
}, builder.ExporterQueueCapacity)
if err != nil {
return err
}
builder.mu.Lock()
defer builder.mu.Unlock()
builder.registrations = append(builder.registrations, reg)
return nil
}
// RegisterExporterQueueSizeCallback sets callback for observable ExporterQueueSize metric.
func (builder *TelemetryBuilder) RegisterExporterQueueSizeCallback(cb metric.Int64Callback) error {
reg, err := builder.meter.RegisterCallback(func(ctx context.Context, o metric.Observer) error {
cb(ctx, &observerInt64{inst: builder.ExporterQueueSize, obs: o})
return nil
}, builder.ExporterQueueSize)
if err != nil {
return err
}
builder.mu.Lock()
defer builder.mu.Unlock()
builder.registrations = append(builder.registrations, reg)
return nil
}
type observerInt64 struct {
embedded.Int64Observer
inst metric.Int64Observable
obs metric.Observer
}
func (oi *observerInt64) Observe(value int64, opts ...metric.ObserveOption) {
oi.obs.ObserveInt64(oi.inst, value, opts...)
}
// Shutdown unregister all registered callbacks for async instruments.
func (builder *TelemetryBuilder) Shutdown() {
builder.mu.Lock()
defer builder.mu.Unlock()
for _, reg := range builder.registrations {
reg.Unregister()
}
}
// NewTelemetryBuilder provides a struct with methods to update all internal telemetry
// for a component
func NewTelemetryBuilder(settings component.TelemetrySettings, options ...TelemetryBuilderOption) (*TelemetryBuilder, error) {
builder := TelemetryBuilder{}
for _, op := range options {
op.apply(&builder)
}
builder.meter = Meter(settings)
var err, errs error
builder.ExporterEnqueueFailedLogRecords, err = builder.meter.Int64Counter(
"otelcol_exporter_enqueue_failed_log_records",
metric.WithDescription("Number of log records failed to be added to the sending queue. [Alpha]"),
metric.WithUnit("{record}"),
)
errs = errors.Join(errs, err)
builder.ExporterEnqueueFailedMetricPoints, err = builder.meter.Int64Counter(
"otelcol_exporter_enqueue_failed_metric_points",
metric.WithDescription("Number of metric points failed to be added to the sending queue. [Alpha]"),
metric.WithUnit("{datapoint}"),
)
errs = errors.Join(errs, err)
builder.ExporterEnqueueFailedProfileSamples, err = builder.meter.Int64Counter(
"otelcol_exporter_enqueue_failed_profile_samples",
metric.WithDescription("Number of profile samples failed to be added to the sending queue. [Development]"),
metric.WithUnit("{sample}"),
)
errs = errors.Join(errs, err)
builder.ExporterEnqueueFailedSpans, err = builder.meter.Int64Counter(
"otelcol_exporter_enqueue_failed_spans",
metric.WithDescription("Number of spans failed to be added to the sending queue. [Alpha]"),
metric.WithUnit("{span}"),
)
errs = errors.Join(errs, err)
builder.ExporterQueueBatchSendSize, err = builder.meter.Int64Histogram(
"otelcol_exporter_queue_batch_send_size",
metric.WithDescription("Number of units in the batch [Development]"),
metric.WithUnit("{unit}"),
metric.WithExplicitBucketBoundaries([]float64{10, 25, 50, 75, 100, 250, 500, 750, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000, 20000, 30000, 50000, 100000}...),
)
errs = errors.Join(errs, err)
builder.ExporterQueueBatchSendSizeBytes, err = builder.meter.Int64Histogram(
"otelcol_exporter_queue_batch_send_size_bytes",
metric.WithDescription("Number of bytes in batch that was sent. Only available on detailed level. [Development]"),
metric.WithUnit("By"),
metric.WithExplicitBucketBoundaries([]float64{10, 25, 50, 75, 100, 250, 500, 750, 1000, 2000, 3000, 4000, 5000, 6000}...),
)
errs = errors.Join(errs, err)
builder.ExporterQueueCapacity, err = builder.meter.Int64ObservableGauge(
"otelcol_exporter_queue_capacity",
metric.WithDescription("Fixed capacity of the retry queue (in batches). [Alpha]"),
metric.WithUnit("{batch}"),
)
errs = errors.Join(errs, err)
builder.ExporterQueueSize, err = builder.meter.Int64ObservableGauge(
"otelcol_exporter_queue_size",
metric.WithDescription("Current size of the retry queue (in batches). [Alpha]"),
metric.WithUnit("{batch}"),
)
errs = errors.Join(errs, err)
builder.ExporterSendFailedLogRecords, err = builder.meter.Int64Counter(
"otelcol_exporter_send_failed_log_records",
metric.WithDescription("Number of log records in failed attempts to send to destination. At detailed telemetry level, includes attributes: error.type (semantic convention), error.permanent. [Alpha]"),
metric.WithUnit("{record}"),
)
errs = errors.Join(errs, err)
builder.ExporterSendFailedMetricPoints, err = builder.meter.Int64Counter(
"otelcol_exporter_send_failed_metric_points",
metric.WithDescription("Number of metric points in failed attempts to send to destination. At detailed telemetry level, includes attributes: error.type (semantic convention), error.permanent. [Alpha]"),
metric.WithUnit("{datapoint}"),
)
errs = errors.Join(errs, err)
builder.ExporterSendFailedProfileSamples, err = builder.meter.Int64Counter(
"otelcol_exporter_send_failed_profile_samples",
metric.WithDescription("Number of profile samples in failed attempts to send to destination. At detailed telemetry level, includes attributes: error.type (semantic convention), error.permanent. [Development]"),
metric.WithUnit("{sample}"),
)
errs = errors.Join(errs, err)
builder.ExporterSendFailedSpans, err = builder.meter.Int64Counter(
"otelcol_exporter_send_failed_spans",
metric.WithDescription("Number of spans in failed attempts to send to destination. At detailed telemetry level, includes attributes: error.type (semantic convention), error.permanent. [Alpha]"),
metric.WithUnit("{span}"),
)
errs = errors.Join(errs, err)
builder.ExporterSentLogRecords, err = builder.meter.Int64Counter(
"otelcol_exporter_sent_log_records",
metric.WithDescription("Number of log record successfully sent to destination. [Alpha]"),
metric.WithUnit("{record}"),
)
errs = errors.Join(errs, err)
builder.ExporterSentMetricPoints, err = builder.meter.Int64Counter(
"otelcol_exporter_sent_metric_points",
metric.WithDescription("Number of metric points successfully sent to destination. [Alpha]"),
metric.WithUnit("{datapoint}"),
)
errs = errors.Join(errs, err)
builder.ExporterSentProfileSamples, err = builder.meter.Int64Counter(
"otelcol_exporter_sent_profile_samples",
metric.WithDescription("Number of profile samples successfully sent to destination. [Development]"),
metric.WithUnit("{sample}"),
)
errs = errors.Join(errs, err)
builder.ExporterSentSpans, err = builder.meter.Int64Counter(
"otelcol_exporter_sent_spans",
metric.WithDescription("Number of spans successfully sent to destination. [Alpha]"),
metric.WithUnit("{span}"),
)
errs = errors.Join(errs, err)
return &builder, errs
}
================================================
FILE: exporter/exporterhelper/internal/metadata/generated_telemetry_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/metric"
embeddedmetric "go.opentelemetry.io/otel/metric/embedded"
noopmetric "go.opentelemetry.io/otel/metric/noop"
"go.opentelemetry.io/otel/trace"
embeddedtrace "go.opentelemetry.io/otel/trace/embedded"
nooptrace "go.opentelemetry.io/otel/trace/noop"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
)
type mockMeter struct {
noopmetric.Meter
name string
}
type mockMeterProvider struct {
embeddedmetric.MeterProvider
}
func (m mockMeterProvider) Meter(name string, opts ...metric.MeterOption) metric.Meter {
return mockMeter{name: name}
}
type mockTracer struct {
nooptrace.Tracer
name string
}
type mockTracerProvider struct {
embeddedtrace.TracerProvider
}
func (m mockTracerProvider) Tracer(name string, opts ...trace.TracerOption) trace.Tracer {
return mockTracer{name: name}
}
func TestProviders(t *testing.T) {
set := component.TelemetrySettings{
MeterProvider: mockMeterProvider{},
TracerProvider: mockTracerProvider{},
}
meter := Meter(set)
if m, ok := meter.(mockMeter); ok {
require.Equal(t, "go.opentelemetry.io/collector/exporter/exporterhelper", m.name)
} else {
require.Fail(t, "returned Meter not mockMeter")
}
tracer := Tracer(set)
if m, ok := tracer.(mockTracer); ok {
require.Equal(t, "go.opentelemetry.io/collector/exporter/exporterhelper", m.name)
} else {
require.Fail(t, "returned Meter not mockTracer")
}
}
func TestNewTelemetryBuilder(t *testing.T) {
set := componenttest.NewNopTelemetrySettings()
applied := false
_, err := NewTelemetryBuilder(set, telemetryBuilderOptionFunc(func(b *TelemetryBuilder) {
applied = true
}))
require.NoError(t, err)
require.True(t, applied)
}
================================================
FILE: exporter/exporterhelper/internal/metadatatest/generated_telemetrytest.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadatatest
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"go.opentelemetry.io/collector/component/componenttest"
)
func AssertEqualExporterEnqueueFailedLogRecords(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_exporter_enqueue_failed_log_records",
Description: "Number of log records failed to be added to the sending queue. [Alpha]",
Unit: "{record}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_exporter_enqueue_failed_log_records")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualExporterEnqueueFailedMetricPoints(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_exporter_enqueue_failed_metric_points",
Description: "Number of metric points failed to be added to the sending queue. [Alpha]",
Unit: "{datapoint}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_exporter_enqueue_failed_metric_points")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualExporterEnqueueFailedProfileSamples(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_exporter_enqueue_failed_profile_samples",
Description: "Number of profile samples failed to be added to the sending queue. [Development]",
Unit: "{sample}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_exporter_enqueue_failed_profile_samples")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualExporterEnqueueFailedSpans(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_exporter_enqueue_failed_spans",
Description: "Number of spans failed to be added to the sending queue. [Alpha]",
Unit: "{span}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_exporter_enqueue_failed_spans")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualExporterQueueBatchSendSize(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.HistogramDataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_exporter_queue_batch_send_size",
Description: "Number of units in the batch [Development]",
Unit: "{unit}",
Data: metricdata.Histogram[int64]{
Temporality: metricdata.CumulativeTemporality,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_exporter_queue_batch_send_size")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualExporterQueueBatchSendSizeBytes(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.HistogramDataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_exporter_queue_batch_send_size_bytes",
Description: "Number of bytes in batch that was sent. Only available on detailed level. [Development]",
Unit: "By",
Data: metricdata.Histogram[int64]{
Temporality: metricdata.CumulativeTemporality,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_exporter_queue_batch_send_size_bytes")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualExporterQueueCapacity(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_exporter_queue_capacity",
Description: "Fixed capacity of the retry queue (in batches). [Alpha]",
Unit: "{batch}",
Data: metricdata.Gauge[int64]{
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_exporter_queue_capacity")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualExporterQueueSize(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_exporter_queue_size",
Description: "Current size of the retry queue (in batches). [Alpha]",
Unit: "{batch}",
Data: metricdata.Gauge[int64]{
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_exporter_queue_size")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualExporterSendFailedLogRecords(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_exporter_send_failed_log_records",
Description: "Number of log records in failed attempts to send to destination. At detailed telemetry level, includes attributes: error.type (semantic convention), error.permanent. [Alpha]",
Unit: "{record}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_exporter_send_failed_log_records")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualExporterSendFailedMetricPoints(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_exporter_send_failed_metric_points",
Description: "Number of metric points in failed attempts to send to destination. At detailed telemetry level, includes attributes: error.type (semantic convention), error.permanent. [Alpha]",
Unit: "{datapoint}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_exporter_send_failed_metric_points")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualExporterSendFailedProfileSamples(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_exporter_send_failed_profile_samples",
Description: "Number of profile samples in failed attempts to send to destination. At detailed telemetry level, includes attributes: error.type (semantic convention), error.permanent. [Development]",
Unit: "{sample}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_exporter_send_failed_profile_samples")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualExporterSendFailedSpans(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_exporter_send_failed_spans",
Description: "Number of spans in failed attempts to send to destination. At detailed telemetry level, includes attributes: error.type (semantic convention), error.permanent. [Alpha]",
Unit: "{span}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_exporter_send_failed_spans")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualExporterSentLogRecords(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_exporter_sent_log_records",
Description: "Number of log record successfully sent to destination. [Alpha]",
Unit: "{record}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_exporter_sent_log_records")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualExporterSentMetricPoints(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_exporter_sent_metric_points",
Description: "Number of metric points successfully sent to destination. [Alpha]",
Unit: "{datapoint}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_exporter_sent_metric_points")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualExporterSentProfileSamples(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_exporter_sent_profile_samples",
Description: "Number of profile samples successfully sent to destination. [Development]",
Unit: "{sample}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_exporter_sent_profile_samples")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualExporterSentSpans(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_exporter_sent_spans",
Description: "Number of spans successfully sent to destination. [Alpha]",
Unit: "{span}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_exporter_sent_spans")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
================================================
FILE: exporter/exporterhelper/internal/metadatatest/generated_telemetrytest_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadatatest
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/metadata"
)
func TestSetupTelemetry(t *testing.T) {
testTel := componenttest.NewTelemetry()
tb, err := metadata.NewTelemetryBuilder(testTel.NewTelemetrySettings())
require.NoError(t, err)
defer tb.Shutdown()
require.NoError(t, tb.RegisterExporterQueueCapacityCallback(func(_ context.Context, observer metric.Int64Observer) error {
observer.Observe(1)
return nil
}))
require.NoError(t, tb.RegisterExporterQueueSizeCallback(func(_ context.Context, observer metric.Int64Observer) error {
observer.Observe(1)
return nil
}))
tb.ExporterEnqueueFailedLogRecords.Add(context.Background(), 1)
tb.ExporterEnqueueFailedMetricPoints.Add(context.Background(), 1)
tb.ExporterEnqueueFailedProfileSamples.Add(context.Background(), 1)
tb.ExporterEnqueueFailedSpans.Add(context.Background(), 1)
tb.ExporterQueueBatchSendSize.Record(context.Background(), 1)
tb.ExporterQueueBatchSendSizeBytes.Record(context.Background(), 1)
tb.ExporterSendFailedLogRecords.Add(context.Background(), 1)
tb.ExporterSendFailedMetricPoints.Add(context.Background(), 1)
tb.ExporterSendFailedProfileSamples.Add(context.Background(), 1)
tb.ExporterSendFailedSpans.Add(context.Background(), 1)
tb.ExporterSentLogRecords.Add(context.Background(), 1)
tb.ExporterSentMetricPoints.Add(context.Background(), 1)
tb.ExporterSentProfileSamples.Add(context.Background(), 1)
tb.ExporterSentSpans.Add(context.Background(), 1)
AssertEqualExporterEnqueueFailedLogRecords(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualExporterEnqueueFailedMetricPoints(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualExporterEnqueueFailedProfileSamples(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualExporterEnqueueFailedSpans(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualExporterQueueBatchSendSize(t, testTel,
[]metricdata.HistogramDataPoint[int64]{{}}, metricdatatest.IgnoreValue(),
metricdatatest.IgnoreTimestamp())
AssertEqualExporterQueueBatchSendSizeBytes(t, testTel,
[]metricdata.HistogramDataPoint[int64]{{}}, metricdatatest.IgnoreValue(),
metricdatatest.IgnoreTimestamp())
AssertEqualExporterQueueCapacity(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualExporterQueueSize(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualExporterSendFailedLogRecords(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualExporterSendFailedMetricPoints(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualExporterSendFailedProfileSamples(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualExporterSendFailedSpans(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualExporterSentLogRecords(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualExporterSentMetricPoints(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualExporterSentProfileSamples(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualExporterSentSpans(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
require.NoError(t, testTel.Shutdown(context.Background()))
}
================================================
FILE: exporter/exporterhelper/internal/new_request.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal"
import (
"context"
"go.uber.org/zap"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/pipeline"
)
type logsExporter struct {
*BaseExporter
consumer.Logs
}
// NewLogsRequest creates new logs exporter based on custom LogsConverter and Sender.
// Experimental: This API is at the early stage of development and may change without backward compatibility
// until https://github.com/open-telemetry/opentelemetry-collector/issues/8122 is resolved.
func NewLogsRequest(
_ context.Context,
set exporter.Settings,
converter request.RequestConverterFunc[plog.Logs],
pusher request.RequestConsumeFunc,
options ...Option,
) (exporter.Logs, error) {
if set.Logger == nil {
return nil, errNilLogger
}
if converter == nil {
return nil, errNilLogsConverter
}
if pusher == nil {
return nil, errNilConsumeRequest
}
be, err := NewBaseExporter(set, pipeline.SignalLogs, pusher, options...)
if err != nil {
return nil, err
}
lc, err := consumer.NewLogs(newConsumeLogs(converter, be, set.Logger), be.ConsumerOptions...)
if err != nil {
return nil, err
}
return &logsExporter{BaseExporter: be, Logs: lc}, nil
}
func newConsumeLogs(converter request.RequestConverterFunc[plog.Logs], be *BaseExporter, logger *zap.Logger) consumer.ConsumeLogsFunc {
return func(ctx context.Context, ld plog.Logs) error {
req, err := converter(ctx, ld)
if err != nil {
logger.Error("Failed to convert logs. Dropping data.",
zap.Int("dropped_log_records", ld.LogRecordCount()),
zap.Error(err))
return consumererror.NewPermanent(err)
}
return be.Send(ctx, req)
}
}
type tracesExporter struct {
*BaseExporter
consumer.Traces
}
// NewTracesRequest creates a new traces exporter based on a custom TracesConverter and Sender.
// Experimental: This API is at the early stage of development and may change without backward compatibility
// until https://github.com/open-telemetry/opentelemetry-collector/issues/8122 is resolved.
func NewTracesRequest(
_ context.Context,
set exporter.Settings,
converter request.RequestConverterFunc[ptrace.Traces],
pusher request.RequestConsumeFunc,
options ...Option,
) (exporter.Traces, error) {
if set.Logger == nil {
return nil, errNilLogger
}
if converter == nil {
return nil, errNilTracesConverter
}
if pusher == nil {
return nil, errNilConsumeRequest
}
be, err := NewBaseExporter(set, pipeline.SignalTraces, pusher, options...)
if err != nil {
return nil, err
}
tc, err := consumer.NewTraces(newConsumeTraces(converter, be, set.Logger), be.ConsumerOptions...)
if err != nil {
return nil, err
}
return &tracesExporter{BaseExporter: be, Traces: tc}, nil
}
func newConsumeTraces(converter request.RequestConverterFunc[ptrace.Traces], be *BaseExporter, logger *zap.Logger) consumer.ConsumeTracesFunc {
return func(ctx context.Context, td ptrace.Traces) error {
req, err := converter(ctx, td)
if err != nil {
logger.Error("Failed to convert traces. Dropping data.",
zap.Int("dropped_spans", td.SpanCount()),
zap.Error(err))
return consumererror.NewPermanent(err)
}
return be.Send(ctx, req)
}
}
type metricsExporter struct {
*BaseExporter
consumer.Metrics
}
// NewMetricsRequest creates a new metrics exporter based on a custom MetricsConverter and Sender.
// Experimental: This API is at the early stage of development and may change without backward compatibility
// until https://github.com/open-telemetry/opentelemetry-collector/issues/8122 is resolved.
func NewMetricsRequest(
_ context.Context,
set exporter.Settings,
converter request.RequestConverterFunc[pmetric.Metrics],
pusher request.RequestConsumeFunc,
options ...Option,
) (exporter.Metrics, error) {
if set.Logger == nil {
return nil, errNilLogger
}
if converter == nil {
return nil, errNilMetricsConverter
}
if pusher == nil {
return nil, errNilConsumeRequest
}
be, err := NewBaseExporter(set, pipeline.SignalMetrics, pusher, options...)
if err != nil {
return nil, err
}
mc, err := consumer.NewMetrics(newConsumeMetrics(converter, be, set.Logger), be.ConsumerOptions...)
if err != nil {
return nil, err
}
return &metricsExporter{BaseExporter: be, Metrics: mc}, nil
}
func newConsumeMetrics(converter request.RequestConverterFunc[pmetric.Metrics], be *BaseExporter, logger *zap.Logger) consumer.ConsumeMetricsFunc {
return func(ctx context.Context, md pmetric.Metrics) error {
req, err := converter(ctx, md)
if err != nil {
logger.Error("Failed to convert metrics. Dropping data.",
zap.Int("dropped_data_points", md.DataPointCount()),
zap.Error(err))
return consumererror.NewPermanent(err)
}
return be.Send(ctx, req)
}
}
================================================
FILE: exporter/exporterhelper/internal/new_request_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal"
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"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/requesttest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sendertest"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
)
func TestLogsRequest_NilLogger(t *testing.T) {
le, err := NewLogsRequest(context.Background(), exporter.Settings{}, requesttest.RequestFromLogsFunc(nil), sendertest.NewNopSenderFunc[request.Request]())
require.Nil(t, le)
require.Equal(t, errNilLogger, err)
}
func TestLogsRequest_NilLogsConverter(t *testing.T) {
le, err := NewLogsRequest(context.Background(), exportertest.NewNopSettings(exportertest.NopType), nil, sendertest.NewNopSenderFunc[request.Request]())
require.Nil(t, le)
require.Equal(t, errNilLogsConverter, err)
}
func TestLogsRequest_NilPushLogsData(t *testing.T) {
le, err := NewLogsRequest(context.Background(), exportertest.NewNopSettings(exportertest.NopType), requesttest.RequestFromLogsFunc(nil), nil)
require.Nil(t, le)
require.Equal(t, errNilConsumeRequest, err)
}
func TestLogsRequest_Default(t *testing.T) {
ld := plog.NewLogs()
le, err := NewLogsRequest(context.Background(), exportertest.NewNopSettings(exportertest.NopType),
requesttest.RequestFromLogsFunc(nil), sendertest.NewNopSenderFunc[request.Request]())
assert.NotNil(t, le)
require.NoError(t, err)
assert.Equal(t, consumer.Capabilities{MutatesData: false}, le.Capabilities())
assert.NoError(t, le.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, le.ConsumeLogs(context.Background(), ld))
assert.NoError(t, le.Shutdown(context.Background()))
}
func TestLogsRequest_WithCapabilities(t *testing.T) {
capabilities := consumer.Capabilities{MutatesData: true}
le, err := NewLogsRequest(context.Background(), exportertest.NewNopSettings(exportertest.NopType),
requesttest.RequestFromLogsFunc(nil), sendertest.NewNopSenderFunc[request.Request](), WithCapabilities(capabilities))
require.NoError(t, err)
require.NotNil(t, le)
assert.Equal(t, capabilities, le.Capabilities())
}
func TestLogsRequest_WithShutdown(t *testing.T) {
shutdownCalled := false
shutdown := func(context.Context) error { shutdownCalled = true; return nil }
le, err := NewLogsRequest(context.Background(), exportertest.NewNopSettings(exportertest.NopType),
requesttest.RequestFromLogsFunc(nil), sendertest.NewNopSenderFunc[request.Request](), WithShutdown(shutdown))
assert.NotNil(t, le)
assert.NoError(t, err)
assert.NoError(t, le.Shutdown(context.Background()))
assert.True(t, shutdownCalled)
}
func TestLogsRequest_Default_ConvertError(t *testing.T) {
ld := plog.NewLogs()
want := errors.New("convert_error")
le, err := NewLogsRequest(context.Background(), exportertest.NewNopSettings(exportertest.NopType),
requesttest.RequestFromLogsFunc(want), sendertest.NewNopSenderFunc[request.Request]())
require.NoError(t, err)
require.NotNil(t, le)
require.Equal(t, consumererror.NewPermanent(want), le.ConsumeLogs(context.Background(), ld))
}
func TestLogsRequest_Default_ExportError(t *testing.T) {
ld := plog.NewLogs()
want := errors.New("export_error")
le, err := NewLogsRequest(context.Background(), exportertest.NewNopSettings(exportertest.NopType),
requesttest.RequestFromLogsFunc(nil), sendertest.NewErrSenderFunc[request.Request](want))
require.NoError(t, err)
require.NotNil(t, le)
require.Equal(t, want, le.ConsumeLogs(context.Background(), ld))
}
func TestLogsRequest_WithShutdown_ReturnError(t *testing.T) {
want := errors.New("my_error")
shutdownErr := func(context.Context) error { return want }
le, err := NewLogsRequest(context.Background(), exportertest.NewNopSettings(exportertest.NopType),
requesttest.RequestFromLogsFunc(nil), sendertest.NewNopSenderFunc[request.Request](), WithShutdown(shutdownErr))
assert.NotNil(t, le)
require.NoError(t, err)
assert.Equal(t, want, le.Shutdown(context.Background()))
}
func TestTracesRequest_NilLogger(t *testing.T) {
te, err := NewTracesRequest(context.Background(), exporter.Settings{}, requesttest.RequestFromTracesFunc(nil), sendertest.NewNopSenderFunc[request.Request]())
require.Nil(t, te)
require.Equal(t, errNilLogger, err)
}
func TestTracesRequest_NilTracesConverter(t *testing.T) {
te, err := NewTracesRequest(context.Background(), exportertest.NewNopSettings(exportertest.NopType), nil, sendertest.NewNopSenderFunc[request.Request]())
require.Nil(t, te)
require.Equal(t, errNilTracesConverter, err)
}
func TestTracesRequest_NilPushTraceData(t *testing.T) {
te, err := NewTracesRequest(context.Background(), exportertest.NewNopSettings(exportertest.NopType), requesttest.RequestFromTracesFunc(nil), nil)
require.Nil(t, te)
require.Equal(t, errNilConsumeRequest, err)
}
func TestTracesRequest_Default(t *testing.T) {
td := ptrace.NewTraces()
te, err := NewTracesRequest(context.Background(), exportertest.NewNopSettings(exportertest.NopType),
requesttest.RequestFromTracesFunc(nil), sendertest.NewNopSenderFunc[request.Request]())
assert.NotNil(t, te)
require.NoError(t, err)
assert.Equal(t, consumer.Capabilities{MutatesData: false}, te.Capabilities())
assert.NoError(t, te.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, te.ConsumeTraces(context.Background(), td))
assert.NoError(t, te.Shutdown(context.Background()))
}
func TestTracesRequest_WithCapabilities(t *testing.T) {
capabilities := consumer.Capabilities{MutatesData: true}
te, err := NewTracesRequest(context.Background(), exportertest.NewNopSettings(exportertest.NopType),
requesttest.RequestFromTracesFunc(nil), sendertest.NewNopSenderFunc[request.Request](), WithCapabilities(capabilities))
assert.NotNil(t, te)
require.NoError(t, err)
assert.Equal(t, capabilities, te.Capabilities())
}
func TestTracesRequest_Default_ConvertError(t *testing.T) {
td := ptrace.NewTraces()
want := errors.New("convert_error")
te, err := NewTracesRequest(context.Background(), exportertest.NewNopSettings(exportertest.NopType),
requesttest.RequestFromTracesFunc(want), sendertest.NewNopSenderFunc[request.Request]())
require.NoError(t, err)
require.NotNil(t, te)
require.Equal(t, consumererror.NewPermanent(want), te.ConsumeTraces(context.Background(), td))
}
func TestTracesRequest_Default_ExportError(t *testing.T) {
td := ptrace.NewTraces()
want := errors.New("export_error")
te, err := NewTracesRequest(context.Background(), exportertest.NewNopSettings(exportertest.NopType),
requesttest.RequestFromTracesFunc(nil), sendertest.NewErrSenderFunc[request.Request](want))
require.NoError(t, err)
require.NotNil(t, te)
require.Equal(t, want, te.ConsumeTraces(context.Background(), td))
}
func TestTracesRequest_WithShutdown(t *testing.T) {
shutdownCalled := false
shutdown := func(context.Context) error { shutdownCalled = true; return nil }
te, err := NewTracesRequest(context.Background(), exportertest.NewNopSettings(exportertest.NopType),
requesttest.RequestFromTracesFunc(nil), sendertest.NewNopSenderFunc[request.Request](), WithShutdown(shutdown))
assert.NotNil(t, te)
assert.NoError(t, err)
assert.NoError(t, te.Shutdown(context.Background()))
assert.True(t, shutdownCalled)
}
func TestTracesRequest_WithShutdown_ReturnError(t *testing.T) {
want := errors.New("my_error")
shutdownErr := func(context.Context) error { return want }
te, err := NewTracesRequest(context.Background(), exportertest.NewNopSettings(exportertest.NopType),
requesttest.RequestFromTracesFunc(nil), sendertest.NewNopSenderFunc[request.Request](), WithShutdown(shutdownErr))
assert.NotNil(t, te)
require.NoError(t, err)
assert.Equal(t, want, te.Shutdown(context.Background()))
}
func TestMetricsRequest_NilLogger(t *testing.T) {
me, err := NewMetricsRequest(context.Background(), exporter.Settings{}, requesttest.RequestFromMetricsFunc(nil), sendertest.NewNopSenderFunc[request.Request]())
require.Nil(t, me)
require.Equal(t, errNilLogger, err)
}
func TestMetricsRequest_NilMetricsConverter(t *testing.T) {
me, err := NewMetricsRequest(context.Background(), exportertest.NewNopSettings(exportertest.NopType), nil, sendertest.NewNopSenderFunc[request.Request]())
require.Nil(t, me)
require.Equal(t, errNilMetricsConverter, err)
}
func TestMetricsRequest_NilPushMetricsData(t *testing.T) {
me, err := NewMetricsRequest(context.Background(), exportertest.NewNopSettings(exportertest.NopType), requesttest.RequestFromMetricsFunc(nil), nil)
require.Nil(t, me)
require.Equal(t, errNilConsumeRequest, err)
}
func TestMetricsRequest_Default(t *testing.T) {
md := pmetric.NewMetrics()
me, err := NewMetricsRequest(context.Background(), exportertest.NewNopSettings(exportertest.NopType),
requesttest.RequestFromMetricsFunc(nil), sendertest.NewNopSenderFunc[request.Request]())
require.NoError(t, err)
assert.NotNil(t, me)
assert.Equal(t, consumer.Capabilities{MutatesData: false}, me.Capabilities())
assert.NoError(t, me.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, me.ConsumeMetrics(context.Background(), md))
assert.NoError(t, me.Shutdown(context.Background()))
}
func TestMetricsRequest_WithCapabilities(t *testing.T) {
capabilities := consumer.Capabilities{MutatesData: true}
me, err := NewMetricsRequest(context.Background(), exportertest.NewNopSettings(exportertest.NopType),
requesttest.RequestFromMetricsFunc(nil), sendertest.NewNopSenderFunc[request.Request](), WithCapabilities(capabilities))
require.NoError(t, err)
assert.NotNil(t, me)
assert.Equal(t, capabilities, me.Capabilities())
}
func TestMetricsRequest_Default_ConvertError(t *testing.T) {
md := pmetric.NewMetrics()
want := errors.New("convert_error")
me, err := NewMetricsRequest(context.Background(), exportertest.NewNopSettings(exportertest.NopType),
requesttest.RequestFromMetricsFunc(want), sendertest.NewNopSenderFunc[request.Request]())
require.NoError(t, err)
require.NotNil(t, me)
require.Equal(t, consumererror.NewPermanent(want), me.ConsumeMetrics(context.Background(), md))
}
func TestMetricsRequest_Default_ExportError(t *testing.T) {
md := pmetric.NewMetrics()
want := errors.New("export_error")
me, err := NewMetricsRequest(context.Background(), exportertest.NewNopSettings(exportertest.NopType),
requesttest.RequestFromMetricsFunc(nil), sendertest.NewErrSenderFunc[request.Request](want))
require.NoError(t, err)
require.NotNil(t, me)
require.Equal(t, want, me.ConsumeMetrics(context.Background(), md))
}
func TestMetricsRequest_WithShutdown(t *testing.T) {
shutdownCalled := false
shutdown := func(context.Context) error { shutdownCalled = true; return nil }
me, err := NewMetricsRequest(context.Background(), exportertest.NewNopSettings(exportertest.NopType),
requesttest.RequestFromMetricsFunc(nil), sendertest.NewNopSenderFunc[request.Request](), WithShutdown(shutdown))
assert.NotNil(t, me)
assert.NoError(t, err)
assert.NoError(t, me.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, me.Shutdown(context.Background()))
assert.True(t, shutdownCalled)
}
func TestMetricsRequest_WithShutdown_ReturnError(t *testing.T) {
want := errors.New("my_error")
shutdownErr := func(context.Context) error { return want }
me, err := NewMetricsRequest(context.Background(), exportertest.NewNopSettings(exportertest.NopType),
requesttest.RequestFromMetricsFunc(nil), sendertest.NewNopSenderFunc[request.Request](), WithShutdown(shutdownErr))
assert.NotNil(t, me)
assert.NoError(t, err)
assert.NoError(t, me.Start(context.Background(), componenttest.NewNopHost()))
assert.Equal(t, want, me.Shutdown(context.Background()))
}
================================================
FILE: exporter/exporterhelper/internal/obs_report_sender.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal"
import (
"context"
"errors"
"go.opentelemetry.io/otel/attribute"
otelcodes "go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/metric"
semconv "go.opentelemetry.io/otel/semconv/v1.38.0"
"go.opentelemetry.io/otel/trace"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/experr"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/metadata"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sender"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/pipeline/xpipeline"
)
const (
// spanNameSep is duplicate between receiver and exporter.
spanNameSep = "/"
// ExporterKey used to identify exporters in metrics and traces.
ExporterKey = "exporter"
// DataTypeKey used to identify the data type in the queue size metric.
DataTypeKey = "data_type"
// ItemsSent used to track number of items sent by exporters.
ItemsSent = "items.sent"
// ItemsFailed used to track number of items that failed to be sent by exporters.
ItemsFailed = "items.failed"
// ErrorPermanentKey indicates whether the error is permanent (non-retryable).
ErrorPermanentKey = "error.permanent"
)
type obsReportSender[K request.Request] struct {
component.StartFunc
component.ShutdownFunc
spanName string
tracer trace.Tracer
spanAttrs trace.SpanStartEventOption
metricAttr metric.MeasurementOption
itemsSentInst metric.Int64Counter
itemsFailedInst metric.Int64Counter
next sender.Sender[K]
}
func newObsReportSender[K request.Request](set exporter.Settings, signal pipeline.Signal, next sender.Sender[K]) (sender.Sender[K], error) {
telemetryBuilder, err := metadata.NewTelemetryBuilder(set.TelemetrySettings)
if err != nil {
return nil, err
}
idStr := set.ID.String()
expAttr := attribute.String(ExporterKey, idStr)
or := &obsReportSender[K]{
spanName: ExporterKey + spanNameSep + idStr + spanNameSep + signal.String(),
tracer: metadata.Tracer(set.TelemetrySettings),
spanAttrs: trace.WithAttributes(expAttr, attribute.String(DataTypeKey, signal.String())),
metricAttr: metric.WithAttributeSet(attribute.NewSet(expAttr)),
next: next,
}
switch signal {
case pipeline.SignalTraces:
or.itemsSentInst = telemetryBuilder.ExporterSentSpans
or.itemsFailedInst = telemetryBuilder.ExporterSendFailedSpans
case pipeline.SignalMetrics:
or.itemsSentInst = telemetryBuilder.ExporterSentMetricPoints
or.itemsFailedInst = telemetryBuilder.ExporterSendFailedMetricPoints
case pipeline.SignalLogs:
or.itemsSentInst = telemetryBuilder.ExporterSentLogRecords
or.itemsFailedInst = telemetryBuilder.ExporterSendFailedLogRecords
case xpipeline.SignalProfiles:
or.itemsSentInst = telemetryBuilder.ExporterSentProfileSamples
or.itemsFailedInst = telemetryBuilder.ExporterSendFailedProfileSamples
}
return or, nil
}
func (ors *obsReportSender[K]) Send(ctx context.Context, req K) error {
// Have to read the number of items before sending the request since the request can
// be modified by the downstream components like the batcher.
c := ors.startOp(ctx)
items := req.ItemsCount()
// Forward the data to the next consumer (this pusher is the next).
err := ors.next.Send(c, req)
ors.endOp(c, items, err)
return err
}
// StartOp creates the span used to trace the operation. Returning
// the updated context and the created span.
func (ors *obsReportSender[K]) startOp(ctx context.Context) context.Context {
ctx, _ = ors.tracer.Start(ctx,
ors.spanName,
ors.spanAttrs,
trace.WithLinks(queuebatch.LinksFromContext(ctx)...))
return ctx
}
// EndOp completes the export operation that was started with StartOp.
func (ors *obsReportSender[K]) endOp(ctx context.Context, numRecords int, err error) {
numSent, numFailedToSend := toNumItems(numRecords, err)
if ors.itemsSentInst != nil {
ors.itemsSentInst.Add(ctx, numSent, ors.metricAttr)
}
if ors.itemsFailedInst != nil && numFailedToSend > 0 {
withFailedAttrs := metric.WithAttributeSet(extractFailureAttributes(err))
ors.itemsFailedInst.Add(ctx, numFailedToSend, ors.metricAttr, withFailedAttrs)
}
span := trace.SpanFromContext(ctx)
defer span.End()
// End the span according to errors.
if span.IsRecording() {
span.SetAttributes(
attribute.Int64(ItemsSent, numSent),
attribute.Int64(ItemsFailed, numFailedToSend),
)
if err != nil {
span.SetStatus(otelcodes.Error, err.Error())
}
}
}
func toNumItems(numExportedItems int, err error) (int64, int64) {
if err != nil {
return 0, int64(numExportedItems)
}
return int64(numExportedItems), 0
}
func extractFailureAttributes(err error) attribute.Set {
if err == nil {
return attribute.NewSet()
}
attrs := []attribute.KeyValue{}
errorType := determineErrorType(err)
attrs = append(attrs, attribute.String(string(semconv.ErrorTypeKey), errorType))
isPermanent := consumererror.IsPermanent(err)
attrs = append(attrs, attribute.Bool(ErrorPermanentKey, isPermanent))
return attribute.NewSet(attrs...)
}
func determineErrorType(err error) string {
if experr.IsShutdownErr(err) {
return "Shutdown"
}
if errors.Is(err, context.Canceled) {
return "Canceled"
}
if errors.Is(err, context.DeadlineExceeded) {
return "Deadline_Exceeded"
}
if st, ok := status.FromError(err); ok && st.Code() != codes.OK {
return st.Code().String()
}
return "_OTHER"
}
================================================
FILE: exporter/exporterhelper/internal/obs_report_sender_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal
import (
"context"
"errors"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
semconv "go.opentelemetry.io/otel/semconv/v1.38.0"
grpccodes "google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/experr"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/metadatatest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/requesttest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sender"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/pipeline/xpipeline"
)
var (
exporterID = component.MustNewID("fakeExporter")
errFake = errors.New("errFake")
)
func TestExportTraceFailureAttributes(t *testing.T) {
tests := []struct {
name string
err error
numItems int
expectedType string
expectedPerm bool
useCustomCtx bool
ctxSetup func() context.Context
}{
{
name: "PermanentError",
err: consumererror.NewPermanent(errors.New("bad data")),
numItems: 5,
expectedType: "_OTHER",
expectedPerm: true,
},
{
name: "ShutdownError",
err: experr.NewShutdownErr(errors.New("shutting down")),
numItems: 3,
expectedType: "Shutdown",
expectedPerm: false,
},
{
name: "ContextCanceled",
err: context.Canceled,
numItems: 2,
expectedType: "Canceled",
expectedPerm: false,
useCustomCtx: true,
ctxSetup: func() context.Context {
ctx, cancel := context.WithCancel(context.Background())
cancel()
return ctx
},
},
{
name: "ContextDeadlineExceeded",
err: context.DeadlineExceeded,
numItems: 4,
expectedType: "Deadline_Exceeded",
expectedPerm: false,
},
{
name: "UnknownError",
err: errFake,
numItems: 8,
expectedType: "_OTHER",
expectedPerm: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
telemetry := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, telemetry.Shutdown(context.Background())) })
obsrep, err := newObsReportSender(
exporter.Settings{ID: exporterID, TelemetrySettings: telemetry.NewTelemetrySettings(), BuildInfo: component.NewDefaultBuildInfo()},
pipeline.SignalTraces,
sender.NewSender(func(context.Context, request.Request) error {
return tt.err
}),
)
require.NoError(t, err)
ctx := context.Background()
if tt.useCustomCtx && tt.ctxSetup != nil {
ctx = tt.ctxSetup()
}
req := &requesttest.FakeRequest{Items: tt.numItems}
sendErr := obsrep.Send(ctx, req)
require.Error(t, sendErr)
wantAttrs := attribute.NewSet(
attribute.String("exporter", exporterID.String()),
attribute.String(string(semconv.ErrorTypeKey), tt.expectedType),
attribute.Bool(ErrorPermanentKey, tt.expectedPerm),
)
metadatatest.AssertEqualExporterSendFailedSpans(t, telemetry,
[]metricdata.DataPoint[int64]{
{
Attributes: wantAttrs,
Value: int64(req.Items),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
})
}
}
func TestExportTraceFailureAttributesGRPCError(t *testing.T) {
tests := []struct {
name string
grpcCode grpccodes.Code
expectedType string
isPermanent bool
}{
{
name: "Unavailable",
grpcCode: grpccodes.Unavailable,
expectedType: "Unavailable",
isPermanent: false,
},
{
name: "ResourceExhausted",
grpcCode: grpccodes.ResourceExhausted,
expectedType: "ResourceExhausted",
isPermanent: false,
},
{
name: "DataLoss",
grpcCode: grpccodes.DataLoss,
expectedType: "DataLoss",
isPermanent: false,
},
{
name: "InvalidArgument",
grpcCode: grpccodes.InvalidArgument,
expectedType: "InvalidArgument",
isPermanent: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
telemetry := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, telemetry.Shutdown(context.Background())) })
grpcErr := status.Error(tt.grpcCode, "test error")
obsrep, err := newObsReportSender(
exporter.Settings{ID: exporterID, TelemetrySettings: telemetry.NewTelemetrySettings(), BuildInfo: component.NewDefaultBuildInfo()},
pipeline.SignalTraces,
sender.NewSender(func(context.Context, request.Request) error {
return grpcErr
}),
)
require.NoError(t, err)
req := &requesttest.FakeRequest{Items: 10}
sendErr := obsrep.Send(context.Background(), req)
require.Error(t, sendErr)
wantAttrs := attribute.NewSet(
attribute.String("exporter", exporterID.String()),
attribute.String(string(semconv.ErrorTypeKey), tt.expectedType),
attribute.Bool(ErrorPermanentKey, tt.isPermanent),
)
metadatatest.AssertEqualExporterSendFailedSpans(t, telemetry,
[]metricdata.DataPoint[int64]{
{
Attributes: wantAttrs,
Value: int64(req.Items),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
})
}
}
func TestExportTraceDataOp(t *testing.T) {
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
parentCtx, parentSpan := tt.NewTelemetrySettings().TracerProvider.Tracer("test").Start(context.Background(), t.Name())
defer parentSpan.End()
var exporterErr error
obsrep, err := newObsReportSender(
exporter.Settings{ID: exporterID, TelemetrySettings: tt.NewTelemetrySettings(), BuildInfo: component.NewDefaultBuildInfo()},
pipeline.SignalTraces,
sender.NewSender(func(context.Context, request.Request) error { return exporterErr }),
)
require.NoError(t, err)
params := []testParams{
{items: 22, err: nil},
{items: 14, err: errFake},
}
for i := range params {
exporterErr = params[i].err
require.ErrorIs(t, obsrep.Send(parentCtx, &requesttest.FakeRequest{Items: params[i].items}), params[i].err)
}
spans := tt.SpanRecorder.Ended()
require.Len(t, spans, len(params))
var sentSpans, failedToSendSpans int
for i, span := range spans {
assert.Equal(t, "exporter/"+exporterID.String()+"/traces", span.Name())
switch {
case params[i].err == nil:
sentSpans += params[i].items
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: ItemsSent, Value: attribute.Int64Value(int64(params[i].items))})
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: ItemsFailed, Value: attribute.Int64Value(0)})
assert.Equal(t, codes.Unset, span.Status().Code)
case errors.Is(params[i].err, errFake):
failedToSendSpans += params[i].items
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: ItemsSent, Value: attribute.Int64Value(0)})
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: ItemsFailed, Value: attribute.Int64Value(int64(params[i].items))})
assert.Equal(t, codes.Error, span.Status().Code)
assert.Equal(t, params[i].err.Error(), span.Status().Description)
default:
t.Fatalf("unexpected error: %v", params[i].err)
}
}
metadatatest.AssertEqualExporterSentSpans(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String("exporter", exporterID.String())),
Value: int64(sentSpans),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
var expectedDataPoints []metricdata.DataPoint[int64]
if failedToSendSpans > 0 {
wantAttrs := attribute.NewSet(
attribute.String("exporter", exporterID.String()),
attribute.String(string(semconv.ErrorTypeKey), "_OTHER"),
attribute.Bool(ErrorPermanentKey, false),
)
expectedDataPoints = []metricdata.DataPoint[int64]{
{
Attributes: wantAttrs,
Value: int64(failedToSendSpans),
},
}
}
metadatatest.AssertEqualExporterSendFailedSpans(t, tt, expectedDataPoints,
metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
}
func TestExportMetricsOp(t *testing.T) {
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
parentCtx, parentSpan := tt.NewTelemetrySettings().TracerProvider.Tracer("test").Start(context.Background(), t.Name())
defer parentSpan.End()
var exporterErr error
obsrep, err := newObsReportSender(
exporter.Settings{ID: exporterID, TelemetrySettings: tt.NewTelemetrySettings(), BuildInfo: component.NewDefaultBuildInfo()},
pipeline.SignalMetrics,
sender.NewSender(func(context.Context, request.Request) error { return exporterErr }),
)
require.NoError(t, err)
params := []testParams{
{items: 17, err: nil},
{items: 23, err: errFake},
}
for i := range params {
exporterErr = params[i].err
require.ErrorIs(t, obsrep.Send(parentCtx, &requesttest.FakeRequest{Items: params[i].items}), params[i].err)
}
spans := tt.SpanRecorder.Ended()
require.Len(t, spans, len(params))
var sentMetricPoints, failedToSendMetricPoints int
for i, span := range spans {
assert.Equal(t, "exporter/"+exporterID.String()+"/metrics", span.Name())
switch {
case params[i].err == nil:
sentMetricPoints += params[i].items
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: ItemsSent, Value: attribute.Int64Value(int64(params[i].items))})
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: ItemsFailed, Value: attribute.Int64Value(0)})
assert.Equal(t, codes.Unset, span.Status().Code)
case errors.Is(params[i].err, errFake):
failedToSendMetricPoints += params[i].items
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: ItemsSent, Value: attribute.Int64Value(0)})
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: ItemsFailed, Value: attribute.Int64Value(int64(params[i].items))})
assert.Equal(t, codes.Error, span.Status().Code)
assert.Equal(t, params[i].err.Error(), span.Status().Description)
default:
t.Fatalf("unexpected error: %v", params[i].err)
}
}
metadatatest.AssertEqualExporterSentMetricPoints(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String("exporter", exporterID.String())),
Value: int64(sentMetricPoints),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
var expectedDataPoints []metricdata.DataPoint[int64]
if failedToSendMetricPoints > 0 {
wantAttrs := attribute.NewSet(
attribute.String("exporter", exporterID.String()),
attribute.String(string(semconv.ErrorTypeKey), "_OTHER"),
attribute.Bool(ErrorPermanentKey, false),
)
expectedDataPoints = []metricdata.DataPoint[int64]{
{
Attributes: wantAttrs,
Value: int64(failedToSendMetricPoints),
},
}
}
metadatatest.AssertEqualExporterSendFailedMetricPoints(t, tt, expectedDataPoints,
metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
}
func TestExportLogsOp(t *testing.T) {
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
parentCtx, parentSpan := tt.NewTelemetrySettings().TracerProvider.Tracer("test").Start(context.Background(), t.Name())
defer parentSpan.End()
var exporterErr error
obsrep, err := newObsReportSender(
exporter.Settings{ID: exporterID, TelemetrySettings: tt.NewTelemetrySettings(), BuildInfo: component.NewDefaultBuildInfo()},
pipeline.SignalLogs,
sender.NewSender(func(context.Context, request.Request) error { return exporterErr }),
)
require.NoError(t, err)
params := []testParams{
{items: 17, err: nil},
{items: 23, err: errFake},
}
for i := range params {
exporterErr = params[i].err
require.ErrorIs(t, obsrep.Send(parentCtx, &requesttest.FakeRequest{Items: params[i].items}), params[i].err)
}
spans := tt.SpanRecorder.Ended()
require.Len(t, spans, len(params))
var sentLogRecords, failedToSendLogRecords int
for i, span := range spans {
assert.Equal(t, "exporter/"+exporterID.String()+"/logs", span.Name())
switch {
case params[i].err == nil:
sentLogRecords += params[i].items
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: ItemsSent, Value: attribute.Int64Value(int64(params[i].items))})
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: ItemsFailed, Value: attribute.Int64Value(0)})
assert.Equal(t, codes.Unset, span.Status().Code)
case errors.Is(params[i].err, errFake):
failedToSendLogRecords += params[i].items
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: ItemsSent, Value: attribute.Int64Value(0)})
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: ItemsFailed, Value: attribute.Int64Value(int64(params[i].items))})
assert.Equal(t, codes.Error, span.Status().Code)
assert.Equal(t, params[i].err.Error(), span.Status().Description)
default:
t.Fatalf("unexpected error: %v", params[i].err)
}
}
metadatatest.AssertEqualExporterSentLogRecords(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String("exporter", exporterID.String())),
Value: int64(sentLogRecords),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
var expectedDataPoints []metricdata.DataPoint[int64]
if failedToSendLogRecords > 0 {
wantAttrs := attribute.NewSet(
attribute.String("exporter", exporterID.String()),
attribute.String(string(semconv.ErrorTypeKey), "_OTHER"),
attribute.Bool(ErrorPermanentKey, false),
)
expectedDataPoints = []metricdata.DataPoint[int64]{
{
Attributes: wantAttrs,
Value: int64(failedToSendLogRecords),
},
}
}
metadatatest.AssertEqualExporterSendFailedLogRecords(t, tt, expectedDataPoints,
metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
}
// TestDetermineErrorType tests the determineErrorType function directly
func TestDetermineErrorType(t *testing.T) {
tests := []struct {
name string
err error
expectedErrorType string
}{
{
name: "shutdown error",
err: experr.NewShutdownErr(errors.New("shutting down")),
expectedErrorType: "Shutdown",
},
{
name: "context canceled",
err: context.Canceled,
expectedErrorType: "Canceled",
},
{
name: "context deadline exceeded",
err: context.DeadlineExceeded,
expectedErrorType: "Deadline_Exceeded",
},
{
name: "unknown error",
err: errors.New("some error"),
expectedErrorType: "_OTHER",
},
{
name: "wrapped context canceled",
err: fmt.Errorf("failed: %w", context.Canceled),
expectedErrorType: "Canceled",
},
{
name: "wrapped context deadline exceeded",
err: fmt.Errorf("timeout: %w", context.DeadlineExceeded),
expectedErrorType: "Deadline_Exceeded",
},
{
name: "gRPC Unavailable",
err: status.Error(grpccodes.Unavailable, "service unavailable"),
expectedErrorType: "Unavailable",
},
{
name: "gRPC ResourceExhausted",
err: status.Error(grpccodes.ResourceExhausted, "quota exceeded"),
expectedErrorType: "ResourceExhausted",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
errorType := determineErrorType(tt.err)
assert.Equal(t, tt.expectedErrorType, errorType, "error.type mismatch")
})
}
}
// TestExtractFailureAttributes tests the extractFailureAttributes function directly
func TestExtractFailureAttributes(t *testing.T) {
tests := []struct {
name string
err error
expected attribute.Set
}{
{
name: "permanent error",
err: consumererror.NewPermanent(errors.New("bad data")),
expected: attribute.NewSet(
attribute.String(string(semconv.ErrorTypeKey), "_OTHER"),
attribute.Bool(ErrorPermanentKey, true),
),
},
{
name: "non-permanent error",
err: errors.New("transient error"),
expected: attribute.NewSet(
attribute.String(string(semconv.ErrorTypeKey), "_OTHER"),
attribute.Bool(ErrorPermanentKey, false),
),
},
{
name: "shutdown error",
err: experr.NewShutdownErr(errors.New("shutdown")),
expected: attribute.NewSet(
attribute.String(string(semconv.ErrorTypeKey), "Shutdown"),
attribute.Bool(ErrorPermanentKey, false),
),
},
{
name: "context canceled",
err: context.Canceled,
expected: attribute.NewSet(
attribute.String(string(semconv.ErrorTypeKey), "Canceled"),
attribute.Bool(ErrorPermanentKey, false),
),
},
{
name: "context deadline exceeded",
err: context.DeadlineExceeded,
expected: attribute.NewSet(
attribute.String(string(semconv.ErrorTypeKey), "Deadline_Exceeded"),
attribute.Bool(ErrorPermanentKey, false),
),
},
{
name: "gRPC Unavailable",
err: status.Error(grpccodes.Unavailable, "service unavailable"),
expected: attribute.NewSet(
attribute.String(string(semconv.ErrorTypeKey), "Unavailable"),
attribute.Bool(ErrorPermanentKey, false),
),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := extractFailureAttributes(tt.err)
assert.Equal(t, tt.expected, result)
})
}
}
func TestExportProfilesOp(t *testing.T) {
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
parentCtx, parentSpan := tt.NewTelemetrySettings().TracerProvider.Tracer("test").Start(context.Background(), t.Name())
defer parentSpan.End()
var exporterErr error
obsrep, err := newObsReportSender(
exporter.Settings{ID: exporterID, TelemetrySettings: tt.NewTelemetrySettings(), BuildInfo: component.NewDefaultBuildInfo()},
xpipeline.SignalProfiles,
sender.NewSender(func(context.Context, request.Request) error { return exporterErr }),
)
require.NoError(t, err)
params := []testParams{
{items: 17, err: nil},
{items: 23, err: errFake},
}
for i := range params {
exporterErr = params[i].err
require.ErrorIs(t, obsrep.Send(parentCtx, &requesttest.FakeRequest{Items: params[i].items}), params[i].err)
}
spans := tt.SpanRecorder.Ended()
require.Len(t, spans, len(params))
var sentProfileRecords, failedToSendProfileRecords int
for i, span := range spans {
assert.Equal(t, "exporter/"+exporterID.String()+"/profiles", span.Name())
switch {
case params[i].err == nil:
sentProfileRecords += params[i].items
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: ItemsSent, Value: attribute.Int64Value(int64(params[i].items))})
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: ItemsFailed, Value: attribute.Int64Value(0)})
assert.Equal(t, codes.Unset, span.Status().Code)
case errors.Is(params[i].err, errFake):
failedToSendProfileRecords += params[i].items
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: ItemsSent, Value: attribute.Int64Value(0)})
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: ItemsFailed, Value: attribute.Int64Value(int64(params[i].items))})
assert.Equal(t, codes.Error, span.Status().Code)
assert.Equal(t, params[i].err.Error(), span.Status().Description)
default:
t.Fatalf("unexpected error: %v", params[i].err)
}
}
metadatatest.AssertEqualExporterSentProfileSamples(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String("exporter", exporterID.String())),
Value: int64(sentProfileRecords),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
var expectedDataPoints []metricdata.DataPoint[int64]
if failedToSendProfileRecords > 0 {
wantAttrs := attribute.NewSet(
attribute.String("exporter", exporterID.String()),
attribute.String(string(semconv.ErrorTypeKey), "_OTHER"),
attribute.Bool(ErrorPermanentKey, false),
)
expectedDataPoints = []metricdata.DataPoint[int64]{
{
Attributes: wantAttrs,
Value: int64(failedToSendProfileRecords),
},
}
}
metadatatest.AssertEqualExporterSendFailedProfileSamples(t, tt, expectedDataPoints,
metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
}
type testParams struct {
items int
err error
}
================================================
FILE: exporter/exporterhelper/internal/oteltest/tracetest.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package oteltest // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/oteltest"
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/codes"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/trace"
)
func CheckStatus(t *testing.T, sd sdktrace.ReadOnlySpan, err error) {
if err != nil {
require.Equal(t, codes.Error, sd.Status().Code)
require.EqualError(t, err, sd.Status().Description)
} else {
require.Equal(t, codes.Unset, sd.Status().Code)
}
}
func FakeSpanContext(t *testing.T) trace.SpanContext {
traceID, err := trace.TraceIDFromHex("0102030405060708090a0b0c0d0e0f10")
require.NoError(t, err)
spanID, err := trace.SpanIDFromHex("0102030405060708")
require.NoError(t, err)
return trace.NewSpanContext(trace.SpanContextConfig{
TraceID: traceID,
SpanID: spanID,
TraceFlags: 0x01,
TraceState: trace.TraceState{},
Remote: true,
})
}
================================================
FILE: exporter/exporterhelper/internal/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: exporter/exporterhelper/internal/queue/async_queue.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queue // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/queue"
import (
"context"
"sync"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/collector/component"
)
type asyncQueue[T any] struct {
readableQueue[T]
numConsumers int
refCounter ReferenceCounter[T]
consumeFunc ConsumeFunc[T]
stopWG sync.WaitGroup
}
func newAsyncQueue[T any](q readableQueue[T], numConsumers int, consumeFunc ConsumeFunc[T], refCounter ReferenceCounter[T]) Queue[T] {
return &asyncQueue[T]{
readableQueue: q,
numConsumers: numConsumers,
refCounter: refCounter,
consumeFunc: consumeFunc,
}
}
// Start ensures that queue and all consumers are started.
func (qc *asyncQueue[T]) Start(ctx context.Context, host component.Host) error {
if err := qc.readableQueue.Start(ctx, host); err != nil {
return err
}
var startWG sync.WaitGroup
for i := 0; i < qc.numConsumers; i++ {
startWG.Add(1)
qc.stopWG.Go(func() { //nolint:contextcheck
startWG.Done()
for {
ctx, req, done, ok := qc.Read(context.Background())
if !ok {
return
}
qc.consumeFunc(ctx, req, done)
if qc.refCounter != nil {
qc.refCounter.Unref(req)
}
}
})
}
startWG.Wait()
return nil
}
func (qc *asyncQueue[T]) Offer(ctx context.Context, req T) error {
span := trace.SpanFromContext(ctx)
if err := qc.readableQueue.Offer(ctx, req); err != nil {
span.AddEvent("Failed to enqueue item.")
return err
}
span.AddEvent("Enqueued item.")
return nil
}
// Shutdown ensures that queue and all consumers are stopped.
func (qc *asyncQueue[T]) Shutdown(ctx context.Context) error {
err := qc.readableQueue.Shutdown(ctx)
qc.stopWG.Wait()
return err
}
================================================
FILE: exporter/exporterhelper/internal/queue/async_queue_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queue
import (
"context"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
)
func TestAsyncMemoryQueue(t *testing.T) {
consumed := &atomic.Int64{}
set := newSettings(request.SizerTypeItems, 100)
ac := newAsyncQueue(newMemoryQueue[intRequest](set),
1, func(_ context.Context, _ intRequest, done Done) {
consumed.Add(1)
done.OnDone(nil)
}, set.ReferenceCounter)
require.NoError(t, ac.Start(context.Background(), componenttest.NewNopHost()))
for range 10 {
require.NoError(t, ac.Offer(context.Background(), 10))
}
require.NoError(t, ac.Shutdown(context.Background()))
assert.EqualValues(t, 10, consumed.Load())
}
func TestAsyncMemoryQueueBlocking(t *testing.T) {
consumed := &atomic.Int64{}
set := newSettings(request.SizerTypeItems, 100)
set.BlockOnOverflow = true
ac := newAsyncQueue(newMemoryQueue[intRequest](set),
4, func(_ context.Context, _ intRequest, done Done) {
consumed.Add(1)
done.OnDone(nil)
}, set.ReferenceCounter)
require.NoError(t, ac.Start(context.Background(), componenttest.NewNopHost()))
wg := &sync.WaitGroup{}
for range 10 {
wg.Go(func() {
for range 100_000 {
assert.NoError(t, ac.Offer(context.Background(), 10))
}
})
}
wg.Wait()
require.NoError(t, ac.Shutdown(context.Background()))
assert.EqualValues(t, 1_000_000, consumed.Load())
}
func TestAsyncMemoryWaitForResultQueueBlocking(t *testing.T) {
consumed := &atomic.Int64{}
set := newSettings(request.SizerTypeItems, 100)
set.BlockOnOverflow = true
set.WaitForResult = true
ac := newAsyncQueue(newMemoryQueue[intRequest](set),
4, func(_ context.Context, _ intRequest, done Done) {
consumed.Add(1)
done.OnDone(nil)
}, set.ReferenceCounter)
require.NoError(t, ac.Start(context.Background(), componenttest.NewNopHost()))
wg := &sync.WaitGroup{}
for range 10 {
wg.Go(func() {
for range 100_000 {
assert.NoError(t, ac.Offer(context.Background(), 10))
}
})
}
wg.Wait()
require.NoError(t, ac.Shutdown(context.Background()))
assert.EqualValues(t, 1_000_000, consumed.Load())
}
func TestAsyncMemoryQueueBlockingCancelled(t *testing.T) {
stop := make(chan struct{})
set := newSettings(request.SizerTypeItems, 10)
set.BlockOnOverflow = true
ac := newAsyncQueue(newMemoryQueue[intRequest](set),
1, func(_ context.Context, _ intRequest, done Done) {
<-stop
done.OnDone(nil)
}, set.ReferenceCounter)
require.NoError(t, ac.Start(context.Background(), componenttest.NewNopHost()))
ctx, cancel := context.WithCancel(context.Background())
wg := sync.WaitGroup{}
wg.Go(func() {
for range 10 {
require.NoError(t, ac.Offer(ctx, 1))
}
assert.ErrorIs(t, ac.Offer(ctx, 3), context.Canceled)
})
// Sleep some time so that the go-routine blocks, it doesn't have to but even if it
// does things will work.
<-time.After(1 * time.Second)
cancel()
wg.Wait()
close(stop)
require.NoError(t, ac.Shutdown(context.Background()))
}
func BenchmarkAsyncMemoryQueue(b *testing.B) {
b.Skip("Consistently fails with 'sending queue is full'")
consumed := &atomic.Int64{}
set := newSettings(request.SizerTypeItems, int64(10*b.N))
ac := newAsyncQueue(newMemoryQueue[intRequest](set), 1, func(_ context.Context, _ intRequest, done Done) {
consumed.Add(1)
done.OnDone(nil)
}, set.ReferenceCounter)
require.NoError(b, ac.Start(context.Background(), componenttest.NewNopHost()))
b.ReportAllocs()
for b.Loop() {
require.NoError(b, ac.Offer(context.Background(), 10))
}
require.NoError(b, ac.Shutdown(context.Background()))
assert.EqualValues(b, b.N, consumed.Load())
}
================================================
FILE: exporter/exporterhelper/internal/queue/cond.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queue // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/queue"
import (
"context"
"sync"
)
// cond is equivalent with sync.Cond, but context.Context aware.
// Which means Wait() will return if context is done before any signal is received.
// Also, it requires the caller to hold the c.L during all calls.
type cond struct {
L sync.Locker
ch chan struct{}
waiting int64
}
func newCond(l sync.Locker) *cond {
return &cond{L: l, ch: make(chan struct{}, 1)}
}
// Signal wakes one goroutine waiting on c, if there is any.
// It requires for the caller to hold c.L during the call.
func (c *cond) Signal() {
if c.waiting == 0 {
return
}
c.waiting--
c.ch <- struct{}{}
}
// Broadcast wakes all goroutines waiting on c.
// It requires for the caller to hold c.L during the call.
func (c *cond) Broadcast() {
for ; c.waiting > 0; c.waiting-- {
c.ch <- struct{}{}
}
}
// Wait atomically unlocks c.L and suspends execution of the calling goroutine. After later resuming execution, Wait locks c.L before returning.
func (c *cond) Wait(ctx context.Context) error {
c.waiting++
c.L.Unlock()
select {
case <-ctx.Done():
c.L.Lock()
if c.waiting == 0 {
// If waiting is 0, it means that there was a signal sent and nobody else waits for it.
// Consume it, so that we don't unblock other consumer unnecessary,
// or we don't block the producer because the channel buffer is full.
<-c.ch
} else {
// Decrease the number of waiting routines.
c.waiting--
}
return ctx.Err()
case <-c.ch:
c.L.Lock()
return nil
}
}
================================================
FILE: exporter/exporterhelper/internal/queue/fg.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queue // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/queue"
import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/metadata"
// assign the feature gate to separate functions to make it possible to override the behavior in tests
// on write and read paths separately.
var (
PersistRequestContextOnRead = metadata.ExporterPersistRequestContextFeatureGate.IsEnabled
PersistRequestContextOnWrite = metadata.ExporterPersistRequestContextFeatureGate.IsEnabled
)
================================================
FILE: exporter/exporterhelper/internal/queue/memory_queue.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queue // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/queue"
import (
"context"
"errors"
"sync"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
)
var blockingDonePool = sync.Pool{
New: func() any {
return &blockingDone{
ch: make(chan error, 1),
}
},
}
var (
errInvalidSize = errors.New("invalid element size")
errSizeTooLarge = errors.New("element size too large")
)
// memoryQueue is an in-memory implementation of a Queue.
type memoryQueue[T request.Request] struct {
component.StartFunc
refCounter ReferenceCounter[T]
sizer request.Sizer
cap int64
mu sync.Mutex
hasMoreElements *sync.Cond
hasMoreSpace *cond
items *linkedQueue[T]
size int64
stopped bool
waitForResult bool
blockOnOverflow bool
}
// newMemoryQueue creates a sized elements channel. Each element is assigned a size by the provided sizer.
// capacity is the capacity of the queue.
func newMemoryQueue[T request.Request](set Settings[T]) readableQueue[T] {
sq := &memoryQueue[T]{
refCounter: set.ReferenceCounter,
sizer: request.NewSizer(set.SizerType),
cap: set.Capacity,
items: &linkedQueue[T]{},
waitForResult: set.WaitForResult,
blockOnOverflow: set.BlockOnOverflow,
}
sq.hasMoreElements = sync.NewCond(&sq.mu)
sq.hasMoreSpace = newCond(&sq.mu)
return sq
}
// Offer puts the element into the queue with the given sized if there is enough capacity.
// Returns an error if the queue is full.
func (mq *memoryQueue[T]) Offer(ctx context.Context, el T) error {
elSize := mq.sizer.Sizeof(el)
// Ignore empty requests, see https://github.com/open-telemetry/opentelemetry-proto/blob/main/docs/specification.md#empty-telemetry-envelopes
if elSize == 0 {
return nil
}
if elSize <= 0 {
return errInvalidSize
}
// If element larger than the capacity, will never been able to add it.
if elSize > mq.cap {
return errSizeTooLarge
}
if mq.refCounter != nil {
mq.refCounter.Ref(el)
}
done, err := mq.add(ctx, el, elSize)
if err != nil {
// Unref in case of an error since there will not be any async worker to pick it up.
if mq.refCounter != nil {
mq.refCounter.Unref(el)
}
return err
}
if mq.waitForResult {
// Only re-add the blockingDone instance back to the pool if successfully received the
// message from the consumer which guarantees consumer will not use that anymore,
// otherwise no guarantee about when the consumer will add the message to the channel so cannot reuse or close.
select {
case doneErr := <-done.ch:
blockingDonePool.Put(done)
return doneErr
case <-ctx.Done():
return ctx.Err()
}
}
return nil
}
func (mq *memoryQueue[T]) add(ctx context.Context, el T, elSize int64) (*blockingDone, error) {
mq.mu.Lock()
defer mq.mu.Unlock()
for mq.size+elSize > mq.cap {
if !mq.blockOnOverflow {
return nil, ErrQueueIsFull
}
// Wait for more space or before the ctx is Done.
if err := mq.hasMoreSpace.Wait(ctx); err != nil {
return nil, err
}
}
mq.size += elSize
done := blockingDonePool.Get().(*blockingDone)
done.reset(elSize, mq)
if !mq.waitForResult {
// 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.
ctx = context.WithoutCancel(ctx)
}
mq.items.push(ctx, el, done)
// Signal one consumer if any.
mq.hasMoreElements.Signal()
return done, nil
}
// Read removes the element from the queue and returns it.
// The call blocks until there is an item available or the queue is stopped.
// The function returns true when an item is consumed or false if the queue is stopped and emptied.
func (mq *memoryQueue[T]) Read(context.Context) (context.Context, T, Done, bool) {
mq.mu.Lock()
defer mq.mu.Unlock()
for {
if mq.items.hasElements() {
elCtx, el, done := mq.items.pop()
return elCtx, el, done, true
}
if mq.stopped {
var el T
return context.Background(), el, nil, false
}
// TODO: Need to change the Queue interface to return an error to allow distinguish between shutdown and context canceled.
// Until then use the sync.Cond.
mq.hasMoreElements.Wait()
}
}
func (mq *memoryQueue[T]) onDone(bd *blockingDone, err error) {
mq.mu.Lock()
defer mq.mu.Unlock()
mq.size -= bd.elSize
mq.hasMoreSpace.Signal()
if mq.waitForResult {
// In this case the done will be added back to the queue by the waiter.
bd.ch <- err
return
}
blockingDonePool.Put(bd)
}
// Shutdown closes the queue channel to initiate draining of the queue.
func (mq *memoryQueue[T]) Shutdown(context.Context) error {
mq.mu.Lock()
defer mq.mu.Unlock()
mq.stopped = true
mq.hasMoreElements.Broadcast()
return nil
}
func (mq *memoryQueue[T]) Size() int64 {
mq.mu.Lock()
defer mq.mu.Unlock()
return mq.size
}
func (mq *memoryQueue[T]) Capacity() int64 {
return mq.cap
}
type node[T any] struct {
ctx context.Context
data T
done Done
next *node[T]
}
type linkedQueue[T any] struct {
head *node[T]
tail *node[T]
}
func (l *linkedQueue[T]) push(ctx context.Context, data T, done Done) {
n := &node[T]{ctx: ctx, data: data, done: done}
// If tail is nil means list is empty so update both head and tail to point to same element.
if l.tail == nil {
l.head = n
l.tail = n
return
}
l.tail.next = n
l.tail = n
}
func (l *linkedQueue[T]) hasElements() bool {
return l.head != nil
}
func (l *linkedQueue[T]) pop() (context.Context, T, Done) {
n := l.head
l.head = n.next
// If it gets to the last element, then update tail as well.
if l.head == nil {
l.tail = nil
}
n.next = nil
return n.ctx, n.data, n.done
}
type blockingDone struct {
queue interface {
onDone(*blockingDone, error)
}
elSize int64
ch chan error
}
func (bd *blockingDone) reset(elSize int64, queue interface{ onDone(*blockingDone, error) }) {
bd.elSize = elSize
bd.queue = queue
}
func (bd *blockingDone) OnDone(err error) {
bd.queue.onDone(bd, err)
}
================================================
FILE: exporter/exporterhelper/internal/queue/memory_queue_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queue
import (
"context"
"errors"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
)
func TestMemoryQueue(t *testing.T) {
set := newSettings(request.SizerTypeItems, 7)
q := newMemoryQueue[intRequest](set)
require.NoError(t, q.Start(context.Background(), componenttest.NewNopHost()))
require.NoError(t, q.Offer(context.Background(), 1))
assert.EqualValues(t, 1, q.Size())
assert.EqualValues(t, 7, q.Capacity())
require.NoError(t, q.Offer(context.Background(), 3))
assert.EqualValues(t, 4, q.Size())
// should not be able to send to the full queue
require.ErrorIs(t, q.Offer(context.Background(), 4), ErrQueueIsFull)
assert.EqualValues(t, 4, q.Size())
assert.True(t, consume(q, func(_ context.Context, el intRequest) error {
assert.EqualValues(t, 1, el)
return nil
}))
assert.EqualValues(t, 3, q.Size())
assert.True(t, consume(q, func(_ context.Context, el intRequest) error {
assert.EqualValues(t, 3, el)
return nil
}))
assert.EqualValues(t, 0, q.Size())
require.NoError(t, q.Shutdown(context.Background()))
assert.False(t, consume(q, func(context.Context, intRequest) error { t.FailNow(); return nil }))
require.NoError(t, q.Shutdown(context.Background()))
}
func TestMemoryQueueBlockingCancelled(t *testing.T) {
set := newSettings(request.SizerTypeItems, 5)
set.BlockOnOverflow = true
q := newMemoryQueue[intRequest](set)
require.NoError(t, q.Start(context.Background(), componenttest.NewNopHost()))
require.NoError(t, q.Offer(context.Background(), 3))
ctx, cancel := context.WithCancel(context.Background())
wg := sync.WaitGroup{}
wg.Go(func() {
assert.ErrorIs(t, q.Offer(ctx, 3), context.Canceled)
})
cancel()
wg.Wait()
assert.EqualValues(t, 3, q.Size())
assert.True(t, consume(q, func(_ context.Context, el intRequest) error {
assert.EqualValues(t, 3, el)
return nil
}))
require.NoError(t, q.Shutdown(context.Background()))
}
func TestMemoryQueueDrainWhenShutdown(t *testing.T) {
set := newSettings(request.SizerTypeItems, 7)
q := newMemoryQueue[intRequest](set)
require.NoError(t, q.Start(context.Background(), componenttest.NewNopHost()))
require.NoError(t, q.Offer(context.Background(), 1))
require.NoError(t, q.Offer(context.Background(), 3))
assert.True(t, consume(q, func(_ context.Context, el intRequest) error {
assert.EqualValues(t, 1, el)
return nil
}))
assert.EqualValues(t, 3, q.Size())
require.NoError(t, q.Shutdown(context.Background()))
assert.EqualValues(t, 3, q.Size())
assert.True(t, consume(q, func(_ context.Context, el intRequest) error {
assert.EqualValues(t, 3, el)
return nil
}))
assert.EqualValues(t, 0, q.Size())
assert.False(t, consume(q, func(context.Context, intRequest) error { t.FailNow(); return nil }))
require.NoError(t, q.Shutdown(context.Background()))
}
func TestMemoryQueueOfferInvalidSize(t *testing.T) {
set := newSettings(request.SizerTypeItems, 1)
q := newMemoryQueue[intRequest](set)
require.NoError(t, q.Start(context.Background(), componenttest.NewNopHost()))
require.ErrorIs(t, q.Offer(context.Background(), -1), errInvalidSize)
require.NoError(t, q.Shutdown(context.Background()))
}
func TestMemoryQueueRejectOverCapacityElements(t *testing.T) {
set := newSettings(request.SizerTypeItems, 1)
set.BlockOnOverflow = true
q := newMemoryQueue[intRequest](set)
require.NoError(t, q.Start(context.Background(), componenttest.NewNopHost()))
require.ErrorIs(t, q.Offer(context.Background(), 8), errSizeTooLarge)
require.NoError(t, q.Shutdown(context.Background()))
}
func TestMemoryQueueOfferZeroSize(t *testing.T) {
set := newSettings(request.SizerTypeItems, 1)
q := newMemoryQueue[intRequest](set)
require.NoError(t, q.Start(context.Background(), componenttest.NewNopHost()))
require.NoError(t, q.Offer(context.Background(), 0))
require.NoError(t, q.Shutdown(context.Background()))
// Because the size 0 is ignored, nothing to drain.
assert.False(t, consume(q, func(context.Context, intRequest) error { t.FailNow(); return nil }))
}
func TestMemoryQueueOverflow(t *testing.T) {
set := newSettings(request.SizerTypeItems, 1)
q := newMemoryQueue[intRequest](set)
require.NoError(t, q.Start(context.Background(), componenttest.NewNopHost()))
require.NoError(t, q.Offer(context.Background(), 1))
require.ErrorIs(t, q.Offer(context.Background(), 1), ErrQueueIsFull)
require.NoError(t, q.Shutdown(context.Background()))
}
func TestMemoryQueueWaitForResultPassErrorBack(t *testing.T) {
wg := sync.WaitGroup{}
myErr := errors.New("test error")
set := newSettings(request.SizerTypeItems, 100)
set.WaitForResult = true
q := newMemoryQueue[intRequest](set)
require.NoError(t, q.Start(context.Background(), componenttest.NewNopHost()))
wg.Go(func() {
_, req, done, ok := q.Read(context.Background())
assert.True(t, ok)
assert.EqualValues(t, 1, req)
done.OnDone(myErr)
})
require.ErrorIs(t, q.Offer(context.Background(), intRequest(1)), myErr)
require.NoError(t, q.Shutdown(context.Background()))
wg.Wait()
}
func TestMemoryQueueWaitForResultCancelIncomingRequest(t *testing.T) {
wg := sync.WaitGroup{}
stop := make(chan struct{})
set := newSettings(request.SizerTypeItems, 100)
set.WaitForResult = true
q := newMemoryQueue[intRequest](set)
require.NoError(t, q.Start(context.Background(), componenttest.NewNopHost()))
// Consume async new data.
wg.Go(func() {
_, _, done, ok := q.Read(context.Background())
assert.True(t, ok)
<-stop
done.OnDone(nil)
})
ctx, cancel := context.WithCancel(context.Background())
wg.Go(func() {
<-time.After(time.Second)
cancel()
})
require.ErrorIs(t, q.Offer(ctx, intRequest(1)), context.Canceled)
close(stop)
require.NoError(t, q.Shutdown(context.Background()))
wg.Wait()
}
func TestMemoryQueueWaitForResultSizeAndCapacity(t *testing.T) {
wg := sync.WaitGroup{}
stop := make(chan struct{})
set := newSettings(request.SizerTypeItems, 100)
set.WaitForResult = true
q := newMemoryQueue[intRequest](set)
require.NoError(t, q.Start(context.Background(), componenttest.NewNopHost()))
// Consume async new data.
wg.Go(func() {
_, _, done, ok := q.Read(context.Background())
assert.True(t, ok)
<-stop
done.OnDone(nil)
})
assert.EqualValues(t, 0, q.Size())
assert.EqualValues(t, 100, q.Capacity())
wg.Go(func() {
assert.NoError(t, q.Offer(context.Background(), intRequest(1)))
})
assert.Eventually(t, func() bool { return q.Size() == 1 }, 1*time.Second, 10*time.Millisecond)
assert.EqualValues(t, 100, q.Capacity())
close(stop)
require.NoError(t, q.Shutdown(context.Background()))
wg.Wait()
}
func BenchmarkMemoryQueueWaitForResult(b *testing.B) {
wg := sync.WaitGroup{}
consumed := &atomic.Int64{}
set := newSettings(request.SizerTypeItems, 100)
set.WaitForResult = true
q := newMemoryQueue[intRequest](set)
require.NoError(b, q.Start(context.Background(), componenttest.NewNopHost()))
// Consume async new data.
wg.Go(func() {
for {
_, req, done, ok := q.Read(context.Background())
if !ok {
return
}
consumed.Add(int64(req))
done.OnDone(nil)
}
})
b.ReportAllocs()
for b.Loop() {
for range 100 {
require.NoError(b, q.Offer(context.Background(), intRequest(1)))
}
}
require.NoError(b, q.Shutdown(context.Background()))
assert.Equal(b, int64(b.N)*100, consumed.Load())
}
func consume[T any](q readableQueue[T], consumeFunc func(context.Context, T) error) bool {
ctx, req, done, ok := q.Read(context.Background())
if !ok {
return false
}
done.OnDone(consumeFunc(ctx, req))
return true
}
================================================
FILE: exporter/exporterhelper/internal/queue/meta.pb.go
================================================
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc v3.21.6
// source: exporter/exporterhelper/internal/queue/meta.proto
package queue
import (
reflect "reflect"
sync "sync"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// PersistentMetadata holds all persistent metadata for the queue.
// The items and bytes sizes are recorded explicitly,
// the requests size can be calculated as (write_index - read_index + len(currently_dispatched_items)).
type PersistentMetadata struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Current total items size of the queue.
ItemsSize int64 `protobuf:"fixed64,1,opt,name=items_size,json=itemsSize,proto3" json:"items_size,omitempty"`
// Current total bytes size of the queue.
BytesSize int64 `protobuf:"fixed64,2,opt,name=bytes_size,json=bytesSize,proto3" json:"bytes_size,omitempty"`
// Index of the next item to be read from the queue.
ReadIndex uint64 `protobuf:"fixed64,3,opt,name=read_index,json=readIndex,proto3" json:"read_index,omitempty"`
// Index where the next item will be written to the queue.
WriteIndex uint64 `protobuf:"fixed64,4,opt,name=write_index,json=writeIndex,proto3" json:"write_index,omitempty"`
// List of item indices currently being processed by consumers.
CurrentlyDispatchedItems []uint64 `protobuf:"fixed64,5,rep,packed,name=currently_dispatched_items,json=currentlyDispatchedItems,proto3" json:"currently_dispatched_items,omitempty"`
}
func (x *PersistentMetadata) Reset() {
*x = PersistentMetadata{}
if protoimpl.UnsafeEnabled {
mi := &file_exporter_exporterhelper_internal_queue_meta_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PersistentMetadata) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PersistentMetadata) ProtoMessage() {}
func (x *PersistentMetadata) ProtoReflect() protoreflect.Message {
mi := &file_exporter_exporterhelper_internal_queue_meta_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PersistentMetadata.ProtoReflect.Descriptor instead.
func (*PersistentMetadata) Descriptor() ([]byte, []int) {
return file_exporter_exporterhelper_internal_queue_meta_proto_rawDescGZIP(), []int{0}
}
func (x *PersistentMetadata) GetItemsSize() int64 {
if x != nil {
return x.ItemsSize
}
return 0
}
func (x *PersistentMetadata) GetBytesSize() int64 {
if x != nil {
return x.BytesSize
}
return 0
}
func (x *PersistentMetadata) GetReadIndex() uint64 {
if x != nil {
return x.ReadIndex
}
return 0
}
func (x *PersistentMetadata) GetWriteIndex() uint64 {
if x != nil {
return x.WriteIndex
}
return 0
}
func (x *PersistentMetadata) GetCurrentlyDispatchedItems() []uint64 {
if x != nil {
return x.CurrentlyDispatchedItems
}
return nil
}
var File_exporter_exporterhelper_internal_queue_meta_proto protoreflect.FileDescriptor
var file_exporter_exporterhelper_internal_queue_meta_proto_rawDesc = []byte{
0x0a, 0x31, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x2f, 0x65, 0x78, 0x70, 0x6f, 0x72,
0x74, 0x65, 0x72, 0x68, 0x65, 0x6c, 0x70, 0x65, 0x72, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
0x61, 0x6c, 0x2f, 0x71, 0x75, 0x65, 0x75, 0x65, 0x2f, 0x6d, 0x65, 0x74, 0x61, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x12, 0x3e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74,
0x72, 0x79, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x65, 0x78, 0x70,
0x6f, 0x72, 0x74, 0x65, 0x72, 0x2e, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x68, 0x65,
0x6c, 0x70, 0x65, 0x72, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x71, 0x75,
0x65, 0x75, 0x65, 0x22, 0xd0, 0x01, 0x0a, 0x12, 0x50, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65,
0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x74,
0x65, 0x6d, 0x73, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x10, 0x52, 0x09,
0x69, 0x74, 0x65, 0x6d, 0x73, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x79, 0x74,
0x65, 0x73, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x10, 0x52, 0x09, 0x62,
0x79, 0x74, 0x65, 0x73, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x61, 0x64,
0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x06, 0x52, 0x09, 0x72, 0x65,
0x61, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x72, 0x69, 0x74, 0x65,
0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x06, 0x52, 0x0a, 0x77, 0x72,
0x69, 0x74, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x3c, 0x0a, 0x1a, 0x63, 0x75, 0x72, 0x72,
0x65, 0x6e, 0x74, 0x6c, 0x79, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64,
0x5f, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x06, 0x52, 0x18, 0x63, 0x75,
0x72, 0x72, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65,
0x64, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x42, 0x46, 0x5a, 0x44, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65,
0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x69, 0x6f, 0x2f, 0x63, 0x6f,
0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2f, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72,
0x2f, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x68, 0x65, 0x6c, 0x70, 0x65, 0x72, 0x2f,
0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x71, 0x75, 0x65, 0x75, 0x65, 0x62, 0x06,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_exporter_exporterhelper_internal_queue_meta_proto_rawDescOnce sync.Once
file_exporter_exporterhelper_internal_queue_meta_proto_rawDescData = file_exporter_exporterhelper_internal_queue_meta_proto_rawDesc
)
func file_exporter_exporterhelper_internal_queue_meta_proto_rawDescGZIP() []byte {
file_exporter_exporterhelper_internal_queue_meta_proto_rawDescOnce.Do(func() {
file_exporter_exporterhelper_internal_queue_meta_proto_rawDescData = protoimpl.X.CompressGZIP(file_exporter_exporterhelper_internal_queue_meta_proto_rawDescData)
})
return file_exporter_exporterhelper_internal_queue_meta_proto_rawDescData
}
var file_exporter_exporterhelper_internal_queue_meta_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_exporter_exporterhelper_internal_queue_meta_proto_goTypes = []interface{}{
(*PersistentMetadata)(nil), // 0: opentelemetry.collector.exporter.exporterhelper.internal.queue.PersistentMetadata
}
var file_exporter_exporterhelper_internal_queue_meta_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_exporter_exporterhelper_internal_queue_meta_proto_init() }
func file_exporter_exporterhelper_internal_queue_meta_proto_init() {
if File_exporter_exporterhelper_internal_queue_meta_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_exporter_exporterhelper_internal_queue_meta_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PersistentMetadata); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_exporter_exporterhelper_internal_queue_meta_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_exporter_exporterhelper_internal_queue_meta_proto_goTypes,
DependencyIndexes: file_exporter_exporterhelper_internal_queue_meta_proto_depIdxs,
MessageInfos: file_exporter_exporterhelper_internal_queue_meta_proto_msgTypes,
}.Build()
File_exporter_exporterhelper_internal_queue_meta_proto = out.File
file_exporter_exporterhelper_internal_queue_meta_proto_rawDesc = nil
file_exporter_exporterhelper_internal_queue_meta_proto_goTypes = nil
file_exporter_exporterhelper_internal_queue_meta_proto_depIdxs = nil
}
================================================
FILE: exporter/exporterhelper/internal/queue/meta.proto
================================================
syntax = "proto3";
package opentelemetry.collector.exporter.exporterhelper.internal.queue;
option go_package = "go.opentelemetry.io/collector/exporter/exporterhelper/internal/queue";
// PersistentMetadata holds all persistent metadata for the queue.
// The items and bytes sizes are recorded explicitly,
// the requests size can be calculated as (write_index - read_index + len(currently_dispatched_items)).
message PersistentMetadata{
// Current total items size of the queue.
sfixed64 items_size = 1;
// Current total bytes size of the queue.
sfixed64 bytes_size = 2;
// Index of the next item to be read from the queue.
fixed64 read_index = 3;
// Index where the next item will be written to the queue.
fixed64 write_index = 4;
// List of item indices currently being processed by consumers.
repeated fixed64 currently_dispatched_items = 5;
}
================================================
FILE: exporter/exporterhelper/internal/queue/obs_queue.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queue // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/queue"
import (
"context"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/metadata"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/pipeline/xpipeline"
)
const (
// ExporterKey used to identify exporters in metrics and traces.
exporterKey = "exporter"
// DataTypeKey used to identify the data type in the queue size metric.
dataTypeKey = "data_type"
)
// obsQueue is a helper to add observability to a queue.
type obsQueue[T request.Request] struct {
Queue[T]
tb *metadata.TelemetryBuilder
metricAttr metric.MeasurementOption
enqueueFailedInst metric.Int64Counter
queueBatchSizeInst metric.Int64Histogram
queueBatchSizeBytesInst metric.Int64Histogram
tracer trace.Tracer
}
func newObsQueue[T request.Request](set Settings[T], delegate Queue[T]) (Queue[T], error) {
tb, err := metadata.NewTelemetryBuilder(set.Telemetry)
if err != nil {
return nil, err
}
exporterAttr := attribute.String(exporterKey, set.ID.String())
asyncAttr := metric.WithAttributeSet(attribute.NewSet(exporterAttr, attribute.String(dataTypeKey, set.Signal.String())))
err = tb.RegisterExporterQueueSizeCallback(func(_ context.Context, o metric.Int64Observer) error {
o.Observe(delegate.Size(), asyncAttr)
return nil
})
if err != nil {
return nil, err
}
err = tb.RegisterExporterQueueCapacityCallback(func(_ context.Context, o metric.Int64Observer) error {
o.Observe(delegate.Capacity(), asyncAttr)
return nil
})
if err != nil {
return nil, err
}
tracer := metadata.Tracer(set.Telemetry)
or := &obsQueue[T]{
Queue: delegate,
tb: tb,
metricAttr: metric.WithAttributeSet(attribute.NewSet(exporterAttr)),
tracer: tracer,
}
switch set.Signal {
case pipeline.SignalTraces:
or.enqueueFailedInst = tb.ExporterEnqueueFailedSpans
case pipeline.SignalMetrics:
or.enqueueFailedInst = tb.ExporterEnqueueFailedMetricPoints
case pipeline.SignalLogs:
or.enqueueFailedInst = tb.ExporterEnqueueFailedLogRecords
case xpipeline.SignalProfiles:
or.enqueueFailedInst = tb.ExporterEnqueueFailedProfileSamples
}
or.queueBatchSizeInst = tb.ExporterQueueBatchSendSize
or.queueBatchSizeBytesInst = tb.ExporterQueueBatchSendSizeBytes
return or, nil
}
func (or *obsQueue[T]) Shutdown(ctx context.Context) error {
defer or.tb.Shutdown()
return or.Queue.Shutdown(ctx)
}
func (or *obsQueue[T]) Offer(ctx context.Context, req T) error {
// Have to read the number of items before sending the request since the request can
// be modified by the downstream components like the batcher.
numItems := req.ItemsCount()
or.queueBatchSizeInst.Record(ctx, int64(numItems), or.metricAttr)
or.queueBatchSizeBytesInst.Record(ctx, int64(req.BytesSize()), or.metricAttr)
ctx, span := or.tracer.Start(ctx, "exporter/enqueue")
err := or.Queue.Offer(ctx, req)
span.End()
// No metrics recorded for profiles, remove enqueueFailedInst check with nil when profiles metrics available.
if err != nil && or.enqueueFailedInst != nil {
or.enqueueFailedInst.Add(ctx, int64(numItems), or.metricAttr)
}
return err
}
================================================
FILE: exporter/exporterhelper/internal/queue/obs_queue_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queue
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/metadatatest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/requesttest"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/pipeline/xpipeline"
)
var exporterID = component.NewID(exportertest.NopType)
type fakeQueue[T any] struct {
Queue[T]
offerErr error
size int64
capacity int64
}
func (fq *fakeQueue[T]) Size() int64 {
return fq.size
}
func (fq *fakeQueue[T]) Capacity() int64 {
return fq.capacity
}
func (fq *fakeQueue[T]) Offer(context.Context, T) error {
return fq.offerErr
}
func newFakeQueue[T request.Request](offerErr error, size, capacity int64) Queue[T] {
return &fakeQueue[T]{offerErr: offerErr, size: size, capacity: capacity}
}
func TestObsQueueLogsSizeCapacity(t *testing.T) {
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
te, err := newObsQueue[request.Request](Settings[request.Request]{
Signal: pipeline.SignalLogs,
ID: exporterID,
Telemetry: tt.NewTelemetrySettings(),
}, newFakeQueue[request.Request](nil, 7, 9))
require.NoError(t, err)
require.NoError(t, te.Offer(context.Background(), &requesttest.FakeRequest{Items: 2}))
metadatatest.AssertEqualExporterQueueSize(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(exporterKey, exporterID.String()),
attribute.String(dataTypeKey, pipeline.SignalLogs.String())),
Value: int64(7),
},
}, metricdatatest.IgnoreTimestamp())
metadatatest.AssertEqualExporterQueueCapacity(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(exporterKey, exporterID.String()),
attribute.String(dataTypeKey, pipeline.SignalLogs.String())),
Value: int64(9),
},
}, metricdatatest.IgnoreTimestamp())
}
func TestObsQueueLogsFailure(t *testing.T) {
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
te, err := newObsQueue[request.Request](Settings[request.Request]{
Signal: pipeline.SignalLogs,
ID: exporterID,
Telemetry: tt.NewTelemetrySettings(),
}, newFakeQueue[request.Request](errors.New("my error"), 7, 9))
require.NoError(t, err)
require.Error(t, te.Offer(context.Background(), &requesttest.FakeRequest{Items: 2}))
metadatatest.AssertEqualExporterEnqueueFailedLogRecords(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(exporterKey, exporterID.String())),
Value: int64(2),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
}
func TestObsQueueTracesSizeCapacity(t *testing.T) {
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
te, err := newObsQueue[request.Request](Settings[request.Request]{
Signal: pipeline.SignalTraces,
ID: exporterID,
Telemetry: tt.NewTelemetrySettings(),
}, newFakeQueue[request.Request](nil, 17, 19))
require.NoError(t, err)
require.NoError(t, te.Offer(context.Background(), &requesttest.FakeRequest{Items: 12}))
metadatatest.AssertEqualExporterQueueSize(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(exporterKey, exporterID.String()),
attribute.String(dataTypeKey, pipeline.SignalTraces.String())),
Value: int64(17),
},
}, metricdatatest.IgnoreTimestamp())
metadatatest.AssertEqualExporterQueueCapacity(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(exporterKey, exporterID.String()),
attribute.String(dataTypeKey, pipeline.SignalTraces.String())),
Value: int64(19),
},
}, metricdatatest.IgnoreTimestamp())
}
func TestObsQueueTracesFailure(t *testing.T) {
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
te, err := newObsQueue[request.Request](Settings[request.Request]{
Signal: pipeline.SignalTraces,
ID: exporterID,
Telemetry: tt.NewTelemetrySettings(),
}, newFakeQueue[request.Request](errors.New("my error"), 0, 0))
require.NoError(t, err)
require.Error(t, te.Offer(context.Background(), &requesttest.FakeRequest{Items: 12}))
metadatatest.AssertEqualExporterEnqueueFailedSpans(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(exporterKey, exporterID.String())),
Value: int64(12),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
}
func TestObsQueueMetrics(t *testing.T) {
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
te, err := newObsQueue[request.Request](Settings[request.Request]{
Signal: pipeline.SignalMetrics,
ID: exporterID,
Telemetry: tt.NewTelemetrySettings(),
}, newFakeQueue[request.Request](nil, 27, 29))
require.NoError(t, err)
require.NoError(t, te.Offer(context.Background(), &requesttest.FakeRequest{Items: 22}))
metadatatest.AssertEqualExporterQueueSize(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(exporterKey, exporterID.String()),
attribute.String(dataTypeKey, pipeline.SignalMetrics.String())),
Value: int64(27),
},
}, metricdatatest.IgnoreTimestamp())
metadatatest.AssertEqualExporterQueueCapacity(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(exporterKey, exporterID.String()),
attribute.String(dataTypeKey, pipeline.SignalMetrics.String())),
Value: int64(29),
},
}, metricdatatest.IgnoreTimestamp())
}
func TestObsQueueMetricsFailure(t *testing.T) {
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
te, err := newObsQueue[request.Request](Settings[request.Request]{
Signal: pipeline.SignalMetrics,
ID: exporterID,
Telemetry: tt.NewTelemetrySettings(),
}, newFakeQueue[request.Request](errors.New("my error"), 0, 0))
require.NoError(t, err)
require.Error(t, te.Offer(context.Background(), &requesttest.FakeRequest{Items: 22}))
metadatatest.AssertEqualExporterEnqueueFailedMetricPoints(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(exporterKey, exporterID.String())),
Value: int64(22),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
}
func TestObsQueueProfiles(t *testing.T) {
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
te, err := newObsQueue[request.Request](Settings[request.Request]{
Signal: xpipeline.SignalProfiles,
ID: exporterID,
Telemetry: tt.NewTelemetrySettings(),
}, newFakeQueue[request.Request](nil, 27, 29))
require.NoError(t, err)
require.NoError(t, te.Offer(context.Background(), &requesttest.FakeRequest{Items: 22}))
metadatatest.AssertEqualExporterQueueSize(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(exporterKey, exporterID.String()),
attribute.String(dataTypeKey, xpipeline.SignalProfiles.String())),
Value: int64(27),
},
}, metricdatatest.IgnoreTimestamp())
metadatatest.AssertEqualExporterQueueCapacity(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(exporterKey, exporterID.String()),
attribute.String(dataTypeKey, xpipeline.SignalProfiles.String())),
Value: int64(29),
},
}, metricdatatest.IgnoreTimestamp())
}
func TestObsQueueProfilesFailure(t *testing.T) {
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
te, err := newObsQueue[request.Request](Settings[request.Request]{
Signal: xpipeline.SignalProfiles,
ID: exporterID,
Telemetry: tt.NewTelemetrySettings(),
}, newFakeQueue[request.Request](errors.New("my error"), 0, 0))
require.NoError(t, err)
require.Error(t, te.Offer(context.Background(), &requesttest.FakeRequest{Items: 22}))
metadatatest.AssertEqualExporterEnqueueFailedProfileSamples(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(exporterKey, exporterID.String())),
Value: int64(22),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
}
func TestObsQueueLogsBatchSize(t *testing.T) {
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
te, err := newObsQueue[request.Request](Settings[request.Request]{
Signal: pipeline.SignalLogs,
ID: exporterID,
Telemetry: tt.NewTelemetrySettings(),
}, newFakeQueue[request.Request](nil, 7, 9))
require.NoError(t, err)
require.NoError(t, te.Offer(context.Background(), &requesttest.FakeRequest{Items: 2, Bytes: 100}))
metadatatest.AssertEqualExporterQueueBatchSendSize(t, tt,
[]metricdata.HistogramDataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(exporterKey, exporterID.String())),
Count: 1,
Bounds: []float64{10, 25, 50, 75, 100, 250, 500, 750, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000, 20000, 30000, 50000, 100000},
BucketCounts: []uint64{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
Min: metricdata.NewExtrema[int64](2),
Max: metricdata.NewExtrema[int64](2),
Sum: 2,
},
}, metricdatatest.IgnoreTimestamp())
}
func TestObsQueueTracesBatchSize(t *testing.T) {
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
te, err := newObsQueue[request.Request](Settings[request.Request]{
Signal: pipeline.SignalTraces,
ID: exporterID,
Telemetry: tt.NewTelemetrySettings(),
}, newFakeQueue[request.Request](nil, 17, 19))
require.NoError(t, err)
require.NoError(t, te.Offer(context.Background(), &requesttest.FakeRequest{Items: 12, Bytes: 200}))
metadatatest.AssertEqualExporterQueueBatchSendSize(t, tt,
[]metricdata.HistogramDataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(exporterKey, exporterID.String())),
Count: 1,
Bounds: []float64{10, 25, 50, 75, 100, 250, 500, 750, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000, 20000, 30000, 50000, 100000},
BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
Min: metricdata.NewExtrema[int64](12),
Max: metricdata.NewExtrema[int64](12),
Sum: 12,
},
}, metricdatatest.IgnoreTimestamp())
}
func TestObsQueueMetricsBatchSize(t *testing.T) {
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
te, err := newObsQueue[request.Request](Settings[request.Request]{
Signal: pipeline.SignalMetrics,
ID: exporterID,
Telemetry: tt.NewTelemetrySettings(),
}, newFakeQueue[request.Request](nil, 27, 29))
require.NoError(t, err)
require.NoError(t, te.Offer(context.Background(), &requesttest.FakeRequest{Items: 22, Bytes: 300}))
metadatatest.AssertEqualExporterQueueBatchSendSize(t, tt,
[]metricdata.HistogramDataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(exporterKey, exporterID.String())),
Count: 1,
Bounds: []float64{10, 25, 50, 75, 100, 250, 500, 750, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000, 20000, 30000, 50000, 100000},
BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
Min: metricdata.NewExtrema[int64](22),
Max: metricdata.NewExtrema[int64](22),
Sum: 22,
},
}, metricdatatest.IgnoreTimestamp())
}
func TestObsQueueProfilesBatchSize(t *testing.T) {
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
te, err := newObsQueue[request.Request](Settings[request.Request]{
Signal: xpipeline.SignalProfiles,
ID: exporterID,
Telemetry: tt.NewTelemetrySettings(),
}, newFakeQueue[request.Request](nil, 27, 29))
require.NoError(t, err)
require.NoError(t, te.Offer(context.Background(), &requesttest.FakeRequest{Items: 22, Bytes: 300}))
metadatatest.AssertEqualExporterQueueBatchSendSize(t, tt,
[]metricdata.HistogramDataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(exporterKey, exporterID.String())),
Count: 1,
Bounds: []float64{10, 25, 50, 75, 100, 250, 500, 750, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000, 20000, 30000, 50000, 100000},
BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
Min: metricdata.NewExtrema[int64](22),
Max: metricdata.NewExtrema[int64](22),
Sum: 22,
},
}, metricdatatest.IgnoreTimestamp())
}
================================================
FILE: exporter/exporterhelper/internal/queue/persistent_queue.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queue // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/queue"
import (
"context"
"encoding/binary"
"errors"
"fmt"
"strconv"
"sync"
"go.uber.org/zap"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/experr"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/extension/xextension/storage"
"go.opentelemetry.io/collector/pipeline"
)
const (
zapKey = "key"
zapErrorCount = "errorCount"
zapNumberOfItems = "numberOfItems"
legacyReadIndexKey = "ri"
legacyWriteIndexKey = "wi"
legacyCurrentlyDispatchedItemsKey = "di"
// metadataKey is the new single key for all queue metadata.
metadataKey = "qmv0"
)
var (
errValueNotSet = errors.New("value not set")
errInvalidValue = errors.New("invalid value")
errNoStorageClient = errors.New("no storage client extension found")
errWrongExtensionType = errors.New("requested extension is not a storage extension")
)
var indexDonePool = sync.Pool{
New: func() any {
return &indexDone{}
},
}
// persistentQueue provides a persistent queue implementation backed by file storage extension
//
// Write index describes the position at which next item is going to be stored.
// Read index describes which item needs to be read next.
// When Write index = Read index, no elements are in the queue.
//
// The items currently dispatched by consumers are not deleted until the processing is finished.
// Their list is stored under a separate key.
//
// ┌───────file extension-backed queue───────┐
// │ │
// │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ │
// │ n+1 │ n │ ... │ 4 │ │ 3 │ │ 2 │ │ 1 │ │
// │ └───┘ └───┘ └─x─┘ └─|─┘ └─x─┘ │
// │ x | x │
// └───────────────────────x─────|─────x─────┘
// ▲ ▲ x | x
// │ │ x | xxxx deleted
// │ │ x |
// write read x └── currently dispatched item
// index index x
// xxxx deleted
type persistentQueue[T request.Request] struct {
logger *zap.Logger
client storage.Client
encoding Encoding[T]
capacity int64
sizerType request.SizerType
activeSizer request.Sizer
itemsSizer request.Sizer
bytesSizer request.Sizer
storageID component.ID
id component.ID
signal pipeline.Signal
// mu guards everything declared below.
mu sync.Mutex
hasMoreElements *sync.Cond
hasMoreSpace *cond
metadata PersistentMetadata
refClient int64
stopped bool
blockOnOverflow bool
}
// newPersistentQueue creates a new queue backed by file storage; name and signal must be a unique combination that identifies the queue storage
func newPersistentQueue[T request.Request](set Settings[T]) readableQueue[T] {
pq := &persistentQueue[T]{
logger: set.Telemetry.Logger,
encoding: set.Encoding,
capacity: set.Capacity,
sizerType: set.SizerType,
activeSizer: request.NewSizer(set.SizerType),
itemsSizer: request.NewItemsSizer(),
bytesSizer: request.NewBytesSizer(),
storageID: *set.StorageID,
id: set.ID,
signal: set.Signal,
blockOnOverflow: set.BlockOnOverflow,
}
pq.hasMoreElements = sync.NewCond(&pq.mu)
pq.hasMoreSpace = newCond(&pq.mu)
return pq
}
// Start starts the persistentQueue with the given number of consumers.
func (pq *persistentQueue[T]) Start(ctx context.Context, host component.Host) error {
storageClient, err := toStorageClient(ctx, pq.storageID, host, pq.id, pq.signal)
if err != nil {
return err
}
pq.initClient(ctx, storageClient)
return nil
}
func (pq *persistentQueue[T]) Size() int64 {
pq.mu.Lock()
defer pq.mu.Unlock()
return pq.internalSize()
}
func (pq *persistentQueue[T]) internalSize() int64 {
switch pq.sizerType {
case request.SizerTypeBytes:
return pq.metadata.BytesSize
case request.SizerTypeItems:
return pq.metadata.ItemsSize
default:
return pq.requestSize()
}
}
func (pq *persistentQueue[T]) requestSize() int64 {
return int64(pq.metadata.WriteIndex-pq.metadata.ReadIndex) + int64(len(pq.metadata.CurrentlyDispatchedItems))
}
func (pq *persistentQueue[T]) Capacity() int64 {
return pq.capacity
}
func (pq *persistentQueue[T]) initClient(ctx context.Context, client storage.Client) {
pq.client = client
// Start with a reference 1 which is the reference we use for the producer goroutines and initialization.
pq.refClient = 1
// Try to load from new consolidated metadata first
err := pq.loadQueueMetadata(ctx)
switch {
case err == nil:
pq.enqueueNotDispatchedReqs(ctx, pq.metadata.CurrentlyDispatchedItems)
pq.metadata.CurrentlyDispatchedItems = nil
case !errors.Is(err, errValueNotSet):
pq.logger.Error("Failed getting metadata, starting with new ones", zap.Error(err))
pq.metadata = PersistentMetadata{}
default:
pq.logger.Info("New queue metadata key not found, attempting to load legacy format.")
pq.loadLegacyMetadata(ctx)
}
}
// loadQueueMetadata loads queue metadata from the consolidated key
func (pq *persistentQueue[T]) loadQueueMetadata(ctx context.Context) error {
buf, err := pq.client.Get(ctx, metadataKey)
if err != nil {
return err
}
if len(buf) == 0 {
return errValueNotSet
}
if err := proto.Unmarshal(buf, &pq.metadata); err != nil {
return err
}
pq.logger.Info("Loaded queue metadata",
zap.Uint64("readIndex", pq.metadata.ReadIndex),
zap.Uint64("writeIndex", pq.metadata.WriteIndex),
zap.Int64("itemsSize", pq.metadata.ItemsSize),
zap.Int64("bytesSize", pq.metadata.BytesSize),
zap.Int("dispatchedItems", len(pq.metadata.CurrentlyDispatchedItems)))
return nil
}
// TODO: Remove legacy format support after 6 months (target: December 2025)
func (pq *persistentQueue[T]) loadLegacyMetadata(ctx context.Context) {
// Fallback to legacy individual keys for backward compatibility
riOp := storage.GetOperation(legacyReadIndexKey)
wiOp := storage.GetOperation(legacyWriteIndexKey)
err := pq.client.Batch(ctx, riOp, wiOp)
if err == nil {
pq.metadata.ReadIndex, err = bytesToItemIndex(riOp.Value)
}
if err == nil {
pq.metadata.WriteIndex, err = bytesToItemIndex(wiOp.Value)
}
if err != nil {
if errors.Is(err, errValueNotSet) {
pq.logger.Info("Initializing new persistent queue")
} else {
pq.logger.Error("Failed getting read/write index, starting with new ones", zap.Error(err))
}
pq.metadata.ReadIndex = 0
pq.metadata.WriteIndex = 0
}
pq.retrieveAndEnqueueNotDispatchedReqs(ctx)
// Save to a new format and clean up legacy keys
metadataBytes, err := proto.Marshal(&pq.metadata)
if err != nil {
pq.logger.Error("Failed to marshal metadata", zap.Error(err))
return
}
if err = pq.client.Set(ctx, metadataKey, metadataBytes); err != nil {
pq.logger.Error("Failed to persist current metadata to storage", zap.Error(err))
return
}
if err = pq.client.Batch(ctx,
storage.DeleteOperation(legacyReadIndexKey),
storage.DeleteOperation(legacyWriteIndexKey),
storage.DeleteOperation(legacyCurrentlyDispatchedItemsKey)); err != nil {
pq.logger.Warn("Failed to cleanup legacy metadata keys", zap.Error(err))
} else {
pq.logger.Info("Successfully migrated to consolidated metadata format")
}
}
func (pq *persistentQueue[T]) Shutdown(ctx context.Context) error {
// If the queue is not initialized, there is nothing to shut down.
if pq.client == nil {
return nil
}
pq.mu.Lock()
defer pq.mu.Unlock()
// Mark this queue as stopped, so consumer don't start any more work.
pq.stopped = true
pq.hasMoreElements.Broadcast()
return pq.unrefClient(ctx)
}
// unrefClient unrefs the client, and closes if no more references. Callers MUST hold the mutex.
// This is needed because consumers of the queue may still process the requests while the queue is shutting down or immediately after.
func (pq *persistentQueue[T]) unrefClient(ctx context.Context) error {
pq.refClient--
if pq.refClient == 0 {
return pq.client.Close(ctx)
}
return nil
}
// Offer inserts the specified element into this queue if it is possible to do so immediately
// without violating capacity restrictions. If success returns no error.
// It returns ErrQueueIsFull if no space is currently available.
func (pq *persistentQueue[T]) Offer(ctx context.Context, req T) error {
pq.mu.Lock()
defer pq.mu.Unlock()
size := pq.activeSizer.Sizeof(req)
for pq.internalSize()+size > pq.capacity {
if !pq.blockOnOverflow {
return ErrQueueIsFull
}
if err := pq.hasMoreSpace.Wait(ctx); err != nil {
return err
}
}
pq.metadata.ItemsSize += pq.itemsSizer.Sizeof(req)
pq.metadata.BytesSize += pq.bytesSizer.Sizeof(req)
return pq.putInternal(ctx, req)
}
// putInternal adds the request to the storage without updating items/bytes sizes.
func (pq *persistentQueue[T]) putInternal(ctx context.Context, req T) error {
pq.metadata.WriteIndex++
metadataBuf, err := proto.Marshal(&pq.metadata)
if err != nil {
return err
}
reqBuf, err := pq.encoding.Marshal(ctx, req)
if err != nil {
return err
}
// Carry out a transaction where we both add the item and update the write index
ops := []*storage.Operation{
storage.SetOperation(metadataKey, metadataBuf),
storage.SetOperation(getItemKey(pq.metadata.WriteIndex-1), reqBuf),
}
if err := pq.client.Batch(ctx, ops...); err != nil {
// At this moment, metadata may be updated in the storage, so we cannot just revert changes to the
// metadata, rely on the sizes being fixed on complete draining.
return err
}
pq.hasMoreElements.Signal()
return nil
}
func (pq *persistentQueue[T]) Read(ctx context.Context) (context.Context, T, Done, bool) {
pq.mu.Lock()
defer pq.mu.Unlock()
for {
if pq.stopped {
var req T
return context.Background(), req, nil, false
}
// Read until either a successful retrieved element or no more elements in the storage.
for pq.metadata.ReadIndex != pq.metadata.WriteIndex {
index, req, reqCtx, consumed := pq.getNextItem(ctx)
// Ensure the used size are in sync when queue is drained.
if pq.requestSize() == 0 {
pq.metadata.BytesSize = 0
pq.metadata.ItemsSize = 0
}
if consumed {
id := indexDonePool.Get().(*indexDone)
id.reset(index, pq.itemsSizer.Sizeof(req), pq.bytesSizer.Sizeof(req), pq)
return reqCtx, req, id, true
}
// More space available, data was dropped.
pq.hasMoreSpace.Signal()
}
// TODO: Need to change the Queue interface to return an error to allow distinguish between shutdown and context canceled.
// Until then use the sync.Cond.
pq.hasMoreElements.Wait()
}
}
// getNextItem pulls the next available item from the persistent storage along with its index. Once processing is
// finished, the index should be called with onDone to clean up the storage. If no new item is available,
// returns false.
func (pq *persistentQueue[T]) getNextItem(ctx context.Context) (uint64, T, context.Context, bool) {
index := pq.metadata.ReadIndex
// Increase here, so even if errors happen below, it always iterates
pq.metadata.ReadIndex++
pq.metadata.CurrentlyDispatchedItems = append(pq.metadata.CurrentlyDispatchedItems, index)
var req T
restoredCtx := context.Background()
metadataBytes, err := proto.Marshal(&pq.metadata)
if err != nil {
return 0, req, restoredCtx, false
}
getOp := storage.GetOperation(getItemKey(index))
err = pq.client.Batch(ctx, storage.SetOperation(metadataKey, metadataBytes), getOp)
if err == nil {
restoredCtx, req, err = pq.encoding.Unmarshal(getOp.Value)
}
if err != nil {
pq.logger.Debug("Failed to dispatch item", zap.Error(err))
// We need to make sure that currently dispatched items list is cleaned
if err = pq.itemDispatchingFinish(ctx, index); err != nil {
pq.logger.Error("Error deleting item from queue", zap.Error(err))
}
return 0, req, restoredCtx, false
}
// Increase the reference count, so the client is not closed while the request is being processed.
// The client cannot be closed because we hold the lock since last we checked `stopped`.
pq.refClient++
return index, req, restoredCtx, true
}
// onDone should be called to remove the item of the given index from the queue once processing is finished.
func (pq *persistentQueue[T]) onDone(index uint64, itemsSize, bytesSize int64, consumeErr error) {
// Delete the item from the persistent storage after it was processed.
pq.mu.Lock()
// Always unref client even if the consumer is shutdown because we always ref it for every valid request.
defer func() {
if err := pq.unrefClient(context.Background()); err != nil {
pq.logger.Error("Error closing the storage client", zap.Error(err))
}
pq.mu.Unlock()
}()
if experr.IsShutdownErr(consumeErr) {
// The queue is shutting down, don't mark the item as dispatched, so it's picked up again after restart.
// TODO: Handle partially delivered requests by updating their values in the storage.
return
}
pq.metadata.BytesSize -= bytesSize
if pq.metadata.BytesSize < 0 {
pq.metadata.BytesSize = 0
}
pq.metadata.ItemsSize -= itemsSize
if pq.metadata.ItemsSize < 0 {
pq.metadata.ItemsSize = 0
}
if err := pq.itemDispatchingFinish(context.Background(), index); err != nil {
pq.logger.Error("Error deleting item from queue", zap.Error(err))
}
// More space available after data are removed from the storage.
pq.hasMoreSpace.Signal()
}
// retrieveAndEnqueueNotDispatchedReqs gets the items for which sending was not finished, cleans the storage
// and moves the items at the back of the queue.
func (pq *persistentQueue[T]) retrieveAndEnqueueNotDispatchedReqs(ctx context.Context) {
var dispatchedItems []uint64
pq.mu.Lock()
defer pq.mu.Unlock()
pq.logger.Debug("Checking if there are items left for dispatch by consumers")
itemKeysBuf, err := pq.client.Get(ctx, legacyCurrentlyDispatchedItemsKey)
if err == nil {
dispatchedItems, err = bytesToItemIndexArray(itemKeysBuf)
}
if err != nil {
pq.logger.Error("Could not fetch items left for dispatch by consumers", zap.Error(err))
return
}
pq.enqueueNotDispatchedReqs(ctx, dispatchedItems)
}
func (pq *persistentQueue[T]) enqueueNotDispatchedReqs(ctx context.Context, dispatchedItems []uint64) {
if len(dispatchedItems) == 0 {
pq.logger.Debug("No items left for dispatch by consumers")
return
}
pq.logger.Info("Fetching items left for dispatch by consumers", zap.Int(zapNumberOfItems,
len(dispatchedItems)))
retrieveBatch := make([]*storage.Operation, len(dispatchedItems))
cleanupBatch := make([]*storage.Operation, len(dispatchedItems))
for i, it := range dispatchedItems {
key := getItemKey(it)
retrieveBatch[i] = storage.GetOperation(key)
cleanupBatch[i] = storage.DeleteOperation(key)
}
retrieveErr := pq.client.Batch(ctx, retrieveBatch...)
cleanupErr := pq.client.Batch(ctx, cleanupBatch...)
if cleanupErr != nil {
pq.logger.Debug("Failed cleaning items left by consumers", zap.Error(cleanupErr))
}
if retrieveErr != nil {
pq.logger.Warn("Failed retrieving items left by consumers", zap.Error(retrieveErr))
return
}
errCount := 0
for _, op := range retrieveBatch {
if op.Value == nil {
pq.logger.Warn("Failed retrieving item", zap.String(zapKey, op.Key), zap.Error(errValueNotSet))
continue
}
reqCtx, req, err := pq.encoding.Unmarshal(op.Value)
// If error happened or item is nil, it will be efficiently ignored
if err != nil {
pq.logger.Warn("Failed unmarshalling item", zap.String(zapKey, op.Key), zap.Error(err))
continue
}
if pq.putInternal(reqCtx, req) != nil { //nolint:contextcheck
errCount++
}
}
if errCount > 0 {
pq.logger.Error("Errors occurred while moving items for dispatching back to queue",
zap.Int(zapNumberOfItems, len(retrieveBatch)), zap.Int(zapErrorCount, errCount))
} else {
pq.logger.Info("Moved items for dispatching back to queue",
zap.Int(zapNumberOfItems, len(retrieveBatch)))
}
}
// itemDispatchingFinish removes the item from the list of currently dispatched items and deletes it from the persistent queue
func (pq *persistentQueue[T]) itemDispatchingFinish(ctx context.Context, index uint64) error {
lenCDI := len(pq.metadata.CurrentlyDispatchedItems)
for i := range lenCDI {
if pq.metadata.CurrentlyDispatchedItems[i] == index {
pq.metadata.CurrentlyDispatchedItems[i] = pq.metadata.CurrentlyDispatchedItems[lenCDI-1]
pq.metadata.CurrentlyDispatchedItems = pq.metadata.CurrentlyDispatchedItems[:lenCDI-1]
break
}
}
// Ensure the used size are in sync when queue is drained.
if pq.requestSize() == 0 {
pq.metadata.BytesSize = 0
pq.metadata.ItemsSize = 0
}
metadataBytes, err := proto.Marshal(&pq.metadata)
if err != nil {
return err
}
setOp := storage.SetOperation(metadataKey, metadataBytes)
deleteOp := storage.DeleteOperation(getItemKey(index))
err = pq.client.Batch(ctx, setOp, deleteOp)
if err == nil {
// Everything ok, exit
return nil
}
// got an error, try to gracefully handle it
pq.logger.Warn("Failed updating currently dispatched items, trying to delete the item first",
zap.Error(err))
if err = pq.client.Batch(ctx, deleteOp); err != nil {
// Return an error here, as this indicates an issue with the underlying storage medium
return fmt.Errorf("failed deleting item from queue, got error from storage: %w", err)
}
if err = pq.client.Batch(ctx, setOp); err != nil {
// even if this fails, we still have the right dispatched items in memory
// at worst, we'll have the wrong list in storage, and we'll discard the nonexistent items during startup
return fmt.Errorf("failed updating currently dispatched items, but deleted item successfully: %w", err)
}
return nil
}
func toStorageClient(ctx context.Context, storageID component.ID, host component.Host, ownerID component.ID, signal pipeline.Signal) (storage.Client, error) {
ext, found := host.GetExtensions()[storageID]
if !found {
return nil, errNoStorageClient
}
storageExt, ok := ext.(storage.Extension)
if !ok {
return nil, errWrongExtensionType
}
return storageExt.GetClient(ctx, component.KindExporter, ownerID, signal.String())
}
func getItemKey(index uint64) string {
return strconv.FormatUint(index, 10)
}
func bytesToItemIndex(buf []byte) (uint64, error) {
if buf == nil {
return uint64(0), errValueNotSet
}
// The sizeof uint64 in binary is 8.
if len(buf) < 8 {
return 0, errInvalidValue
}
return binary.LittleEndian.Uint64(buf), nil
}
func bytesToItemIndexArray(buf []byte) ([]uint64, error) {
if len(buf) == 0 {
return nil, nil
}
// The sizeof uint32 in binary is 4.
if len(buf) < 4 {
return nil, errInvalidValue
}
size := int(binary.LittleEndian.Uint32(buf))
if size == 0 {
return nil, nil
}
buf = buf[4:]
// The sizeof uint64 in binary is 8, so we need to have size*8 bytes.
if len(buf) < size*8 {
return nil, errInvalidValue
}
val := make([]uint64, size)
for i := range size {
val[i] = binary.LittleEndian.Uint64(buf)
buf = buf[8:]
}
return val, nil
}
type indexDone struct {
index uint64
itemsSize int64
bytesSize int64
queue interface {
onDone(uint64, int64, int64, error)
}
}
func (id *indexDone) reset(index uint64, itemsSize, bytesSize int64, queue interface {
onDone(uint64, int64, int64, error)
},
) {
id.index = index
id.itemsSize = itemsSize
id.bytesSize = bytesSize
id.queue = queue
}
func (id *indexDone) OnDone(err error) {
id.queue.onDone(id.index, id.itemsSize, id.bytesSize, err)
}
================================================
FILE: exporter/exporterhelper/internal/queue/persistent_queue_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queue
import (
"context"
"encoding/binary"
"errors"
"fmt"
"strconv"
"sync"
"sync/atomic"
"syscall"
"testing"
"time"
"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/exporter/exporterhelper/internal/experr"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/hosttest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/storagetest"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/extension/extensiontest"
"go.opentelemetry.io/collector/extension/xextension/storage"
"go.opentelemetry.io/collector/pipeline"
)
type intRequest int64
func (i intRequest) MergeSplit(context.Context, int, request.SizerType, request.Request) ([]request.Request, error) {
panic("implement me")
}
func (i intRequest) ItemsCount() int {
return int(i)
}
func (i intRequest) BytesSize() int {
return int(i) * 10
}
type int64Encoding struct {
rc ReferenceCounter[intRequest]
}
func (int64Encoding) Marshal(_ context.Context, val intRequest) ([]byte, error) {
str := strconv.FormatInt(int64(val), 10)
return []byte(str), nil
}
func (ie int64Encoding) Unmarshal(bytes []byte) (context.Context, intRequest, error) {
val, err := strconv.ParseInt(string(bytes), 10, 64)
if err != nil {
return context.Background(), 0, err
}
ie.rc.Ref(intRequest(val))
return context.Background(), intRequest(val), nil
}
func newFakeBoundedStorageClient(maxSizeInBytes int) *fakeBoundedStorageClient {
return &fakeBoundedStorageClient{
st: map[string][]byte{},
maxSizeInBytes: maxSizeInBytes,
}
}
// fakeBoundedStorageClient storage client mimics the behavior of actual storage engines with limited
// storage space available in general, real storage engines often have a per-write-transaction
// storage overhead, needing to keep both the old and the new value stored until the transaction
// is committed this is useful for testing the persistent queue behavior with a full disk.
type fakeBoundedStorageClient struct {
maxSizeInBytes int
st map[string][]byte
sizeInBytes int
mux sync.Mutex
}
func (m *fakeBoundedStorageClient) Get(ctx context.Context, key string) ([]byte, error) {
op := storage.GetOperation(key)
if err := m.Batch(ctx, op); err != nil {
return nil, err
}
return op.Value, nil
}
func (m *fakeBoundedStorageClient) Set(ctx context.Context, key string, value []byte) error {
return m.Batch(ctx, storage.SetOperation(key, value))
}
func (m *fakeBoundedStorageClient) Delete(ctx context.Context, key string) error {
return m.Batch(ctx, storage.DeleteOperation(key))
}
func (m *fakeBoundedStorageClient) Close(context.Context) error {
return nil
}
func (m *fakeBoundedStorageClient) Batch(_ context.Context, ops ...*storage.Operation) error {
m.mux.Lock()
defer m.mux.Unlock()
totalAdded, totalRemoved := m.getTotalSizeChange(ops)
// the assumption here is that the new data needs to coexist with the old data on disk
// for the transaction to succeed
// this seems to be true for the file storage extension at least
if m.sizeInBytes+totalAdded-totalRemoved > m.maxSizeInBytes {
return fmt.Errorf("insufficient space available: %w", syscall.ENOSPC)
}
for _, op := range ops {
switch op.Type {
case storage.Get:
op.Value = m.st[op.Key]
case storage.Set:
m.st[op.Key] = op.Value
case storage.Delete:
delete(m.st, op.Key)
default:
return errors.New("wrong operation type")
}
}
m.sizeInBytes += totalAdded - totalRemoved
return nil
}
func (m *fakeBoundedStorageClient) SetMaxSizeInBytes(newMaxSize int) {
m.mux.Lock()
defer m.mux.Unlock()
m.maxSizeInBytes = newMaxSize
}
func (m *fakeBoundedStorageClient) GetSizeInBytes() int {
m.mux.Lock()
defer m.mux.Unlock()
return m.sizeInBytes
}
func (m *fakeBoundedStorageClient) getTotalSizeChange(ops []*storage.Operation) (totalAdded, totalRemoved int) {
totalAdded, totalRemoved = 0, 0
for _, op := range ops {
switch op.Type {
case storage.Set:
if oldValue, ok := m.st[op.Key]; ok {
totalRemoved += len(oldValue)
} else {
totalAdded += len(op.Key)
}
totalAdded += len(op.Value)
case storage.Delete:
if value, ok := m.st[op.Key]; ok {
totalRemoved += len(op.Key)
totalRemoved += len(value)
}
default:
}
}
return totalAdded, totalRemoved
}
func newFakeStorageClientWithErrors(errors []error) *fakeStorageClientWithErrors {
return &fakeStorageClientWithErrors{
errors: errors,
}
}
// this storage client just returns errors from a list in order
// used for testing error handling
type fakeStorageClientWithErrors struct {
errors []error
nextErrorIndex int
mux sync.Mutex
}
func (m *fakeStorageClientWithErrors) Get(ctx context.Context, key string) ([]byte, error) {
op := storage.GetOperation(key)
err := m.Batch(ctx, op)
if err != nil {
return nil, err
}
return op.Value, nil
}
func (m *fakeStorageClientWithErrors) Set(ctx context.Context, key string, value []byte) error {
return m.Batch(ctx, storage.SetOperation(key, value))
}
func (m *fakeStorageClientWithErrors) Delete(ctx context.Context, key string) error {
return m.Batch(ctx, storage.DeleteOperation(key))
}
func (m *fakeStorageClientWithErrors) Close(context.Context) error {
return nil
}
func (m *fakeStorageClientWithErrors) Batch(context.Context, ...*storage.Operation) error {
m.mux.Lock()
defer m.mux.Unlock()
if m.nextErrorIndex >= len(m.errors) {
return nil
}
m.nextErrorIndex++
return m.errors[m.nextErrorIndex-1]
}
func (m *fakeStorageClientWithErrors) Reset() {
m.mux.Lock()
defer m.mux.Unlock()
m.nextErrorIndex = 0
}
type fakeReferenceCounter struct {
mu sync.Mutex
ref int64
}
func (f *fakeReferenceCounter) Ref(intRequest) {
f.mu.Lock()
defer f.mu.Unlock()
f.ref++
}
func (f *fakeReferenceCounter) Unref(intRequest) {
f.mu.Lock()
defer f.mu.Unlock()
f.ref--
if f.ref < 0 {
panic("this should never happen")
}
}
func newSettings(sizerType request.SizerType, capacity int64) Settings[intRequest] {
rc := &fakeReferenceCounter{}
return Settings[intRequest]{
ReferenceCounter: rc,
SizerType: sizerType,
Capacity: capacity,
Signal: pipeline.SignalTraces,
Encoding: int64Encoding{rc},
ID: component.NewID(exportertest.NopType),
Telemetry: componenttest.NewNopTelemetrySettings(),
}
}
func newSettingsWithStorage(sizerType request.SizerType, capacity int64) Settings[intRequest] {
set := newSettings(sizerType, capacity)
storageID := component.ID{}
set.StorageID = &storageID
return set
}
func createTestPersistentQueueWithClient(client storage.Client) *persistentQueue[intRequest] {
pq := newPersistentQueue[intRequest](newSettingsWithStorage(request.SizerTypeRequests, 1000)).(*persistentQueue[intRequest])
pq.initClient(context.Background(), client)
return pq
}
func createTestPersistentQueueWithRequestsSizer(tb testing.TB, ext storage.Extension, capacity int64) *persistentQueue[intRequest] {
return createTestPersistentQueue(tb, ext, request.SizerTypeRequests, capacity)
}
func createTestPersistentQueueWithItemsSizer(tb testing.TB, ext storage.Extension, capacity int64) *persistentQueue[intRequest] {
return createTestPersistentQueue(tb, ext, request.SizerTypeItems, capacity)
}
func createTestPersistentQueue(tb testing.TB, ext storage.Extension, sizerType request.SizerType, capacity int64) *persistentQueue[intRequest] {
pq := newPersistentQueue[intRequest](newSettingsWithStorage(sizerType, capacity))
require.NoError(tb, pq.Start(context.Background(), hosttest.NewHost(map[component.ID]component.Component{{}: ext})))
return pq.(*persistentQueue[intRequest])
}
func TestPersistentQueue_FullCapacity(t *testing.T) {
tests := []struct {
name string
sizerType request.SizerType
capacity int64
sizeMultiplier int64
}{
{
name: "requests_capacity",
sizerType: request.SizerTypeRequests,
capacity: 5,
sizeMultiplier: 1,
},
{
name: "items_capacity",
sizerType: request.SizerTypeItems,
capacity: 55,
sizeMultiplier: 10,
},
{
name: "bytes_capacity",
sizerType: request.SizerTypeBytes,
capacity: 550,
sizeMultiplier: 100,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ext := storagetest.NewMockStorageExtension(nil)
pq := createTestPersistentQueue(t, ext, tt.sizerType, tt.capacity)
assert.Equal(t, int64(0), pq.Size())
// The consumer picks first request. Wait until the consumer is blocked on done.
require.NoError(t, pq.Offer(context.Background(), intRequest(10)))
assert.Equal(t, 1*tt.sizeMultiplier, pq.Size())
_, _, done, ok := pq.Read(context.Background())
assert.True(t, ok)
done.OnDone(nil)
assert.Equal(t, int64(0), pq.Size())
for i := range 10 {
result := pq.Offer(context.Background(), intRequest(10))
if i < 5 {
require.NoError(t, result)
} else {
require.ErrorIs(t, result, ErrQueueIsFull)
}
}
assert.Equal(t, 5*tt.sizeMultiplier, pq.Size())
require.NoError(t, pq.Shutdown(context.Background()))
})
}
}
func TestPersistentQueue_Shutdown(t *testing.T) {
ext := storagetest.NewMockStorageExtension(nil)
pq := createTestPersistentQueue(t, ext, request.SizerTypeRequests, 1001)
req := intRequest(10)
for range 1000 {
require.NoError(t, pq.Offer(context.Background(), req))
}
require.NoError(t, pq.Shutdown(context.Background()))
}
func TestPersistentQueue_ConsumersProducers(t *testing.T) {
cases := []struct {
numMessagesProduced int
numConsumers int
}{
{
numMessagesProduced: 1,
numConsumers: 1,
},
{
numMessagesProduced: 100,
numConsumers: 1,
},
{
numMessagesProduced: 100,
numConsumers: 3,
},
{
numMessagesProduced: 1,
numConsumers: 100,
},
{
numMessagesProduced: 100,
numConsumers: 100,
},
}
for _, c := range cases {
t.Run(fmt.Sprintf("#messages: %d #consumers: %d", c.numMessagesProduced, c.numConsumers), func(t *testing.T) {
consumed := &atomic.Int64{}
pq := newPersistentQueue[intRequest](newSettingsWithStorage(request.SizerTypeRequests, 1000))
aq := newAsyncQueue[intRequest](pq, c.numConsumers, func(_ context.Context, _ intRequest, done Done) {
consumed.Add(int64(1))
done.OnDone(nil)
}, nil)
require.NoError(t, aq.Start(context.Background(), hosttest.NewHost(map[component.ID]component.Component{
{}: storagetest.NewMockStorageExtension(nil),
},
)))
for i := 0; i < c.numMessagesProduced; i++ {
require.NoError(t, aq.Offer(context.Background(), intRequest(10)))
}
// Because the persistent queue is not draining after Shutdown, need to wait here for the drain.
assert.Eventually(t, func() bool {
return c.numMessagesProduced == int(consumed.Load())
}, 5*time.Second, 10*time.Millisecond)
require.NoError(t, aq.Shutdown(context.Background()))
})
}
}
func TestPersistentBlockingQueue(t *testing.T) {
tests := []struct {
name string
sizerType request.SizerType
}{
{
name: "requests_based",
sizerType: request.SizerTypeRequests,
},
{
name: "items_based",
sizerType: request.SizerTypeItems,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
set := newSettingsWithStorage(tt.sizerType, 1000)
set.BlockOnOverflow = true
pq := newPersistentQueue[intRequest](set)
consumed := &atomic.Int64{}
ac := newAsyncQueue(pq, 10, func(_ context.Context, _ intRequest, done Done) {
consumed.Add(1)
done.OnDone(nil)
}, set.ReferenceCounter)
require.NoError(t, ac.Start(context.Background(), hosttest.NewHost(map[component.ID]component.Component{
{}: storagetest.NewMockStorageExtension(nil),
})))
td := intRequest(10)
wg := &sync.WaitGroup{}
for range 10 {
wg.Go(func() {
for range 100_000 {
assert.NoError(t, pq.Offer(context.Background(), td))
}
})
}
wg.Wait()
// Because the persistent queue is not draining after Shutdown, need to wait here for the drain.
assert.Eventually(t, func() bool {
return int(consumed.Load()) == 1_000_000
}, 5*time.Second, 10*time.Millisecond)
require.NoError(t, ac.Shutdown(context.Background()))
})
}
}
func TestToStorageClient(t *testing.T) {
getStorageClientError := errors.New("unable to create storage client")
testCases := []struct {
name string
storage storage.Extension
numStorages int
storageIndex int
expectedError error
getClientError error
}{
{
name: "obtain storage extension by name",
numStorages: 2,
storageIndex: 0,
expectedError: nil,
},
{
name: "fail on not existing storage extension",
numStorages: 2,
storageIndex: 100,
expectedError: errNoStorageClient,
},
{
name: "invalid extension type",
numStorages: 2,
storageIndex: 100,
expectedError: errNoStorageClient,
},
{
name: "fail on error getting storage client from extension",
numStorages: 1,
storageIndex: 0,
expectedError: getStorageClientError,
getClientError: getStorageClientError,
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
storageID := component.MustNewIDWithName("file_storage", strconv.Itoa(tt.storageIndex))
extensions := map[component.ID]component.Component{}
for i := 0; i < tt.numStorages; i++ {
extensions[component.MustNewIDWithName("file_storage", strconv.Itoa(i))] = storagetest.NewMockStorageExtension(tt.getClientError)
}
host := hosttest.NewHost(extensions)
ownerID := component.MustNewID("foo_exporter")
// execute
client, err := toStorageClient(context.Background(), storageID, host, ownerID, pipeline.SignalTraces)
// verify
if tt.expectedError != nil {
require.ErrorIs(t, err, tt.expectedError)
assert.Nil(t, client)
} else {
require.NoError(t, err)
assert.NotNil(t, client)
}
})
}
}
func TestInvalidStorageExtensionType(t *testing.T) {
storageID := component.MustNewIDWithName("extension", "extension")
// make a test extension
factory := extensiontest.NewNopFactory()
extConfig := factory.CreateDefaultConfig()
settings := extensiontest.NewNopSettings(factory.Type())
extension, err := factory.Create(context.Background(), settings, extConfig)
require.NoError(t, err)
extensions := map[component.ID]component.Component{
storageID: extension,
}
host := hosttest.NewHost(extensions)
ownerID := component.MustNewID("foo_exporter")
// execute
client, err := toStorageClient(context.Background(), storageID, host, ownerID, pipeline.SignalTraces)
// we should get an error about the extension type
require.ErrorIs(t, err, errWrongExtensionType)
assert.Nil(t, client)
}
func TestPersistentQueue_StopAfterBadStart(t *testing.T) {
storageID := component.ID{}
pq := newPersistentQueue[intRequest](Settings[intRequest]{StorageID: &storageID})
// verify that stopping a un-start/started w/error queue does not panic
assert.NoError(t, pq.Shutdown(context.Background()))
}
func TestPersistentQueue_CorruptedData(t *testing.T) {
cases := []struct {
name string
corruptAllData bool
corruptSomeData bool
corruptMetadataKey bool
desiredQueueSize int64
}{
{
name: "corrupted no items",
desiredQueueSize: 3,
},
{
name: "corrupted all items",
corruptAllData: true,
desiredQueueSize: 2, // - the dispatched item which was corrupted.
},
{
name: "corrupted some items",
corruptSomeData: true,
desiredQueueSize: 2, // - the dispatched item which was corrupted.
},
{
name: "corrupted metadata",
corruptMetadataKey: true,
desiredQueueSize: 0,
},
{
name: "corrupted everything",
corruptAllData: true,
corruptMetadataKey: true,
desiredQueueSize: 0,
},
}
badBytes := []byte{0, 1, 2}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
ext := storagetest.NewMockStorageExtension(nil)
ps := createTestPersistentQueueWithRequestsSizer(t, ext, 1000)
// Put some items, make sure they are loaded and shutdown the storage...
for range 3 {
require.NoError(t, ps.Offer(context.Background(), intRequest(50)))
}
assert.Equal(t, int64(3), ps.Size())
require.True(t, consume(ps, func(context.Context, intRequest) error {
return experr.NewShutdownErr(nil)
}))
assert.Equal(t, int64(3), ps.Size())
// We can corrupt data (in several ways) and not worry since we return ShutdownErr client will not be touched.
if c.corruptAllData || c.corruptSomeData {
require.NoError(t, ps.client.Set(context.Background(), "0", badBytes))
}
if c.corruptAllData {
require.NoError(t, ps.client.Set(context.Background(), "1", badBytes))
require.NoError(t, ps.client.Set(context.Background(), "2", badBytes))
}
if c.corruptMetadataKey {
require.NoError(t, ps.client.Set(context.Background(), metadataKey, badBytes))
}
// Cannot close until we corrupt the data because the
require.NoError(t, ps.Shutdown(context.Background()))
// Reload
newPs := createTestPersistentQueueWithRequestsSizer(t, ext, 1000)
assert.Equal(t, c.desiredQueueSize, newPs.Size())
require.NoError(t, newPs.Shutdown(context.Background()))
})
}
}
func TestPersistentQueue_CurrentlyProcessedItems(t *testing.T) {
req := intRequest(50)
ext := storagetest.NewMockStorageExtension(nil)
ps := createTestPersistentQueueWithRequestsSizer(t, ext, 1000)
for range 5 {
require.NoError(t, ps.Offer(context.Background(), req))
}
requireCurrentlyDispatchedItemsEqual(t, ps, []uint64{})
// Takes index 0 in process.
_, readReq, _, found := ps.Read(context.Background())
require.True(t, found)
assert.Equal(t, req, readReq)
requireCurrentlyDispatchedItemsEqual(t, ps, []uint64{0})
// This takes item 1 to process.
_, secondReadReq, secondDone, found := ps.Read(context.Background())
require.True(t, found)
assert.Equal(t, req, secondReadReq)
requireCurrentlyDispatchedItemsEqual(t, ps, []uint64{0, 1})
// Lets mark item 1 as finished, it will remove it from the currently dispatched items list.
secondDone.OnDone(nil)
requireCurrentlyDispatchedItemsEqual(t, ps, []uint64{0})
// Reload the storage. Since items 0 was not finished, this should be re-enqueued at the end.
// The queue should be essentially {3,4,0,2}.
newPs := createTestPersistentQueueWithRequestsSizer(t, ext, 1000)
assert.Equal(t, int64(4), newPs.Size())
requireCurrentlyDispatchedItemsEqual(t, newPs, []uint64{})
// We should be able to pull all remaining items now
for range 4 {
consume(newPs, func(_ context.Context, val intRequest) error {
assert.Equal(t, req, val)
return nil
})
}
// The queue should be now empty
requireCurrentlyDispatchedItemsEqual(t, newPs, []uint64{})
assert.Equal(t, int64(0), newPs.Size())
// The writeIndex should be now set accordingly
require.EqualValues(t, 6, newPs.metadata.WriteIndex)
// There should be no items left in the storage
for i := uint64(0); i < newPs.metadata.WriteIndex; i++ {
bb, err := newPs.client.Get(context.Background(), getItemKey(i))
require.NoError(t, err)
require.Nil(t, bb)
}
}
// this test attempts to check if all the invariants are kept if the queue is recreated while
// close to full and with some items dispatched
func TestPersistentQueueStartWithNonDispatched(t *testing.T) {
req := intRequest(50)
ext := storagetest.NewMockStorageExtension(nil)
ps := createTestPersistentQueueWithRequestsSizer(t, ext, 5)
// Put in items up to capacity
for range 5 {
require.NoError(t, ps.Offer(context.Background(), req))
}
require.Equal(t, int64(5), ps.Size())
require.True(t, consume(ps, func(context.Context, intRequest) error {
// Check that size is still full even when consuming the element.
require.Equal(t, int64(5), ps.Size())
return experr.NewShutdownErr(nil)
}))
require.NoError(t, ps.Shutdown(context.Background()))
// Reload with extra capacity to make sure we re-enqueue in-progress items.
newPs := createTestPersistentQueueWithRequestsSizer(t, ext, 5)
require.Equal(t, int64(5), newPs.Size())
}
func TestPersistentQueueStartWithNonDispatchedConcurrent(t *testing.T) {
req := intRequest(1)
ext := storagetest.NewMockStorageExtensionWithDelay(nil, 20*time.Nanosecond)
pq := createTestPersistentQueueWithItemsSizer(t, ext, 25)
proWg := sync.WaitGroup{}
// Sending small amount of data as windows test can't handle the test fast enough
for range 5 {
proWg.Go(func() {
// Put in items up to capacity
for range 10 {
for {
// retry infinitely so the exact amount of items are added to the queue eventually
if err := pq.Offer(context.Background(), req); err == nil {
break
}
time.Sleep(50 * time.Nanosecond)
}
}
})
}
conWg := sync.WaitGroup{}
for range 5 {
conWg.Go(func() {
for range 10 {
assert.True(t, consume(pq, func(context.Context, intRequest) error { return nil }))
}
})
}
conDone := make(chan struct{})
go func() {
defer close(conDone)
conWg.Wait()
}()
proDone := make(chan struct{})
go func() {
defer close(proDone)
proWg.Wait()
}()
doneCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
select {
case <-conDone:
case <-doneCtx.Done():
assert.Fail(t, "timed out waiting for consumers to complete")
}
select {
case <-proDone:
case <-doneCtx.Done():
assert.Fail(t, "timed out waiting for producers to complete")
}
assert.Zero(t, pq.Size())
}
func TestPersistentQueue_PutCloseReadClose(t *testing.T) {
req := intRequest(50)
ext := storagetest.NewMockStorageExtension(nil)
ps := createTestPersistentQueueWithRequestsSizer(t, ext, 1000)
assert.Equal(t, int64(0), ps.Size())
// Put two elements and close the extension
require.NoError(t, ps.Offer(context.Background(), req))
require.NoError(t, ps.Offer(context.Background(), req))
assert.Equal(t, int64(2), ps.Size())
// TODO: Remove this, after the initialization writes the readIndex.
_, _, _, _ = ps.Read(context.Background())
require.NoError(t, ps.Shutdown(context.Background()))
newPs := createTestPersistentQueueWithRequestsSizer(t, ext, 1000)
require.Equal(t, int64(2), newPs.Size())
// Let's read both of the elements we put
consume(newPs, func(_ context.Context, val intRequest) error {
require.Equal(t, req, val)
return nil
})
assert.Equal(t, int64(1), newPs.Size())
consume(newPs, func(_ context.Context, val intRequest) error {
require.Equal(t, req, val)
return nil
})
require.Equal(t, int64(0), newPs.Size())
require.NoError(t, newPs.Shutdown(context.Background()))
}
func BenchmarkPersistentQueue(b *testing.B) {
ext := storagetest.NewMockStorageExtension(nil)
ps := createTestPersistentQueueWithRequestsSizer(b, ext, 10000000)
req := intRequest(100)
b.ReportAllocs()
for b.Loop() {
for range 100 {
require.NoError(b, ps.Offer(context.Background(), req))
}
for range 100 {
require.True(b, consume(ps, func(context.Context, intRequest) error { return nil }))
}
}
require.NoError(b, ext.Shutdown(context.Background()))
}
func TestItemIndexMarshaling(t *testing.T) {
cases := []struct {
in uint64
out uint64
}{
{
in: 0,
out: 0,
},
{
in: 1,
out: 1,
},
{
in: 0xFFFFFFFFFFFFFFFF,
out: 0xFFFFFFFFFFFFFFFF,
},
}
for _, c := range cases {
t.Run(fmt.Sprintf("#elements:%v", c.in), func(*testing.T) {
buf := binary.LittleEndian.AppendUint64([]byte{}, c.in)
out, err := bytesToItemIndex(buf)
require.NoError(t, err)
require.Equal(t, c.out, out)
})
}
}
func TestItemIndexArrayMarshaling(t *testing.T) {
cases := []struct {
in []uint64
out []uint64
}{
{
in: []uint64{0, 1, 2},
out: []uint64{0, 1, 2},
},
{
in: []uint64{},
out: nil,
},
{
in: nil,
out: nil,
},
}
for _, c := range cases {
t.Run(fmt.Sprintf("#elements:%v", c.in), func(_ *testing.T) {
buf := itemIndexArrayToBytes(c.in)
out, err := bytesToItemIndexArray(buf)
require.NoError(t, err)
require.Equal(t, c.out, out)
})
}
}
func TestPersistentQueue_ShutdownWhileConsuming(t *testing.T) {
ps := createTestPersistentQueueWithRequestsSizer(t, storagetest.NewMockStorageExtension(nil), 1000)
assert.Equal(t, int64(0), ps.Size())
assert.False(t, ps.client.(*storagetest.MockStorageClient).IsClosed())
require.NoError(t, ps.Offer(context.Background(), intRequest(50)))
_, _, done, ok := ps.Read(context.Background())
require.True(t, ok)
assert.False(t, ps.client.(*storagetest.MockStorageClient).IsClosed())
require.NoError(t, ps.Shutdown(context.Background()))
assert.False(t, ps.client.(*storagetest.MockStorageClient).IsClosed())
done.OnDone(nil)
assert.True(t, ps.client.(*storagetest.MockStorageClient).IsClosed())
}
func TestPersistentQueue_StorageFull(t *testing.T) {
marshaled, err := int64Encoding{}.Marshal(context.Background(), intRequest(50))
require.NoError(t, err)
maxSizeInBytes := len(marshaled)*5 + 60 // arbitrary small number
client := newFakeBoundedStorageClient(maxSizeInBytes)
ps := createTestPersistentQueueWithClient(client)
// Put enough items in to fill the underlying storage
reqCount := 0
for {
reqCount++
err = ps.Offer(context.Background(), intRequest(50))
if errors.Is(err, syscall.ENOSPC) {
break
}
require.NoError(t, err)
}
// Check that the size is correct
require.EqualValues(t, reqCount, ps.Size(), "Size must be equal to the number of items inserted")
// Manually set the storage to support writing the dispatch value.
client.SetMaxSizeInBytes(client.GetSizeInBytes() + 19)
// Take out all the items except last. Last one is there only in metadata because the data write failed.
for i := 0; i < reqCount-1; i++ {
require.True(t, consume(ps, func(_ context.Context, val intRequest) error {
require.Equal(t, intRequest(50), val)
return nil
}))
}
require.Equal(t, int64(1), ps.Size())
// Add one more element, and then read (drain) so metadata will be fixed.
require.NoError(t, ps.Offer(context.Background(), intRequest(50)))
require.True(t, consume(ps, func(_ context.Context, val intRequest) error {
require.Equal(t, intRequest(50), val)
return nil
}))
require.Equal(t, int64(0), ps.Size())
}
func TestPersistentQueue_ItemDispatchingFinish_ErrorHandling(t *testing.T) {
errDeletingItem := errors.New("error deleting item")
errUpdatingDispatched := errors.New("error updating dispatched items")
testCases := []struct {
storageErrors []error
expectedError error
name string
}{
{
name: "no errors",
storageErrors: []error{},
expectedError: nil,
},
{
name: "error on first transaction, success afterwards",
storageErrors: []error{
errUpdatingDispatched,
},
expectedError: nil,
},
{
name: "error on first and second transaction",
storageErrors: []error{
errUpdatingDispatched,
errDeletingItem,
},
expectedError: errDeletingItem,
},
{
name: "error on first and third transaction",
storageErrors: []error{
errUpdatingDispatched,
nil,
errUpdatingDispatched,
},
expectedError: errUpdatingDispatched,
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
client := newFakeStorageClientWithErrors(tt.storageErrors)
ps := createTestPersistentQueueWithClient(client)
client.Reset()
require.ErrorIs(t, ps.itemDispatchingFinish(context.Background(), 0), tt.expectedError)
})
}
}
func TestPersistentQueue_ItemsCapacityUsageRestoredOnShutdown(t *testing.T) {
ext := storagetest.NewMockStorageExtension(nil)
pq := createTestPersistentQueueWithItemsSizer(t, ext, 100)
assert.Equal(t, int64(0), pq.Size())
// Fill the queue up to the capacity.
require.NoError(t, pq.Offer(context.Background(), intRequest(40)))
require.NoError(t, pq.Offer(context.Background(), intRequest(40)))
require.NoError(t, pq.Offer(context.Background(), intRequest(20)))
assert.Equal(t, int64(100), pq.Size())
require.ErrorIs(t, pq.Offer(context.Background(), intRequest(25)), ErrQueueIsFull)
assert.Equal(t, int64(100), pq.Size())
assert.True(t, consume(pq, func(_ context.Context, val intRequest) error {
assert.Equal(t, intRequest(40), val)
return nil
}))
assert.Equal(t, int64(60), pq.Size())
require.NoError(t, pq.Shutdown(context.Background()))
newPQ := createTestPersistentQueueWithItemsSizer(t, ext, 100)
// The queue should be restored to the previous size.
assert.Equal(t, int64(60), newPQ.Size())
require.NoError(t, newPQ.Offer(context.Background(), intRequest(10)))
// Check the combined queue size.
assert.Equal(t, int64(70), newPQ.Size())
assert.True(t, consume(newPQ, func(_ context.Context, val intRequest) error {
assert.Equal(t, intRequest(40), val)
return nil
}))
assert.Equal(t, int64(30), newPQ.Size())
assert.True(t, consume(newPQ, func(_ context.Context, val intRequest) error {
assert.Equal(t, intRequest(20), val)
return nil
}))
assert.Equal(t, int64(10), newPQ.Size())
require.NoError(t, newPQ.Shutdown(context.Background()))
}
func TestPersistentQueue_ItemsCapacityIsAlwyasRecorder(t *testing.T) {
ext := storagetest.NewMockStorageExtension(nil)
pq := createTestPersistentQueueWithRequestsSizer(t, ext, 100)
assert.Equal(t, int64(0), pq.Size())
require.NoError(t, pq.Offer(context.Background(), intRequest(40)))
require.NoError(t, pq.Offer(context.Background(), intRequest(20)))
require.NoError(t, pq.Offer(context.Background(), intRequest(25)))
assert.Equal(t, int64(3), pq.Size())
assert.True(t, consume(pq, func(_ context.Context, val intRequest) error {
assert.Equal(t, intRequest(40), val)
return nil
}))
assert.Equal(t, int64(2), pq.Size())
require.NoError(t, pq.Shutdown(context.Background()))
newPQ := createTestPersistentQueueWithItemsSizer(t, ext, 100)
// The queue items size cannot be restored.
assert.Equal(t, int64(45), newPQ.Size())
require.NoError(t, newPQ.Offer(context.Background(), intRequest(10)))
// Only new items are correctly reflected
assert.Equal(t, int64(55), newPQ.Size())
// Consuming a restored request should reduce the restored size by 20 but it should not go to below zero
assert.True(t, consume(newPQ, func(_ context.Context, val intRequest) error {
assert.Equal(t, intRequest(20), val)
return nil
}))
assert.Equal(t, int64(35), newPQ.Size())
// Consuming another restored request should not affect the restored size since it's already dropped to 0.
assert.True(t, consume(newPQ, func(_ context.Context, val intRequest) error {
assert.Equal(t, intRequest(25), val)
return nil
}))
assert.Equal(t, int64(10), newPQ.Size())
// Adding another batch should update the size accordingly
require.NoError(t, newPQ.Offer(context.Background(), intRequest(25)))
assert.Equal(t, int64(35), newPQ.Size())
assert.True(t, consume(newPQ, func(_ context.Context, val intRequest) error {
assert.Equal(t, intRequest(10), val)
return nil
}))
assert.Equal(t, int64(25), newPQ.Size())
require.NoError(t, newPQ.Shutdown(context.Background()))
}
// This test covers the case when the queue is restarted with the less capacity than needed to restore the queued items.
// In that case, the queue has to be restored anyway even if it exceeds the capacity limit.
func TestPersistentQueue_RequestCapacityLessAfterRestart(t *testing.T) {
ext := storagetest.NewMockStorageExtension(nil)
pq := createTestPersistentQueueWithRequestsSizer(t, ext, 100)
assert.Equal(t, int64(0), pq.Size())
require.NoError(t, pq.Offer(context.Background(), intRequest(40)))
require.NoError(t, pq.Offer(context.Background(), intRequest(20)))
require.NoError(t, pq.Offer(context.Background(), intRequest(25)))
require.NoError(t, pq.Offer(context.Background(), intRequest(5)))
// Read the first request just to populate the read index in the storage.
// Otherwise, the write index won't be restored either.
assert.True(t, consume(pq, func(_ context.Context, val intRequest) error {
assert.Equal(t, intRequest(40), val)
return nil
}))
assert.Equal(t, int64(3), pq.Size())
require.NoError(t, pq.Shutdown(context.Background()))
// The queue is restarted with the less capacity than needed to restore the queued items, but with the same
// underlying storage. No need to drop requests that are over capacity since they are already in the storage.
newPQ := createTestPersistentQueueWithRequestsSizer(t, ext, 2)
// The queue items size cannot be restored, fall back to request-based size
assert.Equal(t, int64(3), newPQ.Size())
// Queue is full
require.Error(t, newPQ.Offer(context.Background(), intRequest(10)))
assert.True(t, consume(newPQ, func(_ context.Context, val intRequest) error {
assert.Equal(t, intRequest(20), val)
return nil
}))
assert.Equal(t, int64(2), newPQ.Size())
// Still full
require.Error(t, newPQ.Offer(context.Background(), intRequest(10)))
assert.True(t, consume(newPQ, func(_ context.Context, val intRequest) error {
assert.Equal(t, intRequest(25), val)
return nil
}))
assert.Equal(t, int64(1), newPQ.Size())
// Now it can accept new items
require.NoError(t, newPQ.Offer(context.Background(), intRequest(10)))
require.NoError(t, newPQ.Shutdown(context.Background()))
}
// This test covers the case when the persistent storage is recovered from a snapshot which has
// bigger value for the used size than the size of the actual items in the storage.
func TestPersistentQueue_RestoredUsedSizeIsCorrectedOnDrain(t *testing.T) {
ext := storagetest.NewMockStorageExtension(nil)
pq := createTestPersistentQueueWithItemsSizer(t, ext, 1000)
assert.Equal(t, int64(0), pq.Size())
for range 6 {
require.NoError(t, pq.Offer(context.Background(), intRequest(10)))
}
assert.Equal(t, int64(60), pq.Size())
// Consume 30 items
for range 3 {
assert.True(t, consume(pq, func(context.Context, intRequest) error { return nil }))
}
assert.Equal(t, int64(30), pq.Size())
// Corrupt the size, in reality the size is 30.
// Once the queue is drained, it will be updated to the correct size.
pq.metadata.ItemsSize = 50
assert.Equal(t, int64(50), pq.Size())
assert.True(t, consume(pq, func(context.Context, intRequest) error { return nil }))
assert.True(t, consume(pq, func(context.Context, intRequest) error { return nil }))
assert.Equal(t, int64(30), pq.Size())
// Now the size must be correctly reflected
assert.True(t, consume(pq, func(context.Context, intRequest) error { return nil }))
assert.Equal(t, int64(0), pq.Size())
require.NoError(t, pq.Shutdown(context.Background()))
}
func requireCurrentlyDispatchedItemsEqual(t *testing.T, pq *persistentQueue[intRequest], compare []uint64) {
pq.mu.Lock()
defer pq.mu.Unlock()
assert.ElementsMatch(t, compare, pq.metadata.CurrentlyDispatchedItems)
}
func itemIndexArrayToBytes(arr []uint64) []byte {
size := len(arr)
buf := make([]byte, 0, 4+size*8)
buf = binary.LittleEndian.AppendUint32(buf, uint32(size))
for _, item := range arr {
buf = binary.LittleEndian.AppendUint64(buf, item)
}
return buf
}
================================================
FILE: exporter/exporterhelper/internal/queue/queue.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queue // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/queue"
import (
"context"
"errors"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/pipeline"
)
// ReferenceCounter is an optional interface that can be implemented to provide a way for the request data
// to manage internal locally allocated memory and re-use across multiple requests, etc.
//
// The queue will only call Ref and Unref when requests are executed asynchronously, otherwise these
// funcs are not called.
type ReferenceCounter[T any] interface {
Ref(T)
Unref(T)
}
type Encoding[T any] interface {
// Marshal is a function that can marshal a request into bytes.
Marshal(context.Context, T) ([]byte, error)
// Unmarshal is a function that can unmarshal bytes into a request.
Unmarshal([]byte) (context.Context, T, error)
}
// ErrQueueIsFull is the error returned when an item is offered to the Queue and the queue is full and setup to
// not block.
// Experimental: This API is at the early stage of development and may change without backward compatibility
// until https://github.com/open-telemetry/opentelemetry-collector/issues/8122 is resolved.
var ErrQueueIsFull = errors.New("sending queue is full")
// Done represents the callback that will be called when the read request is completely processed by the
// downstream components.
// Experimental: This API is at the early stage of development and may change without backward compatibility
// until https://github.com/open-telemetry/opentelemetry-collector/issues/8122 is resolved.
type Done interface {
// OnDone needs to be called when processing of the queue item is done.
OnDone(error)
}
type ConsumeFunc[T any] func(context.Context, T, Done)
// Queue defines a producer-consumer exchange which can be backed by e.g. the memory-based ring buffer queue
// (boundedMemoryQueue) or via a disk-based queue (persistentQueue)
// Experimental: This API is at the early stage of development and may change without backward compatibility
// until https://github.com/open-telemetry/opentelemetry-collector/issues/8122 is resolved.
type Queue[T any] interface {
component.Component
// Offer inserts the specified element into this queue if it is possible to do so immediately
// without violating capacity restrictions. If success returns no error.
// It returns ErrQueueIsFull if no space is currently available.
Offer(ctx context.Context, item T) error
// Size returns the current Size of the queue
Size() int64
// Capacity returns the capacity of the queue.
Capacity() int64
}
// Settings define internal parameters for a new Queue creation.
type Settings[T request.Request] struct {
SizerType request.SizerType
Capacity int64
NumConsumers int
WaitForResult bool
BlockOnOverflow bool
Signal pipeline.Signal
StorageID *component.ID
ReferenceCounter ReferenceCounter[T]
Encoding Encoding[T]
ID component.ID
Telemetry component.TelemetrySettings
}
func NewQueue[T request.Request](set Settings[T], next ConsumeFunc[T]) (Queue[T], error) {
q := newBaseQueue(set)
oq, err := newObsQueue(set, newAsyncQueue(q, set.NumConsumers, next, set.ReferenceCounter))
if err != nil {
return nil, err
}
return oq, nil
}
func newBaseQueue[T request.Request](set Settings[T]) readableQueue[T] {
// Configure memory queue or persistent based on the config.
if set.StorageID == nil {
return newMemoryQueue[T](set)
}
return newPersistentQueue[T](set)
}
// TODO: Investigate why linter "unused" fails if add a private "read" func on the Queue.
type readableQueue[T any] interface {
Queue[T]
// Read pulls the next available item from the queue along with its done callback. Once processing is
// finished, the done callback must be called to clean up the storage.
// The function blocks until an item is available or if the queue is stopped.
// If the queue is stopped returns false, otherwise true.
Read(context.Context) (context.Context, T, Done, bool)
}
================================================
FILE: exporter/exporterhelper/internal/queue_sender.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal"
import (
"context"
"time"
"go.uber.org/zap"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sender"
)
// NewDefaultQueueConfig returns the default config for queuebatch.Config.
// By default:
//
// - the queue stores 1000 requests of telemetry
// - is non-blocking when full
// - concurrent exports limited to 10
// - emits batches of 8192 items, timeout 200ms
func NewDefaultQueueConfig() queuebatch.Config {
return queuebatch.Config{
Sizer: request.SizerTypeRequests,
NumConsumers: 10,
QueueSize: 1_000,
BlockOnOverflow: false,
Batch: configoptional.Default(queuebatch.BatchConfig{
FlushTimeout: 200 * time.Millisecond,
Sizer: request.SizerTypeItems,
MinSize: 8192,
}),
}
}
func NewQueueSender(
qSet queuebatch.AllSettings[request.Request],
qCfg queuebatch.Config,
exportFailureMessage string,
next sender.Sender[request.Request],
) (sender.Sender[request.Request], error) {
exportFunc := func(ctx context.Context, req request.Request) error {
// Have to read the number of items before sending the request since the request can
// be modified by the downstream components like the batcher.
itemsCount := req.ItemsCount()
if errSend := next.Send(ctx, req); errSend != nil {
qSet.Telemetry.Logger.Error("Exporting failed. Dropping data."+exportFailureMessage,
zap.Error(errSend), zap.Int("dropped_items", itemsCount))
return errSend
}
return nil
}
return queuebatch.NewQueueBatch(qSet, qCfg, exportFunc)
}
================================================
FILE: exporter/exporterhelper/internal/queue_sender_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/requesttest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sender"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/pipeline"
)
func TestNewQueueSenderFailedRequestDropped(t *testing.T) {
qSet := queuebatch.AllSettings[request.Request]{
Signal: pipeline.SignalMetrics,
ID: component.NewID(exportertest.NopType),
Telemetry: componenttest.NewNopTelemetrySettings(),
}
logger, observed := observer.New(zap.ErrorLevel)
qSet.Telemetry.Logger = zap.New(logger)
qCfg := NewDefaultQueueConfig()
be, err := NewQueueSender(
qSet, qCfg, "", sender.NewSender(func(context.Context, request.Request) error { return errors.New("some error") }))
require.NoError(t, err)
require.NoError(t, be.Start(context.Background(), componenttest.NewNopHost()))
require.NoError(t, be.Send(context.Background(), &requesttest.FakeRequest{Items: 2}))
require.NoError(t, be.Shutdown(context.Background()))
assert.Len(t, observed.All(), 1)
assert.Equal(t, "Exporting failed. Dropping data.", observed.All()[0].Message)
}
func TestQueueConfig_Validate(t *testing.T) {
qCfg := NewDefaultQueueConfig()
require.NoError(t, qCfg.Validate())
qCfg.NumConsumers = 0
require.EqualError(t, qCfg.Validate(), "`num_consumers` must be positive")
qCfg = NewDefaultQueueConfig()
qCfg.QueueSize = 0
require.EqualError(t, qCfg.Validate(), "`queue_size` must be positive")
// Confirm Validate doesn't return error with invalid config when feature is disabled
noCfg := configoptional.None[queuebatch.Config]()
assert.NoError(t, noCfg.Validate())
}
================================================
FILE: exporter/exporterhelper/internal/queuebatch/batch_context.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queuebatch // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
import (
"context"
"go.opentelemetry.io/otel/trace"
)
type traceContextKeyType int
const batchSpanLinksKey traceContextKeyType = iota
// LinksFromContext returns a list of trace links registered in the context.
func LinksFromContext(ctx context.Context) []trace.Link {
if ctx == nil {
return []trace.Link{}
}
if links, ok := ctx.Value(batchSpanLinksKey).([]trace.Link); ok {
return links
}
return []trace.Link{}
}
func parentsFromContext(ctx context.Context) []trace.Link {
if spanCtx := trace.SpanContextFromContext(ctx); spanCtx.IsValid() {
return []trace.Link{{SpanContext: spanCtx}}
}
return LinksFromContext(ctx)
}
func contextWithMergedLinks(mergedCtx, ctx1, ctx2 context.Context) context.Context {
return context.WithValue(
mergedCtx,
batchSpanLinksKey,
append(parentsFromContext(ctx1), parentsFromContext(ctx2)...))
}
================================================
FILE: exporter/exporterhelper/internal/queuebatch/batch_context_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queuebatch
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/collector/component/componenttest"
)
type testTimestampKeyType int
const testTimestampKey testTimestampKeyType = iota
// mergeCtxFunc corresponds to user specified mergeCtx function in the batcher settings.
// This specific merge Context function keeps the greater of timestamps from two contexts.
func mergeCtxFunc(ctx1, ctx2 context.Context) context.Context {
timestamp1 := ctx1.Value(testTimestampKey)
timestamp2 := ctx2.Value(testTimestampKey)
if timestamp1 != nil && timestamp2 != nil {
if timestamp1.(int) > timestamp2.(int) {
return context.WithValue(context.Background(), testTimestampKey, timestamp1)
}
return context.WithValue(context.Background(), testTimestampKey, timestamp2)
}
if timestamp1 != nil {
return context.WithValue(context.Background(), testTimestampKey, timestamp1)
}
return context.WithValue(context.Background(), testTimestampKey, timestamp2)
}
// mergeContextHelper performs the same operation done during batching.
func mergeContextHelper(ctx1, ctx2 context.Context) context.Context {
return contextWithMergedLinks(mergeCtxFunc(ctx1, ctx2), ctx1, ctx2)
}
func TestBatchContextLink(t *testing.T) {
tracerProvider := componenttest.NewTelemetry().NewTelemetrySettings().TracerProvider
tracer := tracerProvider.Tracer("go.opentelemetry.io/collector/exporter/exporterhelper")
ctx1 := context.Background()
ctx2, span2 := tracer.Start(ctx1, "span2")
defer span2.End()
ctx3, span3 := tracer.Start(ctx1, "span3")
defer span3.End()
ctx4, span4 := tracer.Start(ctx1, "span4")
defer span4.End()
batchContext := mergeContextHelper(ctx2, ctx3)
batchContext = mergeContextHelper(batchContext, ctx4)
actualLinks := LinksFromContext(batchContext)
require.Len(t, actualLinks, 3)
require.Equal(t, trace.SpanContextFromContext(ctx2), actualLinks[0].SpanContext)
require.Equal(t, trace.SpanContextFromContext(ctx3), actualLinks[1].SpanContext)
require.Equal(t, trace.SpanContextFromContext(ctx4), actualLinks[2].SpanContext)
}
func TestMergedContext_GetValue(t *testing.T) {
ctx1 := context.WithValue(context.Background(), testTimestampKey, 1234)
ctx2 := context.WithValue(context.Background(), testTimestampKey, 2345)
batchContext := mergeContextHelper(ctx1, ctx2)
require.Equal(t, 2345, batchContext.Value(testTimestampKey))
}
================================================
FILE: exporter/exporterhelper/internal/queuebatch/batcher.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queuebatch // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
import (
"context"
"fmt"
"go.uber.org/zap"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/queue"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sender"
)
// Batcher is in charge of reading items from the queue and send them out asynchronously.
type Batcher[T any] interface {
component.Component
Consume(context.Context, T, queue.Done)
}
type batcherSettings[T any] struct {
partitioner Partitioner[T]
mergeCtx func(context.Context, context.Context) context.Context
next sender.SendFunc[T]
maxWorkers int
logger *zap.Logger
}
func NewBatcher(cfg configoptional.Optional[BatchConfig], set batcherSettings[request.Request]) (Batcher[request.Request], error) {
if !cfg.HasValue() {
return newDisabledBatcher(set.next), nil
}
sizer := request.NewSizer(cfg.Get().Sizer)
if sizer == nil {
return nil, fmt.Errorf("queue_batch: unsupported sizer %q", cfg.Get().Sizer)
}
if set.partitioner == nil {
return newPartitionBatcher(*cfg.Get(), sizer, set.mergeCtx, newWorkerPool(set.maxWorkers), set.next, set.logger, nil), nil
}
mb, err := newMultiBatcher(*cfg.Get(), sizer, newWorkerPool(set.maxWorkers), set.partitioner, set.mergeCtx, set.next, set.logger)
if err != nil {
return nil, fmt.Errorf("error during creating multi batcher: %w", err)
}
return mb, nil
}
================================================
FILE: exporter/exporterhelper/internal/queuebatch/config.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queuebatch // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
import (
"errors"
"fmt"
"strings"
"time"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
)
// Config defines configuration for queueing and batching incoming requests.
type Config struct {
// WaitForResult determines if incoming requests are blocked until the request is processed or not.
// Currently, this option is not available when persistent queue is configured using the storage configuration.
WaitForResult bool `mapstructure:"wait_for_result"`
// Sizer determines the type of size measurement used by this component.
// It accepts "requests", "items", or "bytes".
Sizer request.SizerType `mapstructure:"sizer"`
// QueueSize represents the maximum data size allowed for concurrent storage and processing.
QueueSize int64 `mapstructure:"queue_size"`
// BlockOnOverflow determines the behavior when the component's TotalSize limit is reached.
// If true, the component will wait for space; otherwise, operations will immediately return a retryable error.
BlockOnOverflow bool `mapstructure:"block_on_overflow"`
// StorageID if not empty, enables the persistent storage and uses the component specified
// as a storage extension for the persistent queue.
// TODO: This will be changed to Optional when available.
// See https://github.com/open-telemetry/opentelemetry-collector/issues/13822
StorageID *component.ID `mapstructure:"storage"`
// NumConsumers is the maximum number of concurrent consumers from the queue.
// This applies across all different optional configurations from above (e.g. wait_for_result, block_on_overflow, storage, etc.).
NumConsumers int `mapstructure:"num_consumers"`
// BatchConfig it configures how the requests are consumed from the queue and batch together during consumption.
Batch configoptional.Optional[BatchConfig] `mapstructure:"batch"`
}
func (cfg *Config) Unmarshal(conf *confmap.Conf) error {
if err := conf.Unmarshal(cfg); err != nil {
return err
}
// If all of the following hold:
// 1. the sizer is set,
// 2. the batch sizer is not set and
// 3. the batch section is nonempty,
// then use the same value as the queue sizer.
if conf.IsSet("sizer") && !conf.IsSet("batch::sizer") && conf.IsSet("batch") && conf.Get("batch") != nil {
cfg.Batch.Get().Sizer = cfg.Sizer
}
return nil
}
// Validate checks if the Config is valid
func (cfg *Config) Validate() error {
if cfg.NumConsumers <= 0 {
return errors.New("`num_consumers` must be positive")
}
if cfg.QueueSize <= 0 {
return errors.New("`queue_size` must be positive")
}
// Only support request sizer for persistent queue at this moment.
if cfg.StorageID != nil && cfg.WaitForResult {
return errors.New("`wait_for_result` is not supported with a persistent queue configured with `storage`")
}
if cfg.Batch.HasValue() && cfg.Batch.Get().Sizer == cfg.Sizer {
// Avoid situations where the queue is not able to hold any data.
if cfg.Batch.Get().MinSize > cfg.QueueSize {
return errors.New("`min_size` must be less than or equal to `queue_size`")
}
}
return nil
}
// BatchConfig defines a configuration for batching requests based on a timeout and a minimum number of items.
type BatchConfig struct {
// FlushTimeout sets the time after which a batch will be sent regardless of its size.
FlushTimeout time.Duration `mapstructure:"flush_timeout"`
// Sizer determines the type of size measurement used by the batch.
// If not configured, use the same configuration as the queue.
// It accepts "requests", "items", or "bytes".
Sizer request.SizerType `mapstructure:"sizer"`
// MinSize defines the configuration for the minimum size of a batch.
MinSize int64 `mapstructure:"min_size"`
// MaxSize defines the configuration for the maximum size of a batch.
MaxSize int64 `mapstructure:"max_size"`
// Partition defines the partitioning of the batches configuration.
Partition PartitionConfig `mapstructure:"partition"`
}
// PartitionConfig defines a configuration for partitioning requests based on metadata keys.
type PartitionConfig struct {
// MetadataKeys is a list of client.Metadata keys that will be used to partition
// the data into batches. If this setting is empty, a single batcher instance
// will be used. When this setting is not empty, one batcher will be used per
// distinct combination of values for the listed metadata keys.
//
// Empty value and unset metadata are treated as distinct cases.
//
// Entries are case-insensitive. Duplicated entries will trigger a validation error.
MetadataKeys []string `mapstructure:"metadata_keys"`
}
func (cfg *BatchConfig) Validate() error {
if cfg == nil {
return nil
}
// Only support items or bytes sizer for batch at this moment.
if cfg.Sizer != request.SizerTypeItems && cfg.Sizer != request.SizerTypeBytes {
return fmt.Errorf("`batch` supports only `items` or `bytes` sizer, found %q", cfg.Sizer.String())
}
if cfg.FlushTimeout <= 0 {
return fmt.Errorf("`flush_timeout` must be positive, found %d", cfg.FlushTimeout)
}
if cfg.MinSize < 0 {
return fmt.Errorf("`min_size` must be non-negative, found %d", cfg.MinSize)
}
if cfg.MaxSize < 0 {
return fmt.Errorf("`max_size` must be non-negative, found %d", cfg.MaxSize)
}
if cfg.MaxSize > 0 && cfg.MaxSize < cfg.MinSize {
return fmt.Errorf("`max_size` (%d) must be greater or equal to `min_size` (%d)", cfg.MaxSize, cfg.MinSize)
}
return nil
}
func (cfg *PartitionConfig) Validate() error {
if cfg == nil {
return nil
}
// Validate metadata_keys for duplicates (case-insensitive)
uniq := map[string]bool{}
for _, k := range cfg.MetadataKeys {
l := strings.ToLower(k)
if _, has := uniq[l]; has {
return fmt.Errorf("duplicate entry in metadata_keys: %q (case-insensitive)", l)
}
uniq[l] = true
}
return nil
}
================================================
FILE: exporter/exporterhelper/internal/queuebatch/config.schema.yaml
================================================
$defs:
batch_config:
description: BatchConfig defines a configuration for batching requests based on a timeout and a minimum number of items.
type: object
properties:
flush_timeout:
description: FlushTimeout sets the time after which a batch will be sent regardless of its size.
type: string
x-customType: time.Duration
format: duration
max_size:
description: MaxSize defines the configuration for the maximum size of a batch.
type: integer
x-customType: int64
min_size:
description: MinSize defines the configuration for the minimum size of a batch.
type: integer
x-customType: int64
partition:
description: Partition defines the partitioning of the batches configuration.
$ref: partition_config
sizer:
description: Sizer determines the type of size measurement used by the batch. If not configured, use the same configuration as the queue. It accepts "requests", "items", or "bytes".
type: string
x-customType: go.opentelemetry.io/collector/exporter/exporterhelper/internal/request.SizerType
partition_config:
description: PartitionConfig defines a configuration for partitioning requests based on metadata keys.
type: object
properties:
metadata_keys:
description: MetadataKeys is a list of client.Metadata keys that will be used to partition the data into batches. If this setting is empty, a single batcher instance will be used. When this setting is not empty, one batcher will be used per distinct combination of values for the listed metadata keys. Empty value and unset metadata are treated as distinct cases. Entries are case-insensitive. Duplicated entries will trigger a validation error.
type: array
items:
type: string
config:
description: Config defines configuration for queueing and batching incoming requests.
type: object
properties:
batch:
description: BatchConfig it configures how the requests are consumed from the queue and batch together during consumption.
x-optional: true
$ref: batch_config
block_on_overflow:
description: BlockOnOverflow determines the behavior when the component's TotalSize limit is reached. If true, the component will wait for space; otherwise, operations will immediately return a retryable error.
type: boolean
enabled:
description: Enabled indicates whether to not enqueue and batch before exporting.
type: boolean
num_consumers:
description: NumConsumers is the maximum number of concurrent consumers from the queue. This applies across all different optional configurations from above (e.g. wait_for_result, block_on_overflow, storage, etc.).
type: integer
queue_size:
description: QueueSize represents the maximum data size allowed for concurrent storage and processing.
type: integer
x-customType: int64
sizer:
description: Sizer determines the type of size measurement used by this component. It accepts "requests", "items", or "bytes".
type: string
x-customType: go.opentelemetry.io/collector/exporter/exporterhelper/internal/request.SizerType
storage:
description: 'StorageID if not empty, enables the persistent storage and uses the component specified as a storage extension for the persistent queue. TODO: This will be changed to Optional when available. See https://github.com/open-telemetry/opentelemetry-collector/issues/13822'
x-pointer: true
type: string
x-customType: go.opentelemetry.io/collector/component.ID
wait_for_result:
description: WaitForResult determines if incoming requests are blocked until the request is processed or not. Currently, this option is not available when persistent queue is configured using the storage configuration.
type: boolean
================================================
FILE: exporter/exporterhelper/internal/queuebatch/config_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queuebatch
import (
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/confmap/confmaptest"
"go.opentelemetry.io/collector/confmap/xconfmap"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
)
func TestConfig_Validate(t *testing.T) {
cfg := newTestConfig()
require.NoError(t, xconfmap.Validate(cfg))
cfg.NumConsumers = 0
require.EqualError(t, xconfmap.Validate(cfg), "`num_consumers` must be positive")
cfg = newTestConfig()
cfg.QueueSize = 0
require.EqualError(t, xconfmap.Validate(cfg), "`queue_size` must be positive")
cfg = newTestConfig()
cfg.QueueSize = 0
require.EqualError(t, xconfmap.Validate(cfg), "`queue_size` must be positive")
storageID := component.MustNewID("test")
cfg = newTestConfig()
cfg.WaitForResult = true
cfg.StorageID = &storageID
require.EqualError(t, xconfmap.Validate(cfg), "`wait_for_result` is not supported with a persistent queue configured with `storage`")
cfg = newTestConfig()
cfg.QueueSize = cfg.Batch.Get().MinSize - 1
require.EqualError(t, xconfmap.Validate(cfg), "`min_size` must be less than or equal to `queue_size`")
cfg = newTestConfig()
cfg.Batch.Get().Sizer = request.SizerType{}
require.EqualError(t, xconfmap.Validate(cfg), "batch: `batch` supports only `items` or `bytes` sizer, found \"\"")
cfg = newTestConfig()
cfg.Sizer = request.SizerTypeBytes
require.NoError(t, xconfmap.Validate(cfg))
}
func TestBatchConfig_Validate_MetadataKeys(t *testing.T) {
t.Run("no duplicates - valid", func(t *testing.T) {
cfg := newTestBatchConfig()
cfg.Partition.MetadataKeys = []string{"key1", "key2", "key3"}
require.NoError(t, xconfmap.Validate(cfg))
})
t.Run("duplicate keys mixed case - invalid", func(t *testing.T) {
cfg := newTestBatchConfig()
cfg.Partition.MetadataKeys = []string{"Key1", "kEy1", "key2"}
err := xconfmap.Validate(cfg)
require.Error(t, err)
assert.Contains(t, err.Error(), "duplicate entry in metadata_keys")
assert.Contains(t, err.Error(), "key1")
assert.Contains(t, err.Error(), "case-insensitive")
})
t.Run("empty metadata_keys - valid", func(t *testing.T) {
cfg := newTestBatchConfig()
cfg.Partition.MetadataKeys = []string{}
require.NoError(t, xconfmap.Validate(cfg))
})
t.Run("nil metadata_keys - valid", func(t *testing.T) {
cfg := newTestBatchConfig()
cfg.Partition.MetadataKeys = nil
require.NoError(t, xconfmap.Validate(cfg))
})
t.Run("multiple duplicates - reports first duplicate", func(t *testing.T) {
cfg := newTestBatchConfig()
cfg.Partition.MetadataKeys = []string{"key1", "key2", "key1", "key2"}
err := xconfmap.Validate(cfg)
require.Error(t, err)
assert.Contains(t, err.Error(), "duplicate entry in metadata_keys")
assert.Contains(t, err.Error(), "key1")
})
}
func TestBatchConfig_Validate(t *testing.T) {
cfg := newTestBatchConfig()
require.NoError(t, xconfmap.Validate(cfg))
cfg = newTestBatchConfig()
cfg.FlushTimeout = 0
require.EqualError(t, xconfmap.Validate(cfg), "`flush_timeout` must be positive, found 0")
cfg = newTestBatchConfig()
cfg.MinSize = -1
require.EqualError(t, xconfmap.Validate(cfg), "`min_size` must be non-negative, found -1")
cfg = newTestBatchConfig()
cfg.MaxSize = -1
require.EqualError(t, xconfmap.Validate(cfg), "`max_size` must be non-negative, found -1")
cfg = newTestBatchConfig()
cfg.Sizer = request.SizerTypeRequests
require.EqualError(t, xconfmap.Validate(cfg), "`batch` supports only `items` or `bytes` sizer, found \"requests\"")
cfg = newTestBatchConfig()
cfg.Sizer = request.SizerType{}
require.EqualError(t, xconfmap.Validate(cfg), "`batch` supports only `items` or `bytes` sizer, found \"\"")
cfg = newTestBatchConfig()
cfg.MinSize = 2048
cfg.MaxSize = 1024
require.EqualError(t, xconfmap.Validate(cfg), "`max_size` (1024) must be greater or equal to `min_size` (2048)")
}
func newTestBatchConfig() BatchConfig {
return BatchConfig{
FlushTimeout: 200 * time.Millisecond,
Sizer: request.SizerTypeItems,
MinSize: 2048,
MaxSize: 0,
}
}
func TestUnmarshal(t *testing.T) {
newBaseCfg := func() configoptional.Optional[Config] {
return configoptional.Some(Config{
Sizer: request.SizerTypeRequests,
NumConsumers: 10,
QueueSize: 1_000,
Batch: configoptional.Default(BatchConfig{
FlushTimeout: 200 * time.Millisecond,
Sizer: request.SizerTypeItems,
MinSize: 8192,
}),
})
}
tests := []struct {
path string
expectedErr string
expectedCfg func() configoptional.Optional[Config]
}{
{
path: "batch_set_empty_explicit_sizer.yaml",
expectedCfg: func() configoptional.Optional[Config] {
cfg := newBaseCfg()
cfg.Get().Sizer = request.SizerTypeBytes
// Batch is set, sizer is not overridden
cfg.Get().Batch.GetOrInsertDefault()
return cfg
},
},
{
path: "batch_set_empty_no_explicit_sizer.yaml",
expectedCfg: func() configoptional.Optional[Config] {
cfg := newBaseCfg()
cfg.Get().Batch.GetOrInsertDefault()
return cfg
},
},
{
path: "batch_set_nonempty_explicit_sizer.yaml",
expectedCfg: func() configoptional.Optional[Config] {
cfg := newBaseCfg()
cfg.Get().Sizer = request.SizerTypeBytes
cfg.Get().QueueSize = 2000
cfg.Get().Batch = configoptional.Some(BatchConfig{
FlushTimeout: 200 * time.Millisecond,
// Sizer has been overridden by parent sizer
Sizer: request.SizerTypeBytes,
MinSize: 100,
})
return cfg
},
},
{
path: "batch_set_nonempty_no_explicit_sizer.yaml",
expectedCfg: func() configoptional.Optional[Config] {
cfg := newBaseCfg()
cfg.Get().QueueSize = 2000
cfg.Get().Batch = configoptional.Some(BatchConfig{
FlushTimeout: 200 * time.Millisecond,
// Sizer has NOT been overridden by parent sizer
Sizer: request.SizerTypeItems,
MinSize: 100,
})
return cfg
},
},
{
path: "batch_unset.yaml",
// Batch remains unset, sizer override does not apply.
expectedCfg: newBaseCfg,
},
}
for _, tt := range tests {
t.Run(tt.path, func(t *testing.T) {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", tt.path))
require.NoError(t, err)
cfg := newBaseCfg()
err = cm.Unmarshal(&cfg)
if tt.expectedErr != "" {
assert.ErrorContains(t, err, tt.expectedErr)
return
}
require.NoError(t, err)
assert.Equal(t, tt.expectedCfg(), cfg)
assert.NoError(t, xconfmap.Validate(cfg))
})
}
}
================================================
FILE: exporter/exporterhelper/internal/queuebatch/disabled_batcher.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queuebatch // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/queue"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sender"
)
// disabledBatcher is a special-case of Batcher that has no size limit for sending. Any items read from the queue will
// be sent out (asynchronously) immediately regardless of the size.
type disabledBatcher[T any] struct {
component.StartFunc
component.ShutdownFunc
consumeFunc sender.SendFunc[T]
}
func (db *disabledBatcher[T]) Consume(ctx context.Context, req T, done queue.Done) {
done.OnDone(db.consumeFunc(ctx, req))
}
func newDisabledBatcher[T any](consumeFunc sender.SendFunc[T]) Batcher[T] {
return &disabledBatcher[T]{consumeFunc: consumeFunc}
}
================================================
FILE: exporter/exporterhelper/internal/queuebatch/disabled_batcher_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queuebatch
import (
"context"
"errors"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/queue"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/requesttest"
)
func TestDisabledBatcher(t *testing.T) {
tests := []struct {
name string
maxWorkers int
}{
{
name: "one_worker",
maxWorkers: 1,
},
{
name: "three_workers",
maxWorkers: 3,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sink := requesttest.NewSink()
ba := newDisabledBatcher(sink.Export)
q, err := queue.NewQueue(queue.Settings[request.Request]{
Capacity: 1000,
BlockOnOverflow: true,
NumConsumers: tt.maxWorkers,
Telemetry: componenttest.NewNopTelemetrySettings(),
}, ba.Consume)
require.NoError(t, err)
require.NoError(t, q.Start(context.Background(), componenttest.NewNopHost()))
require.NoError(t, ba.Start(context.Background(), componenttest.NewNopHost()))
t.Cleanup(func() {
require.NoError(t, q.Shutdown(context.Background()))
require.NoError(t, ba.Shutdown(context.Background()))
})
require.NoError(t, q.Offer(context.Background(), &requesttest.FakeRequest{Items: 8}))
sink.SetExportErr(errors.New("transient error"))
require.NoError(t, q.Offer(context.Background(), &requesttest.FakeRequest{Items: 8}))
require.NoError(t, q.Offer(context.Background(), &requesttest.FakeRequest{Items: 17}))
require.NoError(t, q.Offer(context.Background(), &requesttest.FakeRequest{Items: 13}))
require.NoError(t, q.Offer(context.Background(), &requesttest.FakeRequest{Items: 35}))
require.NoError(t, q.Offer(context.Background(), &requesttest.FakeRequest{Items: 2}))
assert.Eventually(t, func() bool {
return sink.RequestsCount() == 5 && sink.ItemsCount() == 75
}, 1*time.Second, 10*time.Millisecond)
})
}
}
================================================
FILE: exporter/exporterhelper/internal/queuebatch/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:generate mdatagen metadata.yaml
// Package queuebatch provides helper functions for exporter's queueing and batching.
package queuebatch // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
================================================
FILE: exporter/exporterhelper/internal/queuebatch/encoding.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queuebatch // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
import "context"
// encoding defines the encoding to be used if persistent queue is configured.
// Duplicate definition with exporterhelper.QueueBatchEncoding since aliasing generics is not supported by default.
type encoding[T any] interface {
// Marshal is a function that can marshal a request and its context into bytes.
Marshal(context.Context, T) ([]byte, error)
// Unmarshal is a function that can unmarshal bytes into a request and its context.
Unmarshal([]byte) (context.Context, T, error)
}
================================================
FILE: exporter/exporterhelper/internal/queuebatch/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package queuebatch
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: exporter/exporterhelper/internal/queuebatch/logs.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queuebatch // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
import (
"context"
"errors"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/queue"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sizer"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/xpdata/pref"
pdatareq "go.opentelemetry.io/collector/pdata/xpdata/request"
)
var (
logsMarshaler = &plog.ProtoMarshaler{}
logsUnmarshaler = &plog.ProtoUnmarshaler{}
)
// NewLogsQueueBatchSettings returns a new QueueBatchSettings to configure to WithQueueBatch when using plog.Logs.
// Experimental: This API is at the early stage of development and may change without backward compatibility
// until https://github.com/open-telemetry/opentelemetry-collector/issues/8122 is resolved.
func NewLogsQueueBatchSettings() Settings[request.Request] {
return Settings[request.Request]{
ReferenceCounter: logsReferenceCounter{},
Encoding: logsEncoding{},
}
}
var (
_ request.Request = (*logsRequest)(nil)
_ request.ErrorHandler = (*logsRequest)(nil)
)
type logsRequest struct {
ld plog.Logs
cachedSize int
}
func newLogsRequest(ld plog.Logs) request.Request {
return &logsRequest{
ld: ld,
cachedSize: -1,
}
}
type logsEncoding struct{}
var _ encoding[request.Request] = logsEncoding{}
func (logsEncoding) Unmarshal(bytes []byte) (context.Context, request.Request, error) {
if queue.PersistRequestContextOnRead() {
ctx, logs, err := pdatareq.UnmarshalLogs(bytes)
if errors.Is(err, pdatareq.ErrInvalidFormat) {
// fall back to unmarshaling without context
logs, err = logsUnmarshaler.UnmarshalLogs(bytes)
}
return ctx, newLogsRequest(logs), err
}
logs, err := logsUnmarshaler.UnmarshalLogs(bytes)
if err != nil {
var req request.Request
return context.Background(), req, err
}
return context.Background(), newLogsRequest(logs), nil
}
func (logsEncoding) Marshal(ctx context.Context, req request.Request) ([]byte, error) {
logs := req.(*logsRequest).ld
if queue.PersistRequestContextOnWrite() {
return pdatareq.MarshalLogs(ctx, logs)
}
return logsMarshaler.MarshalLogs(logs)
}
var _ queue.ReferenceCounter[request.Request] = logsReferenceCounter{}
type logsReferenceCounter struct{}
func (logsReferenceCounter) Ref(req request.Request) {
pref.RefLogs(req.(*logsRequest).ld)
}
func (logsReferenceCounter) Unref(req request.Request) {
pref.UnrefLogs(req.(*logsRequest).ld)
}
func (req *logsRequest) OnError(err error) request.Request {
var logError consumererror.Logs
if errors.As(err, &logError) {
// TODO: Add logic to unref the new request created here.
return newLogsRequest(logError.Data())
}
return req
}
func (req *logsRequest) ItemsCount() int {
return req.ld.LogRecordCount()
}
func (req *logsRequest) size(sizer sizer.LogsSizer) int {
if req.cachedSize == -1 {
req.cachedSize = sizer.LogsSize(req.ld)
}
return req.cachedSize
}
func (req *logsRequest) setCachedSize(size int) {
req.cachedSize = size
}
func (req *logsRequest) BytesSize() int {
return logsMarshaler.LogsSize(req.ld)
}
// RequestConsumeFromLogs returns a RequestConsumeFunc that consumes plog.Logs.
func RequestConsumeFromLogs(pusher consumer.ConsumeLogsFunc) request.RequestConsumeFunc {
return func(ctx context.Context, request request.Request) error {
return pusher.ConsumeLogs(ctx, request.(*logsRequest).ld)
}
}
// RequestFromLogs returns a RequestFromLogsFunc that converts plog.Logs into a Request.
func RequestFromLogs() request.RequestConverterFunc[plog.Logs] {
return func(_ context.Context, ld plog.Logs) (request.Request, error) {
return newLogsRequest(ld), nil
}
}
================================================
FILE: exporter/exporterhelper/internal/queuebatch/logs_batch.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queuebatch // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
import (
"context"
"errors"
"fmt"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sizer"
"go.opentelemetry.io/collector/pdata/plog"
)
// MergeSplit splits and/or merges the provided logs request and the current request into one or more requests
// conforming with the MaxSizeConfig.
func (req *logsRequest) MergeSplit(_ context.Context, maxSize int, szt request.SizerType, r2 request.Request) ([]request.Request, error) {
var sz sizer.LogsSizer
switch szt {
case request.SizerTypeItems:
sz = &sizer.LogsCountSizer{}
case request.SizerTypeBytes:
sz = &sizer.LogsBytesSizer{}
default:
return nil, errors.New("unknown sizer type")
}
if r2 != nil {
req2, ok := r2.(*logsRequest)
if !ok {
return nil, errors.New("invalid input type")
}
req2.mergeTo(req, sz)
}
// If no limit we can simply merge the new request into the current and return.
if maxSize == 0 {
return []request.Request{req}, nil
}
return req.split(maxSize, sz)
}
func (req *logsRequest) mergeTo(dst *logsRequest, sz sizer.LogsSizer) {
if sz != nil {
dst.setCachedSize(dst.size(sz) + req.size(sz))
req.setCachedSize(0)
}
req.ld.ResourceLogs().MoveAndAppendTo(dst.ld.ResourceLogs())
}
func (req *logsRequest) split(maxSize int, sz sizer.LogsSizer) ([]request.Request, error) {
var res []request.Request
for req.size(sz) > maxSize {
ld, removedSize := extractLogs(req.ld, maxSize, sz)
if ld.LogRecordCount() == 0 {
return res, fmt.Errorf("one log record size is greater than max size, dropping items: %d", req.ld.LogRecordCount())
}
req.setCachedSize(req.size(sz) - removedSize)
res = append(res, newLogsRequest(ld))
}
res = append(res, req)
return res, nil
}
// extractLogs extracts logs from the input logs and returns a new logs with the specified number of log records.
func extractLogs(srcLogs plog.Logs, capacity int, sz sizer.LogsSizer) (plog.Logs, int) {
destLogs := plog.NewLogs()
capacityLeft := capacity - sz.LogsSize(destLogs)
removedSize := 0
srcLogs.ResourceLogs().RemoveIf(func(srcRL plog.ResourceLogs) bool {
// If the no more capacity left just return.
if capacityLeft == 0 {
return false
}
rawRlSize := sz.ResourceLogsSize(srcRL)
rlSize := sz.DeltaSize(rawRlSize)
if rlSize > capacityLeft {
extSrcRL, extRlSize := extractResourceLogs(srcRL, capacityLeft, sz)
// This cannot make it to exactly 0 for the bytes,
// force it to be 0 since that is the stopping condition.
capacityLeft = 0
removedSize += extRlSize
// There represents the delta between the delta sizes.
removedSize += rlSize - rawRlSize - (sz.DeltaSize(rawRlSize-extRlSize) - (rawRlSize - extRlSize))
// It is possible that for the bytes scenario, the extracted field contains no log records.
// Do not add it to the destination if that is the case.
if extSrcRL.ScopeLogs().Len() > 0 {
extSrcRL.MoveTo(destLogs.ResourceLogs().AppendEmpty())
}
return extSrcRL.ScopeLogs().Len() != 0
}
capacityLeft -= rlSize
removedSize += rlSize
srcRL.MoveTo(destLogs.ResourceLogs().AppendEmpty())
return true
})
return destLogs, removedSize
}
// extractResourceLogs extracts resource logs and returns a new resource logs with the specified number of log records.
func extractResourceLogs(srcRL plog.ResourceLogs, capacity int, sz sizer.LogsSizer) (plog.ResourceLogs, int) {
destRL := plog.NewResourceLogs()
destRL.SetSchemaUrl(srcRL.SchemaUrl())
srcRL.Resource().CopyTo(destRL.Resource())
// Take into account that this can have max "capacity", so when added to the parent will need space for the extra delta size.
capacityLeft := capacity - (sz.DeltaSize(capacity) - capacity) - sz.ResourceLogsSize(destRL)
removedSize := 0
srcRL.ScopeLogs().RemoveIf(func(srcSL plog.ScopeLogs) bool {
// If the no more capacity left just return.
if capacityLeft == 0 {
return false
}
rawSlSize := sz.ScopeLogsSize(srcSL)
slSize := sz.DeltaSize(rawSlSize)
if slSize > capacityLeft {
extSrcSL, extSlSize := extractScopeLogs(srcSL, capacityLeft, sz)
// This cannot make it to exactly 0 for the bytes,
// force it to be 0 since that is the stopping condition.
capacityLeft = 0
removedSize += extSlSize
// There represents the delta between the delta sizes.
removedSize += slSize - rawSlSize - (sz.DeltaSize(rawSlSize-extSlSize) - (rawSlSize - extSlSize))
// It is possible that for the bytes scenario, the extracted field contains no log records.
// Do not add it to the destination if that is the case.
if extSrcSL.LogRecords().Len() > 0 {
extSrcSL.MoveTo(destRL.ScopeLogs().AppendEmpty())
}
return extSrcSL.LogRecords().Len() != 0
}
capacityLeft -= slSize
removedSize += slSize
srcSL.MoveTo(destRL.ScopeLogs().AppendEmpty())
return true
})
return destRL, removedSize
}
// extractScopeLogs extracts scope logs and returns a new scope logs with the specified number of log records.
func extractScopeLogs(srcSL plog.ScopeLogs, capacity int, sz sizer.LogsSizer) (plog.ScopeLogs, int) {
destSL := plog.NewScopeLogs()
destSL.SetSchemaUrl(srcSL.SchemaUrl())
srcSL.Scope().CopyTo(destSL.Scope())
// Take into account that this can have max "capacity", so when added to the parent will need space for the extra delta size.
capacityLeft := capacity - (sz.DeltaSize(capacity) - capacity) - sz.ScopeLogsSize(destSL)
removedSize := 0
srcSL.LogRecords().RemoveIf(func(srcLR plog.LogRecord) bool {
// If the no more capacity left just return.
if capacityLeft == 0 {
return false
}
rlSize := sz.DeltaSize(sz.LogRecordSize(srcLR))
if rlSize > capacityLeft {
// This cannot make it to exactly 0 for the bytes,
// force it to be 0 since that is the stopping condition.
capacityLeft = 0
return false
}
capacityLeft -= rlSize
removedSize += rlSize
srcLR.MoveTo(destSL.LogRecords().AppendEmpty())
return true
})
return destSL, removedSize
}
================================================
FILE: exporter/exporterhelper/internal/queuebatch/logs_batch_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queuebatch // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sizer"
"go.opentelemetry.io/collector/internal/testutil"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/testdata"
)
func TestMergeLogs(t *testing.T) {
lr1 := newLogsRequest(testdata.GenerateLogs(2))
lr2 := newLogsRequest(testdata.GenerateLogs(3))
res, err := lr1.MergeSplit(context.Background(), 0, request.SizerTypeItems, lr2)
require.NoError(t, err)
require.Equal(t, 5, res[0].ItemsCount())
}
func TestMergeSplitLogs(t *testing.T) {
tests := []struct {
name string
szt request.SizerType
maxSize int
lr1 request.Request
lr2 request.Request
expected []request.Request
}{
{
name: "both_requests_empty",
szt: request.SizerTypeItems, maxSize: 10,
lr1: newLogsRequest(plog.NewLogs()),
lr2: newLogsRequest(plog.NewLogs()),
expected: []request.Request{newLogsRequest(plog.NewLogs())},
},
{
name: "first_request_empty",
szt: request.SizerTypeItems, maxSize: 10,
lr1: newLogsRequest(plog.NewLogs()),
lr2: newLogsRequest(testdata.GenerateLogs(5)),
expected: []request.Request{newLogsRequest(testdata.GenerateLogs(5))},
},
{
name: "first_empty_second_nil",
szt: request.SizerTypeItems, maxSize: 10,
lr1: newLogsRequest(plog.NewLogs()),
lr2: nil,
expected: []request.Request{newLogsRequest(plog.NewLogs())},
},
{
name: "merge_only",
szt: request.SizerTypeItems,
maxSize: 10,
lr1: newLogsRequest(testdata.GenerateLogs(4)),
lr2: newLogsRequest(testdata.GenerateLogs(6)),
expected: []request.Request{newLogsRequest(func() plog.Logs {
logs := testdata.GenerateLogs(4)
testdata.GenerateLogs(6).ResourceLogs().MoveAndAppendTo(logs.ResourceLogs())
return logs
}())},
},
{
name: "split_only",
szt: request.SizerTypeItems,
maxSize: 4,
lr1: newLogsRequest(plog.NewLogs()),
lr2: newLogsRequest(testdata.GenerateLogs(10)),
expected: []request.Request{
newLogsRequest(testdata.GenerateLogs(4)),
newLogsRequest(testdata.GenerateLogs(4)),
newLogsRequest(testdata.GenerateLogs(2)),
},
},
{
name: "merge_and_split",
szt: request.SizerTypeItems,
maxSize: 10,
lr1: newLogsRequest(testdata.GenerateLogs(8)),
lr2: newLogsRequest(testdata.GenerateLogs(20)),
expected: []request.Request{
newLogsRequest(func() plog.Logs {
logs := testdata.GenerateLogs(8)
testdata.GenerateLogs(2).ResourceLogs().MoveAndAppendTo(logs.ResourceLogs())
return logs
}()),
newLogsRequest(testdata.GenerateLogs(10)),
newLogsRequest(testdata.GenerateLogs(8)),
},
},
{
name: "scope_logs_split",
szt: request.SizerTypeItems,
maxSize: 4,
lr1: newLogsRequest(func() plog.Logs {
ld := testdata.GenerateLogs(4)
ld.ResourceLogs().At(0).ScopeLogs().AppendEmpty().LogRecords().AppendEmpty().Body().SetStr("extra log")
return ld
}()),
lr2: newLogsRequest(testdata.GenerateLogs(2)),
expected: []request.Request{
newLogsRequest(testdata.GenerateLogs(4)),
newLogsRequest(func() plog.Logs {
ld := testdata.GenerateLogs(0)
ld.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().AppendEmpty().Body().SetStr("extra log")
testdata.GenerateLogs(2).ResourceLogs().MoveAndAppendTo(ld.ResourceLogs())
return ld
}()),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
res, err := tt.lr1.MergeSplit(context.Background(), tt.maxSize, tt.szt, tt.lr2)
require.NoError(t, err)
assert.Len(t, res, len(tt.expected))
for i := range res {
assert.Equal(t, tt.expected[i].(*logsRequest).ld, res[i].(*logsRequest).ld)
}
})
}
}
func TestMergeSplitLogsBasedOnByteSize(t *testing.T) {
tests := []struct {
name string
szt request.SizerType
maxSize int
lr1 request.Request
lr2 request.Request
expected []request.Request
expectPartialError bool
}{
{
name: "both_requests_empty",
szt: request.SizerTypeBytes,
maxSize: logsMarshaler.LogsSize(testdata.GenerateLogs(10)),
lr1: newLogsRequest(plog.NewLogs()),
lr2: newLogsRequest(plog.NewLogs()),
expected: []request.Request{newLogsRequest(plog.NewLogs())},
},
{
name: "first_request_empty",
szt: request.SizerTypeBytes,
maxSize: logsMarshaler.LogsSize(testdata.GenerateLogs(10)),
lr1: newLogsRequest(plog.NewLogs()),
lr2: newLogsRequest(testdata.GenerateLogs(5)),
expected: []request.Request{newLogsRequest(testdata.GenerateLogs(5))},
},
{
name: "first_empty_second_nil",
szt: request.SizerTypeBytes,
maxSize: logsMarshaler.LogsSize(testdata.GenerateLogs(10)),
lr1: newLogsRequest(plog.NewLogs()),
lr2: nil,
expected: []request.Request{newLogsRequest(plog.NewLogs())},
},
{
name: "merge_only",
szt: request.SizerTypeBytes,
maxSize: logsMarshaler.LogsSize(testdata.GenerateLogs(11)),
lr1: newLogsRequest(testdata.GenerateLogs(4)),
lr2: newLogsRequest(testdata.GenerateLogs(6)),
expected: []request.Request{newLogsRequest(func() plog.Logs {
logs := testdata.GenerateLogs(4)
testdata.GenerateLogs(6).ResourceLogs().MoveAndAppendTo(logs.ResourceLogs())
return logs
}())},
},
{
name: "split_only",
szt: request.SizerTypeBytes,
maxSize: logsMarshaler.LogsSize(testdata.GenerateLogs(4)),
lr1: newLogsRequest(plog.NewLogs()),
lr2: newLogsRequest(testdata.GenerateLogs(10)),
expected: []request.Request{
newLogsRequest(testdata.GenerateLogs(4)),
newLogsRequest(testdata.GenerateLogs(4)),
newLogsRequest(testdata.GenerateLogs(2)),
},
},
{
name: "merge_and_split",
szt: request.SizerTypeBytes,
maxSize: logsMarshaler.LogsSize(testdata.GenerateLogs(10))/2 + logsMarshaler.LogsSize(testdata.GenerateLogs(11))/2,
lr1: newLogsRequest(testdata.GenerateLogs(8)),
lr2: newLogsRequest(testdata.GenerateLogs(20)),
expected: []request.Request{
newLogsRequest(func() plog.Logs {
logs := testdata.GenerateLogs(8)
testdata.GenerateLogs(2).ResourceLogs().MoveAndAppendTo(logs.ResourceLogs())
return logs
}()),
newLogsRequest(testdata.GenerateLogs(10)),
newLogsRequest(testdata.GenerateLogs(8)),
},
},
{
name: "scope_logs_split",
szt: request.SizerTypeBytes,
maxSize: logsMarshaler.LogsSize(testdata.GenerateLogs(4)),
lr1: newLogsRequest(func() plog.Logs {
ld := testdata.GenerateLogs(4)
ld.ResourceLogs().At(0).ScopeLogs().AppendEmpty().LogRecords().AppendEmpty().Body().SetStr("extra log")
return ld
}()),
lr2: newLogsRequest(testdata.GenerateLogs(2)),
expected: []request.Request{
newLogsRequest(testdata.GenerateLogs(4)),
newLogsRequest(func() plog.Logs {
ld := testdata.GenerateLogs(0)
ld.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().AppendEmpty().Body().SetStr("extra log")
testdata.GenerateLogs(2).ResourceLogs().MoveAndAppendTo(ld.ResourceLogs())
return ld
}()),
},
},
{
name: "unsplittable_large_log",
szt: request.SizerTypeBytes,
maxSize: 10,
lr1: newLogsRequest(func() plog.Logs {
ld := testdata.GenerateLogs(1)
ld.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().At(0).Body().SetStr(string(make([]byte, 100)))
return ld
}()),
lr2: nil,
expected: []request.Request{},
expectPartialError: true,
},
{
name: "splittable_then_unsplittable_log",
szt: request.SizerTypeBytes,
maxSize: 1000,
lr1: newLogsRequest(func() plog.Logs {
ld := testdata.GenerateLogs(2)
ld.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().At(0).Body().SetStr(string(make([]byte, 10)))
ld.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().At(1).Body().SetStr(string(make([]byte, 1001)))
return ld
}()),
lr2: nil,
expected: []request.Request{newLogsRequest(func() plog.Logs {
ld := testdata.GenerateLogs(1)
ld.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().At(0).Body().SetStr(string(make([]byte, 10)))
return ld
}())},
expectPartialError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
res, err := tt.lr1.MergeSplit(context.Background(), tt.maxSize, tt.szt, tt.lr2)
if tt.expectPartialError {
require.ErrorContains(t, err, "one log record size is greater than max size, dropping")
} else {
require.NoError(t, err)
}
assert.Len(t, res, len(tt.expected))
for i := range res {
assert.Equal(t, tt.expected[i].(*logsRequest).ld, res[i].(*logsRequest).ld)
assert.Equal(t,
logsMarshaler.LogsSize(tt.expected[i].(*logsRequest).ld),
logsMarshaler.LogsSize(res[i].(*logsRequest).ld))
}
})
}
}
func TestMergeSplitLogsInputNotModifiedIfErrorReturned(t *testing.T) {
r1 := newLogsRequest(testdata.GenerateLogs(18))
r2 := newTracesRequest(testdata.GenerateTraces(3))
_, err := r1.MergeSplit(context.Background(), 10, request.SizerTypeItems, r2)
require.Error(t, err)
assert.Equal(t, 18, r1.ItemsCount())
}
func TestExtractLogs(t *testing.T) {
for i := range 10 {
ld := testdata.GenerateLogs(10)
extractedLogs, _ := extractLogs(ld, i, &sizer.LogsCountSizer{})
assert.Equal(t, i, extractedLogs.LogRecordCount())
assert.Equal(t, 10-i, ld.LogRecordCount())
}
}
func TestMergeSplitManySmallLogs(t *testing.T) {
// All requests merge into a single batch.
merged := []request.Request{newLogsRequest(testdata.GenerateLogs(1))}
for range 1000 {
lr2 := newLogsRequest(testdata.GenerateLogs(10))
res, _ := merged[len(merged)-1].MergeSplit(context.Background(), 10000, request.SizerTypeItems, lr2)
merged = append(merged[0:len(merged)-1], res...)
}
assert.Len(t, merged, 2)
}
func TestLogsMergeSplitExactBytes(t *testing.T) {
pb := plog.ProtoMarshaler{}
// Set max size off by 1, so forces every log to be it's own batch.
lr := newLogsRequest(testdata.GenerateLogs(4))
merged, err := lr.MergeSplit(context.Background(), pb.LogsSize(testdata.GenerateLogs(2))-1, request.SizerTypeBytes, nil)
require.NoError(t, err)
assert.Len(t, merged, 4)
}
func TestLogsMergeSplitExactItems(t *testing.T) {
// Set max size off by 1, so forces every log to be it's own batch.
lr := newLogsRequest(testdata.GenerateLogs(4))
merged, err := lr.MergeSplit(context.Background(), 1, request.SizerTypeItems, nil)
require.NoError(t, err)
assert.Len(t, merged, 4)
}
func TestLogsMergeSplitUnknownSizerType(t *testing.T) {
req := newLogsRequest(plog.NewLogs())
// Call MergeSplit with invalid sizer
_, err := req.MergeSplit(context.Background(), 0, request.SizerType{}, nil)
require.EqualError(t, err, "unknown sizer type")
}
func BenchmarkSplittingBasedOnItemCountManySmallLogs(b *testing.B) {
testutil.SkipGCHeavyBench(b)
// All requests merge into a single batch.
b.ReportAllocs()
for b.Loop() {
merged := []request.Request{newLogsRequest(testdata.GenerateLogs(10))}
for range 1000 {
lr2 := newLogsRequest(testdata.GenerateLogs(10))
res, _ := merged[len(merged)-1].MergeSplit(context.Background(), 10010, request.SizerTypeItems, lr2)
merged = append(merged[0:len(merged)-1], res...)
}
assert.Len(b, merged, 1)
}
}
func BenchmarkSplittingBasedOnByteSizeManySmallLogs(b *testing.B) {
testutil.SkipGCHeavyBench(b)
// All requests merge into a single batch.
b.ReportAllocs()
for b.Loop() {
merged := []request.Request{newLogsRequest(testdata.GenerateLogs(10))}
for range 1000 {
lr2 := newLogsRequest(testdata.GenerateLogs(10))
res, _ := merged[len(merged)-1].MergeSplit(context.Background(), logsMarshaler.LogsSize(testdata.GenerateLogs(11000)), request.SizerTypeBytes, lr2)
merged = append(merged[0:len(merged)-1], res...)
}
assert.Len(b, merged, 1)
}
}
func BenchmarkSplittingBasedOnItemCountManyLogsSlightlyAboveLimit(b *testing.B) {
testutil.SkipGCHeavyBench(b)
// Every incoming request results in a split.
b.ReportAllocs()
for b.Loop() {
merged := []request.Request{newLogsRequest(testdata.GenerateLogs(0))}
for range 10 {
lr2 := newLogsRequest(testdata.GenerateLogs(10001))
res, _ := merged[len(merged)-1].MergeSplit(context.Background(), 10000, request.SizerTypeItems, lr2)
merged = append(merged[0:len(merged)-1], res...)
}
assert.Len(b, merged, 11)
}
}
func BenchmarkSplittingBasedOnByteSizeManyLogsSlightlyAboveLimit(b *testing.B) {
testutil.SkipGCHeavyBench(b)
// Every incoming request results in a split.
b.ReportAllocs()
for b.Loop() {
merged := []request.Request{newLogsRequest(testdata.GenerateLogs(0))}
for range 10 {
lr2 := newLogsRequest(testdata.GenerateLogs(10001))
res, _ := merged[len(merged)-1].MergeSplit(context.Background(), logsMarshaler.LogsSize(testdata.GenerateLogs(10000)), request.SizerTypeBytes, lr2)
assert.Len(b, res, 2)
merged = append(merged[0:len(merged)-1], res...)
}
assert.Len(b, merged, 11)
}
}
func BenchmarkSplittingBasedOnItemCountHugeLogs(b *testing.B) {
testutil.SkipGCHeavyBench(b)
// One request splits into many batches.
b.ReportAllocs()
for b.Loop() {
merged := []request.Request{newLogsRequest(testdata.GenerateLogs(0))}
lr2 := newLogsRequest(testdata.GenerateLogs(100000))
res, _ := merged[len(merged)-1].MergeSplit(context.Background(), 10000, request.SizerTypeItems, lr2)
merged = append(merged[0:len(merged)-1], res...)
assert.Len(b, merged, 10)
}
}
func BenchmarkSplittingBasedOnByteSizeHugeLogs(b *testing.B) {
testutil.SkipGCHeavyBench(b)
// One request splits into many batches.
b.ReportAllocs()
for b.Loop() {
merged := []request.Request{newLogsRequest(testdata.GenerateLogs(0))}
lr2 := newLogsRequest(testdata.GenerateLogs(100000))
res, _ := merged[len(merged)-1].MergeSplit(context.Background(), logsMarshaler.LogsSize(testdata.GenerateLogs(10010)), request.SizerTypeBytes, lr2)
merged = append(merged[0:len(merged)-1], res...)
assert.Len(b, merged, 10)
}
}
================================================
FILE: exporter/exporterhelper/internal/queuebatch/logs_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queuebatch // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/testdata"
)
func TestLogsRequest(t *testing.T) {
lr := newLogsRequest(testdata.GenerateLogs(1))
logErr := consumererror.NewLogs(errors.New("some error"), plog.NewLogs())
assert.Equal(
t,
newLogsRequest(plog.NewLogs()),
lr.(request.ErrorHandler).OnError(logErr),
)
}
================================================
FILE: exporter/exporterhelper/internal/queuebatch/metadata.yaml
================================================
type: queuebatch
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
stability:
beta: [traces, metrics, logs]
================================================
FILE: exporter/exporterhelper/internal/queuebatch/metadata_partitioner.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queuebatch // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
import (
"bytes"
"context"
"go.opentelemetry.io/collector/client"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
)
// metadataKeysPartitioner partitions requests based on client metadata keys.
type metadataKeysPartitioner struct {
keys []string
}
// NewMetadataKeysPartitioner creates a new partitioner that partitions requests
// based on the specified metadata keys. If keys is empty, returns nil.
func NewMetadataKeysPartitioner(keys []string) Partitioner[request.Request] {
if len(keys) == 0 {
return nil
}
return &metadataKeysPartitioner{keys: keys}
}
// GetKey returns a partition key based on the metadata keys configured.
func (p *metadataKeysPartitioner) GetKey(
ctx context.Context,
_ request.Request,
) string {
var kb bytes.Buffer
meta := client.FromContext(ctx).Metadata
var afterFirst bool
for _, k := range p.keys {
if values := meta.Get(k); len(values) != 0 {
if afterFirst {
kb.WriteByte(0)
}
kb.WriteString(k)
afterFirst = true
for _, val := range values {
kb.WriteByte(0)
kb.WriteString(val)
}
}
}
return kb.String()
}
// NewMetadataKeysMergeCtx creates a merge function for contexts that merges
// metadata based on the specified keys. If keys is empty, returns nil.
func NewMetadataKeysMergeCtx(keys []string) func(context.Context, context.Context) context.Context {
if len(keys) == 0 {
return nil
}
return func(ctx1, _ context.Context) context.Context {
m1 := client.FromContext(ctx1).Metadata
m := make(map[string][]string, len(keys))
for _, key := range keys {
v1 := m1.Get(key)
if len(v1) > 0 {
m[key] = v1
}
}
return client.NewContext(
context.Background(),
client.Info{Metadata: client.NewMetadata(m)},
)
}
}
================================================
FILE: exporter/exporterhelper/internal/queuebatch/metadata_partitioner_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queuebatch // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/client"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/requesttest"
)
func TestMetadataKeysPartitioner_MergeCtx(t *testing.T) {
t.Run("merge contexts with same metadata key values", func(t *testing.T) {
mergeCtx := NewMetadataKeysMergeCtx([]string{"key1", "key2"})
ctx1 := client.NewContext(
context.Background(),
client.Info{
Metadata: client.NewMetadata(map[string][]string{
"key1": {"value1"},
"key2": {"value2"},
}),
},
)
ctx2 := client.NewContext(
context.Background(),
client.Info{
Metadata: client.NewMetadata(map[string][]string{
"key1": {"value1"},
"key2": {"value2"},
}),
},
)
mergedCtx := mergeCtx(ctx1, ctx2)
require.NotNil(t, mergedCtx)
mergedMeta := client.FromContext(mergedCtx).Metadata
assert.Equal(t, []string{"value1"}, mergedMeta.Get("key1"))
assert.Equal(t, []string{"value2"}, mergedMeta.Get("key2"))
})
t.Run("merge contexts with same metadata key values and additional keys", func(t *testing.T) {
mergeCtx := NewMetadataKeysMergeCtx([]string{"key1"})
ctx1 := client.NewContext(
context.Background(),
client.Info{
Metadata: client.NewMetadata(map[string][]string{
"key1": {"value1"},
"other": {"other1"},
}),
},
)
ctx2 := client.NewContext(
context.Background(),
client.Info{
Metadata: client.NewMetadata(map[string][]string{
"key1": {"value1"},
"other": {"other2"},
}),
},
)
mergedCtx := mergeCtx(ctx1, ctx2)
require.NotNil(t, mergedCtx)
mergedMeta := client.FromContext(mergedCtx).Metadata
assert.Equal(t, []string{"value1"}, mergedMeta.Get("key1"))
// Other keys should not be in merged metadata since they're not in partitioner keys
assert.Empty(t, mergedMeta.Get("other"))
})
t.Run("merge contexts with empty metadata", func(t *testing.T) {
mergeCtx := NewMetadataKeysMergeCtx([]string{"key1"})
ctx1 := client.NewContext(
context.Background(),
client.Info{
Metadata: client.NewMetadata(map[string][]string{}),
},
)
ctx2 := client.NewContext(
context.Background(),
client.Info{
Metadata: client.NewMetadata(map[string][]string{}),
},
)
mergedCtx := mergeCtx(ctx1, ctx2)
require.NotNil(t, mergedCtx)
mergedMeta := client.FromContext(mergedCtx).Metadata
assert.Empty(t, mergedMeta.Get("key1"))
})
t.Run("merge when one context has metadata and other is empty", func(t *testing.T) {
mergeCtx := NewMetadataKeysMergeCtx([]string{"key1"})
ctx1 := client.NewContext(
context.Background(),
client.Info{
Metadata: client.NewMetadata(map[string][]string{
"key1": {"value1"},
}),
},
)
ctx2 := client.NewContext(
context.Background(),
client.Info{
Metadata: client.NewMetadata(map[string][]string{}),
},
)
mergedCtx := mergeCtx(ctx1, ctx2)
require.NotNil(t, mergedCtx)
mergedMeta := client.FromContext(mergedCtx).Metadata
assert.Equal(t, []string{"value1"}, mergedMeta.Get("key1"))
})
t.Run("merge when contexts have different metadata key values", func(t *testing.T) {
mergeCtx := NewMetadataKeysMergeCtx([]string{"key1"})
ctx1 := client.NewContext(
context.Background(),
client.Info{
Metadata: client.NewMetadata(map[string][]string{
"key1": {"value1"},
}),
},
)
ctx2 := client.NewContext(
context.Background(),
client.Info{
Metadata: client.NewMetadata(map[string][]string{
"key1": {"value2"},
}),
},
)
mergedCtx := mergeCtx(ctx1, ctx2)
require.NotNil(t, mergedCtx)
mergedMeta := client.FromContext(mergedCtx).Metadata
assert.Equal(t, []string{"value1"}, mergedMeta.Get("key1"))
})
t.Run("merge when contexts have different metadata key values for multiple keys", func(t *testing.T) {
mergeCtx := NewMetadataKeysMergeCtx([]string{"key1", "key2"})
ctx1 := client.NewContext(
context.Background(),
client.Info{
Metadata: client.NewMetadata(map[string][]string{
"key1": {"value1"},
"key2": {"value2"},
}),
},
)
ctx2 := client.NewContext(
context.Background(),
client.Info{
Metadata: client.NewMetadata(map[string][]string{
"key1": {"value1"},
"key2": {"different"},
}),
},
)
mergedCtx := mergeCtx(ctx1, ctx2)
require.NotNil(t, mergedCtx)
mergedMeta := client.FromContext(mergedCtx).Metadata
assert.Equal(t, []string{"value1"}, mergedMeta.Get("key1"))
assert.Equal(t, []string{"value2"}, mergedMeta.Get("key2"))
})
t.Run("merge contexts with multiple values for same key", func(t *testing.T) {
mergeCtx := NewMetadataKeysMergeCtx([]string{"key1"})
ctx1 := client.NewContext(
context.Background(),
client.Info{
Metadata: client.NewMetadata(map[string][]string{
"key1": {"value1", "value2"},
}),
},
)
ctx2 := client.NewContext(
context.Background(),
client.Info{
Metadata: client.NewMetadata(map[string][]string{
"key1": {"value1", "value2"},
}),
},
)
mergedCtx := mergeCtx(ctx1, ctx2)
require.NotNil(t, mergedCtx)
mergedMeta := client.FromContext(mergedCtx).Metadata
assert.Equal(t, []string{"value1", "value2"}, mergedMeta.Get("key1"))
})
t.Run("merge when contexts have different multiple values for same key", func(t *testing.T) {
mergeCtx := NewMetadataKeysMergeCtx([]string{"key1"})
ctx1 := client.NewContext(
context.Background(),
client.Info{
Metadata: client.NewMetadata(map[string][]string{
"key1": {"value1", "value2"},
}),
},
)
ctx2 := client.NewContext(
context.Background(),
client.Info{
Metadata: client.NewMetadata(map[string][]string{
"key1": {"value1", "value3"},
}),
},
)
mergedCtx := mergeCtx(ctx1, ctx2)
require.NotNil(t, mergedCtx)
mergedMeta := client.FromContext(mergedCtx).Metadata
assert.Equal(t, []string{"value1", "value2"}, mergedMeta.Get("key1"))
})
}
func TestMetadataKeysPartitioner_GetKey(t *testing.T) {
t.Run("single key with single value", func(t *testing.T) {
partitioner := NewMetadataKeysPartitioner([]string{"key1"})
ctx := client.NewContext(
context.Background(),
client.Info{
Metadata: client.NewMetadata(map[string][]string{
"key1": {"value1"},
}),
},
)
key := partitioner.GetKey(ctx, &requesttest.FakeRequest{})
expected := "key1\x00value1"
assert.Equal(t, expected, key)
})
t.Run("single key with multiple values", func(t *testing.T) {
partitioner := NewMetadataKeysPartitioner([]string{"key1"})
ctx := client.NewContext(
context.Background(),
client.Info{
Metadata: client.NewMetadata(map[string][]string{
"key1": {"value1", "value2", "value3"},
}),
},
)
key := partitioner.GetKey(ctx, &requesttest.FakeRequest{})
// Format: key1\0value1\0value2\0value3
expected := "key1\x00value1\x00value2\x00value3"
assert.Equal(t, expected, key)
})
t.Run("multiple keys with values", func(t *testing.T) {
partitioner := NewMetadataKeysPartitioner([]string{"key1", "key2"})
ctx := client.NewContext(
context.Background(),
client.Info{
Metadata: client.NewMetadata(map[string][]string{
"key1": {"value1"},
"key2": {"value2"},
}),
},
)
key := partitioner.GetKey(ctx, &requesttest.FakeRequest{})
// Format: key1\0value1\0key2\0value2 (separator between keys)
expected := "key1\x00value1\x00key2\x00value2"
assert.Equal(t, expected, key)
})
t.Run("multiple keys with multiple values", func(t *testing.T) {
partitioner := NewMetadataKeysPartitioner([]string{"key1", "key2"})
ctx := client.NewContext(
context.Background(),
client.Info{
Metadata: client.NewMetadata(map[string][]string{
"key1": {"value1", "value1b"},
"key2": {"value2", "value2b"},
}),
},
)
key := partitioner.GetKey(ctx, &requesttest.FakeRequest{})
// Format: key1\0value1\0value1b\0key2\0value2\0value2b
expected := "key1\x00value1\x00value1b\x00key2\x00value2\x00value2b"
assert.Equal(t, expected, key)
})
t.Run("keys that don't exist in metadata are skipped", func(t *testing.T) {
partitioner := NewMetadataKeysPartitioner([]string{"key1", "key2", "key3"})
ctx := client.NewContext(
context.Background(),
client.Info{
Metadata: client.NewMetadata(map[string][]string{
"key1": {"value1"},
// key2 is missing
"key3": {"value3"},
}),
},
)
key := partitioner.GetKey(ctx, &requesttest.FakeRequest{})
// Format: key1\0value1\0key3\0value3 (key2 is skipped)
expected := "key1\x00value1\x00key3\x00value3"
assert.Equal(t, expected, key)
})
t.Run("empty metadata returns empty string", func(t *testing.T) {
partitioner := NewMetadataKeysPartitioner([]string{"key1", "key2"})
ctx := client.NewContext(
context.Background(),
client.Info{
Metadata: client.NewMetadata(map[string][]string{}),
},
)
key := partitioner.GetKey(ctx, &requesttest.FakeRequest{})
assert.Empty(t, key)
})
t.Run("keys with empty values are skipped", func(t *testing.T) {
partitioner := NewMetadataKeysPartitioner([]string{"key1", "key2", "key3"})
ctx := client.NewContext(
context.Background(),
client.Info{
Metadata: client.NewMetadata(map[string][]string{
"key1": {"value1"},
"key2": {}, // empty slice
"key3": {"value3"},
}),
},
)
key := partitioner.GetKey(ctx, &requesttest.FakeRequest{})
// Format: key1\0value1\0key3\0value3 (key2 is skipped)
expected := "key1\x00value1\x00key3\x00value3"
assert.Equal(t, expected, key)
})
t.Run("keys in order respect partitioner key order", func(t *testing.T) {
partitioner := NewMetadataKeysPartitioner([]string{"key3", "key1", "key2"})
ctx := client.NewContext(
context.Background(),
client.Info{
Metadata: client.NewMetadata(map[string][]string{
"key1": {"value1"},
"key2": {"value2"},
"key3": {"value3"},
}),
},
)
key := partitioner.GetKey(ctx, &requesttest.FakeRequest{})
// Format should follow partitioner order: key3\0value3\0key1\0value1\0key2\0value2
expected := "key3\x00value3\x00key1\x00value1\x00key2\x00value2"
assert.Equal(t, expected, key)
})
t.Run("additional metadata keys not in partitioner are ignored", func(t *testing.T) {
partitioner := NewMetadataKeysPartitioner([]string{"key1"})
ctx := client.NewContext(
context.Background(),
client.Info{
Metadata: client.NewMetadata(map[string][]string{
"key1": {"value1"},
"other": {"other1"},
"extra": {"extra1"},
}),
},
)
key := partitioner.GetKey(ctx, &requesttest.FakeRequest{})
// Format: key1\0value1 (other keys are ignored)
expected := "key1\x00value1"
assert.Equal(t, expected, key)
})
t.Run("returns nil partitioner when keys are empty", func(t *testing.T) {
partitioner := NewMetadataKeysPartitioner([]string{})
assert.Nil(t, partitioner)
})
t.Run("returns nil merge function when keys are empty", func(t *testing.T) {
mergeCtx := NewMetadataKeysMergeCtx([]string{})
assert.Nil(t, mergeCtx)
})
}
================================================
FILE: exporter/exporterhelper/internal/queuebatch/metrics.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queuebatch // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
import (
"context"
"errors"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/queue"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sizer"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/xpdata/pref"
pdatareq "go.opentelemetry.io/collector/pdata/xpdata/request"
)
var (
metricsMarshaler = &pmetric.ProtoMarshaler{}
metricsUnmarshaler = &pmetric.ProtoUnmarshaler{}
)
func NewMetricsQueueBatchSettings() Settings[request.Request] {
return Settings[request.Request]{
ReferenceCounter: metricsReferenceCounter{},
Encoding: metricsEncoding{},
}
}
var (
_ request.Request = (*metricsRequest)(nil)
_ request.ErrorHandler = (*metricsRequest)(nil)
)
type metricsRequest struct {
md pmetric.Metrics
cachedSize int
}
func newMetricsRequest(md pmetric.Metrics) request.Request {
return &metricsRequest{
md: md,
cachedSize: -1,
}
}
type metricsEncoding struct{}
var _ encoding[request.Request] = metricsEncoding{}
func (metricsEncoding) Unmarshal(bytes []byte) (context.Context, request.Request, error) {
if queue.PersistRequestContextOnRead() {
ctx, metrics, err := pdatareq.UnmarshalMetrics(bytes)
if errors.Is(err, pdatareq.ErrInvalidFormat) {
// fall back to unmarshaling without context
metrics, err = metricsUnmarshaler.UnmarshalMetrics(bytes)
}
return ctx, newMetricsRequest(metrics), err
}
metrics, err := metricsUnmarshaler.UnmarshalMetrics(bytes)
if err != nil {
var req request.Request
return context.Background(), req, err
}
return context.Background(), newMetricsRequest(metrics), nil
}
func (metricsEncoding) Marshal(ctx context.Context, req request.Request) ([]byte, error) {
metrics := req.(*metricsRequest).md
if queue.PersistRequestContextOnWrite() {
return pdatareq.MarshalMetrics(ctx, metrics)
}
return metricsMarshaler.MarshalMetrics(metrics)
}
var _ queue.ReferenceCounter[request.Request] = metricsReferenceCounter{}
type metricsReferenceCounter struct{}
func (metricsReferenceCounter) Ref(req request.Request) {
pref.RefMetrics(req.(*metricsRequest).md)
}
func (metricsReferenceCounter) Unref(req request.Request) {
pref.UnrefMetrics(req.(*metricsRequest).md)
}
func (req *metricsRequest) OnError(err error) request.Request {
var metricsError consumererror.Metrics
if errors.As(err, &metricsError) {
// TODO: Add logic to unref the new request created here.
return newMetricsRequest(metricsError.Data())
}
return req
}
func (req *metricsRequest) ItemsCount() int {
return req.md.DataPointCount()
}
func (req *metricsRequest) size(sizer sizer.MetricsSizer) int {
if req.cachedSize == -1 {
req.cachedSize = sizer.MetricsSize(req.md)
}
return req.cachedSize
}
func (req *metricsRequest) setCachedSize(count int) {
req.cachedSize = count
}
func (req *metricsRequest) BytesSize() int {
return metricsMarshaler.MetricsSize(req.md)
}
// RequestFromMetrics returns a RequestFromMetricsFunc that converts pdata.Metrics into a Request.
func RequestFromMetrics() request.RequestConverterFunc[pmetric.Metrics] {
return func(_ context.Context, md pmetric.Metrics) (request.Request, error) {
return newMetricsRequest(md), nil
}
}
// RequestConsumeFromMetrics returns a RequestConsumeFunc that consumes pmetric.Metrics.
func RequestConsumeFromMetrics(pusher consumer.ConsumeMetricsFunc) request.RequestConsumeFunc {
return func(ctx context.Context, request request.Request) error {
return pusher.ConsumeMetrics(ctx, request.(*metricsRequest).md)
}
}
================================================
FILE: exporter/exporterhelper/internal/queuebatch/metrics_batch.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queuebatch // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
import (
"context"
"errors"
"fmt"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sizer"
"go.opentelemetry.io/collector/pdata/pmetric"
)
// MergeSplit splits and/or merges the provided metrics request and the current request into one or more requests
// conforming with the MaxSizeConfig.
func (req *metricsRequest) MergeSplit(_ context.Context, maxSize int, szt request.SizerType, r2 request.Request) ([]request.Request, error) {
var sz sizer.MetricsSizer
switch szt {
case request.SizerTypeItems:
sz = &sizer.MetricsCountSizer{}
case request.SizerTypeBytes:
sz = &sizer.MetricsBytesSizer{}
default:
return nil, errors.New("unknown sizer type")
}
if r2 != nil {
req2, ok := r2.(*metricsRequest)
if !ok {
return nil, errors.New("invalid input type")
}
req2.mergeTo(req, sz)
}
// If no limit we can simply merge the new request into the current and return.
if maxSize == 0 {
return []request.Request{req}, nil
}
return req.split(maxSize, sz)
}
func (req *metricsRequest) mergeTo(dst *metricsRequest, sz sizer.MetricsSizer) {
if sz != nil {
dst.setCachedSize(dst.size(sz) + req.size(sz))
req.setCachedSize(0)
}
req.md.ResourceMetrics().MoveAndAppendTo(dst.md.ResourceMetrics())
}
func (req *metricsRequest) split(maxSize int, sz sizer.MetricsSizer) ([]request.Request, error) {
var res []request.Request
for req.size(sz) > maxSize {
md, rmSize := extractMetrics(req.md, maxSize, sz)
if md.DataPointCount() == 0 {
return res, fmt.Errorf("one datapoint size is greater than max size, dropping items: %d", req.md.DataPointCount())
}
req.setCachedSize(req.size(sz) - rmSize)
res = append(res, newMetricsRequest(md))
}
res = append(res, req)
return res, nil
}
// extractMetrics extracts metrics from srcMetrics until capacity is reached.
func extractMetrics(srcMetrics pmetric.Metrics, capacity int, sz sizer.MetricsSizer) (pmetric.Metrics, int) {
destMetrics := pmetric.NewMetrics()
capacityLeft := capacity - sz.MetricsSize(destMetrics)
removedSize := 0
srcMetrics.ResourceMetrics().RemoveIf(func(srcRM pmetric.ResourceMetrics) bool {
// If the no more capacity left just return.
if capacityLeft == 0 {
return false
}
rawRlSize := sz.ResourceMetricsSize(srcRM)
rlSize := sz.DeltaSize(rawRlSize)
if rlSize > capacityLeft {
extSrcRM, extRmSize := extractResourceMetrics(srcRM, capacityLeft, sz)
// This cannot make it to exactly 0 for the bytes,
// force it to be 0 since that is the stopping condition.
capacityLeft = 0
removedSize += extRmSize
// There represents the delta between the delta sizes.
removedSize += rlSize - rawRlSize - (sz.DeltaSize(rawRlSize-extRmSize) - (rawRlSize - extRmSize))
// It is possible that for the bytes scenario, the extracted field contains no scope metrics.
// Do not add it to the destination if that is the case.
if extSrcRM.ScopeMetrics().Len() > 0 {
extSrcRM.MoveTo(destMetrics.ResourceMetrics().AppendEmpty())
}
return extSrcRM.ScopeMetrics().Len() != 0
}
capacityLeft -= rlSize
removedSize += rlSize
srcRM.MoveTo(destMetrics.ResourceMetrics().AppendEmpty())
return true
})
return destMetrics, removedSize
}
// extractResourceMetrics extracts resource metrics and returns a new resource metrics with the specified number of data points.
func extractResourceMetrics(srcRM pmetric.ResourceMetrics, capacity int, sz sizer.MetricsSizer) (pmetric.ResourceMetrics, int) {
destRM := pmetric.NewResourceMetrics()
destRM.SetSchemaUrl(srcRM.SchemaUrl())
srcRM.Resource().CopyTo(destRM.Resource())
// Take into account that this can have max "capacity", so when added to the parent will need space for the extra delta size.
capacityLeft := capacity - (sz.DeltaSize(capacity) - capacity) - sz.ResourceMetricsSize(destRM)
removedSize := 0
srcRM.ScopeMetrics().RemoveIf(func(srcSM pmetric.ScopeMetrics) bool {
// If the no more capacity left just return.
if capacityLeft == 0 {
return false
}
rawSmSize := sz.ScopeMetricsSize(srcSM)
smSize := sz.DeltaSize(rawSmSize)
if smSize > capacityLeft {
extSrcSM, extSmSize := extractScopeMetrics(srcSM, capacityLeft, sz)
// This cannot make it to exactly 0 for the bytes,
// force it to be 0 since that is the stopping condition.
capacityLeft = 0
removedSize += extSmSize
// There represents the delta between the delta sizes.
removedSize += smSize - rawSmSize - (sz.DeltaSize(rawSmSize-extSmSize) - (rawSmSize - extSmSize))
// It is possible that for the bytes scenario, the extracted field contains no scope metrics.
// Do not add it to the destination if that is the case.
if extSrcSM.Metrics().Len() > 0 {
extSrcSM.MoveTo(destRM.ScopeMetrics().AppendEmpty())
}
return extSrcSM.Metrics().Len() != 0
}
capacityLeft -= smSize
removedSize += smSize
srcSM.MoveTo(destRM.ScopeMetrics().AppendEmpty())
return true
})
return destRM, removedSize
}
// extractScopeMetrics extracts scope metrics and returns a new scope metrics with the specified number of data points.
func extractScopeMetrics(srcSM pmetric.ScopeMetrics, capacity int, sz sizer.MetricsSizer) (pmetric.ScopeMetrics, int) {
destSM := pmetric.NewScopeMetrics()
destSM.SetSchemaUrl(srcSM.SchemaUrl())
srcSM.Scope().CopyTo(destSM.Scope())
// Take into account that this can have max "capacity", so when added to the parent will need space for the extra delta size.
capacityLeft := capacity - (sz.DeltaSize(capacity) - capacity) - sz.ScopeMetricsSize(destSM)
removedSize := 0
srcSM.Metrics().RemoveIf(func(srcSM pmetric.Metric) bool {
// If the no more capacity left just return.
if capacityLeft == 0 {
return false
}
rawRmSize := sz.MetricSize(srcSM)
rmSize := sz.DeltaSize(rawRmSize)
if rmSize > capacityLeft {
extSrcDP, extRmSize := extractMetricDataPoints(srcSM, capacityLeft, sz)
// This cannot make it to exactly 0 for the bytes,
// force it to be 0 since that is the stopping condition.
capacityLeft = 0
removedSize += extRmSize
// There represents the delta between the delta sizes.
removedSize += rmSize - rawRmSize - (sz.DeltaSize(rawRmSize-extRmSize) - (rawRmSize - extRmSize))
// It is possible that for the bytes scenario, the extracted field contains no datapoints.
// Do not add it to the destination if that is the case.
if dataPointsLen(extSrcDP) > 0 {
extSrcDP.MoveTo(destSM.Metrics().AppendEmpty())
}
return dataPointsLen(extSrcDP) != 0
}
capacityLeft -= rmSize
removedSize += rmSize
srcSM.MoveTo(destSM.Metrics().AppendEmpty())
return true
})
return destSM, removedSize
}
func extractMetricDataPoints(srcMetric pmetric.Metric, capacity int, sz sizer.MetricsSizer) (pmetric.Metric, int) {
destMetric := pmetric.NewMetric()
destMetric.SetName(srcMetric.Name())
destMetric.SetDescription(srcMetric.Description())
destMetric.SetUnit(srcMetric.Unit())
srcMetric.Metadata().CopyTo(destMetric.Metadata())
var removedSize int
switch srcMetric.Type() {
case pmetric.MetricTypeGauge:
removedSize = extractGaugeDataPoints(srcMetric.Gauge(), destMetric, capacity, sz)
case pmetric.MetricTypeSum:
removedSize = extractSumDataPoints(srcMetric.Sum(), destMetric, capacity, sz)
destMetric.Sum().SetIsMonotonic(srcMetric.Sum().IsMonotonic())
destMetric.Sum().SetAggregationTemporality(srcMetric.Sum().AggregationTemporality())
case pmetric.MetricTypeHistogram:
removedSize = extractHistogramDataPoints(srcMetric.Histogram(), destMetric, capacity, sz)
destMetric.Histogram().SetAggregationTemporality(srcMetric.Histogram().AggregationTemporality())
case pmetric.MetricTypeExponentialHistogram:
removedSize = extractExponentialHistogramDataPoints(srcMetric.ExponentialHistogram(), destMetric, capacity, sz)
destMetric.ExponentialHistogram().SetAggregationTemporality(srcMetric.ExponentialHistogram().AggregationTemporality())
case pmetric.MetricTypeSummary:
removedSize = extractSummaryDataPoints(srcMetric.Summary(), destMetric, capacity, sz)
}
return destMetric, removedSize
}
func dataPointsLen(m pmetric.Metric) int {
switch m.Type() {
case pmetric.MetricTypeGauge:
return m.Gauge().DataPoints().Len()
case pmetric.MetricTypeSum:
return m.Sum().DataPoints().Len()
case pmetric.MetricTypeHistogram:
return m.Histogram().DataPoints().Len()
case pmetric.MetricTypeExponentialHistogram:
return m.ExponentialHistogram().DataPoints().Len()
case pmetric.MetricTypeSummary:
return m.Summary().DataPoints().Len()
}
return 0
}
func extractGaugeDataPoints(srcGauge pmetric.Gauge, destMetric pmetric.Metric, capacity int, sz sizer.MetricsSizer) int {
destGauge := destMetric.SetEmptyGauge()
// Take into account that this can have max "capacity", so when added to the parent will need space for the extra delta size.
capacityLeft := capacity - (sz.DeltaSize(capacity) - capacity) - sz.MetricSize(destMetric)
removedSize := 0
srcGauge.DataPoints().RemoveIf(func(srcDP pmetric.NumberDataPoint) bool {
// If the no more capacity left just return.
if capacityLeft == 0 {
return false
}
rdSize := sz.DeltaSize(sz.NumberDataPointSize(srcDP))
if rdSize > capacityLeft {
// This cannot make it to exactly 0 for the bytes,
// force it to be 0 since that is the stopping condition.
capacityLeft = 0
return false
}
capacityLeft -= rdSize
removedSize += rdSize
srcDP.MoveTo(destGauge.DataPoints().AppendEmpty())
return true
})
return removedSize
}
func extractSumDataPoints(srcSum pmetric.Sum, destMetric pmetric.Metric, capacity int, sz sizer.MetricsSizer) int {
destSum := destMetric.SetEmptySum()
// Take into account that this can have max "capacity", so when added to the parent will need space for the extra delta size.
capacityLeft := capacity - (sz.DeltaSize(capacity) - capacity) - sz.MetricSize(destMetric)
removedSize := 0
srcSum.DataPoints().RemoveIf(func(srcDP pmetric.NumberDataPoint) bool {
// If the no more capacity left just return.
if capacityLeft == 0 {
return false
}
rdSize := sz.DeltaSize(sz.NumberDataPointSize(srcDP))
if rdSize > capacityLeft {
// This cannot make it to exactly 0 for the bytes,
// force it to be 0 since that is the stopping condition.
capacityLeft = 0
return false
}
capacityLeft -= rdSize
removedSize += rdSize
srcDP.MoveTo(destSum.DataPoints().AppendEmpty())
return true
})
return removedSize
}
func extractHistogramDataPoints(srcHistogram pmetric.Histogram, destMetric pmetric.Metric, capacity int, sz sizer.MetricsSizer) int {
destHistogram := destMetric.SetEmptyHistogram()
// Take into account that this can have max "capacity", so when added to the parent will need space for the extra delta size.
capacityLeft := capacity - (sz.DeltaSize(capacity) - capacity) - sz.MetricSize(destMetric)
removedSize := 0
srcHistogram.DataPoints().RemoveIf(func(srcDP pmetric.HistogramDataPoint) bool {
// If the no more capacity left just return.
if capacityLeft == 0 {
return false
}
rdSize := sz.DeltaSize(sz.HistogramDataPointSize(srcDP))
if rdSize > capacityLeft {
// This cannot make it to exactly 0 for the bytes,
// force it to be 0 since that is the stopping condition.
capacityLeft = 0
return false
}
capacityLeft -= rdSize
removedSize += rdSize
srcDP.MoveTo(destHistogram.DataPoints().AppendEmpty())
return true
})
return removedSize
}
func extractExponentialHistogramDataPoints(srcExponentialHistogram pmetric.ExponentialHistogram, destMetric pmetric.Metric, capacity int, sz sizer.MetricsSizer) int {
destExponentialHistogram := destMetric.SetEmptyExponentialHistogram()
// Take into account that this can have max "capacity", so when added to the parent will need space for the extra delta size.
capacityLeft := capacity - (sz.DeltaSize(capacity) - capacity) - sz.MetricSize(destMetric)
removedSize := 0
srcExponentialHistogram.DataPoints().RemoveIf(func(srcDP pmetric.ExponentialHistogramDataPoint) bool {
// If the no more capacity left just return.
if capacityLeft == 0 {
return false
}
rdSize := sz.DeltaSize(sz.ExponentialHistogramDataPointSize(srcDP))
if rdSize > capacityLeft {
// This cannot make it to exactly 0 for the bytes,
// force it to be 0 since that is the stopping condition.
capacityLeft = 0
return false
}
capacityLeft -= rdSize
removedSize += rdSize
srcDP.MoveTo(destExponentialHistogram.DataPoints().AppendEmpty())
return true
})
return removedSize
}
func extractSummaryDataPoints(srcSummary pmetric.Summary, destMetric pmetric.Metric, capacity int, sz sizer.MetricsSizer) int {
destSummary := destMetric.SetEmptySummary()
// Take into account that this can have max "capacity", so when added to the parent will need space for the extra delta size.
capacityLeft := capacity - (sz.DeltaSize(capacity) - capacity) - sz.MetricSize(destMetric)
removedSize := 0
srcSummary.DataPoints().RemoveIf(func(srcDP pmetric.SummaryDataPoint) bool {
// If the no more capacity left just return.
if capacityLeft == 0 {
return false
}
rdSize := sz.DeltaSize(sz.SummaryDataPointSize(srcDP))
if rdSize > capacityLeft {
// This cannot make it to exactly 0 for the bytes,
// force it to be 0 since that is the stopping condition.
capacityLeft = 0
return false
}
capacityLeft -= rdSize
removedSize += rdSize
srcDP.MoveTo(destSummary.DataPoints().AppendEmpty())
return true
})
return removedSize
}
================================================
FILE: exporter/exporterhelper/internal/queuebatch/metrics_batch_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queuebatch // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sizer"
"go.opentelemetry.io/collector/internal/testutil"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/testdata"
)
func TestMergeMetrics(t *testing.T) {
mr1 := newMetricsRequest(testdata.GenerateMetrics(2))
mr2 := newMetricsRequest(testdata.GenerateMetrics(3))
res, err := mr1.MergeSplit(context.Background(), 0, request.SizerTypeItems, mr2)
require.NoError(t, err)
// Every metric has 2 data points.
assert.Equal(t, 2*5, res[0].ItemsCount())
}
func TestMergeSplitMetrics(t *testing.T) {
s := sizer.MetricsCountSizer{}
tests := []struct {
name string
szt request.SizerType
maxSize int
mr1 request.Request
mr2 request.Request
expected []request.Request
}{
{
name: "both_requests_empty",
szt: request.SizerTypeItems,
maxSize: 10,
mr1: newMetricsRequest(pmetric.NewMetrics()),
mr2: newMetricsRequest(pmetric.NewMetrics()),
expected: []request.Request{newMetricsRequest(pmetric.NewMetrics())},
},
{
name: "first_request_empty",
szt: request.SizerTypeItems,
maxSize: 10,
mr1: newMetricsRequest(pmetric.NewMetrics()),
mr2: newMetricsRequest(testdata.GenerateMetrics(5)),
expected: []request.Request{newMetricsRequest(testdata.GenerateMetrics(5))},
},
{
name: "first_empty_second_nil",
szt: request.SizerTypeItems,
maxSize: 10,
mr1: newMetricsRequest(pmetric.NewMetrics()),
mr2: nil,
expected: []request.Request{newMetricsRequest(pmetric.NewMetrics())},
},
{
name: "merge_only",
szt: request.SizerTypeItems,
maxSize: 60,
mr1: newMetricsRequest(testdata.GenerateMetrics(10)),
mr2: newMetricsRequest(testdata.GenerateMetrics(14)),
expected: []request.Request{newMetricsRequest(func() pmetric.Metrics {
metrics := testdata.GenerateMetrics(10)
testdata.GenerateMetrics(14).ResourceMetrics().MoveAndAppendTo(metrics.ResourceMetrics())
return metrics
}())},
},
{
name: "split_only",
szt: request.SizerTypeItems,
maxSize: 14,
mr1: newMetricsRequest(pmetric.NewMetrics()),
mr2: newMetricsRequest(testdata.GenerateMetrics(15)), // 15 metrics, 30 data points
expected: []request.Request{
newMetricsRequest(testdata.GenerateMetrics(7)), // 7 metrics, 14 data points
newMetricsRequest(testdata.GenerateMetrics(7)), // 7 metrics, 14 data points
newMetricsRequest(testdata.GenerateMetrics(1)), // 1 metric, 2 data points
},
},
{
name: "split_and_merge",
szt: request.SizerTypeItems,
maxSize: 28,
mr1: newMetricsRequest(testdata.GenerateMetrics(7)), // 7 metrics, 14 data points
mr2: newMetricsRequest(testdata.GenerateMetrics(25)), // 25 metrics, 50 data points
expected: []request.Request{
newMetricsRequest(func() pmetric.Metrics {
metrics := testdata.GenerateMetrics(7)
testdata.GenerateMetrics(7).ResourceMetrics().MoveAndAppendTo(metrics.ResourceMetrics())
return metrics
}()),
newMetricsRequest(testdata.GenerateMetrics(14)), // 14 metrics, 28 data points
newMetricsRequest(testdata.GenerateMetrics(4)), // 4 metrics, 8 data points
},
},
{
name: "scope_metrics_split",
szt: request.SizerTypeItems,
maxSize: 8,
mr1: newMetricsRequest(func() pmetric.Metrics {
md := testdata.GenerateMetrics(4)
extraScopeMetrics := md.ResourceMetrics().At(0).ScopeMetrics().AppendEmpty()
testdata.GenerateMetrics(4).ResourceMetrics().At(0).ScopeMetrics().At(0).MoveTo(extraScopeMetrics)
extraScopeMetrics.Scope().SetName("extra scope")
return md
}()),
mr2: nil,
expected: []request.Request{
newMetricsRequest(testdata.GenerateMetrics(4)),
newMetricsRequest(func() pmetric.Metrics {
md := testdata.GenerateMetrics(4)
md.ResourceMetrics().At(0).ScopeMetrics().At(0).Scope().SetName("extra scope")
return md
}()),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
res, err := tt.mr1.MergeSplit(context.Background(), tt.maxSize, tt.szt, tt.mr2)
require.NoError(t, err)
assert.Len(t, res, len(tt.expected))
for i := range res {
expected := tt.expected[i].(*metricsRequest)
actual := res[i].(*metricsRequest)
assert.Equal(t, expected.size(&s), actual.size(&s))
}
})
}
}
func TestSplitMetricsWithDataPointSplit(t *testing.T) {
generateTestMetrics := func(metricType pmetric.MetricType) pmetric.Metrics {
md := pmetric.NewMetrics()
m := md.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty()
m.SetName("test_metric")
m.SetDescription("test_description")
m.SetUnit("test_unit")
m.Metadata().PutStr("test_metadata_key", "test_metadata_value")
const numDataPoints = 2
switch metricType {
case pmetric.MetricTypeSum:
sum := m.SetEmptySum()
for i := range numDataPoints {
sum.DataPoints().AppendEmpty().SetIntValue(int64(i + 1))
}
case pmetric.MetricTypeGauge:
gauge := m.SetEmptyGauge()
for i := range numDataPoints {
gauge.DataPoints().AppendEmpty().SetIntValue(int64(i + 1))
}
case pmetric.MetricTypeHistogram:
hist := m.SetEmptyHistogram()
for i := range uint64(numDataPoints) {
hist.DataPoints().AppendEmpty().SetCount(i + 1)
}
case pmetric.MetricTypeExponentialHistogram:
expHist := m.SetEmptyExponentialHistogram()
for i := range uint64(numDataPoints) {
expHist.DataPoints().AppendEmpty().SetCount(i + 1)
}
case pmetric.MetricTypeSummary:
summary := m.SetEmptySummary()
for i := range uint64(numDataPoints) {
summary.DataPoints().AppendEmpty().SetCount(i + 1)
}
}
return md
}
tests := []struct {
name string
metricType pmetric.MetricType
}{
{
name: "sum",
metricType: pmetric.MetricTypeSum,
},
{
name: "gauge",
metricType: pmetric.MetricTypeGauge,
},
{
name: "histogram",
metricType: pmetric.MetricTypeHistogram,
},
{
name: "exponential_histogram",
metricType: pmetric.MetricTypeExponentialHistogram,
},
{
name: "summary",
metricType: pmetric.MetricTypeSummary,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Generate metrics with 2 data points.
mr1 := newMetricsRequest(generateTestMetrics(tt.metricType))
// Split by data point, so maxSize is 1.
res, err := mr1.MergeSplit(context.Background(), 1, request.SizerTypeItems, nil)
require.NoError(t, err)
require.Len(t, res, 2)
for _, req := range res {
actualRequest := req.(*metricsRequest)
// Each split request should contain one data point.
assert.Equal(t, 1, actualRequest.ItemsCount())
m := actualRequest.md.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0)
assert.Equal(t, "test_metric", m.Name())
assert.Equal(t, "test_description", m.Description())
assert.Equal(t, "test_unit", m.Unit())
assert.Equal(t, 1, m.Metadata().Len())
val, ok := m.Metadata().Get("test_metadata_key")
assert.True(t, ok)
assert.Equal(t, "test_metadata_value", val.AsString())
}
})
}
}
func TestMergeSplitMetricsInputNotModifiedIfErrorReturned(t *testing.T) {
r1 := newMetricsRequest(testdata.GenerateMetrics(18)) // 18 metrics, 36 data points
r2 := newLogsRequest(testdata.GenerateLogs(3))
_, err := r1.MergeSplit(context.Background(), 10, request.SizerTypeItems, r2)
require.Error(t, err)
assert.Equal(t, 36, r1.ItemsCount())
}
func TestExtractMetrics(t *testing.T) {
for i := range 20 {
md := testdata.GenerateMetrics(10)
extractedMetrics, _ := extractMetrics(md, i, &sizer.MetricsCountSizer{})
assert.Equal(t, i, extractedMetrics.DataPointCount())
assert.Equal(t, 20-i, md.DataPointCount())
}
}
func TestExtractMetricsInvalidMetric(t *testing.T) {
md := testdata.GenerateMetricsMetricTypeInvalid()
extractedMetrics, _ := extractMetrics(md, 10, &sizer.MetricsCountSizer{})
assert.Equal(t, testdata.GenerateMetricsMetricTypeInvalid(), extractedMetrics)
assert.Equal(t, 0, md.ResourceMetrics().Len())
}
func TestMergeSplitManySmallMetrics(t *testing.T) {
// All requests merge into a single batch.
merged := []request.Request{newMetricsRequest(testdata.GenerateMetrics(1))}
for range 1000 {
lr2 := newMetricsRequest(testdata.GenerateMetrics(10))
res, _ := merged[len(merged)-1].MergeSplit(context.Background(), 20000, request.SizerTypeItems, lr2)
merged = append(merged[0:len(merged)-1], res...)
}
assert.Len(t, merged, 2)
}
func BenchmarkSplittingBasedOnItemCountManySmallMetrics(b *testing.B) {
testutil.SkipGCHeavyBench(b)
// All requests merge into a single batch.
b.ReportAllocs()
for b.Loop() {
merged := []request.Request{newMetricsRequest(testdata.GenerateMetrics(10))}
for range 1000 {
lr2 := newMetricsRequest(testdata.GenerateMetrics(10))
res, _ := merged[len(merged)-1].MergeSplit(context.Background(), 20020, request.SizerTypeItems, lr2)
merged = append(merged[0:len(merged)-1], res...)
}
assert.Len(b, merged, 1)
}
}
func BenchmarkSplittingBasedOnItemCountManyMetricsSlightlyAboveLimit(b *testing.B) {
testutil.SkipGCHeavyBench(b)
// Every incoming request results in a split.
b.ReportAllocs()
for b.Loop() {
merged := []request.Request{newMetricsRequest(testdata.GenerateMetrics(0))}
for range 10 {
lr2 := newMetricsRequest(testdata.GenerateMetrics(10001))
res, _ := merged[len(merged)-1].MergeSplit(context.Background(), 20000, request.SizerTypeItems, lr2)
merged = append(merged[0:len(merged)-1], res...)
}
assert.Len(b, merged, 11)
}
}
func BenchmarkSplittingBasedOnItemCountHugeMetrics(b *testing.B) {
testutil.SkipGCHeavyBench(b)
// One request splits into many batches.
b.ReportAllocs()
for b.Loop() {
merged := []request.Request{newMetricsRequest(testdata.GenerateMetrics(0))}
lr2 := newMetricsRequest(testdata.GenerateMetrics(100000))
res, _ := merged[len(merged)-1].MergeSplit(context.Background(), 20000, request.SizerTypeItems, lr2)
merged = append(merged[0:len(merged)-1], res...)
assert.Len(b, merged, 10)
}
}
func TestMergeSplitMetricsBasedOnByteSize(t *testing.T) {
tests := []struct {
name string
szt request.SizerType
maxSize int
mr1 request.Request
mr2 request.Request
expected []request.Request
expectSplitError bool
}{
{
name: "both_requests_empty",
szt: request.SizerTypeBytes,
maxSize: metricsMarshaler.MetricsSize(testdata.GenerateMetrics(10)),
mr1: newMetricsRequest(pmetric.NewMetrics()),
mr2: newMetricsRequest(pmetric.NewMetrics()),
expected: []request.Request{newMetricsRequest(pmetric.NewMetrics())},
},
{
name: "first_request_empty",
szt: request.SizerTypeBytes,
maxSize: metricsMarshaler.MetricsSize(testdata.GenerateMetrics(10)),
mr1: newMetricsRequest(pmetric.NewMetrics()),
mr2: newMetricsRequest(testdata.GenerateMetrics(5)),
expected: []request.Request{newMetricsRequest(testdata.GenerateMetrics(5))},
},
{
name: "first_empty_second_nil",
szt: request.SizerTypeBytes,
maxSize: metricsMarshaler.MetricsSize(testdata.GenerateMetrics(10)),
mr1: newMetricsRequest(pmetric.NewMetrics()),
mr2: nil,
expected: []request.Request{newMetricsRequest(pmetric.NewMetrics())},
},
{
name: "merge_only",
szt: request.SizerTypeBytes,
maxSize: metricsMarshaler.MetricsSize(testdata.GenerateMetrics(15)) - 1,
mr1: newMetricsRequest(testdata.GenerateMetrics(7)),
mr2: newMetricsRequest(testdata.GenerateMetrics(7)),
expected: []request.Request{newMetricsRequest(func() pmetric.Metrics {
md := testdata.GenerateMetrics(7)
testdata.GenerateMetrics(7).ResourceMetrics().MoveAndAppendTo(md.ResourceMetrics())
return md
}())},
},
{
name: "split_only",
szt: request.SizerTypeBytes,
maxSize: metricsMarshaler.MetricsSize(testdata.GenerateMetrics(7)) + 1,
mr1: newMetricsRequest(pmetric.NewMetrics()),
mr2: newMetricsRequest(testdata.GenerateMetrics(17)),
expected: []request.Request{
newMetricsRequest(testdata.GenerateMetrics(7)),
newMetricsRequest(testdata.GenerateMetrics(7)),
newMetricsRequest(testdata.GenerateMetrics(3)),
},
},
{
name: "merge_and_split",
szt: request.SizerTypeBytes,
maxSize: metricsMarshaler.MetricsSize(testdata.GenerateMetrics(7)) + 1,
mr1: newMetricsRequest(testdata.GenerateMetrics(14)),
mr2: newMetricsRequest(testdata.GenerateMetrics(11)),
expected: []request.Request{
newMetricsRequest(testdata.GenerateMetrics(7)),
newMetricsRequest(testdata.GenerateMetrics(7)),
newMetricsRequest(testdata.GenerateMetrics(7)),
newMetricsRequest(testdata.GenerateMetrics(4)),
},
},
{
name: "scope_metrics_split",
szt: request.SizerTypeBytes,
maxSize: metricsMarshaler.MetricsSize(testdata.GenerateMetrics(7)) + 1,
mr1: newMetricsRequest(func() pmetric.Metrics {
md := testdata.GenerateMetrics(7)
extraScopeMetrics := md.ResourceMetrics().At(0).ScopeMetrics().AppendEmpty()
testdata.GenerateMetrics(7).ResourceMetrics().At(0).ScopeMetrics().At(0).MoveTo(extraScopeMetrics)
extraScopeMetrics.Scope().SetName("extra scope")
return md
}()),
mr2: nil,
expected: []request.Request{
newMetricsRequest(testdata.GenerateMetrics(7)),
newMetricsRequest(func() pmetric.Metrics {
md := testdata.GenerateMetrics(7)
md.ResourceMetrics().At(0).ScopeMetrics().At(0).Scope().SetName("extra scope")
// Remove last data point.
lastDP := md.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(6).Summary().DataPoints().Len()
idx := 0
md.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(6).Summary().DataPoints().RemoveIf(func(pmetric.SummaryDataPoint) bool {
idx++
return idx == lastDP
})
return md
}()),
newMetricsRequest(func() pmetric.Metrics {
md := testdata.GenerateMetrics(7)
md.ResourceMetrics().At(0).ScopeMetrics().At(0).Scope().SetName("extra scope")
// Remove all metrics but last one
lastM := md.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().Len()
idx := 0
md.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().RemoveIf(func(pmetric.Metric) bool {
idx++
return idx != lastM
})
// Remove all data points but last one
lastDP := md.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0).Summary().DataPoints().Len()
idx = 0
md.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0).Summary().DataPoints().RemoveIf(func(pmetric.SummaryDataPoint) bool {
idx++
return idx != lastDP
})
return md
}()),
},
},
{
name: "unsplittable_large_metric",
szt: request.SizerTypeBytes,
maxSize: 10,
mr1: newMetricsRequest(func() pmetric.Metrics {
md := testdata.GenerateMetrics(1)
md.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0).SetDescription(string(make([]byte, 100)))
return md
}()),
mr2: nil,
expected: []request.Request{},
expectSplitError: true,
},
{
name: "splittable_then_unsplittable_metric",
szt: request.SizerTypeBytes,
maxSize: 1000,
mr1: newMetricsRequest(func() pmetric.Metrics {
md := testdata.GenerateMetrics(2)
md.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0).SetDescription(string(make([]byte, 10)))
md.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(1).SetDescription(string(make([]byte, 1001)))
return md
}()),
mr2: nil,
expected: []request.Request{newMetricsRequest(func() pmetric.Metrics {
md := testdata.GenerateMetrics(1)
md.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0).SetDescription(string(make([]byte, 10)))
return md
}())},
expectSplitError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
res, err := tt.mr1.MergeSplit(context.Background(), tt.maxSize, tt.szt, tt.mr2)
if tt.expectSplitError {
require.ErrorContains(t, err, "one datapoint size is greater than max size, dropping items:")
} else {
require.NoError(t, err)
}
require.Len(t, res, len(tt.expected))
for i := range res {
assert.Equal(t, tt.expected[i].(*metricsRequest).md, res[i].(*metricsRequest).md, i)
assert.Equal(t,
metricsMarshaler.MetricsSize(tt.expected[i].(*metricsRequest).md),
metricsMarshaler.MetricsSize(res[i].(*metricsRequest).md))
}
})
}
}
func TestExtractGaugeDataPoints(t *testing.T) {
tests := []struct {
name string
capacity int
numDataPoints int
expectedPoints int
}{
{
name: "extract_all_points",
capacity: 100,
numDataPoints: 2,
expectedPoints: 2,
},
{
name: "extract_partial_points",
capacity: 1,
numDataPoints: 2,
expectedPoints: 1,
},
{
name: "no_capacity",
capacity: 0,
numDataPoints: 2,
expectedPoints: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
srcMetric := pmetric.NewMetric()
gauge := srcMetric.SetEmptyGauge()
for i := 0; i < tt.numDataPoints; i++ {
dp := gauge.DataPoints().AppendEmpty()
dp.SetIntValue(int64(i))
}
sz := &mockMetricsSizer{dpSize: 1}
destMetric := pmetric.NewMetric()
removedSize := extractGaugeDataPoints(gauge, destMetric, tt.capacity, sz)
assert.Equal(t, tt.expectedPoints, destMetric.Gauge().DataPoints().Len())
if tt.expectedPoints > 0 {
assert.Equal(t, tt.expectedPoints, removedSize)
}
})
}
}
func TestExtractSumDataPoints(t *testing.T) {
tests := []struct {
name string
capacity int
numDataPoints int
expectedPoints int
}{
{
name: "extract_all_points",
capacity: 100,
numDataPoints: 2,
expectedPoints: 2,
},
{
name: "extract_partial_points",
capacity: 1,
numDataPoints: 2,
expectedPoints: 1,
},
{
name: "no_capacity",
capacity: 0,
numDataPoints: 2,
expectedPoints: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
srcMetric := pmetric.NewMetric()
sum := srcMetric.SetEmptySum()
for i := 0; i < tt.numDataPoints; i++ {
dp := sum.DataPoints().AppendEmpty()
dp.SetIntValue(int64(i))
}
sz := &mockMetricsSizer{dpSize: 1}
destMetric := pmetric.NewMetric()
removedSize := extractSumDataPoints(sum, destMetric, tt.capacity, sz)
assert.Equal(t, tt.expectedPoints, destMetric.Sum().DataPoints().Len())
if tt.expectedPoints > 0 {
assert.Equal(t, tt.expectedPoints, removedSize)
}
})
}
}
func TestExtractHistogramDataPoints(t *testing.T) {
tests := []struct {
name string
capacity int
numDataPoints int
expectedPoints int
}{
{
name: "extract_all_points",
capacity: 100,
numDataPoints: 2,
expectedPoints: 2,
},
{
name: "extract_partial_points",
capacity: 1,
numDataPoints: 2,
expectedPoints: 1,
},
{
name: "no_capacity",
capacity: 0,
numDataPoints: 2,
expectedPoints: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
srcMetric := pmetric.NewMetric()
histogram := srcMetric.SetEmptyHistogram()
for i := 0; i < tt.numDataPoints; i++ {
dp := histogram.DataPoints().AppendEmpty()
dp.SetCount(uint64(i))
}
sz := &mockMetricsSizer{dpSize: 1}
destMetric := pmetric.NewMetric()
removedSize := extractHistogramDataPoints(histogram, destMetric, tt.capacity, sz)
assert.Equal(t, tt.expectedPoints, destMetric.Histogram().DataPoints().Len())
if tt.expectedPoints > 0 {
assert.Equal(t, tt.expectedPoints, removedSize)
}
})
}
}
func TestExtractExponentialHistogramDataPoints(t *testing.T) {
tests := []struct {
name string
capacity int
numDataPoints int
expectedPoints int
}{
{
name: "extract_all_points",
capacity: 100,
numDataPoints: 2,
expectedPoints: 2,
},
{
name: "extract_partial_points",
capacity: 1,
numDataPoints: 2,
expectedPoints: 1,
},
{
name: "no_capacity",
capacity: 0,
numDataPoints: 2,
expectedPoints: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
srcMetric := pmetric.NewMetric()
expHistogram := srcMetric.SetEmptyExponentialHistogram()
for i := 0; i < tt.numDataPoints; i++ {
dp := expHistogram.DataPoints().AppendEmpty()
dp.SetCount(uint64(i))
}
sz := &mockMetricsSizer{dpSize: 1}
destMetric := pmetric.NewMetric()
removedSize := extractExponentialHistogramDataPoints(expHistogram, destMetric, tt.capacity, sz)
assert.Equal(t, tt.expectedPoints, destMetric.ExponentialHistogram().DataPoints().Len())
if tt.expectedPoints > 0 {
assert.Equal(t, tt.expectedPoints, removedSize)
}
})
}
}
func TestExtractSummaryDataPoints(t *testing.T) {
tests := []struct {
name string
capacity int
numDataPoints int
expectedPoints int
}{
{
name: "extract_all_points",
capacity: 100,
numDataPoints: 2,
expectedPoints: 2,
},
{
name: "extract_partial_points",
capacity: 1,
numDataPoints: 2,
expectedPoints: 1,
},
{
name: "no_capacity",
capacity: 0,
numDataPoints: 2,
expectedPoints: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
srcMetric := pmetric.NewMetric()
summary := srcMetric.SetEmptySummary()
for i := 0; i < tt.numDataPoints; i++ {
dp := summary.DataPoints().AppendEmpty()
dp.SetCount(uint64(i))
}
sz := &mockMetricsSizer{dpSize: 1}
destMetric := pmetric.NewMetric()
removedSize := extractSummaryDataPoints(summary, destMetric, tt.capacity, sz)
assert.Equal(t, tt.expectedPoints, destMetric.Summary().DataPoints().Len())
if tt.expectedPoints > 0 {
assert.Equal(t, tt.expectedPoints, removedSize)
}
})
}
}
func TestMetricsMergeSplitUnknownSizerType(t *testing.T) {
req := newMetricsRequest(pmetric.NewMetrics())
// Call MergeSplit with invalid sizer
_, err := req.MergeSplit(context.Background(), 0, request.SizerType{}, nil)
require.EqualError(t, err, "unknown sizer type")
}
// mockMetricsSizer implements sizer.MetricsSizer interface for testing
type mockMetricsSizer struct {
dpSize int
}
func (m *mockMetricsSizer) MetricsSize(_ pmetric.Metrics) int {
return 0
}
func (m *mockMetricsSizer) MetricSize(_ pmetric.Metric) int {
return 0
}
func (m *mockMetricsSizer) NumberDataPointSize(_ pmetric.NumberDataPoint) int {
return m.dpSize
}
func (m *mockMetricsSizer) HistogramDataPointSize(_ pmetric.HistogramDataPoint) int {
return m.dpSize
}
func (m *mockMetricsSizer) ExponentialHistogramDataPointSize(_ pmetric.ExponentialHistogramDataPoint) int {
return m.dpSize
}
func (m *mockMetricsSizer) SummaryDataPointSize(_ pmetric.SummaryDataPoint) int {
return m.dpSize
}
func (m *mockMetricsSizer) ResourceMetricsSize(_ pmetric.ResourceMetrics) int {
return 0
}
func (m *mockMetricsSizer) ScopeMetricsSize(_ pmetric.ScopeMetrics) int {
return 0
}
func (m *mockMetricsSizer) DeltaSize(size int) int {
return size
}
================================================
FILE: exporter/exporterhelper/internal/queuebatch/metrics_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queuebatch // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/testdata"
)
func TestMetricsRequest(t *testing.T) {
mr := newMetricsRequest(testdata.GenerateMetrics(1))
metricsErr := consumererror.NewMetrics(errors.New("some error"), pmetric.NewMetrics())
assert.Equal(
t,
newMetricsRequest(pmetric.NewMetrics()),
mr.(request.ErrorHandler).OnError(metricsErr),
)
}
================================================
FILE: exporter/exporterhelper/internal/queuebatch/multi_batcher.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queuebatch // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
import (
"context"
"sync"
lru "github.com/hashicorp/golang-lru/v2/simplelru"
"go.uber.org/zap"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/queue"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sender"
)
type multiBatcher struct {
cfg BatchConfig
wp *workerPool
sizer request.Sizer
partitioner Partitioner[request.Request]
mergeCtx func(context.Context, context.Context) context.Context
consumeFunc sender.SendFunc[request.Request]
partitions *lru.LRU[string, *partitionBatcher]
logger *zap.Logger
lock sync.Mutex
}
func newMultiBatcher(
bCfg BatchConfig,
sizer request.Sizer,
wp *workerPool,
partitioner Partitioner[request.Request],
mergeCtx func(context.Context, context.Context) context.Context,
next sender.SendFunc[request.Request],
logger *zap.Logger,
) (*multiBatcher, error) {
mb := &multiBatcher{
cfg: bCfg,
wp: wp,
sizer: sizer,
partitioner: partitioner,
mergeCtx: mergeCtx,
consumeFunc: next,
logger: logger,
}
// Create LRU cache with eviction callback
// TODO: make maxActivePartitionsCount configurable
cache, err := lru.NewLRU[string, *partitionBatcher](10000, func(_ string, pb *partitionBatcher) {
// Flush the partition when evicted
mb.wp.execute(pb.shutdownInternal)
})
if err != nil {
return nil, err
}
mb.partitions = cache
return mb, nil
}
func (mb *multiBatcher) getPartition(ctx context.Context, req request.Request) *partitionBatcher {
key := mb.partitioner.GetKey(ctx, req)
mb.lock.Lock()
defer mb.lock.Unlock()
// Fast path: partition already exists
if pb, ok := mb.partitions.Get(key); ok {
return pb
}
// Create new partition with onEmpty callback to remove from LRU after idle timeout
newPB := newPartitionBatcher(mb.cfg, mb.sizer, mb.mergeCtx, mb.wp, mb.consumeFunc, mb.logger, func() {
mb.lock.Lock()
defer mb.lock.Unlock()
mb.partitions.Remove(key)
})
_ = mb.partitions.Add(key, newPB)
_ = newPB.Start(ctx, nil)
return newPB
}
func (mb *multiBatcher) Start(context.Context, component.Host) error {
return nil
}
func (mb *multiBatcher) Consume(ctx context.Context, req request.Request, done queue.Done) {
shard := mb.getPartition(ctx, req)
shard.Consume(ctx, req, done)
}
// getActivePartitionsCount is test only method
func (mb *multiBatcher) getActivePartitionsCount() int64 {
mb.lock.Lock()
defer mb.lock.Unlock()
return int64(mb.partitions.Len())
}
func (mb *multiBatcher) Shutdown(ctx context.Context) error {
var wg sync.WaitGroup
mb.lock.Lock()
defer mb.lock.Unlock()
for _, key := range mb.partitions.Keys() {
if pb, ok := mb.partitions.Peek(key); ok {
wg.Go(func() {
_ = pb.Shutdown(ctx)
})
}
}
wg.Wait()
mb.partitions.Purge()
return nil
}
================================================
FILE: exporter/exporterhelper/internal/queuebatch/multi_batcher_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queuebatch
import (
"context"
"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/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/requesttest"
)
func TestMultiBatcher_NoTimeout(t *testing.T) {
cfg := BatchConfig{
FlushTimeout: 0,
Sizer: request.SizerTypeItems,
MinSize: 10,
}
sink := requesttest.NewSink()
type partitionKey struct{}
ba, err := newMultiBatcher(cfg,
request.NewItemsSizer(),
newWorkerPool(1),
NewPartitioner(func(ctx context.Context, _ request.Request) string {
return ctx.Value(partitionKey{}).(string)
}),
nil,
sink.Export,
zap.NewNop(),
)
require.NoError(t, err)
require.NoError(t, ba.Start(context.Background(), componenttest.NewNopHost()))
t.Cleanup(func() {
require.NoError(t, ba.Shutdown(context.Background()))
})
done := newFakeDone()
assert.Equal(t, int64(0), ba.getActivePartitionsCount())
ba.Consume(context.WithValue(context.Background(), partitionKey{}, "p1"), &requesttest.FakeRequest{Items: 8}, done)
assert.Equal(t, int64(1), ba.getActivePartitionsCount())
ba.Consume(context.WithValue(context.Background(), partitionKey{}, "p2"), &requesttest.FakeRequest{Items: 6}, done)
assert.Equal(t, int64(2), ba.getActivePartitionsCount())
// Neither batch should be flushed since they haven't reached min threshold.
assert.Equal(t, 0, sink.RequestsCount())
assert.Equal(t, 0, sink.ItemsCount())
ba.Consume(context.WithValue(context.Background(), partitionKey{}, "p1"), &requesttest.FakeRequest{Items: 8}, done)
assert.Eventually(t, func() bool {
return sink.RequestsCount() == 1 && sink.ItemsCount() == 16
}, 500*time.Millisecond, 10*time.Millisecond)
ba.Consume(context.WithValue(context.Background(), partitionKey{}, "p2"), &requesttest.FakeRequest{Items: 6}, done)
assert.Eventually(t, func() bool {
return sink.RequestsCount() == 2 && sink.ItemsCount() == 28
}, 500*time.Millisecond, 10*time.Millisecond)
// Check that done callback is called for the right amount of times.
assert.EqualValues(t, 0, done.errors.Load())
assert.EqualValues(t, 4, done.success.Load())
require.NoError(t, ba.Start(context.Background(), componenttest.NewNopHost()))
}
func TestMultiBatcher_Timeout(t *testing.T) {
cfg := BatchConfig{
FlushTimeout: 100 * time.Millisecond,
Sizer: request.SizerTypeItems,
MinSize: 100,
}
sink := requesttest.NewSink()
type partitionKey struct{}
ba, err := newMultiBatcher(cfg,
request.NewItemsSizer(),
newWorkerPool(1),
NewPartitioner(func(ctx context.Context, _ request.Request) string {
return ctx.Value(partitionKey{}).(string)
}),
nil,
sink.Export,
zap.NewNop(),
)
require.NoError(t, err)
require.NoError(t, ba.Start(context.Background(), componenttest.NewNopHost()))
t.Cleanup(func() {
require.NoError(t, ba.Shutdown(context.Background()))
})
done := newFakeDone()
ba.Consume(context.WithValue(context.Background(), partitionKey{}, "p1"), &requesttest.FakeRequest{Items: 8}, done)
ba.Consume(context.WithValue(context.Background(), partitionKey{}, "p2"), &requesttest.FakeRequest{Items: 6}, done)
// Neither batch should be flushed since they haven't reached min threshold.
assert.Equal(t, 0, sink.RequestsCount())
assert.Equal(t, 0, sink.ItemsCount())
ba.Consume(context.WithValue(context.Background(), partitionKey{}, "p1"), &requesttest.FakeRequest{Items: 8}, done)
ba.Consume(context.WithValue(context.Background(), partitionKey{}, "p2"), &requesttest.FakeRequest{Items: 6}, done)
assert.Eventually(t, func() bool {
return sink.RequestsCount() == 2 && sink.ItemsCount() == 28
}, 1*time.Second, 10*time.Millisecond)
// Check that done callback is called for the right amount of times.
assert.EqualValues(t, 0, done.errors.Load())
assert.EqualValues(t, 4, done.success.Load())
require.NoError(t, ba.Start(context.Background(), componenttest.NewNopHost()))
}
func TestMultiBatcher_PartitionRemovedAfterIdleTimeout(t *testing.T) {
// Use a short FlushTimeout so the idle threshold (partitionIdleCycles*FlushTimeout) is reached quickly.
cfg := BatchConfig{
FlushTimeout: 10 * time.Millisecond,
Sizer: request.SizerTypeItems,
MinSize: 100, // High min size to prevent immediate flush
}
sink := requesttest.NewSink()
type partitionKey struct{}
ba, err := newMultiBatcher(cfg,
request.NewItemsSizer(),
newWorkerPool(1),
NewPartitioner(func(ctx context.Context, _ request.Request) string {
return ctx.Value(partitionKey{}).(string)
}),
nil,
sink.Export,
zap.NewNop(),
)
require.NoError(t, err)
require.NoError(t, ba.Start(context.Background(), componenttest.NewNopHost()))
t.Cleanup(func() {
require.NoError(t, ba.Shutdown(context.Background()))
})
done := newFakeDone()
// Create a partition
ba.Consume(context.WithValue(context.Background(), partitionKey{}, "p1"), &requesttest.FakeRequest{Items: 5}, done)
assert.Equal(t, int64(1), ba.getActivePartitionsCount())
// Wait for the batch to flush via timeout
assert.Eventually(t, func() bool {
return sink.RequestsCount() == 1
}, 500*time.Millisecond, 10*time.Millisecond)
// Wait for idle timeout (partitionIdleCycles * FlushTimeout = 10 * 10ms = 100ms)
// After this, the partition should be removed from the LRU cache.
assert.Eventually(t, func() bool {
return ba.getActivePartitionsCount() == 0
}, 500*time.Millisecond, 10*time.Millisecond)
}
================================================
FILE: exporter/exporterhelper/internal/queuebatch/partition_batcher.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queuebatch // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
import (
"context"
"sync"
"time"
"go.uber.org/multierr"
"go.uber.org/zap"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/queue"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sender"
)
// partitionIdleCycles*FlushTimeout is the duration after which an empty partition is removed.
// TODO make this configurable.
const partitionIdleCycles = 10
var _ Batcher[request.Request] = (*partitionBatcher)(nil)
type batch struct {
ctx context.Context
req request.Request
done multiDone
}
// partitionBatcher continuously batch incoming requests and flushes asynchronously if minimum size limit is met or on timeout.
type partitionBatcher struct {
cfg BatchConfig
wp *workerPool
sizer request.Sizer
mergeCtx func(context.Context, context.Context) context.Context
consumeFunc sender.SendFunc[request.Request]
stopWG sync.WaitGroup
currentBatchMu sync.Mutex
currentBatch *batch
timer *time.Timer
shutdownCh chan struct{}
logger *zap.Logger
onEmpty func() // callback triggered when partition is idle for given time period.
lastDataTime time.Time // tracks when data was last present
active bool // indicates if partition is still active i.e timer is running and shutdown is not called yet. If Consume is called on inactive partition then data is flushed sync because timer is not running.
}
func newPartitionBatcher(
cfg BatchConfig,
sizer request.Sizer,
mergeCtx func(context.Context, context.Context) context.Context,
wp *workerPool,
next sender.SendFunc[request.Request],
logger *zap.Logger,
onEmpty func(),
) *partitionBatcher {
return &partitionBatcher{
cfg: cfg,
wp: wp,
sizer: sizer,
mergeCtx: mergeCtx,
consumeFunc: next,
shutdownCh: make(chan struct{}, 1),
logger: logger,
onEmpty: onEmpty,
lastDataTime: time.Now(),
active: true,
}
}
func (qb *partitionBatcher) resetTimer() {
if qb.cfg.FlushTimeout > 0 {
qb.timer.Reset(qb.cfg.FlushTimeout)
}
}
func (qb *partitionBatcher) consumeInternal(ctx context.Context, req request.Request, done queue.Done) bool {
qb.currentBatchMu.Lock()
isActive := qb.active
qb.lastDataTime = time.Now()
if qb.currentBatch == nil {
reqList, mergeSplitErr := req.MergeSplit(ctx, int(qb.cfg.MaxSize), qb.cfg.Sizer, nil)
if mergeSplitErr != nil {
// Do not return in case of error if there are data, try to export as much as possible.
qb.logger.Warn("Failed to split request.", zap.Error(mergeSplitErr))
}
if len(reqList) == 0 {
done.OnDone(mergeSplitErr)
qb.currentBatchMu.Unlock()
return isActive
}
// If more than one flush is required for this request, call done only when all flushes are done.
numRefs := len(reqList)
// Need to also inform about the mergeSplitErr, consider the errored data as 1 batch.
if mergeSplitErr != nil {
numRefs++
}
if numRefs > 1 {
done = newRefCountDone(done, int64(numRefs))
if mergeSplitErr != nil {
done.OnDone(mergeSplitErr)
}
}
// We have at least one result in the reqList. Last in the list may not have enough data to be flushed.
// Find if it has at least MinSize, and if it does then move that as the current batch.
lastReq := reqList[len(reqList)-1]
if qb.sizer.Sizeof(lastReq) < qb.cfg.MinSize {
// Do not flush the last item and add it to the current batch.
reqList = reqList[:len(reqList)-1]
qb.currentBatch = &batch{
ctx: ctx,
req: lastReq,
done: multiDone{done},
}
qb.resetTimer()
}
qb.currentBatchMu.Unlock()
for i := 0; i < len(reqList); i++ {
qb.flush(ctx, reqList[i], done)
}
return isActive
}
reqList, mergeSplitErr := qb.currentBatch.req.MergeSplit(ctx, int(qb.cfg.MaxSize), qb.cfg.Sizer, req)
// If failed to merge signal all Done callbacks from the current batch as well as the current request and reset the current batch.
if mergeSplitErr != nil {
// Do not return in case of error if there are data, try to export as much as possible.
qb.logger.Warn("Failed to split request.", zap.Error(mergeSplitErr))
}
if len(reqList) == 0 {
done.OnDone(mergeSplitErr)
qb.currentBatchMu.Unlock()
return isActive
}
// If more than one flush is required for this request, call done only when all flushes are done.
numRefs := len(reqList)
// Need to also inform about the mergeSplitErr, consider the errored data as 1 batch.
if mergeSplitErr != nil {
numRefs++
}
if numRefs > 1 {
done = newRefCountDone(done, int64(numRefs))
if mergeSplitErr != nil {
done.OnDone(mergeSplitErr)
}
}
// We have at least one result in the reqList, if more results here is what that means:
// - First result will contain items from the current batch + some results from the current request.
// - All other results except first will contain items only from the current request.
// - Last result may not have enough data to be flushed.
// Logic on how to deal with the current batch:
qb.currentBatch.req = reqList[0]
qb.currentBatch.done = append(qb.currentBatch.done, done)
mergedCtx := context.Background() //nolint:contextcheck
if qb.mergeCtx != nil {
mergedCtx = qb.mergeCtx(qb.currentBatch.ctx, ctx)
}
qb.currentBatch.ctx = contextWithMergedLinks(mergedCtx, qb.currentBatch.ctx, ctx)
// Save the "currentBatch" if we need to flush it, because we want to execute flush without holding the lock, and
// cannot unlock and re-lock because we are not done processing all the responses.
var firstBatch *batch
// Need to check the currentBatch if more than 1 result returned or if 1 result return but larger than MinSize.
if len(reqList) > 1 || qb.sizer.Sizeof(qb.currentBatch.req) >= qb.cfg.MinSize {
firstBatch = qb.currentBatch
qb.currentBatch = nil
}
// At this moment we dealt with the first result which is iter in the currentBatch or in the `firstBatch` we will flush.
reqList = reqList[1:]
// If we still have results to process, then we need to check if the last result has enough data to flush, or we add it to the currentBatch.
if len(reqList) > 0 {
lastReq := reqList[len(reqList)-1]
if qb.sizer.Sizeof(lastReq) < qb.cfg.MinSize {
// Do not flush the last item and add it to the current batch.
reqList = reqList[:len(reqList)-1]
qb.currentBatch = &batch{
ctx: ctx,
req: lastReq,
done: multiDone{done},
}
qb.resetTimer()
}
}
qb.currentBatchMu.Unlock()
if firstBatch != nil {
qb.flush(firstBatch.ctx, firstBatch.req, firstBatch.done)
}
for i := 0; i < len(reqList); i++ {
qb.flush(ctx, reqList[i], done)
}
return isActive
}
func (qb *partitionBatcher) Consume(ctx context.Context, req request.Request, done queue.Done) {
if !qb.consumeInternal(ctx, req, done) {
// Not active partition then flush else let the timer/Shutdown do it's work.
qb.flushCurrentBatchOrRemovePartition()
}
}
// Start starts the goroutine that reads from the queue and flushes asynchronously.
func (qb *partitionBatcher) Start(context.Context, component.Host) error {
if qb.cfg.FlushTimeout <= 0 {
return nil
}
qb.timer = time.NewTimer(qb.cfg.FlushTimeout)
qb.stopWG.Go(func() {
for {
select {
case <-qb.shutdownCh:
return
case <-qb.timer.C:
qb.flushCurrentBatchOrRemovePartition()
}
}
})
return nil
}
// shutdownInternal ensures that queue and all Batcher are stopped.
func (qb *partitionBatcher) shutdownInternal() {
qb.currentBatchMu.Lock()
if !qb.active {
qb.currentBatchMu.Unlock()
return
}
qb.active = false
// don't need to trigger onEmpty during shutdown as partitionBatcher will be purged anyway.
qb.onEmpty = nil
qb.currentBatchMu.Unlock()
close(qb.shutdownCh)
// Make sure execute one last flush if necessary.
qb.flushCurrentBatchOrRemovePartition()
qb.stopWG.Wait()
}
// Shutdown ensures that queue and all Batcher are stopped.
func (qb *partitionBatcher) Shutdown(context.Context) error {
qb.shutdownInternal()
return nil
}
// flushCurrentBatchOrRemovePartition flushes the current batch if not empty,
// or removes the partition from the parent if it's been idle for too long.
func (qb *partitionBatcher) flushCurrentBatchOrRemovePartition() {
qb.currentBatchMu.Lock()
if qb.currentBatch == nil {
// No data to flush - check if idle for too long AND no one holding a reference
idleDuration := time.Since(qb.lastDataTime)
if idleDuration >= (partitionIdleCycles*qb.cfg.FlushTimeout) && qb.onEmpty != nil {
qb.currentBatchMu.Unlock()
qb.onEmpty()
return
}
if qb.timer != nil {
qb.resetTimer()
}
qb.currentBatchMu.Unlock()
return
}
// Has data to flush - update lastDataTime
qb.lastDataTime = time.Now()
batchToFlush := qb.currentBatch
qb.currentBatch = nil
// Reset timer while holding the lock to prevent data race with Consume() which
// also calls resetTimer() under the same lock.
qb.resetTimer()
qb.currentBatchMu.Unlock()
// flush() blocks until successfully started a goroutine for flushing.
qb.flush(batchToFlush.ctx, batchToFlush.req, batchToFlush.done)
}
// flush starts a goroutine that calls consumeFunc. It blocks until a worker is available if necessary.
func (qb *partitionBatcher) flush(ctx context.Context, req request.Request, done queue.Done) {
qb.stopWG.Add(1)
qb.wp.execute(func() {
defer qb.stopWG.Done()
done.OnDone(qb.consumeFunc(ctx, req))
})
}
type workerPool struct {
workers chan struct{}
}
func newWorkerPool(maxWorkers int) *workerPool {
workers := make(chan struct{}, maxWorkers)
for range maxWorkers {
workers <- struct{}{}
}
return &workerPool{workers: workers}
}
func (wp *workerPool) execute(f func()) {
<-wp.workers
go func() {
defer func() {
wp.workers <- struct{}{}
}()
f()
}()
}
type multiDone []queue.Done
func (mdc multiDone) OnDone(err error) {
for _, d := range mdc {
d.OnDone(err)
}
}
type refCountDone struct {
done queue.Done
mu sync.Mutex
refCount int64
err error
}
func newRefCountDone(done queue.Done, refCount int64) queue.Done {
return &refCountDone{
done: done,
refCount: refCount,
}
}
func (rcd *refCountDone) OnDone(err error) {
rcd.mu.Lock()
defer rcd.mu.Unlock()
rcd.err = multierr.Append(rcd.err, err)
rcd.refCount--
if rcd.refCount == 0 {
// No more references, call done.
rcd.done.OnDone(rcd.err)
}
}
================================================
FILE: exporter/exporterhelper/internal/queuebatch/partition_batcher_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queuebatch
import (
"context"
"errors"
"runtime"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/requesttest"
)
type testContextKey string
const timestampKey testContextKey = "timestamp"
func TestPartitionBatcher_NoSplit_MinThresholdZero_TimeoutDisabled(t *testing.T) {
tests := []struct {
name string
sizerType request.SizerType
sizer request.Sizer
maxWorkers int
}{
{
name: "items/one_worker",
sizerType: request.SizerTypeItems,
sizer: request.NewItemsSizer(),
maxWorkers: 1,
},
{
name: "items/three_workers",
sizerType: request.SizerTypeItems,
sizer: request.NewItemsSizer(),
maxWorkers: 3,
},
{
name: "bytes/one_worker",
sizerType: request.SizerTypeBytes,
sizer: request.NewBytesSizer(),
maxWorkers: 1,
},
{
name: "bytes/three_workers",
sizerType: request.SizerTypeBytes,
sizer: request.NewBytesSizer(),
maxWorkers: 3,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := BatchConfig{
FlushTimeout: 0,
Sizer: tt.sizerType,
MinSize: 0,
}
sink := requesttest.NewSink()
ba := newPartitionBatcher(cfg, tt.sizer, nil, newWorkerPool(tt.maxWorkers), sink.Export, zap.NewNop(), nil)
require.NoError(t, ba.Start(context.Background(), componenttest.NewNopHost()))
t.Cleanup(func() {
require.NoError(t, ba.Shutdown(context.Background()))
})
done := newFakeDone()
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 8, Bytes: 8}, done)
sink.SetExportErr(errors.New("transient error"))
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 8, Bytes: 8}, done)
<-time.After(10 * time.Millisecond)
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 17, Bytes: 17}, done)
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 13, Bytes: 13}, done)
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 35, Bytes: 35}, done)
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 2, Bytes: 2}, done)
assert.Eventually(t, func() bool {
return sink.RequestsCount() == 5 && (sink.ItemsCount() == 75 || sink.BytesCount() == 75)
}, 1*time.Second, 10*time.Millisecond)
// Check that done callback is called for the right number of times.
assert.EqualValues(t, 1, done.errors.Load())
assert.EqualValues(t, 5, done.success.Load())
})
}
}
func TestPartitionBatcher_NoSplit_TimeoutDisabled(t *testing.T) {
tests := []struct {
name string
sizerType request.SizerType
sizer request.Sizer
maxWorkers int
}{
{
name: "items/one_worker",
sizerType: request.SizerTypeItems,
sizer: request.NewItemsSizer(),
maxWorkers: 1,
},
{
name: "items/three_workers",
sizerType: request.SizerTypeItems,
sizer: request.NewItemsSizer(),
maxWorkers: 3,
},
{
name: "bytes/one_worker",
sizerType: request.SizerTypeBytes,
sizer: request.NewBytesSizer(),
maxWorkers: 1,
},
{
name: "bytes/three_workers",
sizerType: request.SizerTypeBytes,
sizer: request.NewBytesSizer(),
maxWorkers: 3,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := BatchConfig{
FlushTimeout: 0,
Sizer: tt.sizerType,
MinSize: 10,
}
sink := requesttest.NewSink()
ba := newPartitionBatcher(cfg, tt.sizer, nil, newWorkerPool(tt.maxWorkers), sink.Export, zap.NewNop(), nil)
require.NoError(t, ba.Start(context.Background(), componenttest.NewNopHost()))
done := newFakeDone()
// These two requests will be dropped because of export error.
sink.SetExportErr(errors.New("transient error"))
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 8, Bytes: 8}, done)
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 8, Bytes: 8}, done)
<-time.After(10 * time.Millisecond)
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 7, Bytes: 7}, done)
// This requests will be dropped because of merge error.
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 8, Bytes: 8, MergeErr: errors.New("transient error")}, done)
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 13, Bytes: 13}, done)
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 35, Bytes: 35}, done)
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 2, Bytes: 2}, done)
// Only the requests with 7+13 and 35 will be flushed.
assert.Eventually(t, func() bool {
return sink.RequestsCount() == 2 && (sink.ItemsCount() == 55 || sink.BytesCount() == 55)
}, 1*time.Second, 10*time.Millisecond)
require.NoError(t, ba.Shutdown(context.Background()))
// After shutdown the pending "current batch" is also flushed.
assert.Equal(t, 3, sink.RequestsCount())
assert.True(t, sink.ItemsCount() == 57 || sink.BytesCount() == 57)
// Check that done callback is called for the right number of times.
assert.EqualValues(t, 3, done.errors.Load())
assert.EqualValues(t, 4, done.success.Load())
})
}
}
func TestPartitionBatcher_NoSplit_WithTimeout(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("Skipping test on Windows, see https://github.com/open-telemetry/opentelemetry-collector/issues/11869")
}
tests := []struct {
name string
sizerType request.SizerType
sizer request.Sizer
maxWorkers int
}{
{
name: "items/one_worker",
sizerType: request.SizerTypeItems,
sizer: request.NewItemsSizer(),
maxWorkers: 1,
},
{
name: "items/three_workers",
sizerType: request.SizerTypeItems,
sizer: request.NewItemsSizer(),
maxWorkers: 3,
},
{
name: "bytes/one_worker",
sizerType: request.SizerTypeBytes,
sizer: request.NewBytesSizer(),
maxWorkers: 1,
},
{
name: "bytes/three_workers",
sizerType: request.SizerTypeBytes,
sizer: request.NewBytesSizer(),
maxWorkers: 3,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := BatchConfig{
FlushTimeout: 50 * time.Millisecond,
Sizer: tt.sizerType,
MinSize: 100,
}
sink := requesttest.NewSink()
ba := newPartitionBatcher(cfg, tt.sizer, nil, newWorkerPool(tt.maxWorkers), sink.Export, zap.NewNop(), nil)
require.NoError(t, ba.Start(context.Background(), componenttest.NewNopHost()))
t.Cleanup(func() {
require.NoError(t, ba.Shutdown(context.Background()))
})
done := newFakeDone()
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 8, Bytes: 8}, done)
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 17, Bytes: 17}, done)
// This requests will be dropped because of merge error.
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 8, Bytes: 8, MergeErr: errors.New("transient error")}, done)
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 13, Bytes: 13}, done)
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 35, Bytes: 35}, done)
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 2, Bytes: 2}, done)
assert.Eventually(t, func() bool {
return sink.RequestsCount() == 1 && (sink.ItemsCount() == 75 || sink.BytesCount() == 75)
}, 1*time.Second, 10*time.Millisecond)
// Check that done callback is called for the right number of times.
assert.EqualValues(t, 1, done.errors.Load())
assert.EqualValues(t, 5, done.success.Load())
})
}
}
func TestPartitionBatcher_Split_TimeoutDisabled(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("Skipping test on Windows, see https://github.com/open-telemetry/opentelemetry-collector/issues/11847")
}
tests := []struct {
name string
sizerType request.SizerType
sizer request.Sizer
maxWorkers int
}{
{
name: "items/one_worker",
sizerType: request.SizerTypeItems,
sizer: request.NewItemsSizer(),
maxWorkers: 1,
},
{
name: "items/three_workers",
sizerType: request.SizerTypeItems,
sizer: request.NewItemsSizer(),
maxWorkers: 3,
},
{
name: "bytes/one_worker",
sizerType: request.SizerTypeBytes,
sizer: request.NewBytesSizer(),
maxWorkers: 1,
},
{
name: "bytes/three_workers",
sizerType: request.SizerTypeBytes,
sizer: request.NewBytesSizer(),
maxWorkers: 3,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := BatchConfig{
FlushTimeout: 0,
Sizer: tt.sizerType,
MinSize: 100,
MaxSize: 100,
}
sink := requesttest.NewSink()
ba := newPartitionBatcher(cfg, tt.sizer, nil, newWorkerPool(tt.maxWorkers), sink.Export, zap.NewNop(), nil)
require.NoError(t, ba.Start(context.Background(), componenttest.NewNopHost()))
done := newFakeDone()
// This requests will be dropped because of merge error.
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 8, Bytes: 8, MergeErr: errors.New("transient error")}, done)
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 8, Bytes: 8}, done)
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 17, Bytes: 17}, done)
// This requests will be dropped because of merge error.
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 8, Bytes: 8, MergeErr: errors.New("transient error")}, done)
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 13, Bytes: 13}, done)
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 35, Bytes: 35}, done)
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 2, Bytes: 2}, done)
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 30, Bytes: 30}, done)
assert.Eventually(t, func() bool {
return sink.RequestsCount() == 1 && (sink.ItemsCount() == 100 || sink.BytesCount() == 100)
}, 1*time.Second, 10*time.Millisecond)
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 900, Bytes: 900}, done)
assert.Eventually(t, func() bool {
return sink.RequestsCount() == 10 && (sink.ItemsCount() == 1000 || sink.BytesCount() == 1000)
}, 1*time.Second, 10*time.Millisecond)
// At this point the 7th not failing request is still pending.
assert.EqualValues(t, 6, done.success.Load())
require.NoError(t, ba.Shutdown(context.Background()))
// After shutdown the pending "current batch" is also flushed.
assert.Equal(t, 11, sink.RequestsCount())
assert.True(t, sink.ItemsCount() == 1005 || sink.BytesCount() == 1005)
// Check that done callback is called for the right number of times.
assert.EqualValues(t, 2, done.errors.Load())
assert.EqualValues(t, 7, done.success.Load())
})
}
}
func TestPartitionBatcher_Shutdown(t *testing.T) {
cfg := BatchConfig{
FlushTimeout: 100 * time.Second,
Sizer: request.SizerTypeItems,
MinSize: 10,
}
sink := requesttest.NewSink()
ba := newPartitionBatcher(cfg, request.NewItemsSizer(), nil, newWorkerPool(2), sink.Export, zap.NewNop(), nil)
require.NoError(t, ba.Start(context.Background(), componenttest.NewNopHost()))
done := newFakeDone()
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 1, Bytes: 1}, done)
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 2, Bytes: 2}, done)
assert.Equal(t, 0, sink.RequestsCount())
assert.Equal(t, 0, sink.ItemsCount())
require.NoError(t, ba.Shutdown(context.Background()))
assert.Equal(t, 1, sink.RequestsCount())
assert.Equal(t, 3, sink.ItemsCount())
// Check that done callback is called for the right number of times.
assert.EqualValues(t, 0, done.errors.Load())
assert.EqualValues(t, 2, done.success.Load())
}
func TestPartitionBatcher_MergeError(t *testing.T) {
cfg := BatchConfig{
FlushTimeout: 200 * time.Second,
Sizer: request.SizerTypeItems,
MinSize: 5,
MaxSize: 7,
}
sink := requesttest.NewSink()
ba := newPartitionBatcher(cfg, request.NewItemsSizer(), nil, newWorkerPool(2), sink.Export, zap.NewNop(), nil)
require.NoError(t, ba.Start(context.Background(), componenttest.NewNopHost()))
t.Cleanup(func() {
require.NoError(t, ba.Shutdown(context.Background()))
})
done := newFakeDone()
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 9, Bytes: 9}, done)
assert.Eventually(t, func() bool {
return sink.RequestsCount() == 1 && sink.ItemsCount() == 7
}, 1*time.Second, 10*time.Millisecond)
sink.SetExportErr(errors.New("transient error"))
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 4, Bytes: 4}, done)
assert.Eventually(t, func() bool {
return done.errors.Load() == 2
}, 1*time.Second, 10*time.Millisecond)
// Check that done callback is called for the right number of times.
assert.EqualValues(t, 2, done.errors.Load())
assert.EqualValues(t, 0, done.success.Load())
}
func TestPartitionBatcher_PartialSuccessError(t *testing.T) {
cfg := BatchConfig{
FlushTimeout: 0,
Sizer: request.SizerTypeBytes,
MinSize: 10,
MaxSize: 15,
}
core, observed := observer.New(zap.WarnLevel)
logger := zap.New(core)
sink := requesttest.NewSink()
ba := newPartitionBatcher(cfg, request.NewItemsSizer(), nil, newWorkerPool(1), sink.Export, logger, nil)
require.NoError(t, ba.Start(context.Background(), componenttest.NewNopHost()))
done := newFakeDone()
req := &requesttest.FakeRequest{
Items: 100,
Bytes: 100,
MergeErr: errors.New("split error"),
MergeErrResult: []request.Request{&requesttest.FakeRequest{Items: 10, Bytes: 15}},
}
ba.Consume(context.Background(), req, done)
assert.Eventually(t, func() bool {
logs := observed.All()
if len(logs) == 0 {
return false
}
log := logs[0]
return log.Level == zap.WarnLevel &&
log.Message == "Failed to split request."
}, time.Second, 10*time.Millisecond)
require.NoError(t, ba.Shutdown(context.Background()))
// Verify that done callback was called with the returned batch and error for the split.
assert.Equal(t, int64(1), done.errors.Load())
assert.Equal(t, 1, sink.RequestsCount())
assert.Equal(t, 10, sink.ItemsCount())
assert.Equal(t, 15, sink.BytesCount())
}
func TestSPartitionBatcher_PartialSuccessError_AfterOkRequest(t *testing.T) {
cfg := BatchConfig{
FlushTimeout: 0,
Sizer: request.SizerTypeBytes,
MinSize: 10,
MaxSize: 15,
}
core, observed := observer.New(zap.WarnLevel)
logger := zap.New(core)
sink := requesttest.NewSink()
ba := newPartitionBatcher(cfg, request.NewItemsSizer(), nil, newWorkerPool(1), sink.Export, logger, nil)
require.NoError(t, ba.Start(context.Background(), componenttest.NewNopHost()))
done := newFakeDone()
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 5, Bytes: 5}, done)
req := &requesttest.FakeRequest{
Items: 100,
Bytes: 100,
MergeErr: errors.New("split error"),
MergeErrResult: []request.Request{&requesttest.FakeRequest{Items: 10, Bytes: 15}},
}
ba.Consume(context.Background(), req, done)
assert.Eventually(t, func() bool {
logs := observed.All()
if len(logs) == 0 {
return false
}
log := logs[0]
return log.Level == zap.WarnLevel &&
log.Message == "Failed to split request."
}, time.Second, 10*time.Millisecond)
require.NoError(t, ba.Shutdown(context.Background()))
// Verify that done callback was called with the success for the returned batch and error for the split.
assert.Equal(t, int64(1), done.errors.Load())
assert.Equal(t, int64(1), done.success.Load())
assert.Equal(t, 1, sink.RequestsCount())
assert.Equal(t, 10, sink.ItemsCount())
assert.Equal(t, 15, sink.BytesCount())
}
type fakeDone struct {
errors *atomic.Int64
success *atomic.Int64
}
func newFakeDone() fakeDone {
return fakeDone{
errors: &atomic.Int64{},
success: &atomic.Int64{},
}
}
func (fd fakeDone) OnDone(err error) {
if err != nil {
fd.errors.Add(1)
} else {
fd.success.Add(1)
}
}
func TestShardBatcher_EmptyRequestList(t *testing.T) {
cfg := BatchConfig{
FlushTimeout: 0,
MinSize: 0,
}
sink := requesttest.NewSink()
ba := newPartitionBatcher(cfg, request.NewItemsSizer(), nil, newWorkerPool(1), sink.Export, zap.NewNop(), nil)
require.NoError(t, ba.Start(context.Background(), componenttest.NewNopHost()))
t.Cleanup(func() {
require.NoError(t, ba.Shutdown(context.Background()))
})
done := newFakeDone()
req := &requesttest.FakeRequest{
Items: 1,
MergeErr: errors.New("force empty list"),
}
ba.Consume(context.Background(), req, done)
assert.Eventually(t, func() bool {
return done.errors.Load() == 1
}, time.Second, 10*time.Millisecond)
assert.Equal(t, int64(0), done.success.Load())
assert.Equal(t, 0, sink.RequestsCount())
}
func TestPartitionBatcher_ContextMerging(t *testing.T) {
tests := []struct {
name string
mergeCtxFunc func(ctx1, ctx2 context.Context) context.Context
}{
{
name: "merge_context_with_timestamp",
mergeCtxFunc: func(ctx1, _ context.Context) context.Context {
return context.WithValue(ctx1, timestampKey, 1234)
},
},
{
name: "merge_context_returns_background",
mergeCtxFunc: func(_, _ context.Context) context.Context {
return context.Background()
},
},
{
name: "nil_merge_context",
mergeCtxFunc: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := BatchConfig{
FlushTimeout: 0,
Sizer: request.SizerTypeItems,
MinSize: 10,
}
sink := requesttest.NewSink()
ba := newPartitionBatcher(cfg, request.NewItemsSizer(), tt.mergeCtxFunc, newWorkerPool(1), sink.Export, zap.NewNop(), nil)
require.NoError(t, ba.Start(context.Background(), componenttest.NewNopHost()))
done := newFakeDone()
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 8, Bytes: 8}, done)
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 8, Bytes: 8}, done)
<-time.After(10 * time.Millisecond)
assert.Equal(t, 1, sink.RequestsCount())
assert.EqualValues(t, 2, done.success.Load())
})
}
}
func TestPartitionBatcher_OnEmptyCallbackTriggered(t *testing.T) {
// Use a very short FlushTimeout so the idle threshold (partitionIdleCycles*FlushTimeout) is reached quickly.
cfg := BatchConfig{
FlushTimeout: 10 * time.Millisecond,
Sizer: request.SizerTypeItems,
MinSize: 100, // High min size to ensure data doesn't flush immediately
}
sink := requesttest.NewSink()
onEmptyCalled := &atomic.Int64{}
onEmpty := func() {
onEmptyCalled.Add(1)
}
ba := newPartitionBatcher(cfg, request.NewItemsSizer(), nil, newWorkerPool(1), sink.Export, zap.NewNop(), onEmpty)
require.NoError(t, ba.Start(context.Background(), componenttest.NewNopHost()))
t.Cleanup(func() {
require.NoError(t, ba.Shutdown(context.Background()))
})
// Consume some data below min threshold so it stays in currentBatch
done := newFakeDone()
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 5}, done)
// Wait for the batch to be flushed by timeout
assert.Eventually(t, func() bool {
return sink.RequestsCount() == 1
}, 500*time.Millisecond, 10*time.Millisecond)
// Now wait for idle timeout (partitionIdleCycles * FlushTimeout = 10 * 10ms = 100ms)
// The onEmpty callback should be called after the partition is idle for this duration.
assert.Eventually(t, func() bool {
return onEmptyCalled.Load() >= 1
}, 500*time.Millisecond, 10*time.Millisecond)
}
func TestPartitionBatcher_OnEmptyNotCalledWithActiveData(t *testing.T) {
// Test that onEmpty is NOT called when data keeps flowing
cfg := BatchConfig{
FlushTimeout: 20 * time.Millisecond,
Sizer: request.SizerTypeItems,
MinSize: 5,
}
sink := requesttest.NewSink()
onEmptyCalled := &atomic.Int64{}
onEmpty := func() {
onEmptyCalled.Add(1)
}
ba := newPartitionBatcher(cfg, request.NewItemsSizer(), nil, newWorkerPool(1), sink.Export, zap.NewNop(), onEmpty)
require.NoError(t, ba.Start(context.Background(), componenttest.NewNopHost()))
t.Cleanup(func() {
require.NoError(t, ba.Shutdown(context.Background()))
})
done := newFakeDone()
// Keep sending data to prevent idle timeout
for range 5 {
ba.Consume(context.Background(), &requesttest.FakeRequest{Items: 5}, done)
time.Sleep(15 * time.Millisecond)
}
// Data was flowing, onEmpty should not have been called
assert.Equal(t, int64(0), onEmptyCalled.Load())
// But data should have been flushed
assert.GreaterOrEqual(t, sink.RequestsCount(), 1)
}
================================================
FILE: exporter/exporterhelper/internal/queuebatch/partitioner.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queuebatch // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
import (
"context"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
)
// Partitioner is an interface that returns the the partition key of the given element.
type Partitioner[T any] interface {
GetKey(context.Context, T) string
}
type GetKeyFunc[T any] func(context.Context, T) string
func (f GetKeyFunc[T]) GetKey(ctx context.Context, t T) string {
return f(ctx, t)
}
type basePartitioner struct {
GetKeyFunc[request.Request]
}
func NewPartitioner(
getKeyFunc GetKeyFunc[request.Request],
) Partitioner[request.Request] {
return &basePartitioner{
GetKeyFunc: getKeyFunc,
}
}
================================================
FILE: exporter/exporterhelper/internal/queuebatch/partitioner_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queuebatch
import (
"context"
"strconv"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/client"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/requesttest"
)
func TestPartitioner_GetKeyFromRequest(t *testing.T) {
partitioner := NewPartitioner(func(_ context.Context, req request.Request) string {
return strconv.Itoa(req.(*requesttest.FakeRequest).ItemsCount())
})
require.Equal(t, "2", partitioner.GetKey(context.Background(), &requesttest.FakeRequest{Items: 2}))
require.Equal(t, "3", partitioner.GetKey(context.Background(), &requesttest.FakeRequest{Items: 3}))
require.Equal(t, "4", partitioner.GetKey(context.Background(), &requesttest.FakeRequest{Items: 4}))
}
func TestPartitioner_GetKeyFromContext(t *testing.T) {
partitioner := NewPartitioner(func(ctx context.Context, _ request.Request) string {
return client.FromContext(ctx).Metadata.Get("metadata_key")[0]
})
ctx1 := client.NewContext(context.Background(), client.Info{
Metadata: client.NewMetadata(map[string][]string{"metadata_key": {"partition1"}}),
})
require.Equal(t, "partition1", partitioner.GetKey(ctx1, &requesttest.FakeRequest{Items: 2}))
ctx2 := client.NewContext(context.Background(), client.Info{
Metadata: client.NewMetadata(map[string][]string{"metadata_key": {"partition2"}}),
})
require.Equal(t, "partition2", partitioner.GetKey(ctx2, &requesttest.FakeRequest{Items: 2}))
}
================================================
FILE: exporter/exporterhelper/internal/queuebatch/queue_batch.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queuebatch // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
import (
"context"
"errors"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/queue"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sender"
"go.opentelemetry.io/collector/pipeline"
)
// Settings is a subset of the queuebatch.Settings that are needed when used within an Exporter.
type Settings[T any] struct {
ReferenceCounter queue.ReferenceCounter[T]
Encoding queue.Encoding[T]
Partitioner Partitioner[T]
MergeCtx func(context.Context, context.Context) context.Context
}
// AllSettings defines settings for creating a QueueBatch.
type AllSettings[T any] struct {
Settings[T]
Signal pipeline.Signal
ID component.ID
Telemetry component.TelemetrySettings
}
type QueueBatch struct {
queue queue.Queue[request.Request]
batcher Batcher[request.Request]
}
func NewQueueBatch(
set AllSettings[request.Request],
cfg Config,
next sender.SendFunc[request.Request],
) (*QueueBatch, error) {
b, err := NewBatcher(cfg.Batch, batcherSettings[request.Request]{
partitioner: set.Partitioner,
mergeCtx: set.MergeCtx,
next: next,
maxWorkers: cfg.NumConsumers,
logger: set.Telemetry.Logger,
})
if err != nil {
return nil, err
}
if cfg.Batch.HasValue() && set.Partitioner == nil {
// If batching is enabled and partitioner is not defined then keep the number of queue consumers to 1.
// see: https://github.com/open-telemetry/opentelemetry-collector/issues/12473
cfg.NumConsumers = 1
}
q, err := queue.NewQueue(queue.Settings[request.Request]{
SizerType: cfg.Sizer,
Capacity: cfg.QueueSize,
NumConsumers: cfg.NumConsumers,
WaitForResult: cfg.WaitForResult,
BlockOnOverflow: cfg.BlockOnOverflow,
Signal: set.Signal,
StorageID: cfg.StorageID,
ReferenceCounter: set.ReferenceCounter,
Encoding: set.Encoding,
ID: set.ID,
Telemetry: set.Telemetry,
}, b.Consume)
if err != nil {
return nil, err
}
return &QueueBatch{queue: q, batcher: b}, nil
}
// Start is invoked during service startup.
func (qs *QueueBatch) Start(ctx context.Context, host component.Host) error {
if err := qs.batcher.Start(ctx, host); err != nil {
return err
}
if err := qs.queue.Start(ctx, host); err != nil {
return errors.Join(err, qs.batcher.Shutdown(ctx))
}
return nil
}
// Shutdown is invoked during service shutdown.
func (qs *QueueBatch) Shutdown(ctx context.Context) error {
// Stop the queue and batcher, this will drain the queue and will call the retry (which is stopped) that will only
// try once every request.
return errors.Join(qs.queue.Shutdown(ctx), qs.batcher.Shutdown(ctx))
}
// Send implements the requestSender interface. It puts the request in the queue.
func (qs *QueueBatch) Send(ctx context.Context, req request.Request) error {
return qs.queue.Offer(ctx, req)
}
================================================
FILE: exporter/exporterhelper/internal/queuebatch/queue_batch_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queuebatch
import (
"context"
"errors"
"runtime"
"sync"
"sync/atomic"
"testing"
"time"
"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/configoptional"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/experr"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/hosttest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/queue"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/requesttest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sendertest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/storagetest"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/pipeline"
)
func newFakeRequestSettings() AllSettings[request.Request] {
return AllSettings[request.Request]{
Signal: pipeline.SignalMetrics,
ID: component.NewID(exportertest.NopType),
Telemetry: componenttest.NewNopTelemetrySettings(),
Settings: Settings[request.Request]{
Encoding: newFakeEncoding(&requesttest.FakeRequest{}),
},
}
}
type fakeEncoding struct {
mr request.Request
}
func (f fakeEncoding) Marshal(context.Context, request.Request) ([]byte, error) {
return []byte("mockRequest"), nil
}
func (f fakeEncoding) Unmarshal([]byte) (context.Context, request.Request, error) {
return context.Background(), f.mr, nil
}
func newFakeEncoding(mr request.Request) queue.Encoding[request.Request] {
return &fakeEncoding{mr: mr}
}
func TestQueueBatchStopWhileWaiting(t *testing.T) {
sink := requesttest.NewSink()
cfg := newTestConfig()
cfg.NumConsumers = 1
cfg.Batch = configoptional.Optional[BatchConfig]{}
qb, err := NewQueueBatch(newFakeRequestSettings(), cfg, sink.Export)
require.NoError(t, err)
require.NoError(t, qb.Start(context.Background(), componenttest.NewNopHost()))
sink.SetExportErr(errors.New("transient error"))
require.NoError(t, qb.Send(context.Background(), &requesttest.FakeRequest{Items: 4}))
// Enqueue another request to ensure when calling shutdown we drain the queue.
require.NoError(t, qb.Send(context.Background(), &requesttest.FakeRequest{Items: 3, Delay: 100 * time.Millisecond}))
require.LessOrEqual(t, int64(1), qb.queue.Size())
require.NoError(t, qb.Shutdown(context.Background()))
assert.Equal(t, 1, sink.RequestsCount())
assert.Equal(t, 3, sink.ItemsCount())
require.Zero(t, qb.queue.Size())
}
func TestQueueBatchDoNotPreserveCancellation(t *testing.T) {
sink := requesttest.NewSink()
cfg := newTestConfig()
cfg.NumConsumers = 1
qb, err := NewQueueBatch(newFakeRequestSettings(), cfg, sink.Export)
require.NoError(t, err)
require.NoError(t, qb.Start(context.Background(), componenttest.NewNopHost()))
ctx, cancelFunc := context.WithCancel(context.Background())
cancelFunc()
require.NoError(t, qb.Send(ctx, &requesttest.FakeRequest{Items: 4}))
require.NoError(t, qb.Shutdown(context.Background()))
assert.Equal(t, 1, sink.RequestsCount())
assert.Equal(t, 4, sink.ItemsCount())
require.Zero(t, qb.queue.Size())
}
func TestQueueBatchHappyPath(t *testing.T) {
cfg := newTestConfig()
cfg.BlockOnOverflow = false
cfg.QueueSize = 56
sink := requesttest.NewSink()
qb, err := NewQueueBatch(newFakeRequestSettings(), cfg, sink.Export)
require.NoError(t, err)
for i := range 10 {
require.NoError(t, qb.Send(context.Background(), &requesttest.FakeRequest{Items: i + 1}))
}
// expect queue to be full
require.Error(t, qb.Send(context.Background(), &requesttest.FakeRequest{Items: 2}))
require.NoError(t, qb.Start(context.Background(), componenttest.NewNopHost()))
assert.Eventually(t, func() bool {
// Because batching is used, cannot guarantee that will be 1 batch or multiple because of the flush interval.
// Check only for total items count.
return sink.ItemsCount() == 55
}, 1*time.Second, 10*time.Millisecond)
require.NoError(t, qb.Shutdown(context.Background()))
}
func TestQueueBatchDifferentSizers(t *testing.T) {
// Set up the config so that the request is accepted in the queue
// because the bytes size is used for the queue,
// but split because the items size is used for batch.
cfg := Config{
WaitForResult: false,
Sizer: request.SizerTypeBytes,
QueueSize: 100,
BlockOnOverflow: false,
NumConsumers: 1,
Batch: configoptional.Some(BatchConfig{
FlushTimeout: 200 * time.Millisecond,
Sizer: request.SizerTypeItems,
MinSize: 100,
MaxSize: 200,
}),
}
sink := requesttest.NewSink()
qb, err := NewQueueBatch(newFakeRequestSettings(), cfg, sink.Export)
require.NoError(t, err)
require.NoError(t, qb.Start(context.Background(), componenttest.NewNopHost()))
require.NoError(t, qb.Send(context.Background(), &requesttest.FakeRequest{Items: 1000, Bytes: 100}))
assert.Eventually(t, func() bool {
return sink.RequestsCount() == 5 && sink.ItemsCount() == 1000
}, 1*time.Second, 10*time.Millisecond)
require.NoError(t, qb.Shutdown(context.Background()))
}
func TestQueueBatchPersistenceEnabled(t *testing.T) {
cfg := newTestConfig()
storageID := component.MustNewIDWithName("file_storage", "storage")
cfg.StorageID = &storageID
qb, err := NewQueueBatch(newFakeRequestSettings(), cfg, sendertest.NewNopSenderFunc[request.Request]())
require.NoError(t, err)
host := hosttest.NewHost(map[component.ID]component.Component{
storageID: storagetest.NewMockStorageExtension(nil),
})
// we start correctly with a file storage extension
require.NoError(t, qb.Start(context.Background(), host))
require.NoError(t, qb.Shutdown(context.Background()))
}
func TestQueueBatchPersistenceEnabledStorageError(t *testing.T) {
storageError := errors.New("could not get storage client")
cfg := newTestConfig()
storageID := component.MustNewIDWithName("file_storage", "storage")
cfg.StorageID = &storageID
qb, err := NewQueueBatch(newFakeRequestSettings(), cfg, sendertest.NewNopSenderFunc[request.Request]())
require.NoError(t, err)
host := hosttest.NewHost(map[component.ID]component.Component{
storageID: storagetest.NewMockStorageExtension(storageError),
})
// we fail to start if we get an error creating the storage client
require.Error(t, qb.Start(context.Background(), host), "could not get storage client")
}
func TestQueueBatchPersistentEnabled_NoDataLossOnShutdown(t *testing.T) {
cfg := newTestConfig()
cfg.NumConsumers = 1
storageID := component.MustNewIDWithName("file_storage", "storage")
cfg.StorageID = &storageID
mockReq := &requesttest.FakeRequest{Items: 2}
qSet := newFakeRequestSettings()
qSet.Encoding = newFakeEncoding(mockReq)
consumed := &atomic.Bool{}
done := make(chan struct{})
qb, err := NewQueueBatch(qSet, cfg, func(context.Context, request.Request) error {
consumed.Store(true)
<-done
return experr.NewShutdownErr(errors.New("could not export data"))
})
require.NoError(t, err)
host := hosttest.NewHost(map[component.ID]component.Component{
storageID: storagetest.NewMockStorageExtension(nil),
})
require.NoError(t, qb.Start(context.Background(), host))
// Invoke queuedRetrySender so the producer will put the item for consumer to poll
require.NoError(t, qb.Send(context.Background(), mockReq))
// first wait for the item to be consumed from the queue
assert.Eventually(t, func() bool {
return consumed.Load()
}, 1*time.Second, 10*time.Millisecond)
// shuts down the exporter, unsent data should be preserved as in-flight data in the persistent queue.
close(done)
require.NoError(t, qb.Shutdown(context.Background()))
// start the exporter again replacing the preserved mockRequest in the unmarshaler with a new one that doesn't fail.
sink := requesttest.NewSink()
replacedReq := &requesttest.FakeRequest{Items: 7}
qSet.Encoding = newFakeEncoding(replacedReq)
qb, err = NewQueueBatch(qSet, cfg, sink.Export)
require.NoError(t, err)
require.NoError(t, qb.Start(context.Background(), host))
assert.Eventually(t, func() bool {
return sink.ItemsCount() == 7 && sink.RequestsCount() == 1
}, 1*time.Second, 10*time.Millisecond)
require.NoError(t, qb.Shutdown(context.Background()))
}
func TestQueueBatchNoStartShutdown(t *testing.T) {
qs, err := NewQueueBatch(newFakeRequestSettings(), newTestConfig(), sendertest.NewNopSenderFunc[request.Request]())
require.NoError(t, err)
assert.NoError(t, qs.Shutdown(context.Background()))
}
func TestQueueBatch_Merge(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("skipping flaky test on Windows, see https://github.com/open-telemetry/opentelemetry-collector/issues/10758")
}
tests := []struct {
name string
batchCfg BatchConfig
}{
{
name: "split_disabled",
batchCfg: BatchConfig{
FlushTimeout: 100 * time.Millisecond,
Sizer: request.SizerTypeItems,
MinSize: 10,
},
},
{
name: "split_high_limit",
batchCfg: BatchConfig{
FlushTimeout: 100 * time.Millisecond,
Sizer: request.SizerTypeItems,
MinSize: 10,
MaxSize: 1000,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sink := requesttest.NewSink()
cfg := newTestConfig()
cfg.Batch = configoptional.Some(tt.batchCfg)
qb, err := NewQueueBatch(newFakeRequestSettings(), cfg, sink.Export)
require.NoError(t, err)
require.NoError(t, qb.Start(context.Background(), componenttest.NewNopHost()))
t.Cleanup(func() {
require.NoError(t, qb.Shutdown(context.Background()))
})
require.NoError(t, qb.Send(context.Background(), &requesttest.FakeRequest{Items: 8}))
require.NoError(t, qb.Send(context.Background(), &requesttest.FakeRequest{Items: 3}))
// the first two requests should be merged into one and sent by reaching the minimum items size
assert.Eventually(t, func() bool {
return sink.RequestsCount() == 1 && sink.ItemsCount() == 11
}, 50*time.Millisecond, 10*time.Millisecond)
require.NoError(t, qb.Send(context.Background(), &requesttest.FakeRequest{Items: 3}))
require.NoError(t, qb.Send(context.Background(), &requesttest.FakeRequest{Items: 1}))
// the third and fifth requests should be sent by reaching the timeout
// the fourth request should be ignored because of the merge error.
time.Sleep(50 * time.Millisecond)
// should be ignored because of the merge error.
require.NoError(t, qb.Send(context.Background(), &requesttest.FakeRequest{
Items: 3,
MergeErr: errors.New("merge error"),
}))
assert.Equal(t, 1, sink.RequestsCount())
assert.Eventually(t, func() bool {
return sink.RequestsCount() == 2 && sink.ItemsCount() == 15
}, 1*time.Second, 10*time.Millisecond)
})
}
}
func TestQueueBatch_BatchExportError(t *testing.T) {
tests := []struct {
name string
batchCfg BatchConfig
expectedRequests int
expectedItems int
}{
{
name: "merge_only",
batchCfg: BatchConfig{
FlushTimeout: 200 * time.Millisecond,
Sizer: request.SizerTypeItems,
MinSize: 10,
},
},
{
name: "merge_without_split_triggered",
batchCfg: BatchConfig{
FlushTimeout: 200 * time.Millisecond,
Sizer: request.SizerTypeItems,
MinSize: 10,
MaxSize: 200,
},
},
{
name: "merge_with_split_triggered",
batchCfg: BatchConfig{
FlushTimeout: 200 * time.Millisecond,
Sizer: request.SizerTypeItems,
MinSize: 10,
MaxSize: 20,
},
expectedRequests: 1,
expectedItems: 8,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sink := requesttest.NewSink()
cfg := newTestConfig()
cfg.Batch = configoptional.Some(tt.batchCfg)
qb, err := NewQueueBatch(newFakeRequestSettings(), cfg, sink.Export)
require.NoError(t, err)
require.NoError(t, qb.Start(context.Background(), componenttest.NewNopHost()))
require.NoError(t, qb.Send(context.Background(), &requesttest.FakeRequest{Items: 4}))
require.NoError(t, qb.Send(context.Background(), &requesttest.FakeRequest{Items: 4}))
// the first two requests should be blocked by the batchSender.
time.Sleep(50 * time.Millisecond)
assert.Equal(t, 0, sink.RequestsCount())
// the third request should trigger the export and cause an error.
sink.SetExportErr(errors.New("transient error"))
errReq := &requesttest.FakeRequest{Items: 20}
require.NoError(t, qb.Send(context.Background(), errReq))
// the batch should be dropped since the queue doesn't have re-queuing enabled.
assert.Eventually(t, func() bool {
return sink.RequestsCount() == tt.expectedRequests &&
sink.ItemsCount() == tt.expectedItems &&
qb.queue.Size() == 0
}, 1*time.Second, 10*time.Millisecond)
require.NoError(t, qb.Shutdown(context.Background()))
})
}
}
func TestQueueBatch_MergeOrSplit(t *testing.T) {
sink := requesttest.NewSink()
cfg := newTestConfig()
cfg.Batch = configoptional.Some(BatchConfig{
FlushTimeout: 100 * time.Millisecond,
Sizer: request.SizerTypeItems,
MinSize: 5,
MaxSize: 10,
})
qb, err := NewQueueBatch(newFakeRequestSettings(), cfg, sink.Export)
require.NoError(t, err)
require.NoError(t, qb.Start(context.Background(), componenttest.NewNopHost()))
// should be sent right away by reaching the minimum items size.
require.NoError(t, qb.Send(context.Background(), &requesttest.FakeRequest{Items: 8}))
assert.Eventually(t, func() bool {
return sink.RequestsCount() == 1 && sink.ItemsCount() == 8
}, 1*time.Second, 10*time.Millisecond)
// big request should be broken down into two requests, both are sent right away.
require.NoError(t, qb.Send(context.Background(), &requesttest.FakeRequest{Items: 17}))
assert.Eventually(t, func() bool {
return sink.RequestsCount() == 3 && sink.ItemsCount() == 25
}, 1*time.Second, 10*time.Millisecond)
// request that cannot be split should be dropped.
require.NoError(t, qb.Send(context.Background(), &requesttest.FakeRequest{
Items: 11,
MergeErr: errors.New("split error"),
}))
// big request should be broken down into two requests, both are sent right away.
require.NoError(t, qb.Send(context.Background(), &requesttest.FakeRequest{Items: 13}))
assert.Eventually(t, func() bool {
return sink.RequestsCount() == 5 && sink.ItemsCount() == 38
}, 1*time.Second, 10*time.Millisecond)
require.NoError(t, qb.Shutdown(context.Background()))
}
func TestQueueBatch_MergeOrSplit_Multibatch(t *testing.T) {
sink := requesttest.NewSink()
cfg := newTestConfig()
cfg.Batch = configoptional.Some(BatchConfig{
FlushTimeout: 100 * time.Millisecond,
Sizer: request.SizerTypeItems,
MinSize: 10,
})
type partitionKey struct{}
set := newFakeRequestSettings()
set.Partitioner = NewPartitioner(func(ctx context.Context, _ request.Request) string {
key := ctx.Value(partitionKey{}).(string)
return key
})
qb, err := NewQueueBatch(set, cfg, sink.Export)
require.NoError(t, err)
require.NoError(t, qb.Start(context.Background(), componenttest.NewNopHost()))
// should be sent right away by reaching the minimum items size.
require.NoError(t, qb.Send(context.WithValue(context.Background(), partitionKey{}, "p1"), &requesttest.FakeRequest{Items: 8}))
require.NoError(t, qb.Send(context.WithValue(context.Background(), partitionKey{}, "p2"), &requesttest.FakeRequest{Items: 6}))
// Neither batch should be flushed since they haven't reached min threshold.
assert.Equal(t, 0, sink.RequestsCount())
assert.Equal(t, 0, sink.ItemsCount())
require.NoError(t, qb.Send(context.WithValue(context.Background(), partitionKey{}, "p1"), &requesttest.FakeRequest{Items: 8}))
assert.Eventually(t, func() bool {
return sink.RequestsCount() == 1 && sink.ItemsCount() == 16
}, 500*time.Millisecond, 10*time.Millisecond)
require.NoError(t, qb.Send(context.WithValue(context.Background(), partitionKey{}, "p2"), &requesttest.FakeRequest{Items: 6}))
assert.Eventually(t, func() bool {
return sink.RequestsCount() == 2 && sink.ItemsCount() == 28
}, 500*time.Millisecond, 10*time.Millisecond)
require.NoError(t, qb.Shutdown(context.Background()))
}
func TestQueueBatch_Shutdown(t *testing.T) {
sink := requesttest.NewSink()
qb, err := NewQueueBatch(newFakeRequestSettings(), newTestConfig(), sink.Export)
require.NoError(t, err)
require.NoError(t, qb.Start(context.Background(), componenttest.NewNopHost()))
require.NoError(t, qb.Send(context.Background(), &requesttest.FakeRequest{Items: 3}))
// To make the request reached the batchSender before shutdown.
time.Sleep(50 * time.Millisecond)
require.NoError(t, qb.Shutdown(context.Background()))
// shutdown should force sending the batch
assert.Equal(t, 1, sink.RequestsCount())
assert.Equal(t, 3, sink.ItemsCount())
}
func TestQueueBatch_BatchBlocking(t *testing.T) {
sink := requesttest.NewSink()
cfg := newTestConfig()
cfg.WaitForResult = true
cfg.Batch = configoptional.Some(BatchConfig{Sizer: request.SizerTypeItems, MinSize: 3})
qb, err := NewQueueBatch(newFakeRequestSettings(), cfg, sink.Export)
require.NoError(t, err)
require.NoError(t, qb.Start(context.Background(), componenttest.NewNopHost()))
// send 6 blockOnOverflow requests
wg := sync.WaitGroup{}
for range 6 {
wg.Go(func() {
assert.NoError(t, qb.Send(context.Background(), &requesttest.FakeRequest{Items: 1, Delay: 10 * time.Millisecond}))
})
}
wg.Wait()
// should be sent in two batches since the batch size is 3
assert.Equal(t, 2, sink.RequestsCount())
assert.Equal(t, 6, sink.ItemsCount())
require.NoError(t, qb.Shutdown(context.Background()))
}
func TestQueueBatch_DrainActiveRequests(t *testing.T) {
sink := requesttest.NewSink()
cfg := newTestConfig()
cfg.WaitForResult = true
cfg.Batch = configoptional.Some(BatchConfig{Sizer: request.SizerTypeItems, MinSize: 2})
qb, err := NewQueueBatch(newFakeRequestSettings(), cfg, sink.Export)
require.NoError(t, err)
require.NoError(t, qb.Start(context.Background(), componenttest.NewNopHost()))
// send 3 blockOnOverflow requests with a timeout
go func() {
assert.NoError(t, qb.Send(context.Background(), &requesttest.FakeRequest{Items: 1, Delay: 40 * time.Millisecond}))
}()
go func() {
assert.NoError(t, qb.Send(context.Background(), &requesttest.FakeRequest{Items: 1, Delay: 40 * time.Millisecond}))
}()
go func() {
assert.NoError(t, qb.Send(context.Background(), &requesttest.FakeRequest{Items: 1, Delay: 40 * time.Millisecond}))
}()
// give time for the first two requests to be batched
time.Sleep(20 * time.Millisecond)
// Shutdown should force the active batch to be dispatched and wait for all batches to be delivered.
// It should take 120 milliseconds to complete.
require.NoError(t, qb.Shutdown(context.Background()))
assert.Equal(t, 2, sink.RequestsCount())
assert.Equal(t, 3, sink.ItemsCount())
}
func TestQueueBatchTimerResetNoConflict(t *testing.T) {
sink := requesttest.NewSink()
cfg := newTestConfig()
cfg.WaitForResult = true
cfg.Batch = configoptional.Some(BatchConfig{FlushTimeout: 100 * time.Millisecond, MinSize: 8})
qb, err := NewQueueBatch(newFakeRequestSettings(), cfg, sink.Export)
require.NoError(t, err)
require.NoError(t, qb.Start(context.Background(), componenttest.NewNopHost()))
// Send 2 concurrent requests that should be merged in one batch in the same interval as the flush timer
go func() {
assert.NoError(t, qb.Send(context.Background(), &requesttest.FakeRequest{Items: 4}))
}()
time.Sleep(30 * time.Millisecond)
go func() {
assert.NoError(t, qb.Send(context.Background(), &requesttest.FakeRequest{Items: 4}))
}()
// The batch should be sent either with the flush interval or by reaching the minimum items size with no conflict
assert.EventuallyWithT(t, func(c *assert.CollectT) {
assert.LessOrEqual(c, 1, sink.RequestsCount())
assert.Equal(c, 8, sink.ItemsCount())
}, 1*time.Second, 10*time.Millisecond)
require.NoError(t, qb.Shutdown(context.Background()))
}
func TestQueueBatchTimerFlush(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("skipping flaky test on Windows, see https://github.com/open-telemetry/opentelemetry-collector/issues/10802")
}
sink := requesttest.NewSink()
cfg := newTestConfig()
cfg.WaitForResult = true
cfg.Batch = configoptional.Some(BatchConfig{FlushTimeout: 100 * time.Millisecond, Sizer: request.SizerTypeItems, MinSize: 8})
qb, err := NewQueueBatch(newFakeRequestSettings(), cfg, sink.Export)
require.NoError(t, err)
require.NoError(t, qb.Start(context.Background(), componenttest.NewNopHost()))
time.Sleep(50 * time.Millisecond)
// Send 2 concurrent requests that should be merged in one batch and sent immediately
go func() {
assert.NoError(t, qb.Send(context.Background(), &requesttest.FakeRequest{Items: 4}))
}()
go func() {
assert.NoError(t, qb.Send(context.Background(), &requesttest.FakeRequest{Items: 4}))
}()
assert.EventuallyWithT(t, func(c *assert.CollectT) {
assert.LessOrEqual(c, 1, sink.RequestsCount())
assert.Equal(c, 8, sink.ItemsCount())
}, 30*time.Millisecond, 5*time.Millisecond)
// Send another request that should be flushed after 100ms instead of 50ms since last flush
go func() {
assert.NoError(t, qb.Send(context.Background(), &requesttest.FakeRequest{Items: 4}))
}()
// Confirm that it is not flushed in 50ms
time.Sleep(60 * time.Millisecond)
assert.LessOrEqual(t, 1, sink.RequestsCount())
assert.Equal(t, 8, sink.ItemsCount())
// Confirm that it is flushed after 100ms (using 60+50=110 here to be safe)
time.Sleep(50 * time.Millisecond)
assert.LessOrEqual(t, 2, sink.RequestsCount())
assert.Equal(t, 12, sink.ItemsCount())
require.NoError(t, qb.Shutdown(context.Background()))
}
func newTestConfig() Config {
return Config{
WaitForResult: false,
Sizer: request.SizerTypeItems,
NumConsumers: runtime.NumCPU(),
QueueSize: 100_000,
BlockOnOverflow: true,
Batch: configoptional.Some(BatchConfig{
FlushTimeout: 200 * time.Millisecond,
Sizer: request.SizerTypeItems,
MinSize: 2048,
}),
}
}
================================================
FILE: exporter/exporterhelper/internal/queuebatch/testdata/batch_set_empty_explicit_sizer.yaml
================================================
sizer: bytes
# Batch is set but empty, do not override sizer
batch:
================================================
FILE: exporter/exporterhelper/internal/queuebatch/testdata/batch_set_empty_no_explicit_sizer.yaml
================================================
enabled: true
batch:
================================================
FILE: exporter/exporterhelper/internal/queuebatch/testdata/batch_set_nonempty_explicit_sizer.yaml
================================================
enabled: true
sizer: bytes
queue_size: 2000
batch:
# sizer is overridden here by parent sizer
min_size: 100
================================================
FILE: exporter/exporterhelper/internal/queuebatch/testdata/batch_set_nonempty_no_explicit_sizer.yaml
================================================
enabled: true
queue_size: 2000
batch:
# sizer is NOT overridden here by parent sizer, since parent sizer is not set
min_size: 100
================================================
FILE: exporter/exporterhelper/internal/queuebatch/testdata/batch_unset.yaml
================================================
enabled: true
================================================
FILE: exporter/exporterhelper/internal/queuebatch/traces.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queuebatch // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
import (
"context"
"errors"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/queue"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sizer"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/pdata/xpdata/pref"
pdatareq "go.opentelemetry.io/collector/pdata/xpdata/request"
)
var (
tracesMarshaler = &ptrace.ProtoMarshaler{}
tracesUnmarshaler = &ptrace.ProtoUnmarshaler{}
)
// NewTracesQueueBatchSettings returns a new QueueBatchSettings to configure to WithQueueBatch when using ptrace.Traces.
// Experimental: This API is at the early stage of development and may change without backward compatibility
// until https://github.com/open-telemetry/opentelemetry-collector/issues/8122 is resolved.
func NewTracesQueueBatchSettings() Settings[request.Request] {
return Settings[request.Request]{
ReferenceCounter: tracesReferenceCounter{},
Encoding: tracesEncoding{},
}
}
var (
_ request.Request = (*tracesRequest)(nil)
_ request.ErrorHandler = (*tracesRequest)(nil)
)
type tracesRequest struct {
td ptrace.Traces
cachedSize int
}
func newTracesRequest(td ptrace.Traces) request.Request {
return &tracesRequest{
td: td,
cachedSize: -1,
}
}
type tracesEncoding struct{}
var _ encoding[request.Request] = tracesEncoding{}
func (tracesEncoding) Unmarshal(bytes []byte) (context.Context, request.Request, error) {
if queue.PersistRequestContextOnRead() {
ctx, traces, err := pdatareq.UnmarshalTraces(bytes)
if errors.Is(err, pdatareq.ErrInvalidFormat) {
// fall back to unmarshaling without context
traces, err = tracesUnmarshaler.UnmarshalTraces(bytes)
}
return ctx, newTracesRequest(traces), err
}
traces, err := tracesUnmarshaler.UnmarshalTraces(bytes)
if err != nil {
var req request.Request
return context.Background(), req, err
}
return context.Background(), newTracesRequest(traces), nil
}
func (tracesEncoding) Marshal(ctx context.Context, req request.Request) ([]byte, error) {
traces := req.(*tracesRequest).td
if queue.PersistRequestContextOnWrite() {
return pdatareq.MarshalTraces(ctx, traces)
}
return tracesMarshaler.MarshalTraces(traces)
}
var _ queue.ReferenceCounter[request.Request] = tracesReferenceCounter{}
type tracesReferenceCounter struct{}
func (tracesReferenceCounter) Ref(req request.Request) {
pref.RefTraces(req.(*tracesRequest).td)
}
func (tracesReferenceCounter) Unref(req request.Request) {
pref.UnrefTraces(req.(*tracesRequest).td)
}
func (req *tracesRequest) OnError(err error) request.Request {
var traceError consumererror.Traces
if errors.As(err, &traceError) {
// TODO: Add logic to unref the new request created here.
return newTracesRequest(traceError.Data())
}
return req
}
func (req *tracesRequest) ItemsCount() int {
return req.td.SpanCount()
}
func (req *tracesRequest) size(sizer sizer.TracesSizer) int {
if req.cachedSize == -1 {
req.cachedSize = sizer.TracesSize(req.td)
}
return req.cachedSize
}
func (req *tracesRequest) setCachedSize(size int) {
req.cachedSize = size
}
func (req *tracesRequest) BytesSize() int {
return tracesMarshaler.TracesSize(req.td)
}
// RequestConsumeFromTraces returns a RequestConsumeFunc that consumes ptrace.Traces.
func RequestConsumeFromTraces(pusher consumer.ConsumeTracesFunc) request.RequestConsumeFunc {
return func(ctx context.Context, request request.Request) error {
return pusher.ConsumeTraces(ctx, request.(*tracesRequest).td)
}
}
// RequestFromTraces returns a RequestConverterFunc that converts ptrace.Traces into a Request.
func RequestFromTraces() request.RequestConverterFunc[ptrace.Traces] {
return func(_ context.Context, traces ptrace.Traces) (request.Request, error) {
return newTracesRequest(traces), nil
}
}
================================================
FILE: exporter/exporterhelper/internal/queuebatch/traces_batch.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queuebatch // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
import (
"context"
"errors"
"fmt"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sizer"
"go.opentelemetry.io/collector/pdata/ptrace"
)
// MergeSplit splits and/or merges the provided traces request and the current request into one or more requests
// conforming with the MaxSizeConfig.
func (req *tracesRequest) MergeSplit(_ context.Context, maxSize int, szt request.SizerType, r2 request.Request) ([]request.Request, error) {
var sz sizer.TracesSizer
switch szt {
case request.SizerTypeItems:
sz = &sizer.TracesCountSizer{}
case request.SizerTypeBytes:
sz = &sizer.TracesBytesSizer{}
default:
return nil, errors.New("unknown sizer type")
}
if r2 != nil {
req2, ok := r2.(*tracesRequest)
if !ok {
return nil, errors.New("invalid input type")
}
req2.mergeTo(req, sz)
}
// If no limit we can simply merge the new request into the current and return.
if maxSize == 0 {
return []request.Request{req}, nil
}
return req.split(maxSize, sz)
}
func (req *tracesRequest) mergeTo(dst *tracesRequest, sz sizer.TracesSizer) {
if sz != nil {
dst.setCachedSize(dst.size(sz) + req.size(sz))
req.setCachedSize(0)
}
req.td.ResourceSpans().MoveAndAppendTo(dst.td.ResourceSpans())
}
func (req *tracesRequest) split(maxSize int, sz sizer.TracesSizer) ([]request.Request, error) {
var res []request.Request
for req.size(sz) > maxSize {
td, rmSize := extractTraces(req.td, maxSize, sz)
if td.SpanCount() == 0 {
return res, fmt.Errorf("one span size is greater than max size, dropping items: %d", req.td.SpanCount())
}
req.setCachedSize(req.size(sz) - rmSize)
res = append(res, newTracesRequest(td))
}
res = append(res, req)
return res, nil
}
// extractTraces extracts a new traces with a maximum number of spans.
func extractTraces(srcTraces ptrace.Traces, capacity int, sz sizer.TracesSizer) (ptrace.Traces, int) {
destTraces := ptrace.NewTraces()
capacityLeft := capacity - sz.TracesSize(destTraces)
removedSize := 0
srcTraces.ResourceSpans().RemoveIf(func(srcRS ptrace.ResourceSpans) bool {
// If the no more capacity left just return.
if capacityLeft == 0 {
return false
}
rawRsSize := sz.ResourceSpansSize(srcRS)
rsSize := sz.DeltaSize(rawRsSize)
if rsSize > capacityLeft {
extSrcRS, extRsSize := extractResourceSpans(srcRS, capacityLeft, sz)
// This cannot make it to exactly 0 for the bytes,
// force it to be 0 since that is the stopping condition.
capacityLeft = 0
removedSize += extRsSize
// There represents the delta between the delta sizes.
removedSize += rsSize - rawRsSize - (sz.DeltaSize(rawRsSize-extRsSize) - (rawRsSize - extRsSize))
// It is possible that for the bytes scenario, the extracted field contains no spans.
// Do not add it to the destination if that is the case.
if extSrcRS.ScopeSpans().Len() > 0 {
extSrcRS.MoveTo(destTraces.ResourceSpans().AppendEmpty())
}
return extSrcRS.ScopeSpans().Len() != 0
}
capacityLeft -= rsSize
removedSize += rsSize
srcRS.MoveTo(destTraces.ResourceSpans().AppendEmpty())
return true
})
return destTraces, removedSize
}
// extractResourceSpans extracts spans and returns a new resource spans with the specified number of spans.
func extractResourceSpans(srcRS ptrace.ResourceSpans, capacity int, sz sizer.TracesSizer) (ptrace.ResourceSpans, int) {
destRS := ptrace.NewResourceSpans()
destRS.SetSchemaUrl(srcRS.SchemaUrl())
srcRS.Resource().CopyTo(destRS.Resource())
// Take into account that this can have max "capacity", so when added to the parent will need space for the extra delta size.
capacityLeft := capacity - (sz.DeltaSize(capacity) - capacity) - sz.ResourceSpansSize(destRS)
removedSize := 0
srcRS.ScopeSpans().RemoveIf(func(srcSS ptrace.ScopeSpans) bool {
// If the no more capacity left just return.
if capacityLeft == 0 {
return false
}
rawSlSize := sz.ScopeSpansSize(srcSS)
ssSize := sz.DeltaSize(rawSlSize)
if ssSize > capacityLeft {
extSrcSS, extSsSize := extractScopeSpans(srcSS, capacityLeft, sz)
// This cannot make it to exactly 0 for the bytes,
// force it to be 0 since that is the stopping condition.
capacityLeft = 0
removedSize += extSsSize
// There represents the delta between the delta sizes.
removedSize += ssSize - rawSlSize - (sz.DeltaSize(rawSlSize-extSsSize) - (rawSlSize - extSsSize))
// It is possible that for the bytes scenario, the extracted field contains no spans.
// Do not add it to the destination if that is the case.
if extSrcSS.Spans().Len() > 0 {
extSrcSS.MoveTo(destRS.ScopeSpans().AppendEmpty())
}
return extSrcSS.Spans().Len() != 0
}
capacityLeft -= ssSize
removedSize += ssSize
srcSS.MoveTo(destRS.ScopeSpans().AppendEmpty())
return true
})
return destRS, removedSize
}
// extractScopeSpans extracts spans and returns a new scope spans with the specified number of spans.
func extractScopeSpans(srcSS ptrace.ScopeSpans, capacity int, sz sizer.TracesSizer) (ptrace.ScopeSpans, int) {
destSS := ptrace.NewScopeSpans()
destSS.SetSchemaUrl(srcSS.SchemaUrl())
srcSS.Scope().CopyTo(destSS.Scope())
// Take into account that this can have max "capacity", so when added to the parent will need space for the extra delta size.
capacityLeft := capacity - (sz.DeltaSize(capacity) - capacity) - sz.ScopeSpansSize(destSS)
removedSize := 0
srcSS.Spans().RemoveIf(func(srcSpan ptrace.Span) bool {
// If the no more capacity left just return.
if capacityLeft == 0 {
return false
}
rsSize := sz.DeltaSize(sz.SpanSize(srcSpan))
if rsSize > capacityLeft {
// This cannot make it to exactly 0 for the bytes,
// force it to be 0 since that is the stopping condition.
capacityLeft = 0
return false
}
capacityLeft -= rsSize
removedSize += rsSize
srcSpan.MoveTo(destSS.Spans().AppendEmpty())
return true
})
return destSS, removedSize
}
================================================
FILE: exporter/exporterhelper/internal/queuebatch/traces_batch_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queuebatch // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sizer"
"go.opentelemetry.io/collector/internal/testutil"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/pdata/testdata"
)
func TestMergeTraces(t *testing.T) {
tr1 := newTracesRequest(testdata.GenerateTraces(2))
tr2 := newTracesRequest(testdata.GenerateTraces(3))
res, err := tr1.MergeSplit(context.Background(), 0, request.SizerTypeItems, tr2)
require.NoError(t, err)
assert.Equal(t, 5, res[0].ItemsCount())
}
func TestMergeSplitTraces(t *testing.T) {
tests := []struct {
name string
szt request.SizerType
maxSize int
tr1 request.Request
tr2 request.Request
expected []request.Request
}{
{
name: "both_requests_empty",
szt: request.SizerTypeItems,
maxSize: 10,
tr1: newTracesRequest(ptrace.NewTraces()),
tr2: newTracesRequest(ptrace.NewTraces()),
expected: []request.Request{newTracesRequest(ptrace.NewTraces())},
},
{
name: "first_request_empty",
szt: request.SizerTypeItems,
maxSize: 10,
tr1: newTracesRequest(ptrace.NewTraces()),
tr2: newTracesRequest(testdata.GenerateTraces(5)),
expected: []request.Request{newTracesRequest(testdata.GenerateTraces(5))},
},
{
name: "second_request_empty",
szt: request.SizerTypeItems,
maxSize: 10,
tr1: newTracesRequest(testdata.GenerateTraces(5)),
tr2: newTracesRequest(ptrace.NewTraces()),
expected: []request.Request{newTracesRequest(testdata.GenerateTraces(5))},
},
{
name: "first_empty_second_nil",
szt: request.SizerTypeItems,
maxSize: 10,
tr1: newTracesRequest(ptrace.NewTraces()),
tr2: nil,
expected: []request.Request{newTracesRequest(ptrace.NewTraces())},
},
{
name: "merge_only",
szt: request.SizerTypeItems,
maxSize: 10,
tr1: newTracesRequest(testdata.GenerateTraces(5)),
tr2: newTracesRequest(testdata.GenerateTraces(5)),
expected: []request.Request{newTracesRequest(func() ptrace.Traces {
td := testdata.GenerateTraces(5)
testdata.GenerateTraces(5).ResourceSpans().MoveAndAppendTo(td.ResourceSpans())
return td
}())},
},
{
name: "split_only",
szt: request.SizerTypeItems,
maxSize: 4,
tr1: newTracesRequest(ptrace.NewTraces()),
tr2: newTracesRequest(testdata.GenerateTraces(10)),
expected: []request.Request{
newTracesRequest(testdata.GenerateTraces(4)),
newTracesRequest(testdata.GenerateTraces(4)),
newTracesRequest(testdata.GenerateTraces(2)),
},
},
{
name: "split_and_merge",
szt: request.SizerTypeItems,
maxSize: 10,
tr1: newTracesRequest(testdata.GenerateTraces(4)),
tr2: newTracesRequest(testdata.GenerateTraces(20)),
expected: []request.Request{
newTracesRequest(func() ptrace.Traces {
td := testdata.GenerateTraces(4)
testdata.GenerateTraces(6).ResourceSpans().MoveAndAppendTo(td.ResourceSpans())
return td
}()),
newTracesRequest(testdata.GenerateTraces(10)),
newTracesRequest(testdata.GenerateTraces(4)),
},
},
{
name: "scope_spans_split",
szt: request.SizerTypeItems,
maxSize: 10,
tr1: newTracesRequest(func() ptrace.Traces {
td := testdata.GenerateTraces(10)
extraScopeTraces := testdata.GenerateTraces(5)
extraScopeTraces.ResourceSpans().At(0).ScopeSpans().At(0).Scope().SetName("extra scope")
extraScopeTraces.ResourceSpans().MoveAndAppendTo(td.ResourceSpans())
return td
}()),
tr2: nil,
expected: []request.Request{
newTracesRequest(testdata.GenerateTraces(10)),
newTracesRequest(func() ptrace.Traces {
td := testdata.GenerateTraces(5)
td.ResourceSpans().At(0).ScopeSpans().At(0).Scope().SetName("extra scope")
return td
}()),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
res, err := tt.tr1.MergeSplit(context.Background(), tt.maxSize, tt.szt, tt.tr2)
require.NoError(t, err)
assert.Len(t, res, len(tt.expected))
for i := range res {
assert.Equal(t, tt.expected[i].(*tracesRequest).td, res[i].(*tracesRequest).td)
}
})
}
}
func TestMergeSplitTracesBasedOnByteSize(t *testing.T) {
tests := []struct {
name string
szt request.SizerType
maxSize int
lr1 request.Request
lr2 request.Request
expected []request.Request
expectPartialError bool
}{
{
name: "both_requests_empty",
szt: request.SizerTypeBytes,
maxSize: tracesMarshaler.TracesSize(testdata.GenerateTraces(10)),
lr1: newTracesRequest(ptrace.NewTraces()),
lr2: newTracesRequest(ptrace.NewTraces()),
expected: []request.Request{newTracesRequest(ptrace.NewTraces())},
},
{
name: "first_request_empty",
szt: request.SizerTypeBytes,
maxSize: tracesMarshaler.TracesSize(testdata.GenerateTraces(10)),
lr1: newTracesRequest(ptrace.NewTraces()),
lr2: newTracesRequest(testdata.GenerateTraces(5)),
expected: []request.Request{newTracesRequest(testdata.GenerateTraces(5))},
},
{
name: "first_empty_second_nil",
szt: request.SizerTypeBytes,
maxSize: tracesMarshaler.TracesSize(testdata.GenerateTraces(10)),
lr1: newTracesRequest(ptrace.NewTraces()),
lr2: nil,
expected: []request.Request{newTracesRequest(ptrace.NewTraces())},
},
{
name: "merge_only",
szt: request.SizerTypeBytes,
maxSize: tracesMarshaler.TracesSize(testdata.GenerateTraces(10)),
lr1: newTracesRequest(testdata.GenerateTraces(1)),
lr2: newTracesRequest(testdata.GenerateTraces(6)),
expected: []request.Request{newTracesRequest(func() ptrace.Traces {
traces := testdata.GenerateTraces(1)
testdata.GenerateTraces(6).ResourceSpans().MoveAndAppendTo(traces.ResourceSpans())
return traces
}())},
},
{
name: "split_only",
szt: request.SizerTypeBytes,
maxSize: tracesMarshaler.TracesSize(testdata.GenerateTraces(4)),
lr1: newTracesRequest(ptrace.NewTraces()),
lr2: newTracesRequest(testdata.GenerateTraces(10)),
expected: []request.Request{
newTracesRequest(testdata.GenerateTraces(4)),
newTracesRequest(testdata.GenerateTraces(4)),
newTracesRequest(testdata.GenerateTraces(2)),
},
},
{
name: "merge_and_split",
szt: request.SizerTypeBytes,
maxSize: tracesMarshaler.TracesSize(testdata.GenerateTraces(10))/2 + tracesMarshaler.TracesSize(testdata.GenerateTraces(11))/2,
lr1: newTracesRequest(testdata.GenerateTraces(8)),
lr2: newTracesRequest(testdata.GenerateTraces(20)),
expected: []request.Request{
newTracesRequest(func() ptrace.Traces {
traces := testdata.GenerateTraces(8)
testdata.GenerateTraces(2).ResourceSpans().MoveAndAppendTo(traces.ResourceSpans())
return traces
}()),
newTracesRequest(testdata.GenerateTraces(10)),
newTracesRequest(testdata.GenerateTraces(8)),
},
},
{
name: "scope_spans_split",
szt: request.SizerTypeBytes,
maxSize: tracesMarshaler.TracesSize(testdata.GenerateTraces(4)),
lr1: newTracesRequest(func() ptrace.Traces {
ld := testdata.GenerateTraces(4)
ld.ResourceSpans().At(0).ScopeSpans().AppendEmpty().Spans().AppendEmpty().Attributes().PutStr("attr", "attrvalue")
return ld
}()),
lr2: newTracesRequest(testdata.GenerateTraces(2)),
expected: []request.Request{
newTracesRequest(testdata.GenerateTraces(4)),
newTracesRequest(func() ptrace.Traces {
ld := testdata.GenerateTraces(0)
ld.ResourceSpans().At(0).ScopeSpans().At(0).Spans().AppendEmpty().Attributes().PutStr("attr", "attrvalue")
testdata.GenerateTraces(2).ResourceSpans().MoveAndAppendTo(ld.ResourceSpans())
return ld
}()),
},
expectPartialError: false,
},
{
name: "unsplittable_large_trace",
szt: request.SizerTypeBytes,
maxSize: 10,
lr1: newTracesRequest(func() ptrace.Traces {
ld := testdata.GenerateTraces(1)
ld.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0).Attributes().PutStr("large_attr", string(make([]byte, 100)))
return ld
}()),
lr2: nil,
expected: []request.Request{},
expectPartialError: true,
},
{
name: "splittable_then_unsplittable_trace",
szt: request.SizerTypeBytes,
maxSize: 1000,
lr1: newTracesRequest(func() ptrace.Traces {
ld := testdata.GenerateTraces(2)
ld.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0).Attributes().PutStr("large_attr", string(make([]byte, 10)))
ld.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(1).Attributes().PutStr("large_attr", string(make([]byte, 1001)))
return ld
}()),
lr2: nil,
expected: []request.Request{newTracesRequest(func() ptrace.Traces {
ld := testdata.GenerateTraces(1)
ld.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0).Attributes().PutStr("large_attr", string(make([]byte, 10)))
return ld
}())},
expectPartialError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
res, err := tt.lr1.MergeSplit(context.Background(), tt.maxSize, tt.szt, tt.lr2)
if tt.expectPartialError {
require.ErrorContains(t, err, "one span size is greater than max size, dropping items:")
} else {
require.NoError(t, err)
}
assert.Len(t, res, len(tt.expected))
for i := range res {
assert.Equal(t, tt.expected[i].(*tracesRequest).td, res[i].(*tracesRequest).td)
assert.Equal(t,
tracesMarshaler.TracesSize(tt.expected[i].(*tracesRequest).td),
tracesMarshaler.TracesSize(res[i].(*tracesRequest).td))
}
})
}
}
func TestMergeSplitTracesInputNotModifiedIfErrorReturned(t *testing.T) {
r1 := newTracesRequest(testdata.GenerateTraces(18))
r2 := newLogsRequest(testdata.GenerateLogs(3))
_, err := r1.MergeSplit(context.Background(), 10, request.SizerTypeItems, r2)
require.Error(t, err)
assert.Equal(t, 18, r1.ItemsCount())
}
func TestExtractTraces(t *testing.T) {
for i := range 10 {
td := testdata.GenerateTraces(10)
extractedTraces, removedSize := extractTraces(td, i, &sizer.TracesCountSizer{})
assert.Equal(t, i, extractedTraces.SpanCount())
assert.Equal(t, 10-i, td.SpanCount())
assert.Equal(t, i, removedSize)
}
}
func TestMergeSplitManySmallTraces(t *testing.T) {
merged := []request.Request{newTracesRequest(testdata.GenerateTraces(1))}
for range 1000 {
lr2 := newTracesRequest(testdata.GenerateTraces(10))
res, _ := merged[len(merged)-1].MergeSplit(context.Background(), 10000, request.SizerTypeItems, lr2)
merged = append(merged[0:len(merged)-1], res...)
}
assert.Len(t, merged, 2)
}
func TestTracesMergeSplitExactBytes(t *testing.T) {
pb := ptrace.ProtoMarshaler{}
// Set max size off by 1, so forces every log to be it's own batch.
lr := newTracesRequest(testdata.GenerateTraces(4))
merged, err := lr.MergeSplit(context.Background(), pb.TracesSize(testdata.GenerateTraces(2))-1, request.SizerTypeBytes, nil)
require.NoError(t, err)
assert.Len(t, merged, 4)
}
func TestTracesMergeSplitExactItems(t *testing.T) {
// Set max size off by 1, so forces every log to be it's own batch.
lr := newTracesRequest(testdata.GenerateTraces(4))
merged, err := lr.MergeSplit(context.Background(), 1, request.SizerTypeItems, nil)
require.NoError(t, err)
assert.Len(t, merged, 4)
}
func TestTracesMergeSplitUnknownSizerType(t *testing.T) {
req := newTracesRequest(ptrace.NewTraces())
// Call MergeSplit with invalid sizer
_, err := req.MergeSplit(context.Background(), 0, request.SizerType{}, nil)
require.EqualError(t, err, "unknown sizer type")
}
func BenchmarkSplittingBasedOnItemCountManySmallTraces(b *testing.B) {
testutil.SkipGCHeavyBench(b)
// All requests merge into a single batch.
b.ReportAllocs()
for b.Loop() {
merged := []request.Request{newTracesRequest(testdata.GenerateTraces(10))}
for range 1000 {
lr2 := newTracesRequest(testdata.GenerateTraces(10))
res, _ := merged[len(merged)-1].MergeSplit(context.Background(), 10010, request.SizerTypeItems, lr2)
merged = append(merged[0:len(merged)-1], res...)
}
assert.Len(b, merged, 1)
}
}
func BenchmarkSplittingBasedOnItemCountManyTracesSlightlyAboveLimit(b *testing.B) {
testutil.SkipGCHeavyBench(b)
// Every incoming request results in a split.
b.ReportAllocs()
for b.Loop() {
merged := []request.Request{newTracesRequest(testdata.GenerateTraces(0))}
for range 10 {
lr2 := newTracesRequest(testdata.GenerateTraces(10001))
res, _ := merged[len(merged)-1].MergeSplit(context.Background(), 10000, request.SizerTypeItems, lr2)
merged = append(merged[0:len(merged)-1], res...)
}
assert.Len(b, merged, 11)
}
}
func BenchmarkSplittingBasedOnItemCountHugeTraces(b *testing.B) {
testutil.SkipGCHeavyBench(b)
// One request splits into many batches.
b.ReportAllocs()
for b.Loop() {
merged := []request.Request{newTracesRequest(testdata.GenerateTraces(0))}
lr2 := newTracesRequest(testdata.GenerateTraces(100000))
res, _ := merged[len(merged)-1].MergeSplit(context.Background(), 10000, request.SizerTypeItems, lr2)
merged = append(merged[0:len(merged)-1], res...)
assert.Len(b, merged, 10)
}
}
================================================
FILE: exporter/exporterhelper/internal/queuebatch/traces_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package queuebatch // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/pdata/testdata"
)
func TestTracesRequest(t *testing.T) {
mr := newTracesRequest(testdata.GenerateTraces(1))
traceErr := consumererror.NewTraces(errors.New("some error"), ptrace.NewTraces())
assert.Equal(t, newTracesRequest(ptrace.NewTraces()), mr.(request.ErrorHandler).OnError(traceErr))
}
================================================
FILE: exporter/exporterhelper/internal/request/request.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package request // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
import (
"context"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sender"
)
// Request represents a single request that can be sent to an external endpoint.
// Experimental: This API is at the early stage of development and may change without backward compatibility
// until https://github.com/open-telemetry/opentelemetry-collector/issues/8122 is resolved.
type Request interface {
// ItemsCount returns a number of basic items in the request where item is the smallest piece of data that can be
// sent. For example, for OTLP exporter, this value represents the number of spans,
// metric data points or log records.
ItemsCount() int
// MergeSplit is a function that merge and/or splits this request with another one into multiple requests based on the
// configured limit provided in maxSize.
// MergeSplit does not split if maxSize is zero.
// All the returned requests MUST have a number of items that does not exceed the maximum number of items.
// Size of the last returned request MUST be less or equal than the size of any other returned request.
// The original request MUST not be mutated if error is returned after mutation or if the exporter is
// marked as not mutable. The length of the returned slice MUST not be 0.
// Experimental: This API is at the early stage of development and may change without backward compatibility
// until https://github.com/open-telemetry/opentelemetry-collector/issues/8122 is resolved.
MergeSplit(ctx context.Context, maxSize int, sizerType SizerType, req Request) ([]Request, error)
// BytesSize returns the size of the request in bytes.
BytesSize() int
}
// ErrorHandler is an optional interface that can be implemented by Request to provide a way handle partial
// temporary failures. For example, if some items failed to process and can be retried, this interface allows to
// return a new Request that contains the items left to be sent. Otherwise, the original Request should be returned.
// If not implemented, the original Request will be returned assuming the error is applied to the whole Request.
// Experimental: This API is at the early stage of development and may change without backward compatibility
// until https://github.com/open-telemetry/opentelemetry-collector/issues/8122 is resolved.
type ErrorHandler interface {
Request
// OnError returns a new Request may contain the items left to be sent if some items failed to process and can be retried.
// Otherwise, it should return the original Request.
OnError(error) Request
}
type RequestConverterFunc[T any] func(context.Context, T) (Request, error)
// RequestConsumeFunc processes the request. After the function returns, the request is no longer accessible,
// and accessing it is considered undefined behavior.
type RequestConsumeFunc = sender.SendFunc[Request]
================================================
FILE: exporter/exporterhelper/internal/request/sizer.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package request // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
import (
"encoding"
"fmt"
)
// TODO: Move this back to queuebatch when remove the circular dependency.
var (
_ encoding.TextMarshaler = (*SizerType)(nil)
_ encoding.TextUnmarshaler = (*SizerType)(nil)
)
type SizerType struct {
val string
}
const (
sizerTypeBytes = "bytes"
sizerTypeItems = "items"
sizerTypeRequests = "requests"
)
var (
SizerTypeBytes = SizerType{val: sizerTypeBytes}
SizerTypeItems = SizerType{val: sizerTypeItems}
SizerTypeRequests = SizerType{val: sizerTypeRequests}
)
// UnmarshalText implements TextUnmarshaler interface.
func (s *SizerType) UnmarshalText(text []byte) error {
switch str := string(text); str {
case sizerTypeItems:
*s = SizerTypeItems
case sizerTypeBytes:
*s = SizerTypeBytes
case sizerTypeRequests:
*s = SizerTypeRequests
default:
return fmt.Errorf("invalid sizer: %q", str)
}
return nil
}
func (s *SizerType) MarshalText() ([]byte, error) {
return []byte(s.val), nil
}
func (s *SizerType) String() string {
return s.val
}
// Sizer is an interface that returns the size of the given element.
type Sizer interface {
Sizeof(Request) int64
}
func NewSizer(sizerType SizerType) Sizer {
switch sizerType {
case SizerTypeBytes:
return NewBytesSizer()
case SizerTypeItems:
return NewItemsSizer()
default:
return RequestsSizer{}
}
}
// RequestsSizer is a Sizer implementation that returns the size of a queue element as one request.
type RequestsSizer struct{}
func (rs RequestsSizer) Sizeof(Request) int64 {
return 1
}
type itemsSizer struct{}
func (itemsSizer) Sizeof(req Request) int64 {
return int64(req.ItemsCount())
}
type bytesSizer struct{}
func (bytesSizer) Sizeof(req Request) int64 {
return int64(req.BytesSize())
}
func NewItemsSizer() Sizer {
return itemsSizer{}
}
func NewBytesSizer() Sizer {
return bytesSizer{}
}
================================================
FILE: exporter/exporterhelper/internal/request/sizer_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package request_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/requesttest"
)
func TestItemsSizer(t *testing.T) {
sz := request.NewItemsSizer()
assert.EqualValues(t, 3, sz.Sizeof(&requesttest.FakeRequest{Items: 3}))
}
func TestSizeTypeUnmarshalText(t *testing.T) {
var sizer request.SizerType
require.NoError(t, sizer.UnmarshalText([]byte("bytes")))
require.NoError(t, sizer.UnmarshalText([]byte("items")))
require.NoError(t, sizer.UnmarshalText([]byte("requests")))
require.Error(t, sizer.UnmarshalText([]byte("invalid")))
}
func TestSizeTypeMarshalText(t *testing.T) {
val, err := request.SizerTypeBytes.MarshalText()
require.NoError(t, err)
assert.Equal(t, []byte("bytes"), val)
val, err = request.SizerTypeItems.MarshalText()
require.NoError(t, err)
assert.Equal(t, []byte("items"), val)
val, err = request.SizerTypeRequests.MarshalText()
require.NoError(t, err)
assert.Equal(t, []byte("requests"), val)
}
================================================
FILE: exporter/exporterhelper/internal/requesttest/request.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package requesttest // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/requesttest"
import (
"context"
"errors"
"fmt"
"time"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
)
type errorPartial struct {
fr *FakeRequest
}
func (e errorPartial) Error() string {
return fmt.Sprintf("items: %d", e.fr.Items)
}
type FakeRequest struct {
Items int
Bytes int
Partial int
MergeErr error
MergeErrResult []request.Request
Delay time.Duration
}
func (r *FakeRequest) OnError(err error) request.Request {
var pErr errorPartial
if errors.As(err, &pErr) {
return pErr.fr
}
return r
}
func (r *FakeRequest) ItemsCount() int {
return r.Items
}
func (r *FakeRequest) BytesSize() int {
return r.Bytes
}
func (r *FakeRequest) MergeSplit(_ context.Context, maxSize int, szt request.SizerType, r2 request.Request) ([]request.Request, error) {
if r.MergeErr != nil {
return r.MergeErrResult, r.MergeErr
}
if r2 != nil {
fr2 := r2.(*FakeRequest)
if fr2.MergeErr != nil {
return fr2.MergeErrResult, fr2.MergeErr
}
fr2.mergeTo(r)
}
if maxSize == 0 {
return []request.Request{r}, nil
}
var res []request.Request
switch szt {
case request.SizerTypeItems:
for r.Items != 0 {
if r.Items <= maxSize {
res = append(res, r)
break
}
res = append(res, &FakeRequest{Items: maxSize, Bytes: -1, Delay: r.Delay})
r.Items -= maxSize
r.Bytes = -1
}
case request.SizerTypeBytes:
for r.Bytes != 0 {
if r.Bytes <= maxSize {
res = append(res, r)
break
}
res = append(res, &FakeRequest{Items: -1, Bytes: maxSize, Delay: r.Delay})
r.Items = -1
r.Bytes -= maxSize
}
}
return res, nil
}
func (r *FakeRequest) mergeTo(dst *FakeRequest) {
dst.Items += r.Items
dst.Bytes += r.Bytes
dst.Delay += r.Delay
}
func RequestFromMetricsFunc(err error) func(context.Context, pmetric.Metrics) (request.Request, error) {
return func(_ context.Context, md pmetric.Metrics) (request.Request, error) {
return &FakeRequest{Items: md.DataPointCount()}, err
}
}
func RequestFromTracesFunc(err error) func(context.Context, ptrace.Traces) (request.Request, error) {
return func(_ context.Context, td ptrace.Traces) (request.Request, error) {
return &FakeRequest{Items: td.SpanCount()}, err
}
}
func RequestFromLogsFunc(err error) func(context.Context, plog.Logs) (request.Request, error) {
return func(_ context.Context, ld plog.Logs) (request.Request, error) {
return &FakeRequest{Items: ld.LogRecordCount()}, err
}
}
================================================
FILE: exporter/exporterhelper/internal/requesttest/sink.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package requesttest // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/requesttest"
import (
"context"
"sync"
"time"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
)
func NewSink() *Sink {
return &Sink{}
}
type Sink struct {
requestsCount int
itemsCount int
bytesCount int
mu sync.Mutex
exportErr error
}
func (s *Sink) Export(ctx context.Context, req request.Request) error {
r := req.(*FakeRequest)
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(r.Delay):
}
s.mu.Lock()
defer s.mu.Unlock()
if s.exportErr != nil {
err := s.exportErr
s.exportErr = nil
return err
}
if r.Partial > 0 {
s.requestsCount++
s.itemsCount += r.Items - r.Partial
return errorPartial{fr: &FakeRequest{
Items: r.Partial,
Partial: 0,
MergeErr: r.MergeErr,
Delay: r.Delay,
}}
}
s.requestsCount++
s.itemsCount += r.Items
s.bytesCount += r.Bytes
return nil
}
func (s *Sink) SetExportErr(err error) {
s.mu.Lock()
defer s.mu.Unlock()
s.exportErr = err
}
func (s *Sink) RequestsCount() int {
s.mu.Lock()
defer s.mu.Unlock()
return s.requestsCount
}
func (s *Sink) ItemsCount() int {
s.mu.Lock()
defer s.mu.Unlock()
return s.itemsCount
}
func (s *Sink) BytesCount() int {
s.mu.Lock()
defer s.mu.Unlock()
return s.bytesCount
}
================================================
FILE: exporter/exporterhelper/internal/retry_sender.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal"
import (
"context"
"errors"
"fmt"
"time"
"github.com/cenkalti/backoff/v5"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configretry"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/experr"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sender"
)
// TODO: Clean this by forcing all exporters to return an internal error type that always include the information about retries.
type throttleRetry struct {
err error
delay time.Duration
}
func (t throttleRetry) Error() string {
return "Throttle (" + t.delay.String() + "), error: " + t.err.Error()
}
func (t throttleRetry) Unwrap() error {
return t.err
}
// NewThrottleRetry creates a new throttle retry error.
func NewThrottleRetry(err error, delay time.Duration) error {
return throttleRetry{
err: err,
delay: delay,
}
}
type retrySender struct {
component.StartFunc
cfg configretry.BackOffConfig
stopCh chan struct{}
logger *zap.Logger
next sender.Sender[request.Request]
}
func newRetrySender(config configretry.BackOffConfig, set exporter.Settings, next sender.Sender[request.Request]) *retrySender {
return &retrySender{
cfg: config,
stopCh: make(chan struct{}),
logger: set.Logger,
next: next,
}
}
func (rs *retrySender) Shutdown(context.Context) error {
close(rs.stopCh)
return nil
}
// Send implements the requestSender interface
func (rs *retrySender) Send(ctx context.Context, req request.Request) error {
// 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: rs.cfg.RandomizationFactor,
Multiplier: rs.cfg.Multiplier,
MaxInterval: rs.cfg.MaxInterval,
}
span := trace.SpanFromContext(ctx)
retryNum := int64(0)
var maxElapsedTime time.Time
if rs.cfg.MaxElapsedTime > 0 {
maxElapsedTime = time.Now().Add(rs.cfg.MaxElapsedTime)
}
for {
span.AddEvent(
"Sending request.",
trace.WithAttributes(attribute.Int64("retry_num", retryNum)))
err := rs.next.Send(ctx, req)
if err == nil {
return nil
}
// Immediately drop data on permanent errors.
if consumererror.IsPermanent(err) {
return fmt.Errorf("not retryable error: %w", err)
}
if errReq, ok := req.(request.ErrorHandler); ok {
req = errReq.OnError(err)
}
backoffDelay := expBackoff.NextBackOff()
if backoffDelay == backoff.Stop {
return fmt.Errorf("no more retries left: %w", err)
}
throttleErr := throttleRetry{}
if errors.As(err, &throttleErr) {
backoffDelay = max(backoffDelay, throttleErr.delay)
}
nextRetryTime := time.Now().Add(backoffDelay)
if !maxElapsedTime.IsZero() && maxElapsedTime.Before(nextRetryTime) {
// The delay is longer than the maxElapsedTime.
return fmt.Errorf("no more retries left: %w", err)
}
if deadline, has := ctx.Deadline(); has && deadline.Before(nextRetryTime) {
// The delay is longer than the deadline. There is no point in
// waiting for cancelation.
return fmt.Errorf("request will be cancelled before next retry: %w", err)
}
backoffDelayStr := backoffDelay.String()
span.AddEvent(
"Exporting failed. Will retry the request after interval.",
trace.WithAttributes(
attribute.String("interval", backoffDelayStr),
attribute.String("error", err.Error())))
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 <-ctx.Done():
return fmt.Errorf("request is cancelled or timed out: %w", err)
case <-rs.stopCh:
return experr.NewShutdownErr(err)
case <-time.After(backoffDelay):
}
}
}
================================================
FILE: exporter/exporterhelper/internal/retry_sender_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal
import (
"context"
"errors"
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/config/configretry"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/requesttest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sender"
"go.opentelemetry.io/collector/exporter/exportertest"
)
func TestRetrySenderDropOnPermanentError(t *testing.T) {
rCfg := configretry.NewDefaultBackOffConfig()
sink := requesttest.NewSink()
expErr := consumererror.NewPermanent(errors.New("bad data"))
rs := newRetrySender(rCfg, exportertest.NewNopSettings(exportertest.NopType), sender.NewSender(sink.Export))
require.NoError(t, rs.Start(context.Background(), componenttest.NewNopHost()))
sink.SetExportErr(expErr)
require.ErrorIs(t, rs.Send(context.Background(), &requesttest.FakeRequest{Items: 2}), expErr)
sink.SetExportErr(expErr)
require.ErrorIs(t, rs.Send(context.Background(), &requesttest.FakeRequest{Items: 3}), expErr)
assert.Equal(t, 0, sink.ItemsCount())
assert.Equal(t, 0, sink.RequestsCount())
require.NoError(t, rs.Shutdown(context.Background()))
}
func TestRetrySenderSimpleRetry(t *testing.T) {
rCfg := configretry.NewDefaultBackOffConfig()
rCfg.InitialInterval = 0
sink := requesttest.NewSink()
expErr := errors.New("transient error")
rs := newRetrySender(rCfg, exportertest.NewNopSettings(exportertest.NopType), sender.NewSender(sink.Export))
require.NoError(t, rs.Start(context.Background(), componenttest.NewNopHost()))
sink.SetExportErr(expErr)
require.NoError(t, rs.Send(context.Background(), &requesttest.FakeRequest{Items: 2}))
assert.Equal(t, 2, sink.ItemsCount())
assert.Equal(t, 1, sink.RequestsCount())
require.NoError(t, rs.Shutdown(context.Background()))
}
func TestRetrySenderRetryPartial(t *testing.T) {
rCfg := configretry.NewDefaultBackOffConfig()
rCfg.InitialInterval = 0
sink := requesttest.NewSink()
rs := newRetrySender(rCfg, exportertest.NewNopSettings(exportertest.NopType), sender.NewSender(sink.Export))
require.NoError(t, rs.Start(context.Background(), componenttest.NewNopHost()))
require.NoError(t, rs.Send(context.Background(), &requesttest.FakeRequest{Items: 5, Partial: 3}))
assert.Equal(t, 5, sink.ItemsCount())
assert.Equal(t, 2, sink.RequestsCount())
require.NoError(t, rs.Shutdown(context.Background()))
}
func TestRetrySenderMaxElapsedTime(t *testing.T) {
rCfg := configretry.NewDefaultBackOffConfig()
rCfg.InitialInterval = time.Millisecond
rCfg.MaxElapsedTime = 100 * time.Millisecond
expErr := errors.New("transient error")
rs := newRetrySender(rCfg, exportertest.NewNopSettings(exportertest.NopType), sender.NewSender(func(context.Context, request.Request) error { return expErr }))
require.NoError(t, rs.Start(context.Background(), componenttest.NewNopHost()))
require.ErrorIs(t, rs.Send(context.Background(), &requesttest.FakeRequest{Items: 2}), expErr)
require.NoError(t, rs.Shutdown(context.Background()))
}
func TestRetrySenderThrottleError(t *testing.T) {
rCfg := configretry.NewDefaultBackOffConfig()
rCfg.InitialInterval = 10 * time.Millisecond
sink := requesttest.NewSink()
rs := newRetrySender(rCfg, exportertest.NewNopSettings(exportertest.NopType), sender.NewSender(sink.Export))
require.NoError(t, rs.Start(context.Background(), componenttest.NewNopHost()))
retry := fmt.Errorf("wrappe error: %w", NewThrottleRetry(errors.New("throttle error"), 100*time.Millisecond))
start := time.Now()
sink.SetExportErr(retry)
require.NoError(t, rs.Send(context.Background(), &requesttest.FakeRequest{Items: 5}))
// The initial backoff is 10ms, but because of the throttle this should wait at least 100ms.
assert.Less(t, 100*time.Millisecond, time.Since(start))
assert.Equal(t, 5, sink.ItemsCount())
assert.Equal(t, 1, sink.RequestsCount())
require.NoError(t, rs.Shutdown(context.Background()))
}
func TestRetrySenderWithContextTimeout(t *testing.T) {
const testTimeout = 10 * time.Second
rCfg := configretry.NewDefaultBackOffConfig()
rCfg.Enabled = true
// First attempt after 100ms is attempted
rCfg.InitialInterval = 100 * time.Millisecond
rCfg.RandomizationFactor = 0
// Second attempt is at twice the testTimeout
rCfg.Multiplier = float64(2 * testTimeout / rCfg.InitialInterval)
set := exportertest.NewNopSettings(exportertest.NopType)
logger, observed := observer.New(zap.InfoLevel)
set.Logger = zap.New(logger)
rs := newRetrySender(rCfg, set, sender.NewSender(func(context.Context, request.Request) error { return errors.New("transient error") }))
require.NoError(t, rs.Start(context.Background(), componenttest.NewNopHost()))
start := time.Now()
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
defer cancel()
require.ErrorContains(t,
rs.Send(ctx, &requesttest.FakeRequest{Items: 2}),
"request will be cancelled before next retry: transient error")
assert.Len(t, observed.All(), 1)
assert.Equal(t, "Exporting failed. Will retry the request after interval.", observed.All()[0].Message)
require.Less(t, time.Since(start), testTimeout/2)
require.NoError(t, rs.Shutdown(context.Background()))
}
func TestRetrySenderWithCancelledContext(t *testing.T) {
rCfg := configretry.NewDefaultBackOffConfig()
rCfg.Enabled = true
// First attempt after 1s is attempted
rCfg.InitialInterval = 1 * time.Second
rs := newRetrySender(rCfg, exportertest.NewNopSettings(exportertest.NopType), sender.NewSender(func(context.Context, request.Request) error { return errors.New("transient error") }))
require.NoError(t, rs.Start(context.Background(), componenttest.NewNopHost()))
start := time.Now()
ctx, cancel := context.WithCancelCause(context.Background())
go func() {
<-time.After(100 * time.Millisecond)
cancel(errors.New("my reason"))
}()
require.ErrorContains(t,
rs.Send(ctx, &requesttest.FakeRequest{Items: 2}),
"request is cancelled or timed out: transient error")
require.Less(t, time.Since(start), 1*time.Second)
require.NoError(t, rs.Shutdown(context.Background()))
}
================================================
FILE: exporter/exporterhelper/internal/sender/sender.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sender // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/sender"
import (
"context"
"go.opentelemetry.io/collector/component"
)
type Sender[T any] interface {
component.Component
Send(context.Context, T) error
}
type SendFunc[T any] func(ctx context.Context, data T) error
func NewSender[T any](consFunc SendFunc[T]) Sender[T] {
return &sender[T]{consFunc: consFunc}
}
// sender is a Sender that emits the incoming request to the exporter consumer func.
type sender[T any] struct {
component.StartFunc
component.ShutdownFunc
consFunc SendFunc[T]
}
func (es *sender[T]) Send(ctx context.Context, req T) error {
return es.consFunc(ctx, req)
}
================================================
FILE: exporter/exporterhelper/internal/sender/sender_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sender
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestExportSenderRightArguments(t *testing.T) {
es := NewSender[int64](func(_ context.Context, data int64) error {
assert.Equal(t, int64(1), data)
return nil
})
require.NoError(t, es.Send(context.Background(), int64(1)))
}
func TestExportSenderReturnsError(t *testing.T) {
err := errors.New("test error")
es := NewSender[int64](func(_ context.Context, data int64) error {
assert.Equal(t, int64(1), data)
return err
})
require.ErrorIs(t, es.Send(context.Background(), int64(1)), err)
}
================================================
FILE: exporter/exporterhelper/internal/sendertest/sendertest.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sendertest // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/sendertest"
import (
"context"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sender"
)
func NewNopSenderFunc[T any]() sender.SendFunc[T] {
return func(context.Context, T) error {
return nil
}
}
func NewErrSenderFunc[T any](err error) sender.SendFunc[T] {
return func(context.Context, T) error {
return err
}
}
================================================
FILE: exporter/exporterhelper/internal/sendertest/sendertest_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sendertest
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/require"
)
func TestNewNopSenderFunc(t *testing.T) {
sender := NewNopSenderFunc[int]()
require.NoError(t, sender(context.Background(), 1))
}
func TestNewErrSenderFunc(t *testing.T) {
err := errors.New("test")
sender := NewErrSenderFunc[int](err)
require.ErrorIs(t, sender(context.Background(), 1), err)
}
================================================
FILE: exporter/exporterhelper/internal/sizer/logs_sizer.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sizer // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/sizer"
import (
"go.opentelemetry.io/collector/pdata/plog"
)
type LogsSizer interface {
LogsSize(ld plog.Logs) int
ResourceLogsSize(rl plog.ResourceLogs) int
ScopeLogsSize(sl plog.ScopeLogs) int
LogRecordSize(lr plog.LogRecord) int
// DeltaSize returns the delta size when a ResourceLog, ScopeLog or LogRecord is added.
DeltaSize(newItemSize int) int
}
// LogsBytesSizer returns the byte size of serialized protos.
type LogsBytesSizer struct {
plog.ProtoMarshaler
protoDeltaSizer
}
// LogsCountSizer returns the nunmber of logs entries.
type LogsCountSizer struct{}
func (s *LogsCountSizer) LogsSize(ld plog.Logs) int {
return ld.LogRecordCount()
}
func (s *LogsCountSizer) ResourceLogsSize(rl plog.ResourceLogs) int {
count := 0
for k := 0; k < rl.ScopeLogs().Len(); k++ {
count += rl.ScopeLogs().At(k).LogRecords().Len()
}
return count
}
func (s *LogsCountSizer) ScopeLogsSize(sl plog.ScopeLogs) int {
return sl.LogRecords().Len()
}
func (s *LogsCountSizer) LogRecordSize(_ plog.LogRecord) int {
return 1
}
func (s *LogsCountSizer) DeltaSize(newItemSize int) int {
return newItemSize
}
================================================
FILE: exporter/exporterhelper/internal/sizer/logs_sizer_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sizer // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/sizer"
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/testdata"
)
func TestLogsCountSizer(t *testing.T) {
ld := testdata.GenerateLogs(5)
sizer := LogsCountSizer{}
require.Equal(t, 5, sizer.LogsSize(ld))
rl := ld.ResourceLogs().At(0)
require.Equal(t, 5, sizer.ResourceLogsSize(rl))
sl := rl.ScopeLogs().At(0)
require.Equal(t, 5, sizer.ScopeLogsSize(sl))
require.Equal(t, 1, sizer.LogRecordSize(sl.LogRecords().At(0)))
require.Equal(t, 1, sizer.LogRecordSize(sl.LogRecords().At(1)))
require.Equal(t, 1, sizer.LogRecordSize(sl.LogRecords().At(2)))
require.Equal(t, 1, sizer.LogRecordSize(sl.LogRecords().At(3)))
require.Equal(t, 1, sizer.LogRecordSize(sl.LogRecords().At(4)))
prevSize := sizer.ScopeLogsSize(sl)
lr := sl.LogRecords().At(2)
lr.CopyTo(sl.LogRecords().AppendEmpty())
require.Equal(t, sizer.ScopeLogsSize(sl), prevSize+sizer.DeltaSize(sizer.LogRecordSize(lr)))
}
func TestLogsBytesSizer(t *testing.T) {
ld := testdata.GenerateLogs(5)
sizer := LogsBytesSizer{}
require.Equal(t, 545, sizer.LogsSize(ld))
rl := ld.ResourceLogs().At(0)
require.Equal(t, 542, sizer.ResourceLogsSize(rl))
sl := rl.ScopeLogs().At(0)
require.Equal(t, 497, sizer.ScopeLogsSize(sl))
require.Equal(t, 109, sizer.LogRecordSize(sl.LogRecords().At(0)))
require.Equal(t, 79, sizer.LogRecordSize(sl.LogRecords().At(1)))
require.Equal(t, 109, sizer.LogRecordSize(sl.LogRecords().At(2)))
require.Equal(t, 79, sizer.LogRecordSize(sl.LogRecords().At(3)))
require.Equal(t, 109, sizer.LogRecordSize(sl.LogRecords().At(4)))
prevSize := sizer.ScopeLogsSize(sl)
lr := sl.LogRecords().At(2)
lr.CopyTo(sl.LogRecords().AppendEmpty())
require.Equal(t, sizer.ScopeLogsSize(sl), prevSize+sizer.DeltaSize(sizer.LogRecordSize(lr)))
}
================================================
FILE: exporter/exporterhelper/internal/sizer/metrics_sizer.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sizer // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/sizer"
import (
"go.opentelemetry.io/collector/pdata/pmetric"
) // MetricsCountSizer returns the nunmber of metrics entries.
type MetricsSizer interface {
MetricsSize(md pmetric.Metrics) (count int)
ResourceMetricsSize(rm pmetric.ResourceMetrics) (count int)
ScopeMetricsSize(sm pmetric.ScopeMetrics) (count int)
MetricSize(m pmetric.Metric) int
DeltaSize(newItemSize int) int
NumberDataPointSize(ndp pmetric.NumberDataPoint) int
HistogramDataPointSize(hdp pmetric.HistogramDataPoint) int
ExponentialHistogramDataPointSize(ehdp pmetric.ExponentialHistogramDataPoint) int
SummaryDataPointSize(sdps pmetric.SummaryDataPoint) int
}
type MetricsBytesSizer struct {
pmetric.ProtoMarshaler
protoDeltaSizer
}
var _ MetricsSizer = &MetricsBytesSizer{}
type MetricsCountSizer struct{}
var _ MetricsSizer = &MetricsCountSizer{}
func (s *MetricsCountSizer) MetricsSize(md pmetric.Metrics) int {
return md.DataPointCount()
}
func (s *MetricsCountSizer) ResourceMetricsSize(rm pmetric.ResourceMetrics) (count int) {
for i := 0; i < rm.ScopeMetrics().Len(); i++ {
count += s.ScopeMetricsSize(rm.ScopeMetrics().At(i))
}
return count
}
func (s *MetricsCountSizer) ScopeMetricsSize(sm pmetric.ScopeMetrics) (count int) {
for i := 0; i < sm.Metrics().Len(); i++ {
count += s.MetricSize(sm.Metrics().At(i))
}
return count
}
func (s *MetricsCountSizer) MetricSize(m pmetric.Metric) int {
switch m.Type() {
case pmetric.MetricTypeGauge:
return m.Gauge().DataPoints().Len()
case pmetric.MetricTypeSum:
return m.Sum().DataPoints().Len()
case pmetric.MetricTypeHistogram:
return m.Histogram().DataPoints().Len()
case pmetric.MetricTypeExponentialHistogram:
return m.ExponentialHistogram().DataPoints().Len()
case pmetric.MetricTypeSummary:
return m.Summary().DataPoints().Len()
}
return 0
}
func (s *MetricsCountSizer) DeltaSize(newItemSize int) int {
return newItemSize
}
func (s *MetricsCountSizer) NumberDataPointSize(_ pmetric.NumberDataPoint) int {
return 1
}
func (s *MetricsCountSizer) HistogramDataPointSize(_ pmetric.HistogramDataPoint) int {
return 1
}
func (s *MetricsCountSizer) ExponentialHistogramDataPointSize(_ pmetric.ExponentialHistogramDataPoint) int {
return 1
}
func (s *MetricsCountSizer) SummaryDataPointSize(_ pmetric.SummaryDataPoint) int {
return 1
}
================================================
FILE: exporter/exporterhelper/internal/sizer/metrics_sizer_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sizer // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/sizer"
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/testdata"
)
func TestMetricsCountSizer(t *testing.T) {
md := testdata.GenerateMetrics(7)
sizer := MetricsCountSizer{}
require.Equal(t, 14, sizer.MetricsSize(md))
rm := md.ResourceMetrics().At(0)
require.Equal(t, 14, sizer.ResourceMetricsSize(rm))
sm := rm.ScopeMetrics().At(0)
require.Equal(t, 14, sizer.ScopeMetricsSize(sm))
// Test different metric types
require.Equal(t, 2, sizer.MetricSize(sm.Metrics().At(0)))
// Test data point sizes
require.Equal(t, 1, sizer.NumberDataPointSize(sm.Metrics().At(0).Gauge().DataPoints().At(0)))
require.Equal(t, 1, sizer.NumberDataPointSize(sm.Metrics().At(1).Gauge().DataPoints().At(0)))
require.Equal(t, 1, sizer.NumberDataPointSize(sm.Metrics().At(2).Sum().DataPoints().At(0)))
require.Equal(t, 1, sizer.NumberDataPointSize(sm.Metrics().At(3).Sum().DataPoints().At(0)))
require.Equal(t, 1, sizer.HistogramDataPointSize(sm.Metrics().At(4).Histogram().DataPoints().At(0)))
require.Equal(t, 1, sizer.ExponentialHistogramDataPointSize(sm.Metrics().At(5).ExponentialHistogram().DataPoints().At(0)))
require.Equal(t, 1, sizer.SummaryDataPointSize(sm.Metrics().At(6).Summary().DataPoints().At(0)))
prevSize := sizer.ScopeMetricsSize(sm)
sm.Metrics().At(0).CopyTo(sm.Metrics().AppendEmpty())
require.Equal(t, sizer.ScopeMetricsSize(sm), prevSize+sizer.DeltaSize(sizer.MetricSize(sm.Metrics().At(0))))
}
func TestMetricsBytesSizer(t *testing.T) {
md := testdata.GenerateMetrics(7)
sizer := MetricsBytesSizer{}
require.Equal(t, 1594, sizer.MetricsSize(md))
rm := md.ResourceMetrics().At(0)
require.Equal(t, 1591, sizer.ResourceMetricsSize(rm))
sm := rm.ScopeMetrics().At(0)
require.Equal(t, 1546, sizer.ScopeMetricsSize(sm))
// Test different metric types
require.Equal(t, 130, sizer.MetricSize(sm.Metrics().At(0)))
// Test data point sizes
require.Equal(t, 55, sizer.NumberDataPointSize(sm.Metrics().At(0).Gauge().DataPoints().At(0)))
require.Equal(t, 83, sizer.NumberDataPointSize(sm.Metrics().At(1).Gauge().DataPoints().At(0)))
require.Equal(t, 55, sizer.NumberDataPointSize(sm.Metrics().At(2).Sum().DataPoints().At(0)))
require.Equal(t, 83, sizer.NumberDataPointSize(sm.Metrics().At(3).Sum().DataPoints().At(0)))
require.Equal(t, 92, sizer.HistogramDataPointSize(sm.Metrics().At(4).Histogram().DataPoints().At(0)))
require.Equal(t, 119, sizer.ExponentialHistogramDataPointSize(sm.Metrics().At(5).ExponentialHistogram().DataPoints().At(0)))
require.Equal(t, 92, sizer.SummaryDataPointSize(sm.Metrics().At(6).Summary().DataPoints().At(0)))
prevSize := sizer.ScopeMetricsSize(sm)
sm.Metrics().At(0).CopyTo(sm.Metrics().AppendEmpty())
require.Equal(t, sizer.ScopeMetricsSize(sm), prevSize+sizer.DeltaSize(sizer.MetricSize(sm.Metrics().At(0))))
}
================================================
FILE: exporter/exporterhelper/internal/sizer/profiles_sizer.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sizer // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/sizer"
import (
"go.opentelemetry.io/collector/pdata/pprofile"
)
type ProfilesSizer interface {
ProfilesSize(pd pprofile.Profiles) int
ResourceProfilesSize(rp pprofile.ResourceProfiles) int
ScopeProfilesSize(sp pprofile.ScopeProfiles) int
ProfileSize(p pprofile.Profile) int
DeltaSize(newItemSize int) int
}
// TracesBytesSizer returns the byte size of serialized protos.
type ProfilesBytesSizer struct {
pprofile.ProtoMarshaler
protoDeltaSizer
}
var _ ProfilesSizer = (*ProfilesBytesSizer)(nil)
// ProfilesCountSizer returns the number of profiles in the profiles.
type ProfilesCountSizer struct{}
var _ ProfilesSizer = (*ProfilesCountSizer)(nil)
func (s *ProfilesCountSizer) ProfilesSize(pd pprofile.Profiles) int {
return pd.SampleCount()
}
func (s *ProfilesCountSizer) ResourceProfilesSize(rp pprofile.ResourceProfiles) int {
count := 0
for k := 0; k < rp.ScopeProfiles().Len(); k++ {
count += rp.ScopeProfiles().At(k).Profiles().Len()
}
return count
}
func (s *ProfilesCountSizer) ScopeProfilesSize(sp pprofile.ScopeProfiles) int {
return sp.Profiles().Len()
}
func (s *ProfilesCountSizer) ProfileSize(_ pprofile.Profile) int {
return 1
}
func (s *ProfilesCountSizer) DeltaSize(newItemSize int) int {
return newItemSize
}
================================================
FILE: exporter/exporterhelper/internal/sizer/proto_delta_sizer.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sizer // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/sizer"
import (
math_bits "math/bits"
)
type protoDeltaSizer struct{}
// DeltaSize() returns the delta size of a proto slice when a new item is added.
// Example:
//
// prevSize := proto1.Size()
// proto1.RepeatedField().AppendEmpty() = proto2
//
// Then currSize of proto1 can be calculated as
//
// currSize := (prevSize + sizer.DeltaSize(proto2.Size()))
//
// This is derived from:
// - opentelemetry-collector/pdata/internal/data/protogen/metrics/v1/metrics.pb.go
// - opentelemetry-collector/pdata/internal/data/protogen/logs/v1/logs.pb.go
// - opentelemetry-collector/pdata/internal/data/protogen/traces/v1/traces.pb.go
// - opentelemetry-collector/pdata/internal/data/protogen/profiles/v1development/profiles.pb.go
// which is generated with gogo/protobuf.
func (s *protoDeltaSizer) DeltaSize(newItemSize int) int {
return 1 + newItemSize + sov(uint64(newItemSize))
}
func sov(x uint64) int {
return (math_bits.Len64(x|1) + 6) / 7
}
================================================
FILE: exporter/exporterhelper/internal/sizer/proto_delta_sizer_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sizer
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestMetricsBytesDeltaSize(t *testing.T) {
sizer := protoDeltaSizer{}
require.Equal(t, 129, sizer.DeltaSize(127))
require.Equal(t, 131, sizer.DeltaSize(128))
require.Equal(t, 242, sizer.DeltaSize(239))
}
================================================
FILE: exporter/exporterhelper/internal/sizer/traces_sizer.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sizer // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/sizer"
import (
"go.opentelemetry.io/collector/pdata/ptrace"
)
type TracesSizer interface {
TracesSize(ld ptrace.Traces) int
ResourceSpansSize(rs ptrace.ResourceSpans) int
ScopeSpansSize(ss ptrace.ScopeSpans) int
SpanSize(span ptrace.Span) int
// DeltaSize() returns the delta size when a span is added.
DeltaSize(newItemSize int) int
}
// TracesBytesSizer returns the byte size of serialized protos.
type TracesBytesSizer struct {
ptrace.ProtoMarshaler
protoDeltaSizer
}
// TracesCountSizer returns the number of spans in the traces.
type TracesCountSizer struct{}
func (s *TracesCountSizer) TracesSize(td ptrace.Traces) int {
return td.SpanCount()
}
func (s *TracesCountSizer) ResourceSpansSize(rs ptrace.ResourceSpans) int {
count := 0
for k := 0; k < rs.ScopeSpans().Len(); k++ {
count += rs.ScopeSpans().At(k).Spans().Len()
}
return count
}
func (s *TracesCountSizer) ScopeSpansSize(ss ptrace.ScopeSpans) int {
return ss.Spans().Len()
}
func (s *TracesCountSizer) SpanSize(_ ptrace.Span) int {
return 1
}
func (s *TracesCountSizer) DeltaSize(newItemSize int) int {
return newItemSize
}
================================================
FILE: exporter/exporterhelper/internal/sizer/traces_sizer_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sizer
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/testdata"
)
func TestTracesCountSizer(t *testing.T) {
td := testdata.GenerateTraces(5)
sizer := TracesCountSizer{}
require.Equal(t, 5, sizer.TracesSize(td))
rs := td.ResourceSpans().At(0)
require.Equal(t, 5, sizer.ResourceSpansSize(rs))
ss := rs.ScopeSpans().At(0)
require.Equal(t, 5, sizer.ScopeSpansSize(ss))
require.Equal(t, 1, sizer.SpanSize(ss.Spans().At(0)))
require.Equal(t, 1, sizer.SpanSize(ss.Spans().At(1)))
require.Equal(t, 1, sizer.SpanSize(ss.Spans().At(2)))
require.Equal(t, 1, sizer.SpanSize(ss.Spans().At(3)))
require.Equal(t, 1, sizer.SpanSize(ss.Spans().At(4)))
prevSize := sizer.ScopeSpansSize(ss)
span := ss.Spans().At(2)
span.CopyTo(ss.Spans().AppendEmpty())
require.Equal(t, sizer.ScopeSpansSize(ss), prevSize+sizer.DeltaSize(sizer.SpanSize(span)))
}
func TestTracesBytesSizer(t *testing.T) {
td := testdata.GenerateTraces(2)
sizer := TracesBytesSizer{}
require.Equal(t, 338, sizer.TracesSize(td))
rs := td.ResourceSpans().At(0)
require.Equal(t, 335, sizer.ResourceSpansSize(rs))
ss := rs.ScopeSpans().At(0)
require.Equal(t, 290, sizer.ScopeSpansSize(ss))
require.Equal(t, 187, sizer.SpanSize(ss.Spans().At(0)))
require.Equal(t, 96, sizer.SpanSize(ss.Spans().At(1)))
prevSize := sizer.ScopeSpansSize(ss)
span := ss.Spans().At(1)
spanSize := sizer.SpanSize(span)
span.CopyTo(ss.Spans().AppendEmpty())
ds := sizer.DeltaSize(spanSize)
require.Equal(t, prevSize+ds, sizer.ScopeSpansSize(ss))
}
================================================
FILE: exporter/exporterhelper/internal/storagetest/mock_storage.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package storagetest // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/storagetest"
import (
"context"
"errors"
"sync"
"sync/atomic"
"time"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/extension/xextension/storage"
)
type mockStorageExtension struct {
component.StartFunc
component.ShutdownFunc
st sync.Map
getClientError error
executionDelay time.Duration
}
func (m *mockStorageExtension) GetClient(context.Context, component.Kind, component.ID, string) (storage.Client, error) {
if m.getClientError != nil {
return nil, m.getClientError
}
return &MockStorageClient{st: &m.st, closed: &atomic.Bool{}, executionDelay: m.executionDelay}, nil
}
func NewMockStorageExtension(getClientError error) storage.Extension {
return NewMockStorageExtensionWithDelay(getClientError, 0)
}
func NewMockStorageExtensionWithDelay(getClientError error, executionDelay time.Duration) storage.Extension {
return &mockStorageExtension{
getClientError: getClientError,
executionDelay: executionDelay,
}
}
type MockStorageClient struct {
st *sync.Map
closed *atomic.Bool
executionDelay time.Duration // simulate real storage client delay
}
func (m *MockStorageClient) Get(ctx context.Context, s string) ([]byte, error) {
getOp := storage.GetOperation(s)
err := m.Batch(ctx, getOp)
return getOp.Value, err
}
func (m *MockStorageClient) Set(ctx context.Context, s string, bytes []byte) error {
return m.Batch(ctx, storage.SetOperation(s, bytes))
}
func (m *MockStorageClient) Delete(ctx context.Context, s string) error {
return m.Batch(ctx, storage.DeleteOperation(s))
}
func (m *MockStorageClient) Close(context.Context) error {
m.closed.Store(true)
return nil
}
func (m *MockStorageClient) Batch(_ context.Context, ops ...*storage.Operation) error {
if m.IsClosed() {
panic("client already closed")
}
if m.executionDelay != 0 {
time.Sleep(m.executionDelay)
}
for _, op := range ops {
switch op.Type {
case storage.Get:
val, found := m.st.Load(op.Key)
if !found {
break
}
op.Value = val.([]byte)
case storage.Set:
m.st.Store(op.Key, op.Value)
case storage.Delete:
m.st.Delete(op.Key)
default:
return errors.New("wrong operation type")
}
}
return nil
}
func (m *MockStorageClient) IsClosed() bool {
return m.closed.Load()
}
================================================
FILE: exporter/exporterhelper/internal/timeout_sender.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal"
import (
"context"
"errors"
"time"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sender"
)
// TimeoutConfig for timeout. The timeout applies to individual attempts to send data to the backend.
type TimeoutConfig struct {
// Timeout is the timeout for every attempt to send data to the backend.
// A zero timeout means no timeout.
Timeout time.Duration `mapstructure:"timeout"`
}
func (ts *TimeoutConfig) Validate() error {
// Negative timeouts are not acceptable, since all sends will fail.
if ts.Timeout < 0 {
return errors.New("'timeout' must be non-negative")
}
return nil
}
// NewDefaultTimeoutConfig returns the default config for TimeoutConfig.
func NewDefaultTimeoutConfig() TimeoutConfig {
return TimeoutConfig{
Timeout: 5 * time.Second,
}
}
// timeoutSender is a requestSender that adds a `timeout` to every request that passes this sender.
type timeoutSender[T any] struct {
component.StartFunc
component.ShutdownFunc
cfg TimeoutConfig
next sender.Sender[T]
}
func newTimeoutSender[T any](cfg TimeoutConfig, next sender.Sender[T]) sender.Sender[T] {
return &timeoutSender[T]{cfg: cfg, next: next}
}
func (ts *timeoutSender[T]) Send(ctx context.Context, req T) 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.
tCtx, cancelFunc := context.WithTimeout(ctx, ts.cfg.Timeout)
defer cancelFunc()
return ts.next.Send(tCtx, req)
}
================================================
FILE: exporter/exporterhelper/internal/timeout_sender_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sender"
)
func TestNewDefaultTimeoutConfig(t *testing.T) {
cfg := NewDefaultTimeoutConfig()
require.NoError(t, cfg.Validate())
assert.Equal(t, TimeoutConfig{Timeout: 5 * time.Second}, cfg)
}
func TestInvalidTimeout(t *testing.T) {
cfg := NewDefaultTimeoutConfig()
require.NoError(t, cfg.Validate())
cfg.Timeout = -1
assert.Error(t, cfg.Validate())
}
func TestNewTimeoutSender(t *testing.T) {
cfg := TimeoutConfig{Timeout: 5 * time.Second}
ts := newTimeoutSender(cfg, sender.NewSender(func(ctx context.Context, data int64) error {
deadline, ok := ctx.Deadline()
assert.True(t, ok)
timeout := time.Since(deadline)
assert.LessOrEqual(t, timeout, 5*time.Second)
assert.GreaterOrEqual(t, 4*time.Second, timeout)
assert.Equal(t, int64(7), data)
return nil
}))
require.NoError(t, ts.Start(context.Background(), componenttest.NewNopHost()))
require.NoError(t, ts.Send(context.Background(), 7))
require.NoError(t, ts.Shutdown(context.Background()))
}
================================================
FILE: exporter/exporterhelper/logs.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package exporterhelper // import "go.opentelemetry.io/collector/exporter/exporterhelper"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
)
// NewLogs creates an exporter.Logs that records observability logs and wraps every request with a Span.
func NewLogs(
ctx context.Context,
set exporter.Settings,
cfg component.Config,
pusher consumer.ConsumeLogsFunc,
options ...Option,
) (exporter.Logs, error) {
if cfg == nil {
return nil, errNilConfig
}
if pusher == nil {
return nil, errNilPushLogs
}
return internal.NewLogsRequest(ctx, set, queuebatch.RequestFromLogs(), queuebatch.RequestConsumeFromLogs(pusher),
append([]Option{internal.WithQueueBatchSettings(queuebatch.NewLogsQueueBatchSettings())}, options...)...)
}
================================================
FILE: exporter/exporterhelper/logs_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package exporterhelper
import (
"context"
"errors"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/sdk/trace/tracetest"
semconv "go.opentelemetry.io/otel/semconv/v1.38.0"
"go.opentelemetry.io/otel/trace"
nooptrace "go.opentelemetry.io/otel/trace/noop"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/hosttest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/metadatatest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/oteltest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/queue"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/requesttest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sendertest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/storagetest"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/testdata"
)
const (
fakeLogsParentSpanName = "fake_logs_parent_span_name"
)
var (
fakeLogsName = component.MustNewIDWithName("fake_logs_exporter", "with_name")
fakeLogsConfig = struct{}{}
)
func TestLogs_InvalidName(t *testing.T) {
le, err := NewLogs(context.Background(), exportertest.NewNopSettings(exportertest.NopType), nil, newPushLogsData(nil))
require.Nil(t, le)
require.Equal(t, errNilConfig, err)
}
func TestLogs_NilLogger(t *testing.T) {
le, err := NewLogs(context.Background(), exporter.Settings{}, &fakeLogsConfig, newPushLogsData(nil))
require.Nil(t, le)
require.Equal(t, errNilLogger, err)
}
func TestLogs_NilPushLogsData(t *testing.T) {
le, err := NewLogs(context.Background(), exportertest.NewNopSettings(exportertest.NopType), &fakeLogsConfig, nil)
require.Nil(t, le)
require.Equal(t, errNilPushLogs, err)
}
func TestLogs_Default(t *testing.T) {
ld := plog.NewLogs()
le, err := NewLogs(context.Background(), exportertest.NewNopSettings(exportertest.NopType), &fakeLogsConfig, newPushLogsData(nil))
assert.NotNil(t, le)
require.NoError(t, err)
assert.Equal(t, consumer.Capabilities{MutatesData: false}, le.Capabilities())
assert.NoError(t, le.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, le.ConsumeLogs(context.Background(), ld))
assert.NoError(t, le.Shutdown(context.Background()))
}
func TestLogs_WithCapabilities(t *testing.T) {
capabilities := consumer.Capabilities{MutatesData: true}
le, err := NewLogs(context.Background(), exportertest.NewNopSettings(exportertest.NopType), &fakeLogsConfig, newPushLogsData(nil), WithCapabilities(capabilities))
require.NoError(t, err)
require.NotNil(t, le)
assert.Equal(t, capabilities, le.Capabilities())
}
func TestLogs_Default_ReturnError(t *testing.T) {
ld := plog.NewLogs()
want := errors.New("my_error")
le, err := NewLogs(context.Background(), exportertest.NewNopSettings(exportertest.NopType), &fakeLogsConfig, newPushLogsData(want))
require.NoError(t, err)
require.NotNil(t, le)
require.Equal(t, want, le.ConsumeLogs(context.Background(), ld))
}
func TestLogs_WithPersistentQueue(t *testing.T) {
fgOrigReadState := queue.PersistRequestContextOnRead
fgOrigWriteState := queue.PersistRequestContextOnWrite
qCfg := configoptional.Some(NewDefaultQueueConfig())
storageID := component.MustNewIDWithName("file_storage", "storage")
qCfg.Get().StorageID = &storageID
set := exportertest.NewNopSettings(exportertest.NopType)
set.ID = component.MustNewIDWithName("test_logs", "with_persistent_queue")
host := hosttest.NewHost(map[component.ID]component.Component{
storageID: storagetest.NewMockStorageExtension(nil),
})
spanCtx := oteltest.FakeSpanContext(t)
tests := []struct {
name string
fgEnabledOnWrite bool
fgEnabledOnRead bool
wantData bool
wantSpanCtx bool
}{
{
name: "feature_gate_disabled_on_write_and_read",
wantData: true,
},
{
name: "feature_gate_enabled_on_write_and_read",
fgEnabledOnWrite: true,
fgEnabledOnRead: true,
wantData: true,
wantSpanCtx: true,
},
{
name: "feature_gate_disabled_on_write_enabled_on_read",
wantData: true,
fgEnabledOnRead: true,
},
{
name: "feature_gate_enabled_on_write_disabled_on_read",
fgEnabledOnWrite: true,
wantData: false, // going back from enabled to disabled feature gate isn't supported
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
queue.PersistRequestContextOnRead = func() bool { return tt.fgEnabledOnRead }
queue.PersistRequestContextOnWrite = func() bool { return tt.fgEnabledOnWrite }
t.Cleanup(func() {
queue.PersistRequestContextOnRead = fgOrigReadState
queue.PersistRequestContextOnWrite = fgOrigWriteState
})
ls := consumertest.LogsSink{}
te, err := NewLogs(context.Background(), set, &fakeLogsConfig, ls.ConsumeLogs, WithQueue(qCfg))
require.NoError(t, err)
require.NoError(t, te.Start(context.Background(), host))
t.Cleanup(func() { require.NoError(t, te.Shutdown(context.Background())) })
logs := testdata.GenerateLogs(2)
require.NoError(t, te.ConsumeLogs(trace.ContextWithSpanContext(context.Background(), spanCtx), logs))
if tt.wantData {
require.Eventually(t, func() bool {
return len(ls.AllLogs()) == 1 && ls.LogRecordCount() == 2
}, 500*time.Millisecond, 10*time.Millisecond)
}
// check that the span context is persisted if the feature gate is enabled
if tt.wantSpanCtx {
assert.Len(t, ls.Contexts(), 1)
assert.Equal(t, spanCtx, trace.SpanContextFromContext(ls.Contexts()[0]))
}
})
}
}
func TestLogs_WithRecordMetrics(t *testing.T) {
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
le, err := NewLogs(context.Background(), exporter.Settings{ID: fakeLogsName, TelemetrySettings: tt.NewTelemetrySettings(), BuildInfo: component.NewDefaultBuildInfo()}, &fakeLogsConfig, newPushLogsData(nil))
require.NoError(t, err)
require.NotNil(t, le)
checkRecordedMetricsForLogs(t, tt, fakeLogsName, le, nil)
}
func TestLogs_pLogModifiedDownStream_WithRecordMetrics(t *testing.T) {
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
le, err := NewLogs(context.Background(), exporter.Settings{ID: fakeLogsName, TelemetrySettings: tt.NewTelemetrySettings(), BuildInfo: component.NewDefaultBuildInfo()}, &fakeLogsConfig, newPushLogsDataModifiedDownstream(nil), WithCapabilities(consumer.Capabilities{MutatesData: true}))
assert.NotNil(t, le)
require.NoError(t, err)
ld := testdata.GenerateLogs(2)
require.NoError(t, le.ConsumeLogs(context.Background(), ld))
assert.Equal(t, 0, ld.LogRecordCount())
metadatatest.AssertEqualExporterSentLogRecords(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String("exporter", fakeLogsName.String())),
Value: int64(2),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
}
func TestLogsRequest_WithRecordMetrics(t *testing.T) {
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
le, err := internal.NewLogsRequest(context.Background(),
exporter.Settings{ID: fakeLogsName, TelemetrySettings: tt.NewTelemetrySettings(), BuildInfo: component.NewDefaultBuildInfo()},
requesttest.RequestFromLogsFunc(nil), sendertest.NewNopSenderFunc[request.Request]())
require.NoError(t, err)
require.NotNil(t, le)
checkRecordedMetricsForLogs(t, tt, fakeLogsName, le, nil)
}
func TestLogs_WithRecordMetrics_ReturnError(t *testing.T) {
want := errors.New("my_error")
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
le, err := NewLogs(context.Background(), exporter.Settings{ID: fakeLogsName, TelemetrySettings: tt.NewTelemetrySettings(), BuildInfo: component.NewDefaultBuildInfo()}, &fakeLogsConfig, newPushLogsData(want))
require.NoError(t, err)
require.NotNil(t, le)
checkRecordedMetricsForLogs(t, tt, fakeLogsName, le, want)
}
func TestLogsRequest_WithRecordMetrics_ExportError(t *testing.T) {
want := errors.New("export_error")
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
le, err := internal.NewLogsRequest(context.Background(), exporter.Settings{ID: fakeLogsName, TelemetrySettings: tt.NewTelemetrySettings(), BuildInfo: component.NewDefaultBuildInfo()},
requesttest.RequestFromLogsFunc(nil), sendertest.NewErrSenderFunc[request.Request](want))
require.NoError(t, err)
require.NotNil(t, le)
checkRecordedMetricsForLogs(t, tt, fakeLogsName, le, want)
}
func TestLogs_WithSpan(t *testing.T) {
set := exportertest.NewNopSettings(exportertest.NopType)
sr := new(tracetest.SpanRecorder)
set.TracerProvider = sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))
otel.SetTracerProvider(set.TracerProvider)
defer otel.SetTracerProvider(nooptrace.NewTracerProvider())
le, err := NewLogs(context.Background(), set, &fakeLogsConfig, newPushLogsData(nil))
require.NoError(t, err)
require.NotNil(t, le)
checkWrapSpanForLogs(t, sr, set.TracerProvider.Tracer("test"), le, nil)
}
func TestLogsRequest_WithSpan(t *testing.T) {
set := exportertest.NewNopSettings(exportertest.NopType)
sr := new(tracetest.SpanRecorder)
set.TracerProvider = sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))
otel.SetTracerProvider(set.TracerProvider)
defer otel.SetTracerProvider(nooptrace.NewTracerProvider())
le, err := internal.NewLogsRequest(context.Background(), set, requesttest.RequestFromLogsFunc(nil), sendertest.NewNopSenderFunc[request.Request]())
require.NoError(t, err)
require.NotNil(t, le)
checkWrapSpanForLogs(t, sr, set.TracerProvider.Tracer("test"), le, nil)
}
func TestLogs_WithSpan_ReturnError(t *testing.T) {
set := exportertest.NewNopSettings(exportertest.NopType)
sr := new(tracetest.SpanRecorder)
set.TracerProvider = sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))
otel.SetTracerProvider(set.TracerProvider)
defer otel.SetTracerProvider(nooptrace.NewTracerProvider())
want := errors.New("my_error")
le, err := NewLogs(context.Background(), set, &fakeLogsConfig, newPushLogsData(want))
require.NoError(t, err)
require.NotNil(t, le)
checkWrapSpanForLogs(t, sr, set.TracerProvider.Tracer("test"), le, want)
}
func TestLogsRequest_WithSpan_ReturnError(t *testing.T) {
set := exportertest.NewNopSettings(exportertest.NopType)
sr := new(tracetest.SpanRecorder)
set.TracerProvider = sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))
otel.SetTracerProvider(set.TracerProvider)
defer otel.SetTracerProvider(nooptrace.NewTracerProvider())
want := errors.New("my_error")
le, err := internal.NewLogsRequest(context.Background(), set, requesttest.RequestFromLogsFunc(nil), sendertest.NewErrSenderFunc[request.Request](want))
require.NoError(t, err)
require.NotNil(t, le)
checkWrapSpanForLogs(t, sr, set.TracerProvider.Tracer("test"), le, want)
}
func TestLogs_WithShutdown(t *testing.T) {
shutdownCalled := false
shutdown := func(context.Context) error { shutdownCalled = true; return nil }
le, err := NewLogs(context.Background(), exportertest.NewNopSettings(exportertest.NopType), &fakeLogsConfig, newPushLogsData(nil), WithShutdown(shutdown))
assert.NotNil(t, le)
assert.NoError(t, err)
assert.NoError(t, le.Shutdown(context.Background()))
assert.True(t, shutdownCalled)
}
func TestLogs_WithShutdown_ReturnError(t *testing.T) {
want := errors.New("my_error")
shutdownErr := func(context.Context) error { return want }
le, err := NewLogs(context.Background(), exportertest.NewNopSettings(exportertest.NopType), &fakeLogsConfig, newPushLogsData(nil), WithShutdown(shutdownErr))
assert.NotNil(t, le)
require.NoError(t, err)
assert.Equal(t, want, le.Shutdown(context.Background()))
}
func newPushLogsDataModifiedDownstream(retError error) consumer.ConsumeLogsFunc {
return func(_ context.Context, log plog.Logs) error {
log.ResourceLogs().MoveAndAppendTo(plog.NewResourceLogsSlice())
return retError
}
}
func newPushLogsData(retError error) consumer.ConsumeLogsFunc {
return func(_ context.Context, _ plog.Logs) error {
return retError
}
}
func checkRecordedMetricsForLogs(t *testing.T, tt *componenttest.Telemetry, id component.ID, le exporter.Logs, wantError error) {
ld := testdata.GenerateLogs(2)
const numBatches = 7
for range numBatches {
require.Equal(t, wantError, le.ConsumeLogs(context.Background(), ld))
}
// TODO: When the new metrics correctly count partial dropped fix this.
if wantError != nil {
metadatatest.AssertEqualExporterSendFailedLogRecords(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String("exporter", id.String()),
attribute.String(string(semconv.ErrorTypeKey), "_OTHER"),
attribute.Bool(internal.ErrorPermanentKey, false)),
Value: int64(numBatches * ld.LogRecordCount()),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
} else {
metadatatest.AssertEqualExporterSentLogRecords(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String("exporter", id.String())),
Value: int64(numBatches * ld.LogRecordCount()),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
}
}
func generateLogsTraffic(t *testing.T, tracer trace.Tracer, le exporter.Logs, numRequests int, wantError error) {
ld := testdata.GenerateLogs(1)
ctx, span := tracer.Start(context.Background(), fakeLogsParentSpanName)
defer span.End()
for range numRequests {
require.Equal(t, wantError, le.ConsumeLogs(ctx, ld))
}
}
func checkWrapSpanForLogs(t *testing.T, sr *tracetest.SpanRecorder, tracer trace.Tracer, le exporter.Logs, wantError error) {
const numRequests = 5
generateLogsTraffic(t, tracer, le, numRequests, wantError)
// Inspection time!
gotSpanData := sr.Ended()
require.Len(t, gotSpanData, numRequests+1)
parentSpan := gotSpanData[numRequests]
require.Equalf(t, fakeLogsParentSpanName, parentSpan.Name(), "SpanData %v", parentSpan)
for _, sd := range gotSpanData[:numRequests] {
require.Equalf(t, parentSpan.SpanContext(), sd.Parent(), "Exporter span not a child\nSpanData %v", sd)
oteltest.CheckStatus(t, sd, wantError)
sentLogRecords := int64(1)
failedToSendLogRecords := int64(0)
if wantError != nil {
sentLogRecords = 0
failedToSendLogRecords = 1
}
require.Containsf(t, sd.Attributes(), attribute.KeyValue{Key: internal.ItemsSent, Value: attribute.Int64Value(sentLogRecords)}, "SpanData %v", sd)
require.Containsf(t, sd.Attributes(), attribute.KeyValue{Key: internal.ItemsFailed, Value: attribute.Int64Value(failedToSendLogRecords)}, "SpanData %v", sd)
}
}
================================================
FILE: exporter/exporterhelper/metadata.yaml
================================================
type: exporterhelper
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
codeowners:
active:
- bogdandrutu
- dmitryax
class: pkg
stability:
beta: [traces, metrics, logs]
telemetry:
metrics:
exporter_enqueue_failed_log_records:
enabled: true
stability: alpha
description: Number of log records failed to be added to the sending queue.
unit: "{record}"
sum:
value_type: int
monotonic: true
exporter_enqueue_failed_metric_points:
enabled: true
stability: alpha
description: Number of metric points failed to be added to the sending queue.
unit: "{datapoint}"
sum:
value_type: int
monotonic: true
exporter_enqueue_failed_profile_samples:
enabled: true
stability: development
description: Number of profile samples failed to be added to the sending queue.
unit: "{sample}"
sum:
value_type: int
monotonic: true
exporter_enqueue_failed_spans:
enabled: true
stability: alpha
description: Number of spans failed to be added to the sending queue.
unit: "{span}"
sum:
value_type: int
monotonic: true
exporter_queue_batch_send_size:
enabled: true
description: Number of units in the batch
stability: development
unit: "{unit}"
histogram:
value_type: int
bucket_boundaries:
[
10,
25,
50,
75,
100,
250,
500,
750,
1000,
2000,
3000,
4000,
5000,
6000,
7000,
8000,
9000,
10000,
20000,
30000,
50000,
100000,
]
exporter_queue_batch_send_size_bytes:
enabled: true
description: Number of bytes in batch that was sent. Only available on detailed level.
stability: development
unit: By
histogram:
value_type: int
bucket_boundaries:
[
10,
25,
50,
75,
100,
250,
500,
750,
1000,
2000,
3000,
4000,
5000,
6000,
]
exporter_queue_capacity:
enabled: true
stability: alpha
description: Fixed capacity of the retry queue (in batches).
unit: "{batch}"
gauge:
value_type: int
async: true
exporter_queue_size:
enabled: true
stability: alpha
description: Current size of the retry queue (in batches).
unit: "{batch}"
gauge:
value_type: int
async: true
exporter_send_failed_log_records:
enabled: true
stability: alpha
description: "Number of log records in failed attempts to send to destination. At detailed telemetry level, includes attributes: error.type (semantic convention), error.permanent."
unit: "{record}"
sum:
value_type: int
monotonic: true
exporter_send_failed_metric_points:
enabled: true
stability: alpha
description: "Number of metric points in failed attempts to send to destination. At detailed telemetry level, includes attributes: error.type (semantic convention), error.permanent."
unit: "{datapoint}"
sum:
value_type: int
monotonic: true
exporter_send_failed_profile_samples:
enabled: true
stability: development
description: "Number of profile samples in failed attempts to send to destination. At detailed telemetry level, includes attributes: error.type (semantic convention), error.permanent."
unit: "{sample}"
sum:
value_type: int
monotonic: true
exporter_send_failed_spans:
enabled: true
stability: alpha
description: "Number of spans in failed attempts to send to destination. At detailed telemetry level, includes attributes: error.type (semantic convention), error.permanent."
unit: "{span}"
sum:
value_type: int
monotonic: true
exporter_sent_log_records:
enabled: true
stability: alpha
description: Number of log record successfully sent to destination.
unit: "{record}"
sum:
value_type: int
monotonic: true
exporter_sent_metric_points:
enabled: true
stability: alpha
description: Number of metric points successfully sent to destination.
unit: "{datapoint}"
sum:
value_type: int
monotonic: true
exporter_sent_profile_samples:
enabled: true
stability: development
description: Number of profile samples successfully sent to destination.
unit: "{sample}"
sum:
value_type: int
monotonic: true
exporter_sent_spans:
enabled: true
stability: alpha
description: Number of spans successfully sent to destination.
unit: "{span}"
sum:
value_type: int
monotonic: true
feature_gates:
- id: exporter.PersistRequestContext
description: 'controls whether context should be stored alongside requests in the persistent queue'
stage: beta
from_version: 'v0.128.0'
reference_url: 'https://github.com/open-telemetry/opentelemetry-collector/pull/13188'
================================================
FILE: exporter/exporterhelper/metrics.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package exporterhelper // import "go.opentelemetry.io/collector/exporter/exporterhelper"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
)
// NewMetrics creates an exporter.Metrics that records observability metrics and wraps every request with a Span.
func NewMetrics(
ctx context.Context,
set exporter.Settings,
cfg component.Config,
pusher consumer.ConsumeMetricsFunc,
options ...Option,
) (exporter.Metrics, error) {
if cfg == nil {
return nil, errNilConfig
}
if pusher == nil {
return nil, errNilPushMetrics
}
return internal.NewMetricsRequest(ctx, set, queuebatch.RequestFromMetrics(), queuebatch.RequestConsumeFromMetrics(pusher),
append([]Option{internal.WithQueueBatchSettings(queuebatch.NewMetricsQueueBatchSettings())}, options...)...)
}
================================================
FILE: exporter/exporterhelper/metrics_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package exporterhelper
import (
"context"
"errors"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/sdk/trace/tracetest"
semconv "go.opentelemetry.io/otel/semconv/v1.38.0"
"go.opentelemetry.io/otel/trace"
nooptrace "go.opentelemetry.io/otel/trace/noop"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/hosttest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/metadatatest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/oteltest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/queue"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/requesttest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sendertest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/storagetest"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/testdata"
)
const (
fakeMetricsParentSpanName = "fake_metrics_parent_span_name"
)
var (
fakeMetricsName = component.MustNewIDWithName("fake_metrics_exporter", "with_name")
fakeMetricsConfig = struct{}{}
)
func TestMetrics_NilConfig(t *testing.T) {
me, err := NewMetrics(context.Background(), exportertest.NewNopSettings(exportertest.NopType), nil, newPushMetricsData(nil))
require.Nil(t, me)
require.Equal(t, errNilConfig, err)
}
func TestMetrics_NilLogger(t *testing.T) {
me, err := NewMetrics(context.Background(), exporter.Settings{}, &fakeMetricsConfig, newPushMetricsData(nil))
require.Nil(t, me)
require.Equal(t, errNilLogger, err)
}
func TestMetrics_NilPushMetricsData(t *testing.T) {
me, err := NewMetrics(context.Background(), exportertest.NewNopSettings(exportertest.NopType), &fakeMetricsConfig, nil)
require.Nil(t, me)
require.Equal(t, errNilPushMetrics, err)
}
func TestMetrics_Default(t *testing.T) {
md := pmetric.NewMetrics()
me, err := NewMetrics(context.Background(), exportertest.NewNopSettings(exportertest.NopType), &fakeMetricsConfig, newPushMetricsData(nil))
require.NoError(t, err)
assert.NotNil(t, me)
assert.Equal(t, consumer.Capabilities{MutatesData: false}, me.Capabilities())
assert.NoError(t, me.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, me.ConsumeMetrics(context.Background(), md))
assert.NoError(t, me.Shutdown(context.Background()))
}
func TestMetrics_WithCapabilities(t *testing.T) {
capabilities := consumer.Capabilities{MutatesData: true}
me, err := NewMetrics(context.Background(), exportertest.NewNopSettings(exportertest.NopType), &fakeMetricsConfig, newPushMetricsData(nil), WithCapabilities(capabilities))
require.NoError(t, err)
assert.NotNil(t, me)
assert.Equal(t, capabilities, me.Capabilities())
}
func TestMetrics_Default_ReturnError(t *testing.T) {
md := pmetric.NewMetrics()
want := errors.New("my_error")
me, err := NewMetrics(context.Background(), exportertest.NewNopSettings(exportertest.NopType), &fakeMetricsConfig, newPushMetricsData(want))
require.NoError(t, err)
require.NotNil(t, me)
require.Equal(t, want, me.ConsumeMetrics(context.Background(), md))
}
func TestMetrics_WithPersistentQueue(t *testing.T) {
fgOrigReadState := queue.PersistRequestContextOnRead
fgOrigWriteState := queue.PersistRequestContextOnWrite
qCfg := NewDefaultQueueConfig()
storageID := component.MustNewIDWithName("file_storage", "storage")
qCfg.StorageID = &storageID
set := exportertest.NewNopSettings(exportertest.NopType)
set.ID = component.MustNewIDWithName("test_metrics", "with_persistent_queue")
host := hosttest.NewHost(map[component.ID]component.Component{
storageID: storagetest.NewMockStorageExtension(nil),
})
spanCtx := oteltest.FakeSpanContext(t)
tests := []struct {
name string
fgEnabledOnWrite bool
fgEnabledOnRead bool
wantData bool
wantSpanCtx bool
}{
{
name: "feature_gate_disabled_on_write_and_read",
wantData: true,
},
{
name: "feature_gate_enabled_on_write_and_read",
fgEnabledOnWrite: true,
fgEnabledOnRead: true,
wantData: true,
wantSpanCtx: true,
},
{
name: "feature_gate_disabled_on_write_enabled_on_read",
wantData: true,
fgEnabledOnRead: true,
},
{
name: "feature_gate_enabled_on_write_disabled_on_read",
fgEnabledOnWrite: true,
wantData: false, // going back from enabled to disabled feature gate isn't supported
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
queue.PersistRequestContextOnRead = func() bool { return tt.fgEnabledOnRead }
queue.PersistRequestContextOnWrite = func() bool { return tt.fgEnabledOnWrite }
t.Cleanup(func() {
queue.PersistRequestContextOnRead = fgOrigReadState
queue.PersistRequestContextOnWrite = fgOrigWriteState
})
ms := consumertest.MetricsSink{}
te, err := NewMetrics(context.Background(), set, &fakeMetricsConfig, ms.ConsumeMetrics, WithQueue(configoptional.Some(qCfg)))
require.NoError(t, err)
require.NoError(t, te.Start(context.Background(), host))
t.Cleanup(func() { require.NoError(t, te.Shutdown(context.Background())) })
traces := testdata.GenerateMetrics(2)
require.NoError(t, te.ConsumeMetrics(trace.ContextWithSpanContext(context.Background(), spanCtx), traces))
if tt.wantData {
require.Eventually(t, func() bool {
return len(ms.AllMetrics()) == 1 && ms.DataPointCount() == 4
}, 500*time.Millisecond, 10*time.Millisecond)
}
// check that the span context is persisted if the feature gate is enabled
if tt.wantSpanCtx {
assert.Len(t, ms.Contexts(), 1)
assert.Equal(t, spanCtx, trace.SpanContextFromContext(ms.Contexts()[0]))
}
})
}
}
func TestMetrics_WithRecordMetrics(t *testing.T) {
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
me, err := NewMetrics(context.Background(), exporter.Settings{ID: fakeMetricsName, TelemetrySettings: tt.NewTelemetrySettings(), BuildInfo: component.NewDefaultBuildInfo()}, &fakeMetricsConfig, newPushMetricsData(nil))
require.NoError(t, err)
require.NotNil(t, me)
checkRecordedMetricsForMetrics(t, tt, fakeMetricsName, me, nil)
}
func TestMetrics_pMetricModifiedDownStream_WithRecordMetrics(t *testing.T) {
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
me, err := NewMetrics(context.Background(), exporter.Settings{ID: fakeMetricsName, TelemetrySettings: tt.NewTelemetrySettings(), BuildInfo: component.NewDefaultBuildInfo()}, &fakeMetricsConfig, newPushMetricsDataModifiedDownstream(nil), WithCapabilities(consumer.Capabilities{MutatesData: true}))
require.NoError(t, err)
require.NotNil(t, me)
md := testdata.GenerateMetrics(2)
require.NoError(t, me.ConsumeMetrics(context.Background(), md))
assert.Equal(t, 0, md.MetricCount())
metadatatest.AssertEqualExporterSentMetricPoints(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(internal.ExporterKey, fakeMetricsName.String())),
Value: int64(4),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
}
func TestMetricsRequest_WithRecordMetrics(t *testing.T) {
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
me, err := internal.NewMetricsRequest(context.Background(),
exporter.Settings{ID: fakeMetricsName, TelemetrySettings: tt.NewTelemetrySettings(), BuildInfo: component.NewDefaultBuildInfo()},
requesttest.RequestFromMetricsFunc(nil), sendertest.NewNopSenderFunc[request.Request]())
require.NoError(t, err)
require.NotNil(t, me)
checkRecordedMetricsForMetrics(t, tt, fakeMetricsName, me, nil)
}
func TestMetrics_WithRecordMetrics_ReturnError(t *testing.T) {
want := errors.New("my_error")
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
me, err := NewMetrics(context.Background(), exporter.Settings{ID: fakeMetricsName, TelemetrySettings: tt.NewTelemetrySettings(), BuildInfo: component.NewDefaultBuildInfo()}, &fakeMetricsConfig, newPushMetricsData(want))
require.NoError(t, err)
require.NotNil(t, me)
checkRecordedMetricsForMetrics(t, tt, fakeMetricsName, me, want)
}
func TestMetricsRequest_WithRecordMetrics_ExportError(t *testing.T) {
want := errors.New("my_error")
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
me, err := internal.NewMetricsRequest(context.Background(),
exporter.Settings{ID: fakeMetricsName, TelemetrySettings: tt.NewTelemetrySettings(), BuildInfo: component.NewDefaultBuildInfo()},
requesttest.RequestFromMetricsFunc(nil), sendertest.NewErrSenderFunc[request.Request](want))
require.NoError(t, err)
require.NotNil(t, me)
checkRecordedMetricsForMetrics(t, tt, fakeMetricsName, me, want)
}
func TestMetrics_WithSpan(t *testing.T) {
set := exportertest.NewNopSettings(exportertest.NopType)
sr := new(tracetest.SpanRecorder)
set.TracerProvider = sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))
otel.SetTracerProvider(set.TracerProvider)
defer otel.SetTracerProvider(nooptrace.NewTracerProvider())
me, err := NewMetrics(context.Background(), set, &fakeMetricsConfig, newPushMetricsData(nil))
require.NoError(t, err)
require.NotNil(t, me)
checkWrapSpanForMetrics(t, sr, set.TracerProvider.Tracer("test"), me, nil)
}
func TestMetricsRequest_WithSpan(t *testing.T) {
set := exportertest.NewNopSettings(exportertest.NopType)
sr := new(tracetest.SpanRecorder)
set.TracerProvider = sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))
otel.SetTracerProvider(set.TracerProvider)
defer otel.SetTracerProvider(nooptrace.NewTracerProvider())
me, err := internal.NewMetricsRequest(context.Background(), set, requesttest.RequestFromMetricsFunc(nil), sendertest.NewNopSenderFunc[request.Request]())
require.NoError(t, err)
require.NotNil(t, me)
checkWrapSpanForMetrics(t, sr, set.TracerProvider.Tracer("test"), me, nil)
}
func TestMetrics_WithSpan_ReturnError(t *testing.T) {
set := exportertest.NewNopSettings(exportertest.NopType)
sr := new(tracetest.SpanRecorder)
set.TracerProvider = sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))
otel.SetTracerProvider(set.TracerProvider)
defer otel.SetTracerProvider(nooptrace.NewTracerProvider())
want := errors.New("my_error")
me, err := NewMetrics(context.Background(), set, &fakeMetricsConfig, newPushMetricsData(want))
require.NoError(t, err)
require.NotNil(t, me)
checkWrapSpanForMetrics(t, sr, set.TracerProvider.Tracer("test"), me, want)
}
func TestMetricsRequest_WithSpan_ExportError(t *testing.T) {
set := exportertest.NewNopSettings(exportertest.NopType)
sr := new(tracetest.SpanRecorder)
set.TracerProvider = sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))
otel.SetTracerProvider(set.TracerProvider)
defer otel.SetTracerProvider(nooptrace.NewTracerProvider())
want := errors.New("my_error")
me, err := internal.NewMetricsRequest(context.Background(), set, requesttest.RequestFromMetricsFunc(nil), sendertest.NewErrSenderFunc[request.Request](want))
require.NoError(t, err)
require.NotNil(t, me)
checkWrapSpanForMetrics(t, sr, set.TracerProvider.Tracer("test"), me, want)
}
func TestMetrics_WithShutdown(t *testing.T) {
shutdownCalled := false
shutdown := func(context.Context) error { shutdownCalled = true; return nil }
me, err := NewMetrics(context.Background(), exportertest.NewNopSettings(exportertest.NopType), &fakeMetricsConfig, newPushMetricsData(nil), WithShutdown(shutdown))
assert.NotNil(t, me)
assert.NoError(t, err)
assert.NoError(t, me.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, me.Shutdown(context.Background()))
assert.True(t, shutdownCalled)
}
func TestMetrics_WithShutdown_ReturnError(t *testing.T) {
want := errors.New("my_error")
shutdownErr := func(context.Context) error { return want }
me, err := NewMetrics(context.Background(), exportertest.NewNopSettings(exportertest.NopType), &fakeMetricsConfig, newPushMetricsData(nil), WithShutdown(shutdownErr))
assert.NotNil(t, me)
assert.NoError(t, err)
assert.NoError(t, me.Start(context.Background(), componenttest.NewNopHost()))
assert.Equal(t, want, me.Shutdown(context.Background()))
}
func newPushMetricsData(retError error) consumer.ConsumeMetricsFunc {
return func(_ context.Context, _ pmetric.Metrics) error {
return retError
}
}
func newPushMetricsDataModifiedDownstream(retError error) consumer.ConsumeMetricsFunc {
return func(_ context.Context, metric pmetric.Metrics) error {
metric.ResourceMetrics().MoveAndAppendTo(pmetric.NewResourceMetricsSlice())
return retError
}
}
func checkRecordedMetricsForMetrics(t *testing.T, tt *componenttest.Telemetry, id component.ID, me exporter.Metrics, wantError error) {
md := testdata.GenerateMetrics(2)
const numBatches = 7
for range numBatches {
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 {
metadatatest.AssertEqualExporterSendFailedMetricPoints(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(internal.ExporterKey, id.String()),
attribute.String(string(semconv.ErrorTypeKey), "_OTHER"),
attribute.Bool(internal.ErrorPermanentKey, false)),
Value: numPoints,
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
} else {
metadatatest.AssertEqualExporterSentMetricPoints(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(internal.ExporterKey, id.String())),
Value: numPoints,
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
}
}
func generateMetricsTraffic(t *testing.T, tracer trace.Tracer, me exporter.Metrics, numRequests int, wantError error) {
md := testdata.GenerateMetrics(1)
ctx, span := tracer.Start(context.Background(), fakeMetricsParentSpanName)
defer span.End()
for range numRequests {
require.Equal(t, wantError, me.ConsumeMetrics(ctx, md))
}
}
func checkWrapSpanForMetrics(t *testing.T, sr *tracetest.SpanRecorder, tracer trace.Tracer, me exporter.Metrics, wantError error) {
const numRequests = 5
generateMetricsTraffic(t, tracer, me, numRequests, wantError)
// Inspection time!
gotSpanData := sr.Ended()
require.Len(t, gotSpanData, numRequests+1)
parentSpan := gotSpanData[numRequests]
require.Equalf(t, fakeMetricsParentSpanName, parentSpan.Name(), "SpanData %v", parentSpan)
for _, sd := range gotSpanData[:numRequests] {
require.Equalf(t, parentSpan.SpanContext(), sd.Parent(), "Exporter span not a child\nSpanData %v", sd)
oteltest.CheckStatus(t, sd, wantError)
sentMetricPoints := int64(2)
failedToSendMetricPoints := int64(0)
if wantError != nil {
sentMetricPoints = 0
failedToSendMetricPoints = 2
}
require.Containsf(t, sd.Attributes(), attribute.KeyValue{Key: internal.ItemsSent, Value: attribute.Int64Value(sentMetricPoints)}, "SpanData %v", sd)
require.Containsf(t, sd.Attributes(), attribute.KeyValue{Key: internal.ItemsFailed, Value: attribute.Int64Value(failedToSendMetricPoints)}, "SpanData %v", sd)
}
}
================================================
FILE: exporter/exporterhelper/queue_batch.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package exporterhelper // import "go.opentelemetry.io/collector/exporter/exporterhelper"
import (
"context"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/queue"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
)
// WithQueue overrides the default QueueBatchConfig for an exporter.
// The default QueueBatchConfig is to disable queueing.
// This option cannot be used with the new exporter helpers New[Traces|Metrics|Logs]RequestExporter.
func WithQueue(config configoptional.Optional[QueueBatchConfig]) Option {
return internal.WithQueue(config)
}
// QueueBatchConfig defines configuration for queueing and batching for the exporter.
type QueueBatchConfig = queuebatch.Config
// BatchConfig defines a configuration for batching requests based on a timeout and a minimum number of items.
type BatchConfig = queuebatch.BatchConfig
// QueueBatchEncoding defines the encoding to be used if persistent queue is configured.
// Duplicate definition with queuebatch.Encoding since aliasing generics is not supported by default.
type QueueBatchEncoding[T any] interface {
// Marshal is a function that can marshal a request and its context into bytes.
Marshal(context.Context, T) ([]byte, error)
// Unmarshal is a function that can unmarshal bytes into a request and its context.
Unmarshal([]byte) (context.Context, T, error)
}
var ErrQueueIsFull = queue.ErrQueueIsFull
// NewDefaultQueueConfig returns the default config for QueueBatchConfig.
// By default, the queue stores 1000 requests of telemetry and is non-blocking when full.
var NewDefaultQueueConfig = internal.NewDefaultQueueConfig
================================================
FILE: exporter/exporterhelper/request.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package exporterhelper // import "go.opentelemetry.io/collector/exporter/exporterhelper"
import (
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
)
type RequestSizerType = request.SizerType
var (
RequestSizerTypeBytes = request.SizerTypeBytes
RequestSizerTypeItems = request.SizerTypeItems
RequestSizerTypeRequests = request.SizerTypeRequests
)
================================================
FILE: exporter/exporterhelper/retry_sender.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package exporterhelper // import "go.opentelemetry.io/collector/exporter/exporterhelper"
import (
"time"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal"
)
// NewThrottleRetry creates a new throttle retry error.
func NewThrottleRetry(err error, delay time.Duration) error {
return internal.NewThrottleRetry(err, delay)
}
================================================
FILE: exporter/exporterhelper/timeout_sender.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package exporterhelper // import "go.opentelemetry.io/collector/exporter/exporterhelper"
import (
"go.opentelemetry.io/collector/exporter/exporterhelper/internal"
)
type TimeoutConfig = internal.TimeoutConfig
// NewDefaultTimeoutConfig returns the default config for TimeoutConfig.
func NewDefaultTimeoutConfig() TimeoutConfig {
return internal.NewDefaultTimeoutConfig()
}
================================================
FILE: exporter/exporterhelper/traces.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package exporterhelper // import "go.opentelemetry.io/collector/exporter/exporterhelper"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
)
// NewTraces creates an exporter.Traces that records observability metrics and wraps every request with a Span.
func NewTraces(
ctx context.Context,
set exporter.Settings,
cfg component.Config,
pusher consumer.ConsumeTracesFunc,
options ...Option,
) (exporter.Traces, error) {
if cfg == nil {
return nil, errNilConfig
}
if pusher == nil {
return nil, errNilPushTraces
}
return internal.NewTracesRequest(ctx, set, queuebatch.RequestFromTraces(), queuebatch.RequestConsumeFromTraces(pusher),
append([]Option{internal.WithQueueBatchSettings(queuebatch.NewTracesQueueBatchSettings())}, options...)...)
}
================================================
FILE: exporter/exporterhelper/traces_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package exporterhelper
import (
"context"
"errors"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/sdk/trace/tracetest"
semconv "go.opentelemetry.io/otel/semconv/v1.38.0"
"go.opentelemetry.io/otel/trace"
nooptrace "go.opentelemetry.io/otel/trace/noop"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/hosttest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/metadatatest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/oteltest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/queue"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/requesttest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sendertest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/storagetest"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/pdata/testdata"
)
const (
fakeTraceParentSpanName = "fake_trace_parent_span_name"
)
var (
fakeTracesName = component.MustNewIDWithName("fake_traces_exporter", "with_name")
fakeTracesConfig = struct{}{}
)
func TestTraces_InvalidName(t *testing.T) {
te, err := NewTraces(context.Background(), exportertest.NewNopSettings(exportertest.NopType), nil, newTraceDataPusher(nil))
require.Nil(t, te)
require.Equal(t, errNilConfig, err)
}
func TestTraces_NilLogger(t *testing.T) {
te, err := NewTraces(context.Background(), exporter.Settings{}, &fakeTracesConfig, newTraceDataPusher(nil))
require.Nil(t, te)
require.Equal(t, errNilLogger, err)
}
func TestTraces_NilPushTraceData(t *testing.T) {
te, err := NewTraces(context.Background(), exportertest.NewNopSettings(exportertest.NopType), &fakeTracesConfig, nil)
require.Nil(t, te)
require.Equal(t, errNilPushTraces, err)
}
func TestTraces_Default(t *testing.T) {
td := ptrace.NewTraces()
te, err := NewTraces(context.Background(), exportertest.NewNopSettings(exportertest.NopType), &fakeTracesConfig, newTraceDataPusher(nil))
assert.NotNil(t, te)
require.NoError(t, err)
assert.Equal(t, consumer.Capabilities{MutatesData: false}, te.Capabilities())
assert.NoError(t, te.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, te.ConsumeTraces(context.Background(), td))
assert.NoError(t, te.Shutdown(context.Background()))
}
func TestTraces_WithCapabilities(t *testing.T) {
capabilities := consumer.Capabilities{MutatesData: true}
te, err := NewTraces(context.Background(), exportertest.NewNopSettings(exportertest.NopType), &fakeTracesConfig, newTraceDataPusher(nil), WithCapabilities(capabilities))
assert.NotNil(t, te)
require.NoError(t, err)
assert.Equal(t, capabilities, te.Capabilities())
}
func TestTraces_Default_ReturnError(t *testing.T) {
td := ptrace.NewTraces()
want := errors.New("my_error")
te, err := NewTraces(context.Background(), exportertest.NewNopSettings(exportertest.NopType), &fakeTracesConfig, newTraceDataPusher(want))
require.NoError(t, err)
require.NotNil(t, te)
err = te.ConsumeTraces(context.Background(), td)
require.Equal(t, want, err)
}
func TestTraces_WithPersistentQueue(t *testing.T) {
fgOrigReadState := queue.PersistRequestContextOnRead
fgOrigWriteState := queue.PersistRequestContextOnWrite
qCfg := NewDefaultQueueConfig()
storageID := component.MustNewIDWithName("file_storage", "storage")
qCfg.StorageID = &storageID
set := exportertest.NewNopSettings(exportertest.NopType)
set.ID = component.MustNewIDWithName("test_logs", "with_persistent_queue")
host := hosttest.NewHost(map[component.ID]component.Component{
storageID: storagetest.NewMockStorageExtension(nil),
})
spanCtx := oteltest.FakeSpanContext(t)
tests := []struct {
name string
fgEnabledOnWrite bool
fgEnabledOnRead bool
wantData bool
wantSpanCtx bool
}{
{
name: "feature_gate_disabled_on_write_and_read",
wantData: true,
},
{
name: "feature_gate_enabled_on_write_and_read",
fgEnabledOnWrite: true,
fgEnabledOnRead: true,
wantData: true,
wantSpanCtx: true,
},
{
name: "feature_gate_disabled_on_write_enabled_on_read",
wantData: true,
fgEnabledOnRead: true,
},
{
name: "feature_gate_enabled_on_write_disabled_on_read",
fgEnabledOnWrite: true,
wantData: false, // going back from enabled to disabled feature gate isn't supported
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
queue.PersistRequestContextOnRead = func() bool { return tt.fgEnabledOnRead }
queue.PersistRequestContextOnWrite = func() bool { return tt.fgEnabledOnWrite }
t.Cleanup(func() {
queue.PersistRequestContextOnRead = fgOrigReadState
queue.PersistRequestContextOnWrite = fgOrigWriteState
})
ts := consumertest.TracesSink{}
te, err := NewTraces(context.Background(), set, &fakeTracesConfig, ts.ConsumeTraces, WithQueue(configoptional.Some(qCfg)))
require.NoError(t, err)
require.NoError(t, te.Start(context.Background(), host))
t.Cleanup(func() { require.NoError(t, te.Shutdown(context.Background())) })
traces := testdata.GenerateTraces(2)
require.NoError(t, te.ConsumeTraces(trace.ContextWithSpanContext(context.Background(), spanCtx), traces))
if tt.wantData {
require.Eventually(t, func() bool {
return len(ts.AllTraces()) == 1 && ts.SpanCount() == 2
}, 500*time.Millisecond, 10*time.Millisecond)
}
// check that the span context is persisted if the feature gate is enabled
if tt.wantSpanCtx {
assert.Len(t, ts.Contexts(), 1)
assert.Equal(t, spanCtx, trace.SpanContextFromContext(ts.Contexts()[0]))
}
})
}
}
func TestTraces_WithRecordMetrics(t *testing.T) {
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
te, err := NewTraces(context.Background(), exporter.Settings{ID: fakeTracesName, TelemetrySettings: tt.NewTelemetrySettings(), BuildInfo: component.NewDefaultBuildInfo()}, &fakeTracesConfig, newTraceDataPusher(nil))
require.NoError(t, err)
require.NotNil(t, te)
checkRecordedMetricsForTraces(t, tt, fakeTracesName, te, nil)
}
func TestTraces_pLogModifiedDownStream_WithRecordMetrics(t *testing.T) {
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
te, err := NewTraces(context.Background(), exporter.Settings{ID: fakeTracesName, TelemetrySettings: tt.NewTelemetrySettings(), BuildInfo: component.NewDefaultBuildInfo()}, &fakeTracesConfig, newTraceDataPusherModifiedDownstream(nil), WithCapabilities(consumer.Capabilities{MutatesData: true}))
assert.NotNil(t, te)
require.NoError(t, err)
td := testdata.GenerateTraces(2)
require.NoError(t, te.ConsumeTraces(context.Background(), td))
assert.Equal(t, 0, td.SpanCount())
metadatatest.AssertEqualExporterSentSpans(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(internal.ExporterKey, fakeTracesName.String())),
Value: int64(2),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
}
func TestTracesRequest_WithRecordMetrics(t *testing.T) {
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
te, err := internal.NewTracesRequest(context.Background(),
exporter.Settings{ID: fakeTracesName, TelemetrySettings: tt.NewTelemetrySettings(), BuildInfo: component.NewDefaultBuildInfo()},
requesttest.RequestFromTracesFunc(nil), sendertest.NewNopSenderFunc[request.Request]())
require.NoError(t, err)
require.NotNil(t, te)
checkRecordedMetricsForTraces(t, tt, fakeTracesName, te, nil)
}
func TestTraces_WithRecordMetrics_ReturnError(t *testing.T) {
want := errors.New("my_error")
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
te, err := NewTraces(context.Background(), exporter.Settings{ID: fakeTracesName, TelemetrySettings: tt.NewTelemetrySettings(), BuildInfo: component.NewDefaultBuildInfo()}, &fakeTracesConfig, newTraceDataPusher(want))
require.NoError(t, err)
require.NotNil(t, te)
checkRecordedMetricsForTraces(t, tt, fakeTracesName, te, want)
}
func TestTracesRequest_WithRecordMetrics_RequestSenderError(t *testing.T) {
want := errors.New("export_error")
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
te, err := internal.NewTracesRequest(context.Background(),
exporter.Settings{ID: fakeTracesName, TelemetrySettings: tt.NewTelemetrySettings(), BuildInfo: component.NewDefaultBuildInfo()},
requesttest.RequestFromTracesFunc(nil), sendertest.NewErrSenderFunc[request.Request](want))
require.NoError(t, err)
require.NotNil(t, te)
checkRecordedMetricsForTraces(t, tt, fakeTracesName, te, want)
}
func TestTraces_WithSpan(t *testing.T) {
set := exportertest.NewNopSettings(exportertest.NopType)
sr := new(tracetest.SpanRecorder)
set.TracerProvider = sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))
otel.SetTracerProvider(set.TracerProvider)
defer otel.SetTracerProvider(nooptrace.NewTracerProvider())
te, err := NewTraces(context.Background(), set, &fakeTracesConfig, newTraceDataPusher(nil))
require.NoError(t, err)
require.NotNil(t, te)
checkWrapSpanForTraces(t, sr, set.TracerProvider.Tracer("test"), te, nil)
}
func TestTracesRequest_WithSpan(t *testing.T) {
set := exportertest.NewNopSettings(exportertest.NopType)
sr := new(tracetest.SpanRecorder)
set.TracerProvider = sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))
otel.SetTracerProvider(set.TracerProvider)
defer otel.SetTracerProvider(nooptrace.NewTracerProvider())
te, err := internal.NewTracesRequest(context.Background(), set, requesttest.RequestFromTracesFunc(nil), sendertest.NewNopSenderFunc[request.Request]())
require.NoError(t, err)
require.NotNil(t, te)
checkWrapSpanForTraces(t, sr, set.TracerProvider.Tracer("test"), te, nil)
}
func TestTraces_WithSpan_ReturnError(t *testing.T) {
set := exportertest.NewNopSettings(exportertest.NopType)
sr := new(tracetest.SpanRecorder)
set.TracerProvider = sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))
otel.SetTracerProvider(set.TracerProvider)
defer otel.SetTracerProvider(nooptrace.NewTracerProvider())
want := errors.New("my_error")
te, err := NewTraces(context.Background(), set, &fakeTracesConfig, newTraceDataPusher(want))
require.NoError(t, err)
require.NotNil(t, te)
checkWrapSpanForTraces(t, sr, set.TracerProvider.Tracer("test"), te, want)
}
func TestTracesRequest_WithSpan_ExportError(t *testing.T) {
set := exportertest.NewNopSettings(exportertest.NopType)
sr := new(tracetest.SpanRecorder)
set.TracerProvider = sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))
otel.SetTracerProvider(set.TracerProvider)
defer otel.SetTracerProvider(nooptrace.NewTracerProvider())
want := errors.New("export_error")
te, err := internal.NewTracesRequest(context.Background(), set, requesttest.RequestFromTracesFunc(nil), sendertest.NewErrSenderFunc[request.Request](want))
require.NoError(t, err)
require.NotNil(t, te)
checkWrapSpanForTraces(t, sr, set.TracerProvider.Tracer("test"), te, want)
}
func TestTraces_WithShutdown(t *testing.T) {
shutdownCalled := false
shutdown := func(context.Context) error { shutdownCalled = true; return nil }
te, err := NewTraces(context.Background(), exportertest.NewNopSettings(exportertest.NopType), &fakeTracesConfig, newTraceDataPusher(nil), WithShutdown(shutdown))
assert.NotNil(t, te)
assert.NoError(t, err)
assert.NoError(t, te.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, te.Shutdown(context.Background()))
assert.True(t, shutdownCalled)
}
func TestTraces_WithShutdown_ReturnError(t *testing.T) {
want := errors.New("my_error")
shutdownErr := func(context.Context) error { return want }
te, err := NewTraces(context.Background(), exportertest.NewNopSettings(exportertest.NopType), &fakeTracesConfig, newTraceDataPusher(nil), WithShutdown(shutdownErr))
assert.NotNil(t, te)
assert.NoError(t, err)
assert.NoError(t, te.Start(context.Background(), componenttest.NewNopHost()))
assert.Equal(t, want, te.Shutdown(context.Background()))
}
func newTraceDataPusher(retError error) consumer.ConsumeTracesFunc {
return func(context.Context, ptrace.Traces) error {
return retError
}
}
func newTraceDataPusherModifiedDownstream(retError error) consumer.ConsumeTracesFunc {
return func(_ context.Context, trace ptrace.Traces) error {
trace.ResourceSpans().MoveAndAppendTo(ptrace.NewResourceSpansSlice())
return retError
}
}
func checkRecordedMetricsForTraces(t *testing.T, tt *componenttest.Telemetry, id component.ID, te exporter.Traces, wantError error) {
td := testdata.GenerateTraces(2)
const numBatches = 7
for range numBatches {
require.Equal(t, wantError, te.ConsumeTraces(context.Background(), td))
}
// TODO: When the new metrics correctly count partial dropped fix this.
if wantError != nil {
metadatatest.AssertEqualExporterSendFailedSpans(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(internal.ExporterKey, id.String()),
attribute.String(string(semconv.ErrorTypeKey), "_OTHER"),
attribute.Bool(internal.ErrorPermanentKey, false)),
Value: int64(numBatches * td.SpanCount()),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
} else {
metadatatest.AssertEqualExporterSentSpans(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(internal.ExporterKey, id.String())),
Value: int64(numBatches * td.SpanCount()),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
}
}
func generateTraceTraffic(t *testing.T, tracer trace.Tracer, te exporter.Traces, numRequests int, wantError error) {
td := ptrace.NewTraces()
td.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans().AppendEmpty()
ctx, span := tracer.Start(context.Background(), fakeTraceParentSpanName)
defer span.End()
for range numRequests {
require.Equal(t, wantError, te.ConsumeTraces(ctx, td))
}
}
func checkWrapSpanForTraces(t *testing.T, sr *tracetest.SpanRecorder, tracer trace.Tracer, te exporter.Traces, wantError error) {
const numRequests = 5
generateTraceTraffic(t, tracer, te, numRequests, wantError)
// Inspection time!
gotSpanData := sr.Ended()
require.Len(t, gotSpanData, numRequests+1)
parentSpan := gotSpanData[numRequests]
require.Equalf(t, fakeTraceParentSpanName, parentSpan.Name(), "SpanData %v", parentSpan)
for _, sd := range gotSpanData[:numRequests] {
require.Equalf(t, parentSpan.SpanContext(), sd.Parent(), "Exporter span not a child\nSpanData %v", sd)
oteltest.CheckStatus(t, sd, wantError)
sentSpans := int64(1)
failedToSendSpans := int64(0)
if wantError != nil {
sentSpans = 0
failedToSendSpans = 1
}
require.Containsf(t, sd.Attributes(), attribute.KeyValue{Key: internal.ItemsSent, Value: attribute.Int64Value(sentSpans)}, "SpanData %v", sd)
require.Containsf(t, sd.Attributes(), attribute.KeyValue{Key: internal.ItemsFailed, Value: attribute.Int64Value(failedToSendSpans)}, "SpanData %v", sd)
}
}
================================================
FILE: exporter/exporterhelper/xexporterhelper/Makefile
================================================
include ../../../Makefile.Common
================================================
FILE: exporter/exporterhelper/xexporterhelper/constants.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xexporterhelper // import "go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper"
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")
// errNilConsumeRequest is returned when a nil PushTraces is given.
errNilConsumeRequest = errors.New("nil RequestConsumeFunc")
// errNilPushProfileData is returned when a nil PushProfiles is given.
errNilPushProfileData = errors.New("nil PushProfiles")
// errNilProfilesConverter is returned when a nil RequestFromProfilesFunc is given.
errNilProfilesConverter = errors.New("nil RequestFromProfilesFunc")
)
================================================
FILE: exporter/exporterhelper/xexporterhelper/go.mod
================================================
module go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.opentelemetry.io/collector/config/configoptional v1.54.0
go.opentelemetry.io/collector/consumer v1.54.0
go.opentelemetry.io/collector/consumer/consumererror v0.148.0
go.opentelemetry.io/collector/consumer/consumererror/xconsumererror v0.148.0
go.opentelemetry.io/collector/consumer/consumertest v0.148.0
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0
go.opentelemetry.io/collector/exporter v1.54.0
go.opentelemetry.io/collector/exporter/exporterhelper v0.148.0
go.opentelemetry.io/collector/exporter/exportertest v0.148.0
go.opentelemetry.io/collector/exporter/xexporter v0.148.0
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/collector/pdata/pprofile v0.148.0
go.opentelemetry.io/collector/pdata/testdata v0.148.0
go.opentelemetry.io/collector/pdata/xpdata v0.148.0
go.opentelemetry.io/collector/pipeline/xpipeline v0.148.0
go.opentelemetry.io/otel v1.42.0
go.opentelemetry.io/otel/sdk v1.42.0
go.opentelemetry.io/otel/trace v1.42.0
go.uber.org/zap v1.27.1
)
require (
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/client v1.54.0 // indirect
go.opentelemetry.io/collector/config/configretry v1.54.0 // indirect
go.opentelemetry.io/collector/confmap v1.54.0 // indirect
go.opentelemetry.io/collector/confmap/xconfmap v0.148.0 // indirect
go.opentelemetry.io/collector/extension v1.54.0 // indirect
go.opentelemetry.io/collector/extension/xextension v0.148.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
go.opentelemetry.io/collector/pipeline v1.54.0 // indirect
go.opentelemetry.io/collector/receiver v1.54.0 // indirect
go.opentelemetry.io/collector/receiver/receivertest v0.148.0 // indirect
go.opentelemetry.io/collector/receiver/xreceiver v0.148.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/sys v0.41.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/consumer/consumertest => ../../../consumer/consumertest
replace go.opentelemetry.io/collector/pdata/pprofile => ../../../pdata/pprofile
replace go.opentelemetry.io/collector/pdata/testdata => ../../../pdata/testdata
replace go.opentelemetry.io/collector/exporter => ../../
replace go.opentelemetry.io/collector/consumer => ../../../consumer
replace go.opentelemetry.io/collector/consumer/consumererror/xconsumererror => ../../../consumer/consumererror/xconsumererror
replace go.opentelemetry.io/collector/receiver => ../../../receiver
replace go.opentelemetry.io/collector/consumer/xconsumer => ../../../consumer/xconsumer
replace go.opentelemetry.io/collector/component => ../../../component
replace go.opentelemetry.io/collector/component/componenttest => ../../../component/componenttest
replace go.opentelemetry.io/collector/receiver/xreceiver => ../../../receiver/xreceiver
replace go.opentelemetry.io/collector/receiver/receivertest => ../../../receiver/receivertest
replace go.opentelemetry.io/collector/extension => ../../../extension
replace go.opentelemetry.io/collector/pdata => ../../../pdata
replace go.opentelemetry.io/collector/exporter/xexporter => ../../xexporter
replace go.opentelemetry.io/collector/config/configretry => ../../../config/configretry
replace go.opentelemetry.io/collector/pipeline/xpipeline => ../../../pipeline/xpipeline
replace go.opentelemetry.io/collector/pipeline => ../../../pipeline
replace go.opentelemetry.io/collector/exporter/exportertest => ../../exportertest
replace go.opentelemetry.io/collector/consumer/consumererror => ../../../consumer/consumererror
replace go.opentelemetry.io/collector/extension/extensiontest => ../../../extension/extensiontest
replace go.opentelemetry.io/collector/extension/xextension => ../../../extension/xextension
replace go.opentelemetry.io/collector/featuregate => ../../../featuregate
replace go.opentelemetry.io/collector/client => ../../../client
replace go.opentelemetry.io/collector/pdata/xpdata => ../../../pdata/xpdata
replace go.opentelemetry.io/collector/config/configoptional => ../../../config/configoptional
replace go.opentelemetry.io/collector/confmap => ../../../confmap
replace go.opentelemetry.io/collector/confmap/xconfmap => ../../../confmap/xconfmap
replace go.opentelemetry.io/collector/exporter/exporterhelper => ../
replace go.opentelemetry.io/collector/internal/testutil => ../../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../../internal/componentalias
================================================
FILE: exporter/exporterhelper/xexporterhelper/go.sum
================================================
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: exporter/exporterhelper/xexporterhelper/metadata.yaml
================================================
type: xexporterhelper
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
codeowners:
active:
- mx-psi
- dmathieu
stability:
alpha: [profiles]
================================================
FILE: exporter/exporterhelper/xexporterhelper/new_request.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xexporterhelper // import "go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper"
import (
"context"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exporterhelper"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/queuebatch"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
)
// NewLogsRequest creates new logs exporter based on custom LogsConverter and Sender.
// Experimental: This API is at the early stage of development and may change without backward compatibility
// until https://github.com/open-telemetry/opentelemetry-collector/issues/8122 is resolved.
func NewLogsRequest(
ctx context.Context,
set exporter.Settings,
converter RequestConverterFunc[plog.Logs],
pusher RequestConsumeFunc,
options ...exporterhelper.Option,
) (exporter.Logs, error) {
return internal.NewLogsRequest(ctx, set, converter, pusher, options...)
}
// NewMetricsRequest creates new metrics exporter based on custom MetricsConverter and Sender.
// Experimental: This API is at the early stage of development and may change without backward compatibility
// until https://github.com/open-telemetry/opentelemetry-collector/issues/8122 is resolved.
func NewMetricsRequest(
ctx context.Context,
set exporter.Settings,
converter RequestConverterFunc[pmetric.Metrics],
pusher RequestConsumeFunc,
options ...exporterhelper.Option,
) (exporter.Metrics, error) {
return internal.NewMetricsRequest(ctx, set, converter, pusher, options...)
}
// NewTracesRequest creates new traces exporter based on custom TracesConverter and Sender.
// Experimental: This API is at the early stage of development and may change without backward compatibility
// until https://github.com/open-telemetry/opentelemetry-collector/issues/8122 is resolved.
func NewTracesRequest(
ctx context.Context,
set exporter.Settings,
converter RequestConverterFunc[ptrace.Traces],
pusher RequestConsumeFunc,
options ...exporterhelper.Option,
) (exporter.Traces, error) {
return internal.NewTracesRequest(ctx, set, converter, pusher, options...)
}
// QueueBatchSettings are settings for the QueueBatch component.
// They include things line Encoding to be used with persistent queue, or the available Sizers, etc.
type QueueBatchSettings = queuebatch.Settings[Request]
// NewMetricsQueueBatchSettings returns a new QueueBatchSettings to configure to WithQueueBatch when using pmetric.Metrics.
// Experimental: This API is at the early stage of development and may change without backward compatibility
// until https://github.com/open-telemetry/opentelemetry-collector/issues/8122 is resolved.
func NewMetricsQueueBatchSettings() QueueBatchSettings {
return queuebatch.NewMetricsQueueBatchSettings()
}
// NewLogsQueueBatchSettings returns a new QueueBatchSettings to configure to WithQueueBatch when using plog.Logs.
// Experimental: This API is at the early stage of development and may change without backward compatibility
// until https://github.com/open-telemetry/opentelemetry-collector/issues/8122 is resolved.
func NewLogsQueueBatchSettings() QueueBatchSettings {
return queuebatch.NewLogsQueueBatchSettings()
}
// NewTracesQueueBatchSettings returns a new QueueBatchSettings to configure to WithQueueBatch when using ptrace.Traces.
// Experimental: This API is at the early stage of development and may change without backward compatibility
// until https://github.com/open-telemetry/opentelemetry-collector/issues/8122 is resolved.
func NewTracesQueueBatchSettings() QueueBatchSettings {
return queuebatch.NewTracesQueueBatchSettings()
}
// WithQueueBatch enables queueing and batching for an exporter.
// This option should be used with the new exporter helpers New[Traces|Metrics|Logs]RequestExporter.
// If batch.partition.MetadataKeys is set, it will automatically configure the partitioner and merge function
// to partition batches based on the specified metadata keys.
// Experimental: This API is at the early stage of development and may change without backward compatibility
// until https://github.com/open-telemetry/opentelemetry-collector/issues/8122 is resolved.
func WithQueueBatch(cfg configoptional.Optional[exporterhelper.QueueBatchConfig], set QueueBatchSettings) exporterhelper.Option {
return internal.WithQueueBatch(cfg, set)
}
================================================
FILE: exporter/exporterhelper/xexporterhelper/profiles.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xexporterhelper // import "go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper"
import (
"context"
"errors"
"go.uber.org/zap"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/consumer/consumererror/xconsumererror"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exporterhelper"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/queue"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/request"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sizer"
"go.opentelemetry.io/collector/exporter/xexporter"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/xpdata/pref"
pdatareq "go.opentelemetry.io/collector/pdata/xpdata/request"
"go.opentelemetry.io/collector/pipeline/xpipeline"
)
var (
profilesMarshaler = &pprofile.ProtoMarshaler{}
profilesUnmarshaler = &pprofile.ProtoUnmarshaler{}
)
// NewProfilesQueueBatchSettings returns a new QueueBatchSettings to configure to WithQueueBatch when using pprofile.Profiles.
// Experimental: This API is at the early stage of development and may change without backward compatibility
// until https://github.com/open-telemetry/opentelemetry-collector/issues/8122 is resolved.
func NewProfilesQueueBatchSettings() QueueBatchSettings {
return QueueBatchSettings{
ReferenceCounter: profilesReferenceCounter{},
Encoding: profilesEncoding{},
}
}
var (
_ request.Request = (*profilesRequest)(nil)
_ request.ErrorHandler = (*profilesRequest)(nil)
)
type profilesRequest struct {
pd pprofile.Profiles
cachedSize int
}
func newProfilesRequest(pd pprofile.Profiles) Request {
return &profilesRequest{
pd: pd,
cachedSize: -1,
}
}
type profilesEncoding struct{}
var _ exporterhelper.QueueBatchEncoding[request.Request] = profilesEncoding{}
func (profilesEncoding) Unmarshal(bytes []byte) (context.Context, request.Request, error) {
if queue.PersistRequestContextOnRead() {
ctx, profiles, err := pdatareq.UnmarshalProfiles(bytes)
if errors.Is(err, pdatareq.ErrInvalidFormat) {
// fall back to unmarshaling without context
profiles, err = profilesUnmarshaler.UnmarshalProfiles(bytes)
}
return ctx, newProfilesRequest(profiles), err
}
profiles, err := profilesUnmarshaler.UnmarshalProfiles(bytes)
if err != nil {
var req request.Request
return context.Background(), req, err
}
return context.Background(), newProfilesRequest(profiles), nil
}
func (profilesEncoding) Marshal(ctx context.Context, req request.Request) ([]byte, error) {
profiles := req.(*profilesRequest).pd
if queue.PersistRequestContextOnWrite() {
return pdatareq.MarshalProfiles(ctx, profiles)
}
return profilesMarshaler.MarshalProfiles(profiles)
}
var _ queue.ReferenceCounter[request.Request] = profilesReferenceCounter{}
type profilesReferenceCounter struct{}
func (profilesReferenceCounter) Ref(req request.Request) {
pref.RefProfiles(req.(*profilesRequest).pd)
}
func (profilesReferenceCounter) Unref(req request.Request) {
pref.UnrefProfiles(req.(*profilesRequest).pd)
}
func (req *profilesRequest) OnError(err error) Request {
var profileError xconsumererror.Profiles
if errors.As(err, &profileError) {
// TODO: Add logic to unref the new request created here.
return newProfilesRequest(profileError.Data())
}
return req
}
func (req *profilesRequest) ItemsCount() int {
return req.pd.SampleCount()
}
func (req *profilesRequest) size(sizer sizer.ProfilesSizer) int {
if req.cachedSize == -1 {
req.cachedSize = sizer.ProfilesSize(req.pd)
}
return req.cachedSize
}
func (req *profilesRequest) setCachedSize(size int) {
req.cachedSize = size
}
func (req *profilesRequest) BytesSize() int {
return profilesMarshaler.ProfilesSize(req.pd)
}
type profileExporter struct {
*internal.BaseExporter
xconsumer.Profiles
}
// NewProfiles creates an xexporter.Profiles that records observability metrics and wraps every request with a Span.
func NewProfiles(
ctx context.Context,
set exporter.Settings,
cfg component.Config,
pusher xconsumer.ConsumeProfilesFunc,
options ...exporterhelper.Option,
) (xexporter.Profiles, error) {
if cfg == nil {
return nil, errNilConfig
}
if pusher == nil {
return nil, errNilPushProfileData
}
return NewProfilesRequest(ctx, set, requestFromProfiles(), requestConsumeFromProfiles(pusher),
append([]exporterhelper.Option{internal.WithQueueBatchSettings(NewProfilesQueueBatchSettings())}, options...)...)
}
// requestConsumeFromProfiles returns a RequestConsumeFunc that consumes pprofile.Profiles.
func requestConsumeFromProfiles(pusher xconsumer.ConsumeProfilesFunc) RequestConsumeFunc {
return func(ctx context.Context, request Request) error {
return pusher.ConsumeProfiles(ctx, request.(*profilesRequest).pd)
}
}
// requestFromProfiles returns a RequestFromProfilesFunc that converts pprofile.Profiles into a Request.
func requestFromProfiles() RequestConverterFunc[pprofile.Profiles] {
return func(_ context.Context, profiles pprofile.Profiles) (Request, error) {
return newProfilesRequest(profiles), nil
}
}
// NewProfilesRequest creates a new profiles exporter based on a custom ProfilesConverter and Sender.
// Experimental: This API is at the early stage of development and may change without backward compatibility
// until https://github.com/open-telemetry/opentelemetry-collector/issues/8122 is resolved.
func NewProfilesRequest(
_ context.Context,
set exporter.Settings,
converter RequestConverterFunc[pprofile.Profiles],
pusher RequestConsumeFunc,
options ...exporterhelper.Option,
) (xexporter.Profiles, error) {
if set.Logger == nil {
return nil, errNilLogger
}
if converter == nil {
return nil, errNilProfilesConverter
}
if pusher == nil {
return nil, errNilConsumeRequest
}
be, err := internal.NewBaseExporter(set, xpipeline.SignalProfiles, pusher, options...)
if err != nil {
return nil, err
}
tc, err := xconsumer.NewProfiles(newConsumeProfiles(converter, be, set.Logger), be.ConsumerOptions...)
if err != nil {
return nil, err
}
return &profileExporter{BaseExporter: be, Profiles: tc}, nil
}
func newConsumeProfiles(converter RequestConverterFunc[pprofile.Profiles], be *internal.BaseExporter, logger *zap.Logger) xconsumer.ConsumeProfilesFunc {
return func(ctx context.Context, pd pprofile.Profiles) error {
req, err := converter(ctx, pd)
if err != nil {
logger.Error("Failed to convert profiles. Dropping data.",
zap.Int("dropped_samples", pd.SampleCount()),
zap.Error(err))
return consumererror.NewPermanent(err)
}
return be.Send(ctx, req)
}
}
================================================
FILE: exporter/exporterhelper/xexporterhelper/profiles_batch.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xexporterhelper // import "go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper"
import (
"context"
"errors"
"fmt"
"go.opentelemetry.io/collector/exporter/exporterhelper"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sizer"
"go.opentelemetry.io/collector/pdata/pprofile"
)
// MergeSplit splits and/or merges the profiles into multiple requests based on the MaxSizeConfig.
//
// Following the OTLP 1.7.0 upgrade, this is currently a noop.
// See https://github.com/open-telemetry/opentelemetry-collector/issues/13106
func (req *profilesRequest) MergeSplit(_ context.Context, maxSize int, szt exporterhelper.RequestSizerType, r2 Request) ([]Request, error) {
var sz sizer.ProfilesSizer
switch szt {
case exporterhelper.RequestSizerTypeItems:
sz = &sizer.ProfilesCountSizer{}
case exporterhelper.RequestSizerTypeBytes:
sz = &sizer.ProfilesBytesSizer{}
default:
return nil, errors.New("unknown sizer type")
}
if r2 != nil && r2.ItemsCount() > 0 {
req2, ok := r2.(*profilesRequest)
if !ok {
return nil, errors.New("invalid input type")
}
err := req2.mergeTo(req, sz)
if err != nil {
return nil, fmt.Errorf("failed merging profiles; %w", err)
}
}
// If no limit we can simply merge the new request into the current and return.
if maxSize == 0 {
return []Request{req}, nil
}
return req.split(maxSize, sz)
}
func (req *profilesRequest) mergeTo(dst *profilesRequest, sz sizer.ProfilesSizer) error {
if sz != nil {
dst.setCachedSize(dst.size(sz) + req.size(sz))
req.setCachedSize(0)
}
return req.pd.MergeTo(dst.pd)
}
func (req *profilesRequest) split(maxSize int, sz sizer.ProfilesSizer) ([]Request, error) {
var res []Request
for req.size(sz) > maxSize {
pd, rmSize := extractProfiles(req.pd, maxSize, sz)
if pd.SampleCount() == 0 {
return res, fmt.Errorf("one sample size is greater than max size, dropping items: %d", req.pd.SampleCount())
}
req.setCachedSize(req.size(sz) - rmSize)
res = append(res, newProfilesRequest(pd))
}
res = append(res, req)
return res, nil
}
// extractProfiles extracts a new profiles with a maximum number of samples.
func extractProfiles(srcProfiles pprofile.Profiles, capacity int, sz sizer.ProfilesSizer) (pprofile.Profiles, int) {
destProfiles := pprofile.NewProfiles()
capacityLeft := capacity - sz.ProfilesSize(destProfiles)
removedSize := 0
srcProfiles.Dictionary().CopyTo(destProfiles.Dictionary())
srcProfiles.ResourceProfiles().RemoveIf(func(srcRP pprofile.ResourceProfiles) bool {
// If the no more capacity left just return.
if capacityLeft == 0 {
return false
}
rawRpSize := sz.ResourceProfilesSize(srcRP)
rpSize := sz.DeltaSize(rawRpSize)
if rpSize > capacityLeft {
extSrcRP, extRpSize := extractResourceProfiles(srcRP, capacityLeft, sz)
// This cannot make it to exactly 0 for the bytes,
// force it to be 0 since that is the stopping condition.
capacityLeft = 0
removedSize += extRpSize
// There represents the delta between the delta sizes.
removedSize += rpSize - rawRpSize - (sz.DeltaSize(rawRpSize-extRpSize) - (rawRpSize - extRpSize))
// It is possible that for the bytes scenario, the extracted field contains no profiles.
// Do not add it to the destination if that is the case.
if extSrcRP.ScopeProfiles().Len() > 0 {
extSrcRP.MoveTo(destProfiles.ResourceProfiles().AppendEmpty())
}
return extSrcRP.ScopeProfiles().Len() != 0
}
capacityLeft -= rpSize
removedSize += rpSize
srcRP.MoveTo(destProfiles.ResourceProfiles().AppendEmpty())
return true
})
return destProfiles, removedSize
}
// extractResourceProfiles extracts profiles and returns a new resource profiles with the specified number of profiles.
func extractResourceProfiles(srcRP pprofile.ResourceProfiles, capacity int, sz sizer.ProfilesSizer) (pprofile.ResourceProfiles, int) {
destRP := pprofile.NewResourceProfiles()
destRP.SetSchemaUrl(srcRP.SchemaUrl())
srcRP.Resource().CopyTo(destRP.Resource())
// Take into account that this can have max "capacity", so when added to the parent will need space for the extra delta size.
capacityLeft := capacity - (sz.DeltaSize(capacity) - capacity) - sz.ResourceProfilesSize(destRP)
removedSize := 0
srcRP.ScopeProfiles().RemoveIf(func(srcSS pprofile.ScopeProfiles) bool {
// If the no more capacity left just return.
if capacityLeft == 0 {
return false
}
rawSlSize := sz.ScopeProfilesSize(srcSS)
ssSize := sz.DeltaSize(rawSlSize)
if ssSize > capacityLeft {
extSrcSS, extSsSize := extractScopeProfiles(srcSS, capacityLeft, sz)
// This cannot make it to exactly 0 for the bytes,
// force it to be 0 since that is the stopping condition.
capacityLeft = 0
removedSize += extSsSize
// There represents the delta between the delta sizes.
removedSize += ssSize - rawSlSize - (sz.DeltaSize(rawSlSize-extSsSize) - (rawSlSize - extSsSize))
// It is possible that for the bytes scenario, the extracted field contains no profiles.
// Do not add it to the destination if that is the case.
if extSrcSS.Profiles().Len() > 0 {
extSrcSS.MoveTo(destRP.ScopeProfiles().AppendEmpty())
}
return extSrcSS.Profiles().Len() != 0
}
capacityLeft -= ssSize
removedSize += ssSize
srcSS.MoveTo(destRP.ScopeProfiles().AppendEmpty())
return true
})
return destRP, removedSize
}
// extractScopeProfiles extracts profiles and returns a new scope profiles with the specified number of profiles.
func extractScopeProfiles(srcSS pprofile.ScopeProfiles, capacity int, sz sizer.ProfilesSizer) (pprofile.ScopeProfiles, int) {
destSS := pprofile.NewScopeProfiles()
destSS.SetSchemaUrl(srcSS.SchemaUrl())
srcSS.Scope().CopyTo(destSS.Scope())
// Take into account that this can have max "capacity", so when added to the parent will need space for the extra delta size.
capacityLeft := capacity - (sz.DeltaSize(capacity) - capacity) - sz.ScopeProfilesSize(destSS)
removedSize := 0
srcSS.Profiles().RemoveIf(func(srcProfile pprofile.Profile) bool {
// If the no more capacity left just return.
if capacityLeft == 0 {
return false
}
rsSize := sz.DeltaSize(sz.ProfileSize(srcProfile))
if rsSize > capacityLeft {
// This cannot make it to exactly 0 for the bytes,
// force it to be 0 since that is the stopping condition.
capacityLeft = 0
return false
}
capacityLeft -= rsSize
removedSize += rsSize
srcProfile.MoveTo(destSS.Profiles().AppendEmpty())
return true
})
return destSS, removedSize
}
================================================
FILE: exporter/exporterhelper/xexporterhelper/profiles_batch_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xexporterhelper
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/exporter/exporterhelper"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/requesttest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sizer"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/testdata"
)
func TestMergeProfiles(t *testing.T) {
pr1 := newProfilesRequest(testdata.GenerateProfiles(2))
pr2 := newProfilesRequest(testdata.GenerateProfiles(3))
res, err := pr1.MergeSplit(context.Background(), 0, exporterhelper.RequestSizerTypeItems, pr2)
require.NoError(t, err)
assert.Len(t, res, 1)
assert.Equal(t, 5, res[0].ItemsCount())
}
func TestMergeProfilesInvalidInput(t *testing.T) {
pr2 := newProfilesRequest(testdata.GenerateProfiles(3))
_, err := pr2.MergeSplit(context.Background(), 0, exporterhelper.RequestSizerTypeItems, &requesttest.FakeRequest{Items: 1})
require.Error(t, err)
}
func TestMergeSplitProfiles(t *testing.T) {
tests := []struct {
name string
szt exporterhelper.RequestSizerType
maxSize int
pr1 Request
pr2 Request
expected []Request
}{
{
name: "both_requests_empty",
szt: exporterhelper.RequestSizerTypeItems,
maxSize: 10,
pr1: newProfilesRequest(pprofile.NewProfiles()),
pr2: newProfilesRequest(pprofile.NewProfiles()),
expected: []Request{newProfilesRequest(pprofile.NewProfiles())},
},
{
name: "first_request_empty",
szt: exporterhelper.RequestSizerTypeItems,
maxSize: 10,
pr1: newProfilesRequest(testdata.GenerateProfiles(0)),
pr2: newProfilesRequest(testdata.GenerateProfiles(5)),
expected: []Request{newProfilesRequest(func() pprofile.Profiles {
profiles := testdata.GenerateProfiles(0)
_ = testdata.GenerateProfiles(5).MergeTo(profiles)
return profiles
}())},
},
{
name: "first_empty_second_nil",
szt: exporterhelper.RequestSizerTypeItems,
maxSize: 10,
pr1: newProfilesRequest(pprofile.NewProfiles()),
pr2: nil,
expected: []Request{newProfilesRequest(pprofile.NewProfiles())},
},
{
name: "merge_only",
szt: exporterhelper.RequestSizerTypeItems,
maxSize: 10,
pr1: newProfilesRequest(testdata.GenerateProfiles(4)),
pr2: newProfilesRequest(testdata.GenerateProfiles(6)),
expected: []Request{newProfilesRequest(func() pprofile.Profiles {
profiles := testdata.GenerateProfiles(4)
testdata.GenerateProfiles(6).ResourceProfiles().MoveAndAppendTo(profiles.ResourceProfiles())
return profiles
}())},
},
{
name: "split_only",
szt: exporterhelper.RequestSizerTypeItems,
maxSize: 4,
pr1: newProfilesRequest(testdata.GenerateProfiles(10)),
pr2: nil,
expected: []Request{
newProfilesRequest(testdata.GenerateProfiles(4)),
newProfilesRequest(testdata.GenerateProfiles(4)),
newProfilesRequest(testdata.GenerateProfiles(2)),
},
},
{
name: "merge_and_split",
szt: exporterhelper.RequestSizerTypeItems,
maxSize: 10,
pr1: newProfilesRequest(testdata.GenerateProfiles(8)),
pr2: newProfilesRequest(testdata.GenerateProfiles(20)),
expected: []Request{
newProfilesRequest(func() pprofile.Profiles {
profiles := testdata.GenerateProfiles(8)
testdata.GenerateProfiles(2).ResourceProfiles().MoveAndAppendTo(profiles.ResourceProfiles())
return profiles
}()),
newProfilesRequest(testdata.GenerateProfiles(10)),
newProfilesRequest(testdata.GenerateProfiles(8)),
},
},
{
name: "scope_profiles_split",
szt: exporterhelper.RequestSizerTypeItems,
maxSize: 4,
pr1: newProfilesRequest(func() pprofile.Profiles {
return testdata.GenerateProfiles(6)
}()),
pr2: nil,
expected: []Request{
newProfilesRequest(testdata.GenerateProfiles(4)),
newProfilesRequest(func() pprofile.Profiles {
return testdata.GenerateProfiles(2)
}()),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
res, err := tt.pr1.MergeSplit(context.Background(), tt.maxSize, tt.szt, tt.pr2)
require.NoError(t, err)
require.Len(t, res, len(tt.expected))
for i, r := range res {
assert.Equal(t, tt.expected[i].(*profilesRequest).pd, r.(*profilesRequest).pd)
}
})
}
}
func TestMergeSplitProfilesBasedOnByteSize(t *testing.T) {
tests := []struct {
name string
szt exporterhelper.RequestSizerType
maxSize int
pr1 Request
pr2 Request
expected []Request
}{
{
name: "both_requests_empty",
szt: exporterhelper.RequestSizerTypeItems,
maxSize: 10,
pr1: newProfilesRequest(pprofile.NewProfiles()),
pr2: newProfilesRequest(pprofile.NewProfiles()),
expected: []Request{newProfilesRequest(pprofile.NewProfiles())},
},
{
name: "first_request_empty",
szt: exporterhelper.RequestSizerTypeItems,
maxSize: 10,
pr1: newProfilesRequest(pprofile.NewProfiles()),
pr2: newProfilesRequest(testdata.GenerateProfiles(5)),
expected: []Request{newProfilesRequest(testdata.GenerateProfiles(5))},
},
{
name: "first_empty_second_nil",
szt: exporterhelper.RequestSizerTypeItems,
maxSize: 10,
pr1: newProfilesRequest(pprofile.NewProfiles()),
pr2: nil,
expected: []Request{newProfilesRequest(pprofile.NewProfiles())},
},
{
name: "merge_only",
szt: exporterhelper.RequestSizerTypeItems,
maxSize: 10,
pr1: newProfilesRequest(testdata.GenerateProfiles(4)),
pr2: newProfilesRequest(testdata.GenerateProfiles(6)),
expected: []Request{newProfilesRequest(func() pprofile.Profiles {
profiles := testdata.GenerateProfiles(4)
testdata.GenerateProfiles(6).ResourceProfiles().MoveAndAppendTo(profiles.ResourceProfiles())
return profiles
}())},
},
{
name: "split_only",
szt: exporterhelper.RequestSizerTypeItems,
maxSize: 4,
pr1: newProfilesRequest(testdata.GenerateProfiles(10)),
pr2: nil,
expected: []Request{
newProfilesRequest(testdata.GenerateProfiles(4)),
newProfilesRequest(testdata.GenerateProfiles(4)),
newProfilesRequest(testdata.GenerateProfiles(2)),
},
},
{
name: "merge_and_split",
szt: exporterhelper.RequestSizerTypeItems,
maxSize: 10,
pr1: newProfilesRequest(testdata.GenerateProfiles(8)),
pr2: newProfilesRequest(testdata.GenerateProfiles(20)),
expected: []Request{
newProfilesRequest(func() pprofile.Profiles {
profiles := testdata.GenerateProfiles(8)
testdata.GenerateProfiles(2).ResourceProfiles().MoveAndAppendTo(profiles.ResourceProfiles())
return profiles
}()),
newProfilesRequest(testdata.GenerateProfiles(10)),
newProfilesRequest(testdata.GenerateProfiles(8)),
},
},
{
name: "scope_profiles_split",
szt: exporterhelper.RequestSizerTypeItems,
maxSize: 4,
pr1: newProfilesRequest(func() pprofile.Profiles {
return testdata.GenerateProfiles(6)
}()),
pr2: nil,
expected: []Request{
newProfilesRequest(testdata.GenerateProfiles(4)),
newProfilesRequest(func() pprofile.Profiles {
return testdata.GenerateProfiles(2)
}()),
},
},
{
name: "both_requests_empty",
szt: exporterhelper.RequestSizerTypeBytes,
maxSize: profilesMarshaler.ProfilesSize(testdata.GenerateProfiles(10)),
pr1: newProfilesRequest(pprofile.NewProfiles()),
pr2: newProfilesRequest(pprofile.NewProfiles()),
expected: []Request{newProfilesRequest(pprofile.NewProfiles())},
},
{
name: "first_request_empty",
szt: exporterhelper.RequestSizerTypeBytes,
maxSize: profilesMarshaler.ProfilesSize(testdata.GenerateProfiles(10)),
pr1: newProfilesRequest(pprofile.NewProfiles()),
pr2: newProfilesRequest(testdata.GenerateProfiles(5)),
expected: []Request{newProfilesRequest(testdata.GenerateProfiles(5))},
},
{
name: "first_empty_second_nil",
szt: exporterhelper.RequestSizerTypeBytes,
maxSize: profilesMarshaler.ProfilesSize(testdata.GenerateProfiles(10)),
pr1: newProfilesRequest(pprofile.NewProfiles()),
pr2: nil,
expected: []Request{newProfilesRequest(pprofile.NewProfiles())},
},
{
name: "merge_only",
szt: exporterhelper.RequestSizerTypeBytes,
maxSize: profilesMarshaler.ProfilesSize(testdata.GenerateProfiles(13)),
pr1: newProfilesRequest(testdata.GenerateProfiles(4)),
pr2: newProfilesRequest(testdata.GenerateProfiles(6)),
expected: []Request{newProfilesRequest(func() pprofile.Profiles {
profiles := testdata.GenerateProfiles(4)
testdata.GenerateProfiles(6).ResourceProfiles().MoveAndAppendTo(profiles.ResourceProfiles())
return profiles
}())},
},
{
name: "split_only",
szt: exporterhelper.RequestSizerTypeBytes,
maxSize: profilesMarshaler.ProfilesSize(testdata.GenerateProfiles(4)),
pr1: newProfilesRequest(testdata.GenerateProfiles(0)),
pr2: newProfilesRequest(testdata.GenerateProfiles(10)),
expected: []Request{
newProfilesRequest(testdata.GenerateProfiles(4)),
newProfilesRequest(testdata.GenerateProfiles(5)),
newProfilesRequest(testdata.GenerateProfiles(1)),
},
},
{
name: "merge_and_split",
szt: exporterhelper.RequestSizerTypeBytes,
maxSize: profilesMarshaler.ProfilesSize(testdata.GenerateProfiles(10)),
pr1: newProfilesRequest(testdata.GenerateProfiles(8)),
pr2: newProfilesRequest(testdata.GenerateProfiles(20)),
expected: []Request{
newProfilesRequest(func() pprofile.Profiles {
profiles := testdata.GenerateProfiles(7)
testdata.GenerateProfiles(3).ResourceProfiles().MoveAndAppendTo(profiles.ResourceProfiles())
return profiles
}()),
newProfilesRequest(testdata.GenerateProfiles(11)),
newProfilesRequest(testdata.GenerateProfiles(7)),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
res, err := tt.pr1.MergeSplit(context.Background(), tt.maxSize, tt.szt, tt.pr2)
require.NoError(t, err)
require.Len(t, res, len(tt.expected))
for i, r := range res {
assert.Equal(t, tt.expected[i].(*profilesRequest).pd.SampleCount(), r.(*profilesRequest).pd.SampleCount(), i)
}
})
}
}
func TestExtractProfiles(t *testing.T) {
for i := range 10 {
ld := testdata.GenerateProfiles(10)
extractedProfiles, _ := extractProfiles(ld, i, &sizer.ProfilesCountSizer{})
assert.Equal(t, i, extractedProfiles.SampleCount())
assert.Equal(t, 10-i, ld.SampleCount())
}
}
func TestMergeSplitManySmallProfiles(t *testing.T) {
// All requests merge into a single batch.
merged := []Request{newProfilesRequest(testdata.GenerateProfiles(1))}
for range 1000 {
pr2 := newProfilesRequest(testdata.GenerateProfiles(10))
res, _ := merged[len(merged)-1].MergeSplit(context.Background(), 10000, exporterhelper.RequestSizerTypeItems, pr2)
merged = append(merged[0:len(merged)-1], res...)
}
assert.Len(t, merged, 2)
}
func BenchmarkSplittingBasedOnByteSizeManySmallProfiles(b *testing.B) {
// All requests merge into a single batch.
b.ReportAllocs()
for b.Loop() {
merged := []Request{newProfilesRequest(testdata.GenerateProfiles(10))}
for range 1000 {
pr2 := newProfilesRequest(testdata.GenerateProfiles(10))
res, _ := merged[len(merged)-1].MergeSplit(
context.Background(),
profilesMarshaler.ProfilesSize(testdata.GenerateProfiles(11000)),
exporterhelper.RequestSizerTypeBytes,
pr2,
)
merged = append(merged[0:len(merged)-1], res...)
}
assert.Len(b, merged, 2)
}
}
func BenchmarkSplittingBasedOnByteSizeManyProfilesSlightlyAboveLimit(b *testing.B) {
// Every incoming request results in a split.
b.ReportAllocs()
for b.Loop() {
merged := []Request{newProfilesRequest(testdata.GenerateProfiles(0))}
for range 10 {
pr2 := newProfilesRequest(testdata.GenerateProfiles(10001))
res, _ := merged[len(merged)-1].MergeSplit(
context.Background(),
profilesMarshaler.ProfilesSize(testdata.GenerateProfiles(10000)),
exporterhelper.RequestSizerTypeBytes,
pr2,
)
assert.Len(b, res, 2)
merged = append(merged[0:len(merged)-1], res...)
}
assert.Len(b, merged, 11)
}
}
func BenchmarkSplittingBasedOnByteSizeHugeProfiles(b *testing.B) {
// One request splits into many batches.
b.ReportAllocs()
for b.Loop() {
merged := []Request{newProfilesRequest(testdata.GenerateProfiles(0))}
pr2 := newProfilesRequest(testdata.GenerateProfiles(100000))
res, _ := merged[len(merged)-1].MergeSplit(
context.Background(),
profilesMarshaler.ProfilesSize(testdata.GenerateProfiles(10010)),
exporterhelper.RequestSizerTypeBytes,
pr2,
)
merged = append(merged[0:len(merged)-1], res...)
assert.Len(b, merged, 10)
}
}
================================================
FILE: exporter/exporterhelper/xexporterhelper/profiles_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xexporterhelper
import (
"context"
"errors"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/sdk/trace/tracetest"
"go.opentelemetry.io/otel/trace"
nooptrace "go.opentelemetry.io/otel/trace/noop"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/consumer/consumererror/xconsumererror"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exporterhelper"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/hosttest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/oteltest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/queue"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/requesttest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/sendertest"
"go.opentelemetry.io/collector/exporter/exporterhelper/internal/storagetest"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/exporter/xexporter"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/testdata"
)
const (
fakeProfilesParentSpanName = "fake_profiles_parent_span_name"
)
var fakeProfilesExporterConfig = struct{}{}
func TestProfilesRequest(t *testing.T) {
lr := newProfilesRequest(testdata.GenerateProfiles(1))
profileErr := xconsumererror.NewProfiles(errors.New("some error"), pprofile.NewProfiles())
assert.Equal(
t,
newProfilesRequest(pprofile.NewProfiles()),
lr.(RequestErrorHandler).OnError(profileErr),
)
}
func TestProfilesExporter_InvalidName(t *testing.T) {
le, err := NewProfiles(context.Background(), exportertest.NewNopSettings(exportertest.NopType), nil, newPushProfilesData(nil))
require.Nil(t, le)
require.Equal(t, errNilConfig, err)
}
func TestProfilesExporter_NilLogger(t *testing.T) {
le, err := NewProfiles(context.Background(), exporter.Settings{}, &fakeProfilesExporterConfig, newPushProfilesData(nil))
require.Nil(t, le)
require.Equal(t, errNilLogger, err)
}
func TestProfilesRequestExporter_NilLogger(t *testing.T) {
le, err := NewProfilesRequest(context.Background(), exporter.Settings{}, requestFromProfilesFunc(nil), sendertest.NewNopSenderFunc[Request]())
require.Nil(t, le)
require.Equal(t, errNilLogger, err)
}
func TestProfilesExporter_NilPushProfilesData(t *testing.T) {
le, err := NewProfiles(context.Background(), exportertest.NewNopSettings(exportertest.NopType), &fakeProfilesExporterConfig, nil)
require.Nil(t, le)
require.Equal(t, errNilPushProfileData, err)
}
func TestProfilesExporter_NilProfilesConverter(t *testing.T) {
te, err := NewProfilesRequest(context.Background(), exportertest.NewNopSettings(exportertest.NopType), nil, sendertest.NewNopSenderFunc[Request]())
require.Nil(t, te)
require.Equal(t, errNilProfilesConverter, err)
}
func TestProfilesRequestExporter_NilProfilesConverter(t *testing.T) {
le, err := NewProfilesRequest(context.Background(), exportertest.NewNopSettings(exportertest.NopType), requestFromProfilesFunc(nil), nil)
require.Nil(t, le)
require.Equal(t, errNilConsumeRequest, err)
}
func TestProfilesExporter_Default(t *testing.T) {
ld := pprofile.NewProfiles()
le, err := NewProfiles(context.Background(), exportertest.NewNopSettings(exportertest.NopType), &fakeProfilesExporterConfig, newPushProfilesData(nil))
assert.NotNil(t, le)
require.NoError(t, err)
assert.Equal(t, consumer.Capabilities{MutatesData: false}, le.Capabilities())
require.NoError(t, le.Start(context.Background(), componenttest.NewNopHost()))
require.NoError(t, le.ConsumeProfiles(context.Background(), ld))
require.NoError(t, le.Shutdown(context.Background()))
}
func TestProfilesRequestExporter_Default(t *testing.T) {
ld := pprofile.NewProfiles()
le, err := NewProfilesRequest(context.Background(), exportertest.NewNopSettings(exportertest.NopType),
requestFromProfilesFunc(nil), sendertest.NewNopSenderFunc[Request]())
assert.NotNil(t, le)
require.NoError(t, err)
assert.Equal(t, consumer.Capabilities{MutatesData: false}, le.Capabilities())
require.NoError(t, le.Start(context.Background(), componenttest.NewNopHost()))
require.NoError(t, le.ConsumeProfiles(context.Background(), ld))
require.NoError(t, le.Shutdown(context.Background()))
}
func TestProfilesExporter_WithCapabilities(t *testing.T) {
capabilities := consumer.Capabilities{MutatesData: true}
le, err := NewProfiles(context.Background(), exportertest.NewNopSettings(exportertest.NopType), &fakeProfilesExporterConfig, newPushProfilesData(nil), exporterhelper.WithCapabilities(capabilities))
require.NoError(t, err)
require.NotNil(t, le)
assert.Equal(t, capabilities, le.Capabilities())
}
func TestProfilesRequestExporter_WithCapabilities(t *testing.T) {
capabilities := consumer.Capabilities{MutatesData: true}
le, err := NewProfilesRequest(context.Background(), exportertest.NewNopSettings(exportertest.NopType),
requestFromProfilesFunc(nil), sendertest.NewNopSenderFunc[Request](), exporterhelper.WithCapabilities(capabilities))
require.NoError(t, err)
require.NotNil(t, le)
assert.Equal(t, capabilities, le.Capabilities())
}
func TestProfilesExporter_Default_ReturnError(t *testing.T) {
ld := pprofile.NewProfiles()
want := errors.New("my_error")
le, err := NewProfiles(context.Background(), exportertest.NewNopSettings(exportertest.NopType), &fakeProfilesExporterConfig, newPushProfilesData(want))
require.NoError(t, err)
require.NotNil(t, le)
require.Equal(t, want, le.ConsumeProfiles(context.Background(), ld))
}
func TestProfilesRequestExporter_Default_ConvertError(t *testing.T) {
ld := pprofile.NewProfiles()
want := errors.New("convert_error")
le, err := NewProfilesRequest(context.Background(), exportertest.NewNopSettings(exportertest.NopType),
requestFromProfilesFunc(want), sendertest.NewNopSenderFunc[Request]())
require.NoError(t, err)
require.NotNil(t, le)
require.Equal(t, consumererror.NewPermanent(want), le.ConsumeProfiles(context.Background(), ld))
}
func TestProfilesRequestExporter_Default_ExportError(t *testing.T) {
ld := pprofile.NewProfiles()
want := errors.New("export_error")
le, err := NewProfilesRequest(context.Background(), exportertest.NewNopSettings(exportertest.NopType),
requestFromProfilesFunc(nil), sendertest.NewErrSenderFunc[Request](want))
require.NoError(t, err)
require.NotNil(t, le)
require.Equal(t, want, le.ConsumeProfiles(context.Background(), ld))
}
func TestProfiles_WithPersistentQueue(t *testing.T) {
fgOrigReadState := queue.PersistRequestContextOnRead
fgOrigWriteState := queue.PersistRequestContextOnWrite
qCfg := exporterhelper.NewDefaultQueueConfig()
storageID := component.MustNewIDWithName("file_storage", "storage")
qCfg.StorageID = &storageID
set := exportertest.NewNopSettings(exportertest.NopType)
set.ID = component.MustNewIDWithName("test_logs", "with_persistent_queue")
host := hosttest.NewHost(map[component.ID]component.Component{
storageID: storagetest.NewMockStorageExtension(nil),
})
spanCtx := oteltest.FakeSpanContext(t)
tests := []struct {
name string
fgEnabledOnWrite bool
fgEnabledOnRead bool
wantData bool
wantSpanCtx bool
}{
{
name: "feature_gate_disabled_on_write_and_read",
wantData: true,
},
{
name: "feature_gate_enabled_on_write_and_read",
fgEnabledOnWrite: true,
fgEnabledOnRead: true,
wantData: true,
wantSpanCtx: true,
},
{
name: "feature_gate_disabled_on_write_enabled_on_read",
wantData: true,
fgEnabledOnRead: true,
},
{
name: "feature_gate_enabled_on_write_disabled_on_read",
fgEnabledOnWrite: true,
wantData: false, // going back from enabled to disabled feature gate isn't supported
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
queue.PersistRequestContextOnRead = func() bool { return tt.fgEnabledOnRead }
queue.PersistRequestContextOnWrite = func() bool { return tt.fgEnabledOnWrite }
t.Cleanup(func() {
queue.PersistRequestContextOnRead = fgOrigReadState
queue.PersistRequestContextOnWrite = fgOrigWriteState
})
ts := consumertest.ProfilesSink{}
te, err := NewProfiles(context.Background(), set, &fakeProfilesExporterConfig, ts.ConsumeProfiles, exporterhelper.WithQueue(configoptional.Some(qCfg)))
require.NoError(t, err)
require.NoError(t, te.Start(context.Background(), host))
t.Cleanup(func() { require.NoError(t, te.Shutdown(context.Background())) })
profiles := testdata.GenerateProfiles(2)
require.NoError(t, te.ConsumeProfiles(trace.ContextWithSpanContext(context.Background(), spanCtx), profiles))
if tt.wantData {
require.Eventually(t, func() bool {
return len(ts.AllProfiles()) == 1 && ts.SampleCount() == 2
}, 500*time.Millisecond, 10*time.Millisecond)
}
// check that the span context is persisted if the feature gate is enabled
if tt.wantSpanCtx {
assert.Len(t, ts.Contexts(), 1)
assert.Equal(t, spanCtx, trace.SpanContextFromContext(ts.Contexts()[0]))
}
})
}
}
func TestProfilesExporter_WithSpan(t *testing.T) {
set := exportertest.NewNopSettings(exportertest.NopType)
sr := new(tracetest.SpanRecorder)
set.TracerProvider = sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))
otel.SetTracerProvider(set.TracerProvider)
defer otel.SetTracerProvider(nooptrace.NewTracerProvider())
le, err := NewProfiles(context.Background(), set, &fakeProfilesExporterConfig, newPushProfilesData(nil))
require.NoError(t, err)
require.NotNil(t, le)
checkWrapSpanForProfilesExporter(t, sr, set.TracerProvider.Tracer("test"), le, nil)
}
func TestProfilesRequestExporter_WithSpan(t *testing.T) {
set := exportertest.NewNopSettings(exportertest.NopType)
sr := new(tracetest.SpanRecorder)
set.TracerProvider = sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))
otel.SetTracerProvider(set.TracerProvider)
defer otel.SetTracerProvider(nooptrace.NewTracerProvider())
le, err := NewProfilesRequest(context.Background(), set, requestFromProfilesFunc(nil), sendertest.NewNopSenderFunc[Request]())
require.NoError(t, err)
require.NotNil(t, le)
checkWrapSpanForProfilesExporter(t, sr, set.TracerProvider.Tracer("test"), le, nil)
}
func TestProfilesExporter_WithSpan_ReturnError(t *testing.T) {
set := exportertest.NewNopSettings(exportertest.NopType)
sr := new(tracetest.SpanRecorder)
set.TracerProvider = sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))
otel.SetTracerProvider(set.TracerProvider)
defer otel.SetTracerProvider(nooptrace.NewTracerProvider())
want := errors.New("my_error")
le, err := NewProfiles(context.Background(), set, &fakeProfilesExporterConfig, newPushProfilesData(want))
require.NoError(t, err)
require.NotNil(t, le)
checkWrapSpanForProfilesExporter(t, sr, set.TracerProvider.Tracer("test"), le, want)
}
func TestProfilesRequestExporter_WithSpan_ReturnError(t *testing.T) {
set := exportertest.NewNopSettings(exportertest.NopType)
sr := new(tracetest.SpanRecorder)
set.TracerProvider = sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))
otel.SetTracerProvider(set.TracerProvider)
defer otel.SetTracerProvider(nooptrace.NewTracerProvider())
want := errors.New("my_error")
le, err := NewProfilesRequest(context.Background(), set, requestFromProfilesFunc(nil), sendertest.NewErrSenderFunc[Request](want))
require.NoError(t, err)
require.NotNil(t, le)
checkWrapSpanForProfilesExporter(t, sr, set.TracerProvider.Tracer("test"), le, want)
}
func TestProfilesExporter_WithShutdown(t *testing.T) {
shutdownCalled := false
shutdown := func(context.Context) error { shutdownCalled = true; return nil }
le, err := NewProfiles(context.Background(), exportertest.NewNopSettings(exportertest.NopType), &fakeProfilesExporterConfig, newPushProfilesData(nil), exporterhelper.WithShutdown(shutdown))
assert.NotNil(t, le)
require.NoError(t, err)
require.NoError(t, le.Shutdown(context.Background()))
assert.True(t, shutdownCalled)
}
func TestProfilesRequestExporter_WithShutdown(t *testing.T) {
shutdownCalled := false
shutdown := func(context.Context) error { shutdownCalled = true; return nil }
le, err := NewProfilesRequest(context.Background(), exportertest.NewNopSettings(exportertest.NopType),
requestFromProfilesFunc(nil), sendertest.NewNopSenderFunc[Request](), exporterhelper.WithShutdown(shutdown))
assert.NotNil(t, le)
require.NoError(t, err)
require.NoError(t, le.Shutdown(context.Background()))
assert.True(t, shutdownCalled)
}
func TestProfilesExporter_WithShutdown_ReturnError(t *testing.T) {
want := errors.New("my_error")
shutdownErr := func(context.Context) error { return want }
le, err := NewProfiles(context.Background(), exportertest.NewNopSettings(exportertest.NopType), &fakeProfilesExporterConfig, newPushProfilesData(nil), exporterhelper.WithShutdown(shutdownErr))
assert.NotNil(t, le)
require.NoError(t, err)
assert.Equal(t, want, le.Shutdown(context.Background()))
}
func TestProfilesRequestExporter_WithShutdown_ReturnError(t *testing.T) {
want := errors.New("my_error")
shutdownErr := func(context.Context) error { return want }
le, err := NewProfilesRequest(context.Background(), exportertest.NewNopSettings(exportertest.NopType),
requestFromProfilesFunc(nil), sendertest.NewNopSenderFunc[Request](), exporterhelper.WithShutdown(shutdownErr))
assert.NotNil(t, le)
require.NoError(t, err)
assert.Equal(t, want, le.Shutdown(context.Background()))
}
func newPushProfilesData(retError error) xconsumer.ConsumeProfilesFunc {
return func(_ context.Context, _ pprofile.Profiles) error {
return retError
}
}
func generateProfilesTraffic(t *testing.T, tracer trace.Tracer, le xexporter.Profiles, numRequests int, wantError error) {
ld := testdata.GenerateProfiles(1)
ctx, span := tracer.Start(context.Background(), fakeProfilesParentSpanName)
defer span.End()
for range numRequests {
require.Equal(t, wantError, le.ConsumeProfiles(ctx, ld))
}
}
func checkWrapSpanForProfilesExporter(t *testing.T, sr *tracetest.SpanRecorder, tracer trace.Tracer, le xexporter.Profiles, wantError error) {
const numRequests = 5
generateProfilesTraffic(t, tracer, le, numRequests, wantError)
// Inspection time!
gotSpanData := sr.Ended()
require.Len(t, gotSpanData, numRequests+1)
parentSpan := gotSpanData[numRequests]
require.Equalf(t, fakeProfilesParentSpanName, parentSpan.Name(), "SpanData %v", parentSpan)
for _, sd := range gotSpanData[:numRequests] {
require.Equalf(t, parentSpan.SpanContext(), sd.Parent(), "Exporter span not a child\nSpanData %v", sd)
oteltest.CheckStatus(t, sd, wantError)
sentSampleRecords := int64(1)
failedToSendSampleRecords := int64(0)
if wantError != nil {
sentSampleRecords = 0
failedToSendSampleRecords = 1
}
require.Containsf(t, sd.Attributes(), attribute.KeyValue{Key: internal.ItemsSent, Value: attribute.Int64Value(sentSampleRecords)}, "SpanData %v", sd)
require.Containsf(t, sd.Attributes(), attribute.KeyValue{Key: internal.ItemsFailed, Value: attribute.Int64Value(failedToSendSampleRecords)}, "SpanData %v", sd)
}
}
func requestFromProfilesFunc(err error) func(context.Context, pprofile.Profiles) (Request, error) {
return func(_ context.Context, pd pprofile.Profiles) (Request, error) {
return &requesttest.FakeRequest{Items: pd.SampleCount()}, err
}
}
================================================
FILE: exporter/exporterhelper/xexporterhelper/request.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xexporterhelper // import "go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper"
import "go.opentelemetry.io/collector/exporter/exporterhelper/internal/request" // Request represents a single request that can be sent to an external endpoint.
// Request represents a single request that can be sent to an external endpoint.
// Experimental: This API is at the early stage of development and may change without backward compatibility
// until https://github.com/open-telemetry/opentelemetry-collector/issues/8122 is resolved.
type Request = request.Request
// RequestErrorHandler is an optional interface that can be implemented by Request to provide a way handle partial
// temporary failures. For example, if some items failed to process and can be retried, this interface allows to
// return a new Request that contains the items left to be sent. Otherwise, the original Request should be returned.
// If not implemented, the original Request will be returned assuming the error is applied to the whole Request.
// Experimental: This API is at the early stage of development and may change without backward compatibility
// until https://github.com/open-telemetry/opentelemetry-collector/issues/8122 is resolved.
type RequestErrorHandler = request.ErrorHandler
// RequestConverterFunc converts pdata telemetry into a user-defined Request.
// Experimental: This API is at the early stage of development and may change without backward compatibility
// until https://github.com/open-telemetry/opentelemetry-collector/issues/8122 is resolved.
type RequestConverterFunc[T any] = request.RequestConverterFunc[T]
// RequestConsumeFunc processes the request. After the function returns, the request is no longer accessible,
// and accessing it is considered undefined behavior.
type RequestConsumeFunc = request.RequestConsumeFunc
================================================
FILE: exporter/exportertest/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: exporter/exportertest/contract_checker.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package exportertest // import "go.opentelemetry.io/collector/exporter/exportertest"
import (
"context"
"fmt"
"strconv"
"testing"
"time"
"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/exporter"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/receivertest"
)
// uniqueIDAttrName is the attribute name that is used in log records/spans/datapoints as the unique identifier.
const uniqueIDAttrName = "test_id"
// uniqueIDAttrVal is the value type of the uniqueIDAttrName.
type uniqueIDAttrVal string
type CheckConsumeContractParams struct {
T *testing.T
NumberOfTestElements int
Signal pipeline.Signal
// ExporterFactory to create an exporter to be tested.
ExporterFactory exporter.Factory
ExporterConfig component.Config
// ReceiverFactory to create a mock receiver.
ReceiverFactory receiver.Factory
ReceiverConfig component.Config
}
func CheckConsumeContract(params CheckConsumeContractParams) {
// Different scenarios to test for.
// The decision function defines the testing scenario (i.e. to test for
// success case or for error case or a mix of both). See for example randomErrorsConsumeDecision.
scenarios := []struct {
name string
decisionFunc func() error
checkIfTestPassed func(*testing.T, int, requestCounter)
}{
{
name: "always_succeed",
// Always succeed. We expect all data to be delivered as is.
decisionFunc: func() error { return nil },
checkIfTestPassed: alwaysSucceedsPassed,
},
{
name: "random_non_permanent_error",
decisionFunc: randomNonPermanentErrorConsumeDecision,
checkIfTestPassed: randomNonPermanentErrorConsumeDecisionPassed,
},
{
name: "random_permanent_error",
decisionFunc: randomPermanentErrorConsumeDecision,
checkIfTestPassed: randomPermanentErrorConsumeDecisionPassed,
},
{
name: "random_error",
decisionFunc: randomErrorsConsumeDecision,
checkIfTestPassed: randomErrorConsumeDecisionPassed,
},
}
for _, scenario := range scenarios {
params.T.Run(
scenario.name, func(t *testing.T) {
checkConsumeContractScenario(t, params, scenario.decisionFunc, scenario.checkIfTestPassed)
},
)
}
}
func checkConsumeContractScenario(t *testing.T, params CheckConsumeContractParams, decisionFunc func() error, checkIfTestPassed func(*testing.T, int, requestCounter)) {
mockConsumerInstance := newMockConsumer(decisionFunc)
switch params.Signal {
case pipeline.SignalLogs:
r, err := params.ReceiverFactory.CreateLogs(context.Background(), receivertest.NewNopSettings(params.ReceiverFactory.Type()), params.ReceiverConfig, &mockConsumerInstance)
require.NoError(t, err)
require.NoError(t, r.Start(context.Background(), componenttest.NewNopHost()))
checkLogs(t, params, r, &mockConsumerInstance, checkIfTestPassed)
case pipeline.SignalTraces:
r, err := params.ReceiverFactory.CreateTraces(context.Background(), receivertest.NewNopSettings(params.ReceiverFactory.Type()), params.ReceiverConfig, &mockConsumerInstance)
require.NoError(t, err)
require.NoError(t, r.Start(context.Background(), componenttest.NewNopHost()))
checkTraces(t, params, r, &mockConsumerInstance, checkIfTestPassed)
case pipeline.SignalMetrics:
r, err := params.ReceiverFactory.CreateMetrics(context.Background(), receivertest.NewNopSettings(params.ReceiverFactory.Type()), params.ReceiverConfig, &mockConsumerInstance)
require.NoError(t, err)
require.NoError(t, r.Start(context.Background(), componenttest.NewNopHost()))
checkMetrics(t, params, r, &mockConsumerInstance, checkIfTestPassed)
default:
require.FailNow(t, "must specify a valid DataType to test for")
}
}
func checkMetrics(t *testing.T, params CheckConsumeContractParams, mockReceiver component.Component,
mockConsumer *mockConsumer, checkIfTestPassed func(*testing.T, int, requestCounter),
) {
ctx := context.Background()
var exp exporter.Metrics
var err error
exp, err = params.ExporterFactory.CreateMetrics(ctx, NewNopSettings(params.ExporterFactory.Type()), params.ExporterConfig)
require.NoError(t, err)
require.NotNil(t, exp)
err = exp.Start(ctx, componenttest.NewNopHost())
require.NoError(t, err)
defer func(exp exporter.Metrics, ctx context.Context) {
err = exp.Shutdown(ctx)
require.NoError(t, err)
err = mockReceiver.Shutdown(ctx)
require.NoError(t, err)
mockConsumer.clear()
}(exp, ctx)
for i := 0; i < params.NumberOfTestElements; i++ {
id := uniqueIDAttrVal(strconv.Itoa(i))
data := createOneMetricWithID(id)
err = exp.ConsumeMetrics(ctx, data)
}
reqCounter := mockConsumer.getRequestCounter()
// The overall number of requests sent by exporter
fmt.Printf("Number of export tries: %d\n", reqCounter.total)
// Successfully delivered items
fmt.Printf("Total items received successfully: %d\n", reqCounter.success)
// Number of errors that happened
fmt.Printf("Number of permanent errors: %d\n", reqCounter.error.permanent)
fmt.Printf("Number of non-permanent errors: %d\n", reqCounter.error.nonpermanent)
assert.EventuallyWithT(t, func(*assert.CollectT) {
checkIfTestPassed(t, params.NumberOfTestElements, *reqCounter)
}, 2*time.Second, 100*time.Millisecond)
}
func checkTraces(t *testing.T, params CheckConsumeContractParams, mockReceiver component.Component, mockConsumer *mockConsumer, checkIfTestPassed func(*testing.T, int, requestCounter)) {
ctx := context.Background()
var exp exporter.Traces
var err error
exp, err = params.ExporterFactory.CreateTraces(ctx, NewNopSettings(params.ExporterFactory.Type()), params.ExporterConfig)
require.NoError(t, err)
require.NotNil(t, exp)
err = exp.Start(ctx, componenttest.NewNopHost())
require.NoError(t, err)
defer func(exp exporter.Traces, ctx context.Context) {
err = exp.Shutdown(ctx)
require.NoError(t, err)
err = mockReceiver.Shutdown(ctx)
require.NoError(t, err)
mockConsumer.clear()
}(exp, ctx)
for i := 0; i < params.NumberOfTestElements; i++ {
id := uniqueIDAttrVal(strconv.Itoa(i))
data := createOneTraceWithID(id)
err = exp.ConsumeTraces(ctx, data)
}
reqCounter := mockConsumer.getRequestCounter()
// The overall number of requests sent by exporter
fmt.Printf("Number of export tries: %d\n", reqCounter.total)
// Successfully delivered items
fmt.Printf("Total items received successfully: %d\n", reqCounter.success)
// Number of errors that happened
fmt.Printf("Number of permanent errors: %d\n", reqCounter.error.permanent)
fmt.Printf("Number of non-permanent errors: %d\n", reqCounter.error.nonpermanent)
assert.EventuallyWithT(t, func(*assert.CollectT) {
checkIfTestPassed(t, params.NumberOfTestElements, *reqCounter)
}, 2*time.Second, 100*time.Millisecond)
}
func checkLogs(t *testing.T, params CheckConsumeContractParams, mockReceiver component.Component, mockConsumer *mockConsumer, checkIfTestPassed func(*testing.T, int, requestCounter)) {
ctx := context.Background()
var exp exporter.Logs
var err error
exp, err = params.ExporterFactory.CreateLogs(ctx, NewNopSettings(params.ExporterFactory.Type()), params.ExporterConfig)
require.NoError(t, err)
require.NotNil(t, exp)
err = exp.Start(ctx, componenttest.NewNopHost())
require.NoError(t, err)
defer func(exp exporter.Logs, ctx context.Context) {
err = exp.Shutdown(ctx)
require.NoError(t, err)
err = mockReceiver.Shutdown(ctx)
require.NoError(t, err)
mockConsumer.clear()
}(exp, ctx)
for i := 0; i < params.NumberOfTestElements; i++ {
id := uniqueIDAttrVal(strconv.Itoa(i))
data := createOneLogWithID(id)
err = exp.ConsumeLogs(ctx, data)
}
reqCounter := mockConsumer.getRequestCounter()
// The overall number of requests sent by exporter
fmt.Printf("Number of export tries: %d\n", reqCounter.total)
// Successfully delivered items
fmt.Printf("Total items received successfully: %d\n", reqCounter.success)
// Number of errors that happened
fmt.Printf("Number of permanent errors: %d\n", reqCounter.error.permanent)
fmt.Printf("Number of non-permanent errors: %d\n", reqCounter.error.nonpermanent)
assert.EventuallyWithT(t, func(*assert.CollectT) {
checkIfTestPassed(t, params.NumberOfTestElements, *reqCounter)
}, 2*time.Second, 100*time.Millisecond)
}
// Test is successful if all the elements were received successfully and no error was returned
func alwaysSucceedsPassed(t *testing.T, allRecordsNumber int, reqCounter requestCounter) {
require.Equal(t, allRecordsNumber, reqCounter.success)
require.Equal(t, allRecordsNumber, reqCounter.total)
require.Equal(t, 0, reqCounter.error.nonpermanent)
require.Equal(t, 0, reqCounter.error.permanent)
}
// Test is successful if all the elements were retried on non-permanent errors
func randomNonPermanentErrorConsumeDecisionPassed(t *testing.T, allRecordsNumber int, reqCounter requestCounter) {
// more or equal tries than successes
require.GreaterOrEqual(t, reqCounter.total, reqCounter.success)
// it is retried on every error
require.Equal(t, reqCounter.total-reqCounter.error.nonpermanent, reqCounter.success)
require.Equal(t, allRecordsNumber+reqCounter.error.nonpermanent, reqCounter.total)
}
// Test is successful if the calls are not retried on permanent errors
func randomPermanentErrorConsumeDecisionPassed(t *testing.T, allRecordsNumber int, reqCounter requestCounter) {
require.Equal(t, allRecordsNumber-reqCounter.error.permanent, reqCounter.success)
require.Equal(t, reqCounter.total, allRecordsNumber)
}
// Test is successful if the calls are not retried on permanent errors
func randomErrorConsumeDecisionPassed(t *testing.T, allRecordsNumber int, reqCounter requestCounter) {
require.Equal(t, allRecordsNumber-reqCounter.error.permanent, reqCounter.success)
require.Equal(t, reqCounter.total, allRecordsNumber+reqCounter.error.nonpermanent)
}
func createOneLogWithID(id uniqueIDAttrVal) plog.Logs {
data := plog.NewLogs()
data.ResourceLogs().AppendEmpty().ScopeLogs().AppendEmpty().LogRecords().AppendEmpty().Attributes().PutStr(
uniqueIDAttrName,
string(id),
)
return data
}
func createOneTraceWithID(id uniqueIDAttrVal) ptrace.Traces {
data := ptrace.NewTraces()
data.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans().AppendEmpty().Attributes().PutStr(
uniqueIDAttrName,
string(id),
)
return data
}
func createOneMetricWithID(id uniqueIDAttrVal) pmetric.Metrics {
data := pmetric.NewMetrics()
data.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty().SetEmptyHistogram().
DataPoints().AppendEmpty().Attributes().PutStr(uniqueIDAttrName, string(id))
return data
}
================================================
FILE: exporter/exportertest/contract_checker_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package exportertest
import (
"context"
"testing"
"time"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configretry"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exporterhelper"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/receiver"
)
// retryConfig is a configuration to quickly retry failed exports.
var retryConfig = func() configretry.BackOffConfig {
c := configretry.NewDefaultBackOffConfig()
c.InitialInterval = time.Millisecond
return c
}()
// mockReceiver is a receiver with pass-through consumers.
type mockReceiver struct {
component.StartFunc
component.ShutdownFunc
consumer.Traces
consumer.Metrics
consumer.Logs
}
// mockFactory is a factory to create exporters sending data to the mockReceiver.
type mockFactory struct {
mr *mockReceiver
component.StartFunc
component.ShutdownFunc
}
func (mef *mockFactory) createMockTraces(
ctx context.Context,
set exporter.Settings,
cfg component.Config,
) (exporter.Traces, error) {
return exporterhelper.NewTraces(ctx, set, cfg,
mef.mr.ConsumeTraces,
exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}),
exporterhelper.WithRetry(retryConfig),
)
}
func (mef *mockFactory) createMockMetrics(
ctx context.Context,
set exporter.Settings,
cfg component.Config,
) (exporter.Metrics, error) {
return exporterhelper.NewMetrics(ctx, set, cfg,
mef.mr.ConsumeMetrics,
exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}),
exporterhelper.WithRetry(retryConfig),
)
}
func (mef *mockFactory) createMockLogs(
ctx context.Context,
set exporter.Settings,
cfg component.Config,
) (exporter.Logs, error) {
return exporterhelper.NewLogs(ctx, set, cfg,
mef.mr.ConsumeLogs,
exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}),
exporterhelper.WithRetry(retryConfig),
)
}
func newMockFactory(mr *mockReceiver) exporter.Factory {
mef := &mockFactory{mr: mr}
return exporter.NewFactory(
component.MustNewType("pass_through_exporter"),
func() component.Config { return &nopConfig{} },
exporter.WithTraces(mef.createMockTraces, component.StabilityLevelBeta),
exporter.WithMetrics(mef.createMockMetrics, component.StabilityLevelBeta),
exporter.WithLogs(mef.createMockLogs, component.StabilityLevelBeta),
)
}
func newMockReceiverFactory(mr *mockReceiver) receiver.Factory {
return receiver.NewFactory(component.MustNewType("pass_through_receiver"),
func() component.Config { return &nopConfig{} },
receiver.WithTraces(func(_ context.Context, _ receiver.Settings, _ component.Config, c consumer.Traces) (receiver.Traces, error) {
mr.Traces = c
return mr, nil
}, component.StabilityLevelStable),
receiver.WithMetrics(func(_ context.Context, _ receiver.Settings, _ component.Config, c consumer.Metrics) (receiver.Metrics, error) {
mr.Metrics = c
return mr, nil
}, component.StabilityLevelStable),
receiver.WithLogs(func(_ context.Context, _ receiver.Settings, _ component.Config, c consumer.Logs) (receiver.Logs, error) {
mr.Logs = c
return mr, nil
}, component.StabilityLevelStable),
)
}
func TestCheckConsumeContractLogs(t *testing.T) {
mr := &mockReceiver{}
params := CheckConsumeContractParams{
T: t,
ExporterFactory: newMockFactory(mr),
Signal: pipeline.SignalLogs,
ExporterConfig: nopConfig{},
NumberOfTestElements: 10,
ReceiverFactory: newMockReceiverFactory(mr),
}
CheckConsumeContract(params)
}
func TestCheckConsumeContractMetrics(t *testing.T) {
mr := &mockReceiver{}
CheckConsumeContract(CheckConsumeContractParams{
T: t,
ExporterFactory: newMockFactory(mr),
Signal: pipeline.SignalMetrics, // Change to the appropriate data type
ExporterConfig: nopConfig{},
NumberOfTestElements: 10,
ReceiverFactory: newMockReceiverFactory(mr),
})
}
func TestCheckConsumeContractTraces(t *testing.T) {
mr := &mockReceiver{}
CheckConsumeContract(CheckConsumeContractParams{
T: t,
ExporterFactory: newMockFactory(mr),
Signal: pipeline.SignalTraces,
ExporterConfig: nopConfig{},
NumberOfTestElements: 10,
ReceiverFactory: newMockReceiverFactory(mr),
})
}
================================================
FILE: exporter/exportertest/go.mod
================================================
module go.opentelemetry.io/collector/exporter/exportertest
go 1.25.0
require (
github.com/google/uuid v1.6.0
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.opentelemetry.io/collector/config/configretry v1.54.0
go.opentelemetry.io/collector/consumer v1.54.0
go.opentelemetry.io/collector/consumer/consumererror v0.148.0
go.opentelemetry.io/collector/consumer/consumertest v0.148.0
go.opentelemetry.io/collector/exporter v1.54.0
go.opentelemetry.io/collector/exporter/exporterhelper v0.148.0
go.opentelemetry.io/collector/exporter/xexporter v0.148.0
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/collector/pdata/pprofile v0.148.0
go.opentelemetry.io/collector/pipeline v1.54.0
go.opentelemetry.io/collector/receiver v1.54.0
go.opentelemetry.io/collector/receiver/receivertest v0.148.0
google.golang.org/grpc v1.79.3
)
require (
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/client v1.54.0 // indirect
go.opentelemetry.io/collector/config/configoptional v1.54.0 // indirect
go.opentelemetry.io/collector/confmap v1.54.0 // indirect
go.opentelemetry.io/collector/confmap/xconfmap v0.148.0 // indirect
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0 // indirect
go.opentelemetry.io/collector/extension v1.54.0 // indirect
go.opentelemetry.io/collector/extension/xextension v0.148.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
go.opentelemetry.io/collector/pdata/xpdata v0.148.0 // indirect
go.opentelemetry.io/collector/pipeline/xpipeline v0.148.0 // indirect
go.opentelemetry.io/collector/receiver/xreceiver v0.148.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/sys v0.41.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/exporter => ../../exporter
replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile
replace go.opentelemetry.io/collector/receiver => ../../receiver
replace go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest
replace go.opentelemetry.io/collector/receiver/xreceiver => ../../receiver/xreceiver
replace go.opentelemetry.io/collector/receiver/receivertest => ../../receiver/receivertest
replace go.opentelemetry.io/collector/extension => ../../extension
replace go.opentelemetry.io/collector/config/configretry => ../../config/configretry
replace go.opentelemetry.io/collector/pipeline => ../../pipeline
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/exporter/xexporter => ../xexporter
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest
replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer
replace go.opentelemetry.io/collector/pdata/testdata => ../../pdata/testdata
replace go.opentelemetry.io/collector/consumer => ../../consumer
replace go.opentelemetry.io/collector/consumer/consumererror => ../../consumer/consumererror
replace go.opentelemetry.io/collector/extension/extensiontest => ../../extension/extensiontest
replace go.opentelemetry.io/collector/extension/xextension => ../../extension/xextension
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/client => ../../client
replace go.opentelemetry.io/collector/pdata/xpdata => ../../pdata/xpdata
replace go.opentelemetry.io/collector/config/configoptional => ../../config/configoptional
replace go.opentelemetry.io/collector/confmap => ../../confmap
replace go.opentelemetry.io/collector/confmap/xconfmap => ../../confmap/xconfmap
replace go.opentelemetry.io/collector/exporter/exporterhelper => ../exporterhelper
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
replace go.opentelemetry.io/collector/pipeline/xpipeline => ../../pipeline/xpipeline
================================================
FILE: exporter/exportertest/go.sum
================================================
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: exporter/exportertest/metadata.yaml
================================================
type: exporter/exportertest
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: exporter/exportertest/mock_consumer.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package exportertest // import "go.opentelemetry.io/collector/exporter/exportertest"
import (
"context"
"fmt"
"math/rand/v2"
"sync"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
)
var (
errNonPermanent = status.Error(codes.DeadlineExceeded, "non Permanent error")
errPermanent = status.Error(codes.Internal, "Permanent error")
)
// // randomNonPermanentErrorConsumeDecision is a decision function that succeeds approximately
// // half of the time and fails with a non-permanent error the rest of the time.
func randomNonPermanentErrorConsumeDecision() error {
if rand.Float32() < 0.5 {
return errNonPermanent
}
return nil
}
// randomPermanentErrorConsumeDecision is a decision function that succeeds approximately
// half of the time and fails with a permanent error the rest of the time.
func randomPermanentErrorConsumeDecision() error {
if rand.Float32() < 0.5 {
return consumererror.NewPermanent(errPermanent)
}
return nil
}
// randomErrorsConsumeDecision is a decision function that succeeds approximately
// a third of the time, fails with a permanent error the third of the time and fails with
// a non-permanent error the rest of the time.
func randomErrorsConsumeDecision() error {
r := rand.Float64()
third := 1.0 / 3.0
if r < third {
return consumererror.NewPermanent(errPermanent)
}
if r < 2*third {
return errNonPermanent
}
return nil
}
type mockConsumer struct {
consumer.Traces
consumer.Logs
consumer.Metrics
reqCounter *requestCounter
mux sync.Mutex
exportErrorFunction func() error
receivedTraces []ptrace.Traces
receivedMetrics []pmetric.Metrics
receivedLogs []plog.Logs
}
func newMockConsumer(decisionFunc func() error) mockConsumer {
return mockConsumer{
reqCounter: newRequestCounter(),
mux: sync.Mutex{},
exportErrorFunction: decisionFunc,
receivedTraces: nil,
receivedMetrics: nil,
receivedLogs: nil,
}
}
func (r *mockConsumer) ConsumeLogs(_ context.Context, ld plog.Logs) error {
r.mux.Lock()
defer r.mux.Unlock()
r.reqCounter.total++
generatedError := r.exportErrorFunction()
if generatedError != nil {
r.processError(generatedError)
return generatedError
}
r.reqCounter.success++
r.receivedLogs = append(r.receivedLogs, ld)
return nil
}
func (r *mockConsumer) ConsumeTraces(_ context.Context, td ptrace.Traces) error {
r.mux.Lock()
defer r.mux.Unlock()
r.reqCounter.total++
generatedError := r.exportErrorFunction()
if generatedError != nil {
r.processError(generatedError)
return generatedError
}
r.reqCounter.success++
r.receivedTraces = append(r.receivedTraces, td)
return nil
}
func (r *mockConsumer) ConsumeMetrics(_ context.Context, md pmetric.Metrics) error {
r.mux.Lock()
defer r.mux.Unlock()
r.reqCounter.total++
generatedError := r.exportErrorFunction()
if generatedError != nil {
r.processError(generatedError)
return generatedError
}
r.reqCounter.success++
r.receivedMetrics = append(r.receivedMetrics, md)
return nil
}
func (r *mockConsumer) Capabilities() consumer.Capabilities {
return consumer.Capabilities{}
}
func (r *mockConsumer) processError(err error) {
if consumererror.IsPermanent(err) {
r.reqCounter.error.permanent++
} else {
r.reqCounter.error.nonpermanent++
}
}
func (r *mockConsumer) clear() {
r.mux.Lock()
defer r.mux.Unlock()
r.reqCounter = newRequestCounter()
}
func (r *mockConsumer) getRequestCounter() *requestCounter {
return r.reqCounter
}
type requestCounter struct {
success int
error errorCounter
total int
}
type errorCounter struct {
permanent int
nonpermanent int
}
func newErrorCounter() errorCounter {
return errorCounter{
permanent: 0,
nonpermanent: 0,
}
}
func newRequestCounter() *requestCounter {
return &requestCounter{
success: 0,
error: newErrorCounter(),
total: 0,
}
}
func idFromLogs(data plog.Logs) (string, error) {
var logID string
rss := data.ResourceLogs()
key, exists := rss.At(0).ScopeLogs().At(0).LogRecords().At(0).Attributes().Get(uniqueIDAttrName)
if !exists {
return "", fmt.Errorf("invalid data element, attribute %q is missing", uniqueIDAttrName)
}
if key.Type() != pcommon.ValueTypeStr {
return "", fmt.Errorf("invalid data element, attribute %q is wrong type %v", uniqueIDAttrName, key.Type())
}
logID = key.Str()
return logID, nil
}
func idFromTraces(data ptrace.Traces) (string, error) {
var traceID string
rss := data.ResourceSpans()
key, exists := rss.At(0).ScopeSpans().At(0).Spans().At(0).Attributes().Get(uniqueIDAttrName)
if !exists {
return "", fmt.Errorf("invalid data element, attribute %q is missing", uniqueIDAttrName)
}
if key.Type() != pcommon.ValueTypeStr {
return "", fmt.Errorf("invalid data element, attribute %q is wrong type %v", uniqueIDAttrName, key.Type())
}
traceID = key.Str()
return traceID, nil
}
func idFromMetrics(data pmetric.Metrics) (string, error) {
var metricID string
rss := data.ResourceMetrics()
key, exists := rss.At(0).ScopeMetrics().At(0).Metrics().At(0).Histogram().DataPoints().At(0).Attributes().Get(
uniqueIDAttrName)
if !exists {
return "", fmt.Errorf("invalid data element, attribute %q is missing", uniqueIDAttrName)
}
if key.Type() != pcommon.ValueTypeStr {
return "", fmt.Errorf("invalid data element, attribute %q is wrong type %v", uniqueIDAttrName, key.Type())
}
metricID = key.Str()
return metricID, nil
}
================================================
FILE: exporter/exportertest/mock_consumer_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package exportertest
import (
"context"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
)
func createLog(id string) plog.Logs {
validData := plog.NewLogs()
validData.ResourceLogs().AppendEmpty().ScopeLogs().AppendEmpty().LogRecords().AppendEmpty().Attributes().PutStr(
uniqueIDAttrName,
id,
)
return validData
}
func createTrace(id string) ptrace.Traces {
validData := ptrace.NewTraces()
validData.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans().AppendEmpty().Attributes().PutStr(
uniqueIDAttrName,
id,
)
return validData
}
func createMetric(id string) pmetric.Metrics {
validData := pmetric.NewMetrics()
validData.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty().SetEmptyHistogram().DataPoints().AppendEmpty().Attributes().PutStr(uniqueIDAttrName, id)
return validData
}
func TestIDFromMetrics(t *testing.T) {
// Test case 1: Valid data
id := "metric_id"
validData := createMetric(id)
metricID, err := idFromMetrics(validData)
assert.Equal(t, metricID, id)
require.NoError(t, err)
// Test case 2: Missing uniqueIDAttrName attribute
invalidData := pmetric.NewMetrics() // Create an invalid pmetric.Metrics object with missing attribute
invalidData.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty().SetEmptyHistogram().DataPoints().AppendEmpty().Attributes()
_, err = idFromMetrics(invalidData)
require.EqualError(t, err, fmt.Sprintf("invalid data element, attribute %q is missing", uniqueIDAttrName))
// Test case 3: Wrong attribute type
var intID int64 = 12
wrongAttribute := pmetric.NewMetrics() // Create a valid pmetric.Metrics object
wrongAttribute.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty().
SetEmptyHistogram().DataPoints().AppendEmpty().Attributes().PutInt(uniqueIDAttrName, intID)
_, err = idFromMetrics(wrongAttribute)
assert.EqualError(t, err, fmt.Sprintf("invalid data element, attribute %q is wrong type Int", uniqueIDAttrName))
}
func TestIDFromTraces(t *testing.T) {
// Test case 1: Valid data
id := "trace_id"
validData := createTrace(id)
traceID, err := idFromTraces(validData)
assert.Equal(t, traceID, id)
require.NoError(t, err)
// Test case 2: Missing uniqueIDAttrName attribute
invalidData := ptrace.NewTraces()
invalidData.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans().AppendEmpty().Attributes()
_, err = idFromTraces(invalidData)
require.EqualError(t, err, fmt.Sprintf("invalid data element, attribute %q is missing", uniqueIDAttrName))
// Test case 3: Wrong attribute type
var intID int64 = 12
wrongAttribute := ptrace.NewTraces()
wrongAttribute.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans().AppendEmpty().Attributes().
PutInt(uniqueIDAttrName, intID)
_, err = idFromTraces(wrongAttribute)
assert.EqualError(t, err, fmt.Sprintf("invalid data element, attribute %q is wrong type Int", uniqueIDAttrName))
}
func TestIDFromLogs(t *testing.T) {
// Test case 1: Valid data
id := "log_id"
validData := createLog(id)
logID, err := idFromLogs(validData)
assert.Equal(t, logID, id)
require.NoError(t, err)
// Test case 2: Missing uniqueIDAttrName attribute
invalidData := plog.NewLogs()
invalidData.ResourceLogs().AppendEmpty().ScopeLogs().AppendEmpty().LogRecords().AppendEmpty().Attributes()
_, err = idFromLogs(invalidData)
require.EqualError(t, err, fmt.Sprintf("invalid data element, attribute %q is missing", uniqueIDAttrName))
// Test case 3: Wrong attribute type
var intID int64 = 12
wrongAttribute := plog.NewLogs() // Create a valid plog.Metrics object
wrongAttribute.ResourceLogs().AppendEmpty().ScopeLogs().AppendEmpty().LogRecords().AppendEmpty().Attributes().
PutInt(uniqueIDAttrName, intID)
_, err = idFromLogs(wrongAttribute)
assert.EqualError(t, err, fmt.Sprintf("invalid data element, attribute %q is wrong type Int", uniqueIDAttrName))
}
func returnNonPermanentError() error {
return errNonPermanent
}
func returnPermanentError() error {
return errPermanent
}
func TestConsumeLogsNonPermanent(t *testing.T) {
mc := newMockConsumer(returnNonPermanentError)
validData := createLog("logId")
err := mc.ConsumeLogs(context.Background(), validData)
if err != nil {
return
}
assert.Equal(t, 1, mc.reqCounter.error.nonpermanent)
assert.Equal(t, 0, mc.reqCounter.error.permanent)
assert.Equal(t, 0, mc.reqCounter.success)
assert.Equal(t, 1, mc.reqCounter.total)
}
func TestConsumeLogsPermanent(t *testing.T) {
mc := newMockConsumer(returnPermanentError)
validData := createLog("logId")
err := mc.ConsumeLogs(context.Background(), validData)
if err != nil {
return
}
assert.Equal(t, 0, mc.reqCounter.error.nonpermanent)
assert.Equal(t, 1, mc.reqCounter.error.permanent)
assert.Equal(t, 0, mc.reqCounter.success)
assert.Equal(t, 1, mc.reqCounter.total)
}
func TestConsumeLogsSuccess(t *testing.T) {
mc := newMockConsumer(func() error { return nil })
validData := createLog("logId")
err := mc.ConsumeLogs(context.Background(), validData)
if err != nil {
return
}
assert.Equal(t, 0, mc.reqCounter.error.nonpermanent)
assert.Equal(t, 0, mc.reqCounter.error.permanent)
assert.Equal(t, 1, mc.reqCounter.success)
assert.Equal(t, 1, mc.reqCounter.total)
}
func TestConsumeTracesNonPermanent(t *testing.T) {
mc := newMockConsumer(returnNonPermanentError)
validData := createTrace("traceId")
err := mc.ConsumeTraces(context.Background(), validData)
if err != nil {
return
}
assert.Equal(t, 1, mc.reqCounter.error.nonpermanent)
assert.Equal(t, 0, mc.reqCounter.error.permanent)
assert.Equal(t, 0, mc.reqCounter.success)
assert.Equal(t, 1, mc.reqCounter.total)
}
func TestConsumeTracesPermanent(t *testing.T) {
mc := newMockConsumer(returnPermanentError)
validData := createTrace("traceId")
err := mc.ConsumeTraces(context.Background(), validData)
if err != nil {
return
}
assert.Equal(t, 0, mc.reqCounter.error.nonpermanent)
assert.Equal(t, 1, mc.reqCounter.error.permanent)
assert.Equal(t, 0, mc.reqCounter.success)
assert.Equal(t, 1, mc.reqCounter.total)
}
func TestConsumeTracesSuccess(t *testing.T) {
mc := newMockConsumer(func() error { return nil })
validData := createTrace("traceId")
err := mc.ConsumeTraces(context.Background(), validData)
if err != nil {
return
}
assert.Equal(t, 0, mc.reqCounter.error.nonpermanent)
assert.Equal(t, 0, mc.reqCounter.error.permanent)
assert.Equal(t, 1, mc.reqCounter.success)
assert.Equal(t, 1, mc.reqCounter.total)
}
func TestConsumeMetricsNonPermanent(t *testing.T) {
mc := newMockConsumer(returnNonPermanentError)
validData := createMetric("metricId")
err := mc.ConsumeMetrics(context.Background(), validData)
if err != nil {
return
}
assert.Equal(t, 1, mc.reqCounter.error.nonpermanent)
assert.Equal(t, 0, mc.reqCounter.error.permanent)
assert.Equal(t, 0, mc.reqCounter.success)
assert.Equal(t, 1, mc.reqCounter.total)
}
func TestConsumeMetricsPermanent(t *testing.T) {
mc := newMockConsumer(returnPermanentError)
validData := createMetric("metricId")
err := mc.ConsumeMetrics(context.Background(), validData)
if err != nil {
return
}
assert.Equal(t, 0, mc.reqCounter.error.nonpermanent)
assert.Equal(t, 1, mc.reqCounter.error.permanent)
assert.Equal(t, 0, mc.reqCounter.success)
assert.Equal(t, 1, mc.reqCounter.total)
}
func TestConsumeMetricsSuccess(t *testing.T) {
mc := newMockConsumer(func() error { return nil })
validData := createMetric("metricId")
err := mc.ConsumeMetrics(context.Background(), validData)
if err != nil {
return
}
assert.Equal(t, 0, mc.reqCounter.error.nonpermanent)
assert.Equal(t, 0, mc.reqCounter.error.permanent)
assert.Equal(t, 1, mc.reqCounter.success)
assert.Equal(t, 1, mc.reqCounter.total)
}
func TestCapabilities(t *testing.T) {
mc := newMockConsumer(func() error { return nil })
assert.Equal(t, consumer.Capabilities{}, mc.Capabilities())
}
================================================
FILE: exporter/exportertest/nop_exporter.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package exportertest // import "go.opentelemetry.io/collector/exporter/exportertest"
import (
"context"
"github.com/google/uuid"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/xexporter"
)
var NopType = component.MustNewType("nop")
// NewNopSettings returns a new nop settings for Create* functions with the given type.
func NewNopSettings(typ component.Type) exporter.Settings {
return exporter.Settings{
ID: component.NewIDWithName(typ, uuid.NewString()),
TelemetrySettings: componenttest.NewNopTelemetrySettings(),
BuildInfo: component.NewDefaultBuildInfo(),
}
}
// NewNopFactory returns an exporter.Factory that constructs nop exporters.
func NewNopFactory() exporter.Factory {
return xexporter.NewFactory(
NopType,
func() component.Config { return &nopConfig{} },
xexporter.WithTraces(createTraces, component.StabilityLevelStable),
xexporter.WithMetrics(createMetrics, component.StabilityLevelStable),
xexporter.WithLogs(createLogs, component.StabilityLevelStable),
xexporter.WithProfiles(createProfiles, component.StabilityLevelAlpha),
)
}
func createTraces(context.Context, exporter.Settings, component.Config) (exporter.Traces, error) {
return nopInstance, nil
}
func createMetrics(context.Context, exporter.Settings, component.Config) (exporter.Metrics, error) {
return nopInstance, nil
}
func createLogs(context.Context, exporter.Settings, component.Config) (exporter.Logs, error) {
return nopInstance, nil
}
func createProfiles(context.Context, exporter.Settings, component.Config) (xexporter.Profiles, error) {
return nopInstance, nil
}
type nopConfig struct{}
var nopInstance = &nop{
Consumer: consumertest.NewNop(),
}
// nop stores consumed traces, metrics, logs and profiles for testing purposes.
type nop struct {
component.StartFunc
component.ShutdownFunc
consumertest.Consumer
}
================================================
FILE: exporter/exportertest/nop_exporter_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package exportertest
import (
"context"
"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/exporter/xexporter"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/ptrace"
)
func TestNewNopFactory(t *testing.T) {
factory := NewNopFactory()
require.NotNil(t, factory)
assert.Equal(t, component.MustNewType("nop"), factory.Type())
cfg := factory.CreateDefaultConfig()
assert.Equal(t, &nopConfig{}, cfg)
traces, err := factory.CreateTraces(context.Background(), NewNopSettings(NopType), cfg)
require.NoError(t, err)
assert.NoError(t, traces.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, traces.ConsumeTraces(context.Background(), ptrace.NewTraces()))
assert.NoError(t, traces.Shutdown(context.Background()))
metrics, err := factory.CreateMetrics(context.Background(), NewNopSettings(NopType), cfg)
require.NoError(t, err)
assert.NoError(t, metrics.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, metrics.ConsumeMetrics(context.Background(), pmetric.NewMetrics()))
assert.NoError(t, metrics.Shutdown(context.Background()))
logs, err := factory.CreateLogs(context.Background(), NewNopSettings(NopType), cfg)
require.NoError(t, err)
assert.NoError(t, logs.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, logs.ConsumeLogs(context.Background(), plog.NewLogs()))
assert.NoError(t, logs.Shutdown(context.Background()))
profiles, err := factory.(xexporter.Factory).CreateProfiles(context.Background(), NewNopSettings(NopType), cfg)
require.NoError(t, err)
assert.NoError(t, profiles.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, profiles.ConsumeProfiles(context.Background(), pprofile.NewProfiles()))
assert.NoError(t, profiles.Shutdown(context.Background()))
}
================================================
FILE: exporter/go.mod
================================================
module go.opentelemetry.io/collector/exporter
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/config/configoptional v1.54.0
go.opentelemetry.io/collector/config/configretry v1.54.0
go.opentelemetry.io/collector/consumer v1.54.0
go.opentelemetry.io/collector/consumer/consumertest v0.148.0
go.opentelemetry.io/collector/exporter/exporterhelper v0.148.0
go.opentelemetry.io/collector/internal/componentalias v0.148.0
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/collector/pipeline v1.54.0
go.uber.org/goleak v1.3.0
go.uber.org/zap v1.27.1
)
require (
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/collector/client v1.54.0 // indirect
go.opentelemetry.io/collector/confmap v1.54.0 // indirect
go.opentelemetry.io/collector/confmap/xconfmap v0.148.0 // indirect
go.opentelemetry.io/collector/consumer/consumererror v0.148.0 // indirect
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0 // indirect
go.opentelemetry.io/collector/extension v1.54.0 // indirect
go.opentelemetry.io/collector/extension/xextension v0.148.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/pdata/pprofile v0.148.0 // indirect
go.opentelemetry.io/collector/pdata/xpdata v0.148.0 // indirect
go.opentelemetry.io/collector/pipeline/xpipeline v0.148.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/sys v0.41.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/component => ../component
replace go.opentelemetry.io/collector/component/componenttest => ../component/componenttest
replace go.opentelemetry.io/collector/consumer => ../consumer
replace go.opentelemetry.io/collector/extension => ../extension
replace go.opentelemetry.io/collector/pdata => ../pdata
replace go.opentelemetry.io/collector/pdata/testdata => ../pdata/testdata
replace go.opentelemetry.io/collector/pdata/pprofile => ../pdata/pprofile
replace go.opentelemetry.io/collector/pipeline => ../pipeline
replace go.opentelemetry.io/collector/receiver => ../receiver
retract v0.76.0 // Depends on retracted pdata v1.0.0-rc10 module
replace go.opentelemetry.io/collector/config/configretry => ../config/configretry
replace go.opentelemetry.io/collector/consumer/xconsumer => ../consumer/xconsumer
replace go.opentelemetry.io/collector/consumer/consumertest => ../consumer/consumertest
replace go.opentelemetry.io/collector/receiver/xreceiver => ../receiver/xreceiver
replace go.opentelemetry.io/collector/receiver/receivertest => ../receiver/receivertest
replace go.opentelemetry.io/collector/exporter/xexporter => ./xexporter
replace go.opentelemetry.io/collector/exporter/exportertest => ./exportertest
replace go.opentelemetry.io/collector/consumer/consumererror => ../consumer/consumererror
replace go.opentelemetry.io/collector/extension/extensiontest => ../extension/extensiontest
replace go.opentelemetry.io/collector/extension/xextension => ../extension/xextension
replace go.opentelemetry.io/collector/featuregate => ../featuregate
replace go.opentelemetry.io/collector/client => ../client
replace go.opentelemetry.io/collector/pdata/xpdata => ../pdata/xpdata
replace go.opentelemetry.io/collector/confmap => ../confmap
replace go.opentelemetry.io/collector/config/configoptional => ../config/configoptional
replace go.opentelemetry.io/collector/confmap/xconfmap => ../confmap/xconfmap
replace go.opentelemetry.io/collector/exporter/exporterhelper => ./exporterhelper
replace go.opentelemetry.io/collector/internal/testutil => ../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../internal/componentalias
replace go.opentelemetry.io/collector/pipeline/xpipeline => ../pipeline/xpipeline
================================================
FILE: exporter/go.sum
================================================
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: exporter/internal/experr/err.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package experr // import "go.opentelemetry.io/collector/exporter/internal/experr"
import (
"fmt"
"go.opentelemetry.io/collector/component"
)
func ErrIDMismatch(id component.ID, typ component.Type) error {
return fmt.Errorf("component type mismatch: component ID %q does not have type %q", id, typ)
}
================================================
FILE: exporter/metadata.yaml
================================================
type: exporter
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: exporter/nopexporter/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: exporter/nopexporter/README.md
================================================
# No-op Exporter
| Status | |
| ------------- |-----------|
| Stability | [alpha]: profiles |
| | [beta]: traces, metrics, logs |
| Distributions | [core], [contrib], [k8s] |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Aexporter%2Fnop) [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aclosed+is%3Aissue+label%3Aexporter%2Fnop) |
| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@evan-bradley](https://www.github.com/evan-bradley) |
[alpha]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#alpha
[beta]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#beta
[core]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
[k8s]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-k8s
Serves as a placeholder exporter in a pipeline. This can be useful if you want
to e.g. start a Collector with only extensions enabled, or for testing Collector
pipeline throughput without worrying about an exporter.
## Getting Started
All that is required to enable the No-op exporter is to include it in the
exporter definitions. It takes no configuration.
```yaml
exporters:
nop: {} # Explicitly set in case the config is re-serialized (e.g. with the Operator)
```
================================================
FILE: exporter/nopexporter/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:generate mdatagen metadata.yaml
// Package nopexporter serves as a placeholder exporter.
package nopexporter // import "go.opentelemetry.io/collector/exporter/nopexporter"
================================================
FILE: exporter/nopexporter/generated_component_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package nopexporter
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/confmap/confmaptest"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
)
var typ = component.MustNewType("nop")
func TestComponentFactoryType(t *testing.T) {
require.Equal(t, typ, NewFactory().Type())
}
func TestComponentConfigStruct(t *testing.T) {
require.NoError(t, componenttest.CheckConfigStruct(NewFactory().CreateDefaultConfig()))
}
func TestComponentLifecycle(t *testing.T) {
factory := NewFactory()
tests := []struct {
createFn func(ctx context.Context, set exporter.Settings, cfg component.Config) (component.Component, error)
name string
}{
{
name: "logs",
createFn: func(ctx context.Context, set exporter.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateLogs(ctx, set, cfg)
},
},
{
name: "metrics",
createFn: func(ctx context.Context, set exporter.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateMetrics(ctx, set, cfg)
},
},
{
name: "traces",
createFn: func(ctx context.Context, set exporter.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateTraces(ctx, set, cfg)
},
},
}
cm, err := confmaptest.LoadConf("metadata.yaml")
require.NoError(t, err)
cfg := factory.CreateDefaultConfig()
sub, err := cm.Sub("tests::config")
require.NoError(t, err)
require.NoError(t, sub.Unmarshal(&cfg))
for _, tt := range tests {
t.Run(tt.name+"-shutdown", func(t *testing.T) {
c, err := tt.createFn(context.Background(), exportertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
err = c.Shutdown(context.Background())
require.NoError(t, err)
})
t.Run(tt.name+"-lifecycle", func(t *testing.T) {
c, err := tt.createFn(context.Background(), exportertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
host := newMdatagenNopHost()
err = c.Start(context.Background(), host)
require.NoError(t, err)
require.NotPanics(t, func() {
switch tt.name {
case "logs":
e, ok := c.(exporter.Logs)
require.True(t, ok)
logs := generateLifecycleTestLogs()
if !e.Capabilities().MutatesData {
logs.MarkReadOnly()
}
err = e.ConsumeLogs(context.Background(), logs)
case "metrics":
e, ok := c.(exporter.Metrics)
require.True(t, ok)
metrics := generateLifecycleTestMetrics()
if !e.Capabilities().MutatesData {
metrics.MarkReadOnly()
}
err = e.ConsumeMetrics(context.Background(), metrics)
case "traces":
e, ok := c.(exporter.Traces)
require.True(t, ok)
traces := generateLifecycleTestTraces()
if !e.Capabilities().MutatesData {
traces.MarkReadOnly()
}
err = e.ConsumeTraces(context.Background(), traces)
}
})
require.NoError(t, err)
err = c.Shutdown(context.Background())
require.NoError(t, err)
})
}
}
func generateLifecycleTestLogs() plog.Logs {
logs := plog.NewLogs()
rl := logs.ResourceLogs().AppendEmpty()
rl.Resource().Attributes().PutStr("resource", "R1")
l := rl.ScopeLogs().AppendEmpty().LogRecords().AppendEmpty()
l.Body().SetStr("test log message")
l.SetTimestamp(pcommon.NewTimestampFromTime(time.Now()))
return logs
}
func generateLifecycleTestMetrics() pmetric.Metrics {
metrics := pmetric.NewMetrics()
rm := metrics.ResourceMetrics().AppendEmpty()
rm.Resource().Attributes().PutStr("resource", "R1")
m := rm.ScopeMetrics().AppendEmpty().Metrics().AppendEmpty()
m.SetName("test_metric")
dp := m.SetEmptyGauge().DataPoints().AppendEmpty()
dp.Attributes().PutStr("test_attr", "value_1")
dp.SetIntValue(123)
dp.SetTimestamp(pcommon.NewTimestampFromTime(time.Now()))
return metrics
}
func generateLifecycleTestTraces() ptrace.Traces {
traces := ptrace.NewTraces()
rs := traces.ResourceSpans().AppendEmpty()
rs.Resource().Attributes().PutStr("resource", "R1")
span := rs.ScopeSpans().AppendEmpty().Spans().AppendEmpty()
span.Attributes().PutStr("test_attr", "value_1")
span.SetName("test_span")
span.SetStartTimestamp(pcommon.NewTimestampFromTime(time.Now().Add(-1 * time.Second)))
span.SetEndTimestamp(pcommon.NewTimestampFromTime(time.Now()))
return traces
}
var _ component.Host = (*mdatagenNopHost)(nil)
type mdatagenNopHost struct{}
func newMdatagenNopHost() component.Host {
return &mdatagenNopHost{}
}
func (mnh *mdatagenNopHost) GetExtensions() map[component.ID]component.Component {
return nil
}
func (mnh *mdatagenNopHost) GetFactory(_ component.Kind, _ component.Type) component.Factory {
return nil
}
================================================
FILE: exporter/nopexporter/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package nopexporter
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: exporter/nopexporter/go.mod
================================================
module go.opentelemetry.io/collector/exporter/nopexporter
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.opentelemetry.io/collector/confmap v1.54.0
go.opentelemetry.io/collector/consumer/consumertest v0.148.0
go.opentelemetry.io/collector/exporter v1.54.0
go.opentelemetry.io/collector/exporter/exportertest v0.148.0
go.opentelemetry.io/collector/exporter/xexporter v0.148.0
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/collector/pdata/pprofile v0.148.0
go.uber.org/goleak v1.3.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/consumer v1.54.0 // indirect
go.opentelemetry.io/collector/consumer/consumererror v0.148.0 // indirect
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
go.opentelemetry.io/collector/pipeline v1.54.0 // indirect
go.opentelemetry.io/collector/receiver v1.54.0 // indirect
go.opentelemetry.io/collector/receiver/receivertest v0.148.0 // indirect
go.opentelemetry.io/collector/receiver/xreceiver v0.148.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/sys v0.41.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest
replace go.opentelemetry.io/collector/consumer => ../../consumer
replace go.opentelemetry.io/collector/exporter => ../
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/pdata/testdata => ../../pdata/testdata
replace go.opentelemetry.io/collector/config/configretry => ../../config/configretry
replace go.opentelemetry.io/collector/receiver => ../../receiver
replace go.opentelemetry.io/collector/confmap => ../../confmap
replace go.opentelemetry.io/collector/extension => ../../extension
replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile
replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer
replace go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest
replace go.opentelemetry.io/collector/receiver/xreceiver => ../../receiver/xreceiver
replace go.opentelemetry.io/collector/receiver/receivertest => ../../receiver/receivertest
replace go.opentelemetry.io/collector/exporter/xexporter => ../xexporter
replace go.opentelemetry.io/collector/pipeline => ../../pipeline
replace go.opentelemetry.io/collector/exporter/exportertest => ../exportertest
replace go.opentelemetry.io/collector/consumer/consumererror => ../../consumer/consumererror
replace go.opentelemetry.io/collector/extension/extensiontest => ../../extension/extensiontest
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/extension/xextension => ../../extension/xextension
replace go.opentelemetry.io/collector/client => ../../client
replace go.opentelemetry.io/collector/pdata/xpdata => ../../pdata/xpdata
replace go.opentelemetry.io/collector/config/configoptional => ../../config/configoptional
replace go.opentelemetry.io/collector/confmap/xconfmap => ../../confmap/xconfmap
replace go.opentelemetry.io/collector/exporter/exporterhelper => ../exporterhelper
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
replace go.opentelemetry.io/collector/pipeline/xpipeline => ../../pipeline/xpipeline
================================================
FILE: exporter/nopexporter/go.sum
================================================
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: exporter/nopexporter/internal/metadata/generated_status.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/component"
)
var (
Type = component.MustNewType("nop")
ScopeName = "go.opentelemetry.io/collector/exporter/nopexporter"
)
const (
ProfilesStability = component.StabilityLevelAlpha
TracesStability = component.StabilityLevelBeta
MetricsStability = component.StabilityLevelBeta
LogsStability = component.StabilityLevelBeta
)
================================================
FILE: exporter/nopexporter/metadata.yaml
================================================
display_name: No-op Exporter
type: nop
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
codeowners:
active:
- evan-bradley
class: exporter
stability:
beta: [traces, metrics, logs]
alpha: [profiles]
distributions: [core, contrib, k8s]
================================================
FILE: exporter/nopexporter/nop_exporter.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package nopexporter // import "go.opentelemetry.io/collector/exporter/nopexporter"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/nopexporter/internal/metadata"
"go.opentelemetry.io/collector/exporter/xexporter"
)
// NewFactory returns an exporter.Factory that constructs nop exporters.
func NewFactory() exporter.Factory {
return xexporter.NewFactory(
metadata.Type,
func() component.Config { return &struct{}{} },
xexporter.WithTraces(createTraces, metadata.TracesStability),
xexporter.WithMetrics(createMetrics, metadata.MetricsStability),
xexporter.WithLogs(createLogs, metadata.LogsStability),
xexporter.WithProfiles(createProfiles, metadata.ProfilesStability),
)
}
func createTraces(context.Context, exporter.Settings, component.Config) (exporter.Traces, error) {
return nopInstance, nil
}
func createMetrics(context.Context, exporter.Settings, component.Config) (exporter.Metrics, error) {
return nopInstance, nil
}
func createLogs(context.Context, exporter.Settings, component.Config) (exporter.Logs, error) {
return nopInstance, nil
}
func createProfiles(context.Context, exporter.Settings, component.Config) (xexporter.Profiles, error) {
return nopInstance, nil
}
var nopInstance = &nop{
Consumer: consumertest.NewNop(),
}
type nop struct {
component.StartFunc
component.ShutdownFunc
consumertest.Consumer
}
================================================
FILE: exporter/nopexporter/nop_exporter_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package nopexporter
import (
"context"
"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/exporter/exportertest"
"go.opentelemetry.io/collector/exporter/xexporter"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/ptrace"
)
func TestNewNopFactory(t *testing.T) {
factory := NewFactory()
require.NotNil(t, factory)
assert.Equal(t, component.MustNewType("nop"), factory.Type())
cfg := factory.CreateDefaultConfig()
assert.Equal(t, &struct{}{}, cfg)
traces, err := factory.CreateTraces(context.Background(), exportertest.NewNopSettings(factory.Type()), cfg)
require.NoError(t, err)
assert.NoError(t, traces.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, traces.ConsumeTraces(context.Background(), ptrace.NewTraces()))
assert.NoError(t, traces.Shutdown(context.Background()))
metrics, err := factory.CreateMetrics(context.Background(), exportertest.NewNopSettings(factory.Type()), cfg)
require.NoError(t, err)
assert.NoError(t, metrics.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, metrics.ConsumeMetrics(context.Background(), pmetric.NewMetrics()))
assert.NoError(t, metrics.Shutdown(context.Background()))
logs, err := factory.CreateLogs(context.Background(), exportertest.NewNopSettings(factory.Type()), cfg)
require.NoError(t, err)
assert.NoError(t, logs.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, logs.ConsumeLogs(context.Background(), plog.NewLogs()))
assert.NoError(t, logs.Shutdown(context.Background()))
profiles, err := factory.(xexporter.Factory).CreateProfiles(context.Background(), exportertest.NewNopSettings(factory.Type()), cfg)
require.NoError(t, err)
assert.NoError(t, profiles.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, profiles.ConsumeProfiles(context.Background(), pprofile.NewProfiles()))
assert.NoError(t, profiles.Shutdown(context.Background()))
}
================================================
FILE: exporter/otlpexporter/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: exporter/otlpexporter/README.md
================================================
# OTLP gRPC Exporter
| Status | |
| ------------- |-----------|
| Stability | [alpha]: profiles |
| | [stable]: traces, metrics, logs |
| Distributions | [core], [contrib], [k8s], [otlp] |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Aexporter%2Fotlp) [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aclosed+is%3Aissue+label%3Aexporter%2Fotlp) |
[alpha]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#alpha
[stable]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#stable
[core]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
[k8s]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-k8s
[otlp]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-otlp
Export data via gRPC using [OTLP](
https://github.com/open-telemetry/opentelemetry-proto/blob/main/docs/specification.md)
format. By default, this exporter requires TLS and offers queued retry capabilities.
## 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).
If a scheme of `https` is used then client transport security is enabled and overrides the `insecure` setting.
- `tls`: see [TLS Configuration Settings](../../config/configtls/README.md) for the full set of available options.
- `retry_on_failure`: see [Retry on Failure](../exporterhelper/README.md#retry-on-failure) for the full set of available options.
- `sending_queue`: see [Sending Queue](../exporterhelper/README.md#sending-queue) for the full set of available options.
- `timeout` (default = 5s): Time to wait per individual attempt to send data to a backend.
Example:
```yaml
exporters:
otlp_grpc:
endpoint: otelcol2:4317
tls:
cert_file: file.cert
key_file: file.key
otlp/2:
endpoint: otelcol2:4317
tls:
insecure: true
```
By default, `gzip` compression is enabled. See [compression comparison](../../config/configgrpc/README.md#compression-comparison) for details benchmark information. To disable, configure as follows:
```yaml
exporters:
otlp_grpc:
...
compression: none
```
## Advanced Configuration
Several helper files are leveraged to provide additional capabilities automatically:
- [gRPC settings](https://github.com/open-telemetry/opentelemetry-collector/blob/main/config/configgrpc/README.md)
- [TLS and mTLS settings](https://github.com/open-telemetry/opentelemetry-collector/blob/main/config/configtls/README.md)
- [Queuing, batching, retry and timeout settings](https://github.com/open-telemetry/opentelemetry-collector/blob/main/exporter/exporterhelper/README.md)
================================================
FILE: exporter/otlpexporter/cfg-schema.yaml
================================================
type: '*otlpexporter.Config'
fields:
- name: timeout
type: time.Duration
kind: int64
default: 5s
doc: |
Timeout is the timeout for every attempt to send data to the backend.
- name: sending_queue
type: exporterhelper.QueueConfig
kind: struct
fields:
- name: enabled
kind: bool
default: true
doc: |
Enabled indicates whether to not enqueue batches before sending to the consumerSender.
- name: num_consumers
kind: int
default: 10
doc: |
NumConsumers is the number of consumers from the queue.
- name: queue_size
kind: int
default: 1000
doc: |
QueueSize is the maximum number of batches allowed in queue at a given time.
- name: retry_on_failure
type: exporterhelper.RetrySettings
kind: struct
fields:
- name: enabled
kind: bool
default: true
doc: |
Enabled indicates whether to not retry sending batches in case of export failure.
- name: initial_interval
type: time.Duration
kind: int64
default: 5s
doc: |
InitialInterval the time to wait after the first failure before retrying.
- name: randomization_factor
kind: float64
default: 0.5
doc: |
RandomizationFactor is a random factor used to calculate next backoffs.
Randomized interval = RetryInterval * (1 ± RandomizationFactor)
- name: multiplier
kind: float64
default: 1.5
doc: |
Multiplier is the value multiplied by the backoff interval bounds
- name: max_interval
type: time.Duration
kind: int64
default: 30s
doc: |
MaxInterval is the upper bound on backoff interval. Once this value is reached the delay between
consecutive retries will always be `MaxInterval`.
- name: max_elapsed_time
type: time.Duration
kind: int64
default: 5m0s
doc: |
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.
- name: endpoint
kind: string
doc: |
The target to which the exporter is going to send traces, metrics, logs or
profiles using the gRPC protocol. The valid syntax is described at
https://github.com/grpc/grpc/blob/master/doc/naming.md.
- name: compression
kind: string
doc: |
The compression key for supported compression types within
collector. Supports `gzip`, `snappy` and `zstd`.
- name: ca_file
kind: string
doc: |
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)
- name: cert_file
kind: string
doc: |
Path to the TLS cert to use for TLS required connections. (optional)
- name: key_file
kind: string
doc: |
Path to the TLS key to use for TLS required connections. (optional)
- name: insecure
kind: bool
doc: |
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)
- name: server_name_override
kind: string
doc: |
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)
- name: keepalive
type: '*configgrpc.KeepaliveClientConfig'
kind: ptr
doc: |
The keepalive parameters for gRPC client. See grpc.WithKeepaliveParams
(https://godoc.org/google.golang.org/grpc#WithKeepaliveParams).
fields:
- name: time
type: time.Duration
kind: int64
- name: timeout
type: time.Duration
kind: int64
- name: permit_without_stream
kind: bool
- name: read_buffer_size
kind: int
doc: |
ReadBufferSize for gRPC client. See grpc.WithReadBufferSize
(https://godoc.org/google.golang.org/grpc#WithReadBufferSize).
- name: write_buffer_size
kind: int
default: 524288
doc: |
WriteBufferSize for gRPC gRPC. See grpc.WithWriteBufferSize
(https://godoc.org/google.golang.org/grpc#WithWriteBufferSize).
- name: wait_for_ready
kind: bool
doc: |
WaitForReady parameter configures client to wait for ready state before sending data.
(https://github.com/grpc/grpc/blob/master/doc/wait-for-ready.md)
- name: headers
type: map[string]string
kind: map
doc: |
The headers associated with gRPC requests.
- name: per_rpc_auth
type: '*configgrpc.PerRPCAuthConfig'
kind: ptr
doc: |
PerRPCAuth parameter configures the client to send authentication data on a per-RPC basis.
fields:
- name: type
kind: string
doc: |
AuthType represents the authentication type to use. Currently, only 'bearer' is supported.
- name: bearer_token
kind: string
doc: |
BearerToken specifies the bearer token to use for every RPC.
- name: balancer_name
kind: string
doc: |
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
================================================
FILE: exporter/otlpexporter/config.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlpexporter // import "go.opentelemetry.io/collector/exporter/otlpexporter"
import (
"errors"
"regexp"
"strings"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configgrpc"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/config/configretry"
"go.opentelemetry.io/collector/confmap/xconfmap"
"go.opentelemetry.io/collector/exporter/exporterhelper"
)
// Config defines configuration for OTLP exporter.
type Config struct {
TimeoutConfig exporterhelper.TimeoutConfig `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct.
QueueConfig configoptional.Optional[exporterhelper.QueueBatchConfig] `mapstructure:"sending_queue"`
RetryConfig configretry.BackOffConfig `mapstructure:"retry_on_failure"`
ClientConfig configgrpc.ClientConfig `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct.
// prevent unkeyed literal initialization
_ struct{}
}
var (
_ component.Config = (*Config)(nil)
_ xconfmap.Validator = (*Config)(nil)
)
func (c *Config) Validate() error {
if endpoint := c.sanitizedEndpoint(); endpoint == "" {
return errors.New(`requires a non-empty "endpoint"`)
}
return nil
}
func (c *Config) sanitizedEndpoint() string {
switch {
case strings.HasPrefix(c.ClientConfig.Endpoint, "http://"):
return strings.TrimPrefix(c.ClientConfig.Endpoint, "http://")
case strings.HasPrefix(c.ClientConfig.Endpoint, "https://"):
return strings.TrimPrefix(c.ClientConfig.Endpoint, "https://")
case strings.HasPrefix(c.ClientConfig.Endpoint, "dns://"):
r := regexp.MustCompile(`^dns:///?`)
return r.ReplaceAllString(c.ClientConfig.Endpoint, "")
default:
return c.ClientConfig.Endpoint
}
}
================================================
FILE: exporter/otlpexporter/config.yaml
================================================
description: Config defines configuration for OTLP exporter.
type: object
properties:
retry_on_failure:
$ref: go.opentelemetry.io/collector/config/configretry.back_off_config
sending_queue:
x-optional: true
$ref: go.opentelemetry.io/collector/exporter/exporterhelper.queue_batch_config
allOf:
- $ref: go.opentelemetry.io/collector/exporter/exporterhelper.timeout_config
- $ref: go.opentelemetry.io/collector/config/configgrpc.client_config
================================================
FILE: exporter/otlpexporter/config_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlpexporter
import (
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configauth"
"go.opentelemetry.io/collector/config/configgrpc"
"go.opentelemetry.io/collector/config/configopaque"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/config/configretry"
"go.opentelemetry.io/collector/config/configtls"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/confmaptest"
"go.opentelemetry.io/collector/confmap/xconfmap"
"go.opentelemetry.io/collector/exporter/exporterhelper"
)
func TestUnmarshalDefaultConfig(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
require.NoError(t, confmap.New().Unmarshal(&cfg))
assert.Equal(t, factory.CreateDefaultConfig(), cfg)
}
func TestUnmarshalConfig(t *testing.T) {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
require.NoError(t, err)
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
require.NoError(t, cm.Unmarshal(&cfg))
require.NoError(t, xconfmap.Validate(&cfg))
assert.Equal(t,
&Config{
TimeoutConfig: exporterhelper.TimeoutConfig{
Timeout: 10 * time.Second,
},
RetryConfig: configretry.BackOffConfig{
Enabled: true,
InitialInterval: 10 * time.Second,
RandomizationFactor: 0.7,
Multiplier: 1.3,
MaxInterval: 1 * time.Minute,
MaxElapsedTime: 10 * time.Minute,
},
QueueConfig: configoptional.Some(exporterhelper.QueueBatchConfig{
Sizer: exporterhelper.RequestSizerTypeItems,
NumConsumers: 2,
QueueSize: 100000,
Batch: configoptional.Some(exporterhelper.BatchConfig{
FlushTimeout: 200 * time.Millisecond,
Sizer: exporterhelper.RequestSizerTypeItems,
MinSize: 1000,
MaxSize: 10000,
}),
}),
ClientConfig: configgrpc.ClientConfig{
Headers: configopaque.MapList{
{Name: "another", Value: "somevalue"},
{Name: "can you have a . here?", Value: "F0000000-0000-0000-0000-000000000000"},
{Name: "header1", Value: "234"},
},
Endpoint: "1.2.3.4:1234",
Compression: "gzip",
TLS: configtls.ClientConfig{
Config: configtls.Config{
CAFile: "/var/lib/mycert.pem",
},
Insecure: false,
},
Keepalive: configoptional.Some(configgrpc.KeepaliveClientConfig{
Time: 20 * time.Second,
PermitWithoutStream: true,
Timeout: 30 * time.Second,
}),
WriteBufferSize: 512 * 1024,
BalancerName: "round_robin",
Auth: configoptional.Some(configauth.Config{AuthenticatorID: component.MustNewID("nop")}),
},
}, cfg)
}
func TestUnmarshalDefaultBatchConfig(t *testing.T) {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "default-batch.yaml"))
require.NoError(t, err)
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
require.NoError(t, cm.Unmarshal(&cfg))
require.NoError(t, xconfmap.Validate(&cfg))
assert.Equal(t,
&Config{
TimeoutConfig: exporterhelper.TimeoutConfig{
Timeout: 10 * time.Second,
},
RetryConfig: configretry.NewDefaultBackOffConfig(),
QueueConfig: configoptional.Some(exporterhelper.QueueBatchConfig{
Sizer: exporterhelper.RequestSizerTypeRequests,
QueueSize: 1000,
NumConsumers: 10,
Batch: configoptional.Some(exporterhelper.BatchConfig{
FlushTimeout: 200 * time.Millisecond,
Sizer: exporterhelper.RequestSizerTypeItems,
MinSize: 8192,
}),
}),
ClientConfig: configgrpc.ClientConfig{
Endpoint: "1.2.3.4:1234",
BalancerName: "round_robin",
Compression: "gzip",
WriteBufferSize: 512 * 1024,
},
}, cfg)
}
func TestUnmarshalInvalidConfig(t *testing.T) {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "invalid_configs.yaml"))
require.NoError(t, err)
factory := NewFactory()
for _, tt := range []struct {
name string
errorMsg string
}{
{
name: "no_endpoint",
errorMsg: `requires a non-empty "endpoint"`,
},
{
name: "https_endpoint",
errorMsg: `requires a non-empty "endpoint"`,
},
{
name: "http_endpoint",
errorMsg: `requires a non-empty "endpoint"`,
},
{
name: "invalid_timeout",
errorMsg: `'timeout' must be non-negative`,
},
{
name: "invalid_retry",
errorMsg: `'randomization_factor' must be within [0, 1]`,
},
{
name: "invalid_tls",
errorMsg: `invalid TLS min_version: unsupported TLS version: "asd"`,
},
{
name: "missing_port",
errorMsg: `missing port in address`,
},
{
name: "invalid_port",
errorMsg: `invalid port "port"`,
},
{
name: "invalid_unix_socket",
errorMsg: "unix socket path cannot be empty",
},
} {
t.Run(tt.name, func(t *testing.T) {
cfg := factory.CreateDefaultConfig()
sub, err := cm.Sub(tt.name)
require.NoError(t, err)
assert.NoError(t, sub.Unmarshal(&cfg))
assert.ErrorContains(t, xconfmap.Validate(cfg), tt.errorMsg)
})
}
}
func TestValidDNSEndpoint(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig().(*Config)
cfg.ClientConfig.Endpoint = "dns://authority/backend.example.com:4317"
assert.NoError(t, xconfmap.Validate(cfg))
}
func TestValidUnixSocketEndpoint(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig().(*Config)
cfg.ClientConfig.Endpoint = "unix:///my/unix/socket.sock"
assert.NoError(t, xconfmap.Validate(cfg))
}
================================================
FILE: exporter/otlpexporter/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:generate mdatagen metadata.yaml
// Package otlpexporter exports data by using the OTLP format to a gRPC endpoint.
package otlpexporter // import "go.opentelemetry.io/collector/exporter/otlpexporter"
================================================
FILE: exporter/otlpexporter/factory.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlpexporter // import "go.opentelemetry.io/collector/exporter/otlpexporter"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configcompression"
"go.opentelemetry.io/collector/config/configgrpc"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/config/configretry"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exporterhelper"
"go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper"
"go.opentelemetry.io/collector/exporter/otlpexporter/internal/metadata"
"go.opentelemetry.io/collector/exporter/xexporter"
)
// NewFactory creates a factory for OTLP exporter.
func NewFactory() exporter.Factory {
return xexporter.NewFactory(
metadata.Type,
createDefaultConfig,
xexporter.WithDeprecatedTypeAlias(metadata.DeprecatedType),
xexporter.WithTraces(createTraces, metadata.TracesStability),
xexporter.WithMetrics(createMetrics, metadata.MetricsStability),
xexporter.WithLogs(createLogs, metadata.LogsStability),
xexporter.WithProfiles(createProfilesExporter, metadata.ProfilesStability),
)
}
func createDefaultConfig() component.Config {
clientCfg := configgrpc.NewDefaultClientConfig()
// Default to gzip compression
clientCfg.Compression = configcompression.TypeGzip
// We almost read 0 bytes, so no need to tune ReadBufferSize.
clientCfg.WriteBufferSize = 512 * 1024
// For backward compatibility:
clientCfg.Keepalive = configoptional.None[configgrpc.KeepaliveClientConfig]()
return &Config{
TimeoutConfig: exporterhelper.NewDefaultTimeoutConfig(),
RetryConfig: configretry.NewDefaultBackOffConfig(),
QueueConfig: configoptional.Some(exporterhelper.NewDefaultQueueConfig()),
ClientConfig: clientCfg,
}
}
func createTraces(
ctx context.Context,
set exporter.Settings,
cfg component.Config,
) (exporter.Traces, error) {
oce := newExporter(cfg, set)
oCfg := cfg.(*Config)
return exporterhelper.NewTraces(ctx, set, cfg,
oce.pushTraces,
exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}),
exporterhelper.WithTimeout(oCfg.TimeoutConfig),
exporterhelper.WithRetry(oCfg.RetryConfig),
exporterhelper.WithQueue(oCfg.QueueConfig),
exporterhelper.WithStart(oce.start),
exporterhelper.WithShutdown(oce.shutdown),
)
}
func createMetrics(
ctx context.Context,
set exporter.Settings,
cfg component.Config,
) (exporter.Metrics, error) {
oce := newExporter(cfg, set)
oCfg := cfg.(*Config)
return exporterhelper.NewMetrics(ctx, set, cfg,
oce.pushMetrics,
exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}),
exporterhelper.WithTimeout(oCfg.TimeoutConfig),
exporterhelper.WithRetry(oCfg.RetryConfig),
exporterhelper.WithQueue(oCfg.QueueConfig),
exporterhelper.WithStart(oce.start),
exporterhelper.WithShutdown(oce.shutdown),
)
}
func createLogs(
ctx context.Context,
set exporter.Settings,
cfg component.Config,
) (exporter.Logs, error) {
oce := newExporter(cfg, set)
oCfg := cfg.(*Config)
return exporterhelper.NewLogs(ctx, set, cfg,
oce.pushLogs,
exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}),
exporterhelper.WithTimeout(oCfg.TimeoutConfig),
exporterhelper.WithRetry(oCfg.RetryConfig),
exporterhelper.WithQueue(oCfg.QueueConfig),
exporterhelper.WithStart(oce.start),
exporterhelper.WithShutdown(oce.shutdown),
)
}
func createProfilesExporter(
ctx context.Context,
set exporter.Settings,
cfg component.Config,
) (xexporter.Profiles, error) {
oce := newExporter(cfg, set)
oCfg := cfg.(*Config)
return xexporterhelper.NewProfiles(ctx, set, cfg,
oce.pushProfiles,
exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}),
exporterhelper.WithTimeout(oCfg.TimeoutConfig),
exporterhelper.WithRetry(oCfg.RetryConfig),
exporterhelper.WithQueue(oCfg.QueueConfig),
exporterhelper.WithStart(oce.start),
exporterhelper.WithShutdown(oce.shutdown),
)
}
================================================
FILE: exporter/otlpexporter/factory_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlpexporter
import (
"context"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/config/configcompression"
"go.opentelemetry.io/collector/config/configgrpc"
"go.opentelemetry.io/collector/config/configopaque"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/config/configretry"
"go.opentelemetry.io/collector/config/configtls"
"go.opentelemetry.io/collector/exporter/exporterhelper"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/exporter/xexporter"
"go.opentelemetry.io/collector/internal/testutil"
)
func TestCreateDefaultConfig(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
assert.NotNil(t, cfg, "failed to create default config")
require.NoError(t, componenttest.CheckConfigStruct(cfg))
ocfg, ok := factory.CreateDefaultConfig().(*Config)
assert.True(t, ok)
assert.Equal(t, configretry.NewDefaultBackOffConfig(), ocfg.RetryConfig)
assert.Equal(t, configoptional.Some(exporterhelper.NewDefaultQueueConfig()), ocfg.QueueConfig)
assert.Equal(t, exporterhelper.NewDefaultTimeoutConfig(), ocfg.TimeoutConfig)
assert.Equal(t, configcompression.TypeGzip, ocfg.ClientConfig.Compression)
assert.Equal(t, configgrpc.BalancerName(), ocfg.ClientConfig.BalancerName)
}
func TestCreateMetrics(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig().(*Config)
cfg.ClientConfig.Endpoint = testutil.GetAvailableLocalAddress(t)
set := exportertest.NewNopSettings(factory.Type())
oexp, err := factory.CreateMetrics(context.Background(), set, cfg)
require.NoError(t, err)
require.NotNil(t, oexp)
}
func TestCreateTraces(t *testing.T) {
endpoint := testutil.GetAvailableLocalAddress(t)
tests := []struct {
name string
config *Config
mustFailOnStart bool
}{
{
name: "UseSecure",
config: &Config{
ClientConfig: configgrpc.ClientConfig{
Endpoint: endpoint,
TLS: configtls.ClientConfig{
Insecure: false,
},
},
},
},
{
name: "Keepalive",
config: &Config{
ClientConfig: configgrpc.ClientConfig{
Endpoint: endpoint,
Keepalive: configoptional.Some(configgrpc.KeepaliveClientConfig{
Time: 30 * time.Second,
Timeout: 25 * time.Second,
PermitWithoutStream: true,
}),
},
},
},
{
name: "NoneCompression",
config: &Config{
ClientConfig: configgrpc.ClientConfig{
Endpoint: endpoint,
Compression: "none",
},
},
},
{
name: "GzipCompression",
config: &Config{
ClientConfig: configgrpc.ClientConfig{
Endpoint: endpoint,
Compression: configcompression.TypeGzip,
},
},
},
{
name: "SnappyCompression",
config: &Config{
ClientConfig: configgrpc.ClientConfig{
Endpoint: endpoint,
Compression: configcompression.TypeSnappy,
},
},
},
{
name: "ZstdCompression",
config: &Config{
ClientConfig: configgrpc.ClientConfig{
Endpoint: endpoint,
Compression: configcompression.TypeZstd,
},
},
},
{
name: "Headers",
config: &Config{
ClientConfig: configgrpc.ClientConfig{
Endpoint: endpoint,
Headers: configopaque.MapList{
{Name: "hdr1", Value: "val1"},
{Name: "hdr2", Value: "val2"},
},
},
},
},
{
name: "NumConsumers",
config: &Config{
ClientConfig: configgrpc.ClientConfig{
Endpoint: endpoint,
},
},
},
{
name: "CaCert",
config: &Config{
ClientConfig: configgrpc.ClientConfig{
Endpoint: endpoint,
TLS: configtls.ClientConfig{
Config: configtls.Config{
CAFile: filepath.Join("testdata", "test_cert.pem"),
},
},
},
},
},
{
name: "CertPemFileError",
config: &Config{
ClientConfig: configgrpc.ClientConfig{
Endpoint: endpoint,
TLS: configtls.ClientConfig{
Config: configtls.Config{
CAFile: "nosuchfile",
},
},
},
},
mustFailOnStart: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
factory := NewFactory()
set := exportertest.NewNopSettings(factory.Type())
consumer, err := factory.CreateTraces(context.Background(), set, tt.config)
require.NoError(t, err)
assert.NotNil(t, consumer)
err = consumer.Start(context.Background(), componenttest.NewNopHost())
if tt.mustFailOnStart {
require.Error(t, err)
} else {
require.NoError(t, err)
}
// Shutdown is called even when Start fails
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, "rpc error: code = Canceled desc = grpc: the client connection is closing", err.Error())
}
})
}
}
func TestCreateLogs(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig().(*Config)
cfg.ClientConfig.Endpoint = testutil.GetAvailableLocalAddress(t)
set := exportertest.NewNopSettings(factory.Type())
oexp, err := factory.CreateLogs(context.Background(), set, cfg)
require.NoError(t, err)
require.NotNil(t, oexp)
}
func TestCreateProfiles(t *testing.T) {
endpoint := testutil.GetAvailableLocalAddress(t)
tests := []struct {
name string
config *Config
mustFailOnStart bool
}{
{
name: "UseSecure",
config: &Config{
ClientConfig: configgrpc.ClientConfig{
Endpoint: endpoint,
TLS: configtls.ClientConfig{
Insecure: false,
},
},
},
},
{
name: "Keepalive",
config: &Config{
ClientConfig: configgrpc.ClientConfig{
Endpoint: endpoint,
Keepalive: configoptional.Some(configgrpc.KeepaliveClientConfig{
Time: 30 * time.Second,
Timeout: 25 * time.Second,
PermitWithoutStream: true,
}),
},
},
},
{
name: "NoneCompression",
config: &Config{
ClientConfig: configgrpc.ClientConfig{
Endpoint: endpoint,
Compression: "none",
},
},
},
{
name: "GzipCompression",
config: &Config{
ClientConfig: configgrpc.ClientConfig{
Endpoint: endpoint,
Compression: configcompression.TypeGzip,
},
},
},
{
name: "SnappyCompression",
config: &Config{
ClientConfig: configgrpc.ClientConfig{
Endpoint: endpoint,
Compression: configcompression.TypeSnappy,
},
},
},
{
name: "ZstdCompression",
config: &Config{
ClientConfig: configgrpc.ClientConfig{
Endpoint: endpoint,
Compression: configcompression.TypeZstd,
},
},
},
{
name: "Headers",
config: &Config{
ClientConfig: configgrpc.ClientConfig{
Endpoint: endpoint,
Headers: configopaque.MapList{
{Name: "hdr1", Value: "val1"},
{Name: "hdr2", Value: "val2"},
},
},
},
},
{
name: "NumConsumers",
config: &Config{
ClientConfig: configgrpc.ClientConfig{
Endpoint: endpoint,
},
},
},
{
name: "CaCert",
config: &Config{
ClientConfig: configgrpc.ClientConfig{
Endpoint: endpoint,
TLS: configtls.ClientConfig{
Config: configtls.Config{
CAFile: filepath.Join("testdata", "test_cert.pem"),
},
},
},
},
},
{
name: "CertPemFileError",
config: &Config{
ClientConfig: configgrpc.ClientConfig{
Endpoint: endpoint,
TLS: configtls.ClientConfig{
Config: configtls.Config{
CAFile: "nosuchfile",
},
},
},
},
mustFailOnStart: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
factory := NewFactory()
set := exportertest.NewNopSettings(factory.Type())
consumer, err := factory.(xexporter.Factory).CreateProfiles(context.Background(), set, tt.config)
require.NoError(t, err)
assert.NotNil(t, consumer)
err = consumer.Start(context.Background(), componenttest.NewNopHost())
if tt.mustFailOnStart {
require.Error(t, err)
} else {
require.NoError(t, err)
}
// Shutdown is called even when Start fails
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, "rpc error: code = Canceled desc = grpc: the client connection is closing", err.Error())
}
})
}
}
================================================
FILE: exporter/otlpexporter/generated_component_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package otlpexporter
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/confmap/confmaptest"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
)
var typ = component.MustNewType("otlp_grpc")
func TestComponentFactoryType(t *testing.T) {
require.Equal(t, typ, NewFactory().Type())
}
func TestComponentConfigStruct(t *testing.T) {
require.NoError(t, componenttest.CheckConfigStruct(NewFactory().CreateDefaultConfig()))
}
func TestComponentLifecycle(t *testing.T) {
factory := NewFactory()
tests := []struct {
createFn func(ctx context.Context, set exporter.Settings, cfg component.Config) (component.Component, error)
name string
}{
{
name: "logs",
createFn: func(ctx context.Context, set exporter.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateLogs(ctx, set, cfg)
},
},
{
name: "metrics",
createFn: func(ctx context.Context, set exporter.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateMetrics(ctx, set, cfg)
},
},
{
name: "traces",
createFn: func(ctx context.Context, set exporter.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateTraces(ctx, set, cfg)
},
},
}
cm, err := confmaptest.LoadConf("metadata.yaml")
require.NoError(t, err)
cfg := factory.CreateDefaultConfig()
sub, err := cm.Sub("tests::config")
require.NoError(t, err)
require.NoError(t, sub.Unmarshal(&cfg))
for _, tt := range tests {
t.Run(tt.name+"-shutdown", func(t *testing.T) {
c, err := tt.createFn(context.Background(), exportertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
err = c.Shutdown(context.Background())
require.NoError(t, err)
})
t.Run(tt.name+"-lifecycle", func(t *testing.T) {
c, err := tt.createFn(context.Background(), exportertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
host := newMdatagenNopHost()
err = c.Start(context.Background(), host)
require.NoError(t, err)
require.NotPanics(t, func() {
switch tt.name {
case "logs":
e, ok := c.(exporter.Logs)
require.True(t, ok)
logs := generateLifecycleTestLogs()
if !e.Capabilities().MutatesData {
logs.MarkReadOnly()
}
err = e.ConsumeLogs(context.Background(), logs)
case "metrics":
e, ok := c.(exporter.Metrics)
require.True(t, ok)
metrics := generateLifecycleTestMetrics()
if !e.Capabilities().MutatesData {
metrics.MarkReadOnly()
}
err = e.ConsumeMetrics(context.Background(), metrics)
case "traces":
e, ok := c.(exporter.Traces)
require.True(t, ok)
traces := generateLifecycleTestTraces()
if !e.Capabilities().MutatesData {
traces.MarkReadOnly()
}
err = e.ConsumeTraces(context.Background(), traces)
}
})
require.NoError(t, err)
err = c.Shutdown(context.Background())
require.NoError(t, err)
})
}
}
func generateLifecycleTestLogs() plog.Logs {
logs := plog.NewLogs()
rl := logs.ResourceLogs().AppendEmpty()
rl.Resource().Attributes().PutStr("resource", "R1")
l := rl.ScopeLogs().AppendEmpty().LogRecords().AppendEmpty()
l.Body().SetStr("test log message")
l.SetTimestamp(pcommon.NewTimestampFromTime(time.Now()))
return logs
}
func generateLifecycleTestMetrics() pmetric.Metrics {
metrics := pmetric.NewMetrics()
rm := metrics.ResourceMetrics().AppendEmpty()
rm.Resource().Attributes().PutStr("resource", "R1")
m := rm.ScopeMetrics().AppendEmpty().Metrics().AppendEmpty()
m.SetName("test_metric")
dp := m.SetEmptyGauge().DataPoints().AppendEmpty()
dp.Attributes().PutStr("test_attr", "value_1")
dp.SetIntValue(123)
dp.SetTimestamp(pcommon.NewTimestampFromTime(time.Now()))
return metrics
}
func generateLifecycleTestTraces() ptrace.Traces {
traces := ptrace.NewTraces()
rs := traces.ResourceSpans().AppendEmpty()
rs.Resource().Attributes().PutStr("resource", "R1")
span := rs.ScopeSpans().AppendEmpty().Spans().AppendEmpty()
span.Attributes().PutStr("test_attr", "value_1")
span.SetName("test_span")
span.SetStartTimestamp(pcommon.NewTimestampFromTime(time.Now().Add(-1 * time.Second)))
span.SetEndTimestamp(pcommon.NewTimestampFromTime(time.Now()))
return traces
}
var _ component.Host = (*mdatagenNopHost)(nil)
type mdatagenNopHost struct{}
func newMdatagenNopHost() component.Host {
return &mdatagenNopHost{}
}
func (mnh *mdatagenNopHost) GetExtensions() map[component.ID]component.Component {
return nil
}
func (mnh *mdatagenNopHost) GetFactory(_ component.Kind, _ component.Type) component.Factory {
return nil
}
================================================
FILE: exporter/otlpexporter/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package otlpexporter
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: exporter/otlpexporter/go.mod
================================================
module go.opentelemetry.io/collector/exporter/otlpexporter
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector v0.148.0
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.opentelemetry.io/collector/config/configauth v1.54.0
go.opentelemetry.io/collector/config/configcompression v1.54.0
go.opentelemetry.io/collector/config/configgrpc v0.148.0
go.opentelemetry.io/collector/config/configopaque v1.54.0
go.opentelemetry.io/collector/config/configoptional v1.54.0
go.opentelemetry.io/collector/config/configretry v1.54.0
go.opentelemetry.io/collector/config/configtls v1.54.0
go.opentelemetry.io/collector/confmap v1.54.0
go.opentelemetry.io/collector/confmap/xconfmap v0.148.0
go.opentelemetry.io/collector/consumer v1.54.0
go.opentelemetry.io/collector/consumer/consumererror v0.148.0
go.opentelemetry.io/collector/exporter v1.54.0
go.opentelemetry.io/collector/exporter/exporterhelper v0.148.0
go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper v0.148.0
go.opentelemetry.io/collector/exporter/exportertest v0.148.0
go.opentelemetry.io/collector/exporter/xexporter v0.148.0
go.opentelemetry.io/collector/internal/testutil v0.148.0
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/collector/pdata/pprofile v0.148.0
go.opentelemetry.io/collector/pdata/testdata v0.148.0
go.uber.org/goleak v1.3.0
go.uber.org/zap v1.27.1
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171
google.golang.org/grpc v1.79.3
google.golang.org/protobuf v1.36.11
)
require (
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-tpm v0.9.8 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/mostynb/go-grpc-compression v1.2.3 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/client v1.54.0 // indirect
go.opentelemetry.io/collector/config/configmiddleware v1.54.0 // indirect
go.opentelemetry.io/collector/config/confignet v1.54.0 // indirect
go.opentelemetry.io/collector/consumer/consumererror/xconsumererror v0.148.0 // indirect
go.opentelemetry.io/collector/consumer/consumertest v0.148.0 // indirect
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0 // indirect
go.opentelemetry.io/collector/extension v1.54.0 // indirect
go.opentelemetry.io/collector/extension/extensionauth v1.54.0 // indirect
go.opentelemetry.io/collector/extension/extensionmiddleware v0.148.0 // indirect
go.opentelemetry.io/collector/extension/xextension v0.148.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
go.opentelemetry.io/collector/pdata/xpdata v0.148.0 // indirect
go.opentelemetry.io/collector/pipeline v1.54.0 // indirect
go.opentelemetry.io/collector/pipeline/xpipeline v0.148.0 // indirect
go.opentelemetry.io/collector/receiver v1.54.0 // indirect
go.opentelemetry.io/collector/receiver/receivertest v0.148.0 // indirect
go.opentelemetry.io/collector/receiver/xreceiver v0.148.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest
replace go.opentelemetry.io/collector/config/configcompression => ../../config/configcompression
replace go.opentelemetry.io/collector/config/configauth => ../../config/configauth
replace go.opentelemetry.io/collector/config/configgrpc => ../../config/configgrpc
replace go.opentelemetry.io/collector/config/confignet => ../../config/confignet
replace go.opentelemetry.io/collector/config/configopaque => ../../config/configopaque
replace go.opentelemetry.io/collector/config/configtls => ../../config/configtls
replace go.opentelemetry.io/collector/confmap => ../../confmap
replace go.opentelemetry.io/collector/confmap/xconfmap => ../../confmap/xconfmap
replace go.opentelemetry.io/collector/exporter => ../
replace go.opentelemetry.io/collector/extension => ../../extension
replace go.opentelemetry.io/collector/extension/extensionauth => ../../extension/extensionauth
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/pdata/testdata => ../../pdata/testdata
replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile
replace go.opentelemetry.io/collector/receiver => ../../receiver
replace go.opentelemetry.io/collector/consumer => ../../consumer
replace go.opentelemetry.io/collector/client => ../../client
replace go.opentelemetry.io/collector/config/configretry => ../../config/configretry
replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer
replace go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest
replace go.opentelemetry.io/collector/receiver/xreceiver => ../../receiver/xreceiver
replace go.opentelemetry.io/collector/receiver/receivertest => ../../receiver/receivertest
replace go.opentelemetry.io/collector/exporter/xexporter => ../xexporter
replace go.opentelemetry.io/collector/pipeline => ../../pipeline
replace go.opentelemetry.io/collector => ../..
replace go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper => ../exporterhelper/xexporterhelper
replace go.opentelemetry.io/collector/consumer/consumererror/xconsumererror => ../../consumer/consumererror/xconsumererror
replace go.opentelemetry.io/collector/pipeline/xpipeline => ../../pipeline/xpipeline
replace go.opentelemetry.io/collector/consumer/consumererror => ../../consumer/consumererror
retract (
v0.76.0 // Depends on retracted pdata v1.0.0-rc10 module, use v0.76.1
v0.69.0 // Release failed, use v0.69.1
)
replace go.opentelemetry.io/collector/exporter/exportertest => ../exportertest
replace go.opentelemetry.io/collector/extension/extensiontest => ../../extension/extensiontest
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/extension/xextension => ../../extension/xextension
replace go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest => ../../extension/extensionauth/extensionauthtest
replace go.opentelemetry.io/collector/extension/extensionmiddleware => ../../extension/extensionmiddleware
replace go.opentelemetry.io/collector/config/configmiddleware => ../../config/configmiddleware
replace go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest => ../../extension/extensionmiddleware/extensionmiddlewaretest
replace go.opentelemetry.io/collector/pdata/xpdata => ../../pdata/xpdata
replace go.opentelemetry.io/collector/config/configoptional => ../../config/configoptional
replace go.opentelemetry.io/collector/exporter/exporterhelper => ../exporterhelper
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
================================================
FILE: exporter/otlpexporter/go.sum
================================================
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f h1:RJ+BDPLSHQO7cSjKBqjPJSbi1qfk9WcsjQDtZiw3dZw=
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f/go.mod h1:VHbbch/X4roIY22jL1s3qRbZhCiRIgUAF/PdSUcx2io=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/go-tpm-tools v0.4.7 h1:J3ycC8umYxM9A4eF73EofRZu4BxY0jjQnUnkhIBbvws=
github.com/google/go-tpm-tools v0.4.7/go.mod h1:gSyXTZHe3fgbzb6WEGd90QucmsnT1SRdlye82gH8QjQ=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mostynb/go-grpc-compression v1.2.3 h1:42/BKWMy0KEJGSdWvzqIyOZ95YcR9mLPqKctH7Uo//I=
github.com/mostynb/go-grpc-compression v1.2.3/go.mod h1:AghIxF3P57umzqM9yz795+y1Vjs47Km/Y2FE6ouQ7Lg=
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: exporter/otlpexporter/internal/metadata/generated_status.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/component"
)
var (
Type = component.MustNewType("otlp_grpc")
DeprecatedType = component.MustNewType("otlp")
ScopeName = "go.opentelemetry.io/collector/exporter/otlpexporter"
)
const (
ProfilesStability = component.StabilityLevelAlpha
TracesStability = component.StabilityLevelStable
MetricsStability = component.StabilityLevelStable
LogsStability = component.StabilityLevelStable
)
================================================
FILE: exporter/otlpexporter/metadata.yaml
================================================
display_name: OTLP gRPC Exporter
type: otlp_grpc
deprecated_type: otlp
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: exporter
stability:
stable: [traces, metrics, logs]
alpha: [profiles]
distributions: [core, contrib, k8s, otlp]
tests:
config:
endpoint: otelcol:4317
================================================
FILE: exporter/otlpexporter/otlp.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlpexporter // import "go.opentelemetry.io/collector/exporter/otlpexporter"
import (
"context"
"errors"
"fmt"
"runtime"
"go.uber.org/zap"
"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/component"
"go.opentelemetry.io/collector/config/configgrpc"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exporterhelper"
"go.opentelemetry.io/collector/internal/statusutil"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/plog/plogotlp"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/pmetric/pmetricotlp"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/pprofile/pprofileotlp"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp"
)
type baseExporter struct {
// Input configuration.
config *Config
// gRPC clients and connection.
traceExporter ptraceotlp.GRPCClient
metricExporter pmetricotlp.GRPCClient
logExporter plogotlp.GRPCClient
profileExporter pprofileotlp.GRPCClient
clientConn *grpc.ClientConn
metadata metadata.MD
callOptions []grpc.CallOption
settings component.TelemetrySettings
// Default user-agent header.
userAgent string
}
func newExporter(cfg component.Config, set exporter.Settings) *baseExporter {
oCfg := cfg.(*Config)
userAgent := fmt.Sprintf("%s/%s (%s/%s)",
set.BuildInfo.Description, set.BuildInfo.Version, runtime.GOOS, runtime.GOARCH)
return &baseExporter{config: oCfg, settings: set.TelemetrySettings, userAgent: userAgent}
}
// start actually creates the gRPC connection. The client construction is deferred till this point as this
// is the only place we get hold of Extensions which are required to construct auth round tripper.
func (e *baseExporter) start(ctx context.Context, host component.Host) (err error) {
agentOpt := configgrpc.WithGrpcDialOption(grpc.WithUserAgent(e.userAgent))
if e.clientConn, err = e.config.ClientConfig.ToClientConn(ctx, host.GetExtensions(), e.settings, agentOpt); err != nil {
return err
}
e.traceExporter = ptraceotlp.NewGRPCClient(e.clientConn)
e.metricExporter = pmetricotlp.NewGRPCClient(e.clientConn)
e.logExporter = plogotlp.NewGRPCClient(e.clientConn)
e.profileExporter = pprofileotlp.NewGRPCClient(e.clientConn)
headers := map[string]string{}
for k, v := range e.config.ClientConfig.Headers.Iter {
headers[k] = string(v)
}
e.metadata = metadata.New(headers)
e.callOptions = []grpc.CallOption{
grpc.WaitForReady(e.config.ClientConfig.WaitForReady),
}
return err
}
func (e *baseExporter) shutdown(context.Context) error {
if e.clientConn != nil {
return e.clientConn.Close()
}
return nil
}
func (e *baseExporter) pushTraces(ctx context.Context, td ptrace.Traces) error {
if e.traceExporter == nil {
return errors.New("otlp exporter not started")
}
req := ptraceotlp.NewExportRequestFromTraces(td)
resp, respErr := e.traceExporter.Export(ctx, req, e.callOptions...)
if err := processError(respErr); err != nil {
return err
}
partialSuccess := resp.PartialSuccess()
if partialSuccess.ErrorMessage() != "" || partialSuccess.RejectedSpans() != 0 {
e.settings.Logger.Warn("Partial success response",
zap.String("message", resp.PartialSuccess().ErrorMessage()),
zap.Int64("dropped_spans", resp.PartialSuccess().RejectedSpans()),
)
}
return nil
}
func (e *baseExporter) pushMetrics(ctx context.Context, md pmetric.Metrics) error {
if e.metricExporter == nil {
return errors.New("otlp exporter not started")
}
req := pmetricotlp.NewExportRequestFromMetrics(md)
resp, respErr := e.metricExporter.Export(ctx, req, e.callOptions...)
if err := processError(respErr); err != nil {
return err
}
partialSuccess := resp.PartialSuccess()
if partialSuccess.ErrorMessage() != "" || partialSuccess.RejectedDataPoints() != 0 {
e.settings.Logger.Warn("Partial success response",
zap.String("message", resp.PartialSuccess().ErrorMessage()),
zap.Int64("dropped_data_points", resp.PartialSuccess().RejectedDataPoints()),
)
}
return nil
}
func (e *baseExporter) pushLogs(ctx context.Context, ld plog.Logs) error {
if e.logExporter == nil {
return errors.New("otlp exporter not started")
}
req := plogotlp.NewExportRequestFromLogs(ld)
resp, respErr := e.logExporter.Export(ctx, req, e.callOptions...)
if err := processError(respErr); err != nil {
return err
}
partialSuccess := resp.PartialSuccess()
if partialSuccess.ErrorMessage() != "" || partialSuccess.RejectedLogRecords() != 0 {
e.settings.Logger.Warn("Partial success response",
zap.String("message", resp.PartialSuccess().ErrorMessage()),
zap.Int64("dropped_log_records", resp.PartialSuccess().RejectedLogRecords()),
)
}
return nil
}
func (e *baseExporter) pushProfiles(ctx context.Context, td pprofile.Profiles) error {
if e.profileExporter == nil {
return errors.New("otlp exporter not started")
}
req := pprofileotlp.NewExportRequestFromProfiles(td)
resp, respErr := e.profileExporter.Export(ctx, req, e.callOptions...)
if err := processError(respErr); err != nil {
return err
}
partialSuccess := resp.PartialSuccess()
if partialSuccess.ErrorMessage() != "" || partialSuccess.RejectedProfiles() != 0 {
e.settings.Logger.Warn("Partial success response",
zap.String("message", resp.PartialSuccess().ErrorMessage()),
zap.Int64("dropped_profiles", resp.PartialSuccess().RejectedProfiles()),
)
}
return nil
}
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 a real error.
retryInfo := statusutil.GetRetryInfo(st)
if !shouldRetry(st.Code(), retryInfo) {
// It is not a retryable error, we should not retry.
return consumererror.NewPermanent(err)
}
// Check if server returned throttling information.
throttleDuration := retryInfo.GetRetryDelay().AsDuration()
if throttleDuration != 0 {
// We are throttled. Wait before retrying as requested by the server.
return exporterhelper.NewThrottleRetry(err, throttleDuration)
}
// Need to retry.
return err
}
func shouldRetry(code codes.Code, retryInfo *errdetails.RetryInfo) bool {
switch code {
case codes.Canceled,
codes.DeadlineExceeded,
codes.Aborted,
codes.OutOfRange,
codes.Unavailable,
codes.DataLoss:
// These are retryable errors.
return true
case codes.ResourceExhausted:
// Retry only if RetryInfo was supplied by the server.
// This indicates that the server can still recover from resource exhaustion.
return retryInfo != nil
}
// Don't retry on any other code.
return false
}
================================================
FILE: exporter/otlpexporter/otlp_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlpexporter
import (
"context"
"net"
"path/filepath"
"runtime"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/durationpb"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/config/configgrpc"
"go.opentelemetry.io/collector/config/configopaque"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/config/configtls"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exporterhelper"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/exporter/xexporter"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/plog/plogotlp"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/pmetric/pmetricotlp"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/pprofile/pprofileotlp"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp"
"go.opentelemetry.io/collector/pdata/testdata"
)
type mockReceiver struct {
srv *grpc.Server
requestCount *atomic.Int64
totalItems *atomic.Int64
mux sync.Mutex
metadata metadata.MD
exportError error
}
func (r *mockReceiver) getMetadata() metadata.MD {
r.mux.Lock()
defer r.mux.Unlock()
return r.metadata
}
func (r *mockReceiver) setExportError(err error) {
r.mux.Lock()
defer r.mux.Unlock()
r.exportError = err
}
var _ ptraceotlp.GRPCServer = &mockTracesReceiver{}
type mockTracesReceiver struct {
ptraceotlp.UnimplementedGRPCServer
mockReceiver
exportResponse func() ptraceotlp.ExportResponse
lastRequest ptrace.Traces
}
func (r *mockTracesReceiver) Export(ctx context.Context, req ptraceotlp.ExportRequest) (ptraceotlp.ExportResponse, error) {
r.requestCount.Add(1)
td := req.Traces()
r.totalItems.Add(int64(td.SpanCount()))
r.mux.Lock()
defer r.mux.Unlock()
r.lastRequest = td
r.metadata, _ = metadata.FromIncomingContext(ctx)
return r.exportResponse(), r.exportError
}
func (r *mockTracesReceiver) getLastRequest() ptrace.Traces {
r.mux.Lock()
defer r.mux.Unlock()
return r.lastRequest
}
func (r *mockTracesReceiver) setExportResponse(fn func() ptraceotlp.ExportResponse) {
r.mux.Lock()
defer r.mux.Unlock()
r.exportResponse = fn
}
func otlpTracesReceiverOnGRPCServer(ln net.Listener, useTLS bool) (*mockTracesReceiver, error) {
sopts := []grpc.ServerOption{}
if useTLS {
_, currentFile, _, _ := runtime.Caller(0)
basepath := filepath.Dir(currentFile)
certpath := filepath.Join(basepath, filepath.Join("testdata", "test_cert.pem"))
keypath := filepath.Join(basepath, filepath.Join("testdata", "test_key.pem"))
creds, err := credentials.NewServerTLSFromFile(certpath, keypath)
if err != nil {
return nil, err
}
sopts = append(sopts, grpc.Creds(creds))
}
rcv := &mockTracesReceiver{
mockReceiver: mockReceiver{
srv: grpc.NewServer(sopts...),
requestCount: new(atomic.Int64),
totalItems: new(atomic.Int64),
},
exportResponse: ptraceotlp.NewExportResponse,
}
// Now run it as a gRPC server
ptraceotlp.RegisterGRPCServer(rcv.srv, rcv)
go func() {
_ = rcv.srv.Serve(ln)
}()
return rcv, nil
}
var _ plogotlp.GRPCServer = &mockLogsReceiver{}
type mockLogsReceiver struct {
plogotlp.UnimplementedGRPCServer
mockReceiver
exportResponse func() plogotlp.ExportResponse
lastRequest plog.Logs
}
func (r *mockLogsReceiver) Export(ctx context.Context, req plogotlp.ExportRequest) (plogotlp.ExportResponse, error) {
r.requestCount.Add(1)
ld := req.Logs()
r.totalItems.Add(int64(ld.LogRecordCount()))
r.mux.Lock()
defer r.mux.Unlock()
r.lastRequest = ld
r.metadata, _ = metadata.FromIncomingContext(ctx)
return r.exportResponse(), r.exportError
}
func (r *mockLogsReceiver) getLastRequest() plog.Logs {
r.mux.Lock()
defer r.mux.Unlock()
return r.lastRequest
}
func (r *mockLogsReceiver) setExportResponse(fn func() plogotlp.ExportResponse) {
r.mux.Lock()
defer r.mux.Unlock()
r.exportResponse = fn
}
func otlpLogsReceiverOnGRPCServer(ln net.Listener) *mockLogsReceiver {
rcv := &mockLogsReceiver{
mockReceiver: mockReceiver{
srv: grpc.NewServer(),
requestCount: new(atomic.Int64),
totalItems: new(atomic.Int64),
},
exportResponse: plogotlp.NewExportResponse,
}
// Now run it as a gRPC server
plogotlp.RegisterGRPCServer(rcv.srv, rcv)
go func() {
_ = rcv.srv.Serve(ln)
}()
return rcv
}
var _ pmetricotlp.GRPCServer = &mockMetricsReceiver{}
type mockMetricsReceiver struct {
pmetricotlp.UnimplementedGRPCServer
mockReceiver
exportResponse func() pmetricotlp.ExportResponse
lastRequest pmetric.Metrics
}
func (r *mockMetricsReceiver) Export(ctx context.Context, req pmetricotlp.ExportRequest) (pmetricotlp.ExportResponse, error) {
md := req.Metrics()
r.requestCount.Add(1)
r.totalItems.Add(int64(md.DataPointCount()))
r.mux.Lock()
defer r.mux.Unlock()
r.lastRequest = md
r.metadata, _ = metadata.FromIncomingContext(ctx)
return r.exportResponse(), r.exportError
}
func (r *mockMetricsReceiver) getLastRequest() pmetric.Metrics {
r.mux.Lock()
defer r.mux.Unlock()
return r.lastRequest
}
func (r *mockMetricsReceiver) setExportResponse(fn func() pmetricotlp.ExportResponse) {
r.mux.Lock()
defer r.mux.Unlock()
r.exportResponse = fn
}
func otlpMetricsReceiverOnGRPCServer(ln net.Listener) *mockMetricsReceiver {
rcv := &mockMetricsReceiver{
mockReceiver: mockReceiver{
srv: grpc.NewServer(),
requestCount: new(atomic.Int64),
totalItems: new(atomic.Int64),
},
exportResponse: pmetricotlp.NewExportResponse,
}
// Now run it as a gRPC server
pmetricotlp.RegisterGRPCServer(rcv.srv, rcv)
go func() {
_ = rcv.srv.Serve(ln)
}()
return rcv
}
type mockProfilesReceiver struct {
pprofileotlp.UnimplementedGRPCServer
mockReceiver
exportResponse func() pprofileotlp.ExportResponse
lastRequest pprofile.Profiles
}
func (r *mockProfilesReceiver) Export(ctx context.Context, req pprofileotlp.ExportRequest) (pprofileotlp.ExportResponse, error) {
r.requestCount.Add(1)
td := req.Profiles()
r.totalItems.Add(int64(td.SampleCount()))
r.mux.Lock()
defer r.mux.Unlock()
r.lastRequest = td
r.metadata, _ = metadata.FromIncomingContext(ctx)
return r.exportResponse(), r.exportError
}
func (r *mockProfilesReceiver) getLastRequest() pprofile.Profiles {
r.mux.Lock()
defer r.mux.Unlock()
return r.lastRequest
}
func (r *mockProfilesReceiver) setExportResponse(fn func() pprofileotlp.ExportResponse) {
r.mux.Lock()
defer r.mux.Unlock()
r.exportResponse = fn
}
func otlpProfilesReceiverOnGRPCServer(ln net.Listener, useTLS bool) (*mockProfilesReceiver, error) {
sopts := []grpc.ServerOption{}
if useTLS {
_, currentFile, _, _ := runtime.Caller(0)
basepath := filepath.Dir(currentFile)
certpath := filepath.Join(basepath, filepath.Join("testdata", "test_cert.pem"))
keypath := filepath.Join(basepath, filepath.Join("testdata", "test_key.pem"))
creds, err := credentials.NewServerTLSFromFile(certpath, keypath)
if err != nil {
return nil, err
}
sopts = append(sopts, grpc.Creds(creds))
}
rcv := &mockProfilesReceiver{
mockReceiver: mockReceiver{
requestCount: &atomic.Int64{},
totalItems: &atomic.Int64{},
srv: grpc.NewServer(sopts...),
},
exportResponse: pprofileotlp.NewExportResponse,
}
// Now run it as a gRPC server
pprofileotlp.RegisterGRPCServer(rcv.srv, rcv)
go func() {
_ = rcv.srv.Serve(ln)
}()
return rcv, nil
}
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, _ := otlpTracesReceiverOnGRPCServer(ln, false)
// Also closes the connection.
defer rcv.srv.GracefulStop()
// 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 any errors.
cfg.QueueConfig = configoptional.None[exporterhelper.QueueBatchConfig]()
cfg.ClientConfig = configgrpc.ClientConfig{
Endpoint: ln.Addr().String(),
TLS: configtls.ClientConfig{
Insecure: true,
},
Headers: configopaque.MapList{
{Name: "header", Value: "header-value"},
},
}
set := exportertest.NewNopSettings(factory.Type())
set.BuildInfo.Description = "Collector"
set.BuildInfo.Version = "1.2.3test"
// For testing the "Partial success" warning.
logger, observed := observer.New(zap.DebugLevel)
set.Logger = zap.New(logger)
exp, err := factory.CreateTraces(context.Background(), set, cfg)
require.NoError(t, err)
require.NotNil(t, exp)
defer func() {
assert.NoError(t, exp.Shutdown(context.Background()))
}()
host := componenttest.NewNopHost()
require.NoError(t, exp.Start(context.Background(), host))
// Ensure that initially there is no data in the receiver.
assert.EqualValues(t, 0, rcv.requestCount.Load())
// Send empty trace.
td := ptrace.NewTraces()
require.NoError(t, exp.ConsumeTraces(context.Background(), td))
// Wait until it is received.
assert.Eventually(t, func() bool {
return rcv.requestCount.Load() > 0
}, 10*time.Second, 5*time.Millisecond)
// Ensure it was received empty.
assert.EqualValues(t, 0, rcv.totalItems.Load())
// A trace with 2 spans.
td = testdata.GenerateTraces(2)
err = exp.ConsumeTraces(context.Background(), td)
require.NoError(t, err)
// Wait until it is received.
assert.Eventually(t, func() bool {
return rcv.requestCount.Load() > 1
}, 10*time.Second, 5*time.Millisecond)
expectedHeader := []string{"header-value"}
// Verify received span.
assert.EqualValues(t, 2, rcv.totalItems.Load())
assert.EqualValues(t, 2, rcv.requestCount.Load())
assert.Equal(t, td, rcv.getLastRequest())
md := rcv.getMetadata()
require.Equal(t, expectedHeader, md.Get("header"))
require.Len(t, md.Get("User-Agent"), 1)
require.Contains(t, md.Get("User-Agent")[0], "Collector/1.2.3test")
// Return partial success
rcv.setExportResponse(func() ptraceotlp.ExportResponse {
response := ptraceotlp.NewExportResponse()
partialSuccess := response.PartialSuccess()
partialSuccess.SetErrorMessage("Some spans were not ingested")
partialSuccess.SetRejectedSpans(1)
return response
})
// A request with 2 Trace entries.
td = testdata.GenerateTraces(2)
err = exp.ConsumeTraces(context.Background(), td)
require.NoError(t, err)
assert.Len(t, observed.FilterLevelExact(zap.WarnLevel).All(), 1)
assert.Contains(t, observed.FilterLevelExact(zap.WarnLevel).All()[0].Message, "Partial success")
}
func TestSendTracesWhenEndpointHasHTTPScheme(t *testing.T) {
tests := []struct {
name string
useTLS bool
scheme string
gRPCClientSettings configgrpc.ClientConfig
}{
{
name: "Use https scheme",
useTLS: true,
scheme: "https://",
gRPCClientSettings: configgrpc.ClientConfig{},
},
{
name: "Use http scheme",
useTLS: false,
scheme: "http://",
gRPCClientSettings: configgrpc.ClientConfig{
TLS: configtls.ClientConfig{
Insecure: true,
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(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, err := otlpTracesReceiverOnGRPCServer(ln, test.useTLS)
require.NoError(t, err, "Failed to start mock OTLP receiver")
// Also closes the connection.
defer rcv.srv.GracefulStop()
// Start an OTLP exporter and point to the receiver.
factory := NewFactory()
cfg := factory.CreateDefaultConfig().(*Config)
cfg.ClientConfig = test.gRPCClientSettings
cfg.ClientConfig.Endpoint = test.scheme + ln.Addr().String()
if test.useTLS {
cfg.ClientConfig.TLS.InsecureSkipVerify = true
}
set := exportertest.NewNopSettings(factory.Type())
exp, err := factory.CreateTraces(context.Background(), set, cfg)
require.NoError(t, err)
require.NotNil(t, exp)
defer func() {
assert.NoError(t, exp.Shutdown(context.Background()))
}()
host := componenttest.NewNopHost()
require.NoError(t, exp.Start(context.Background(), host))
// Ensure that initially there is no data in the receiver.
assert.EqualValues(t, 0, rcv.requestCount.Load())
// Send empty trace.
td := ptrace.NewTraces()
require.NoError(t, exp.ConsumeTraces(context.Background(), td))
// Wait until it is received.
assert.Eventually(t, func() bool {
return rcv.requestCount.Load() > 0
}, 10*time.Second, 5*time.Millisecond)
// Ensure it was received empty.
assert.EqualValues(t, 0, rcv.totalItems.Load())
})
}
}
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)
// Disable queuing to ensure that we execute the request when calling ConsumeMetrics
// otherwise we will not see any errors.
cfg.QueueConfig = configoptional.None[exporterhelper.QueueBatchConfig]()
cfg.ClientConfig = configgrpc.ClientConfig{
Endpoint: ln.Addr().String(),
TLS: configtls.ClientConfig{
Insecure: true,
},
Headers: configopaque.MapList{
{Name: "header", Value: "header-value"},
},
}
set := exportertest.NewNopSettings(factory.Type())
set.BuildInfo.Description = "Collector"
set.BuildInfo.Version = "1.2.3test"
// For testing the "Partial success" warning.
logger, observed := observer.New(zap.DebugLevel)
set.Logger = zap.New(logger)
exp, err := factory.CreateMetrics(context.Background(), set, cfg)
require.NoError(t, err)
require.NotNil(t, exp)
defer func() {
assert.NoError(t, exp.Shutdown(context.Background()))
}()
host := componenttest.NewNopHost()
require.NoError(t, exp.Start(context.Background(), host))
// Ensure that initially there is no data in the receiver.
assert.EqualValues(t, 0, rcv.requestCount.Load())
// Send empty metric.
md := pmetric.NewMetrics()
require.NoError(t, exp.ConsumeMetrics(context.Background(), md))
// Wait until it is received.
assert.Eventually(t, func() bool {
return rcv.requestCount.Load() > 0
}, 10*time.Second, 5*time.Millisecond)
// Ensure it was received empty.
assert.EqualValues(t, 0, rcv.totalItems.Load())
// Send two metrics.
md = testdata.GenerateMetrics(2)
err = exp.ConsumeMetrics(context.Background(), md)
require.NoError(t, err)
// Wait until it is received.
assert.Eventually(t, func() bool {
return rcv.requestCount.Load() > 1
}, 10*time.Second, 5*time.Millisecond)
expectedHeader := []string{"header-value"}
// Verify received metrics.
assert.EqualValues(t, 2, rcv.requestCount.Load())
assert.EqualValues(t, 4, rcv.totalItems.Load())
assert.Equal(t, md, rcv.getLastRequest())
mdata := rcv.getMetadata()
require.Equal(t, expectedHeader, mdata.Get("header"))
require.Len(t, mdata.Get("User-Agent"), 1)
require.Contains(t, mdata.Get("User-Agent")[0], "Collector/1.2.3test")
st := status.New(codes.InvalidArgument, "Invalid argument")
rcv.setExportError(st.Err())
// Send two metrics..
md = testdata.GenerateMetrics(2)
err = exp.ConsumeMetrics(context.Background(), md)
require.Error(t, err)
rcv.setExportError(nil)
// Return partial success
rcv.setExportResponse(func() pmetricotlp.ExportResponse {
response := pmetricotlp.NewExportResponse()
partialSuccess := response.PartialSuccess()
partialSuccess.SetErrorMessage("Some data points were not ingested")
partialSuccess.SetRejectedDataPoints(1)
return response
})
// Send two metrics.
md = testdata.GenerateMetrics(2)
require.NoError(t, exp.ConsumeMetrics(context.Background(), md))
assert.Len(t, observed.FilterLevelExact(zap.WarnLevel).All(), 1)
assert.Contains(t, observed.FilterLevelExact(zap.WarnLevel).All()[0].Message, "Partial success")
}
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.QueueConfig = configoptional.None[exporterhelper.QueueBatchConfig]()
cfg.ClientConfig = configgrpc.ClientConfig{
Endpoint: ln.Addr().String(),
TLS: configtls.ClientConfig{
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,
}
set := exportertest.NewNopSettings(factory.Type())
exp, err := factory.CreateTraces(context.Background(), set, cfg)
require.NoError(t, err)
require.NotNil(t, exp)
defer func() {
assert.NoError(t, exp.Shutdown(context.Background()))
}()
host := componenttest.NewNopHost()
require.NoError(t, exp.Start(context.Background(), host))
// A trace with 2 spans.
td := testdata.GenerateTraces(2)
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
require.Error(t, exp.ConsumeTraces(ctx, td))
assert.Equal(t, context.DeadlineExceeded, ctx.Err())
cancel()
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
require.Error(t, exp.ConsumeTraces(ctx, td))
assert.Equal(t, context.DeadlineExceeded, ctx.Err())
cancel()
startServerAndMakeRequest(t, exp, td, ln)
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
require.Error(t, exp.ConsumeTraces(ctx, td))
assert.Equal(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)
require.Error(t, exp.ConsumeTraces(ctx, td))
assert.Equal(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.ClientConfig = configgrpc.ClientConfig{
Endpoint: ln.Addr().String(),
TLS: configtls.ClientConfig{
Insecure: true,
},
}
set := exportertest.NewNopSettings(factory.Type())
exp, err := factory.CreateTraces(context.Background(), set, cfg)
require.NoError(t, err)
require.NotNil(t, exp)
defer func() {
assert.NoError(t, exp.Shutdown(context.Background()))
}()
host := componenttest.NewNopHost()
require.NoError(t, exp.Start(context.Background(), host))
// A trace with 2 spans.
td := testdata.GenerateTraces(2)
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, _ := otlpTracesReceiverOnGRPCServer(ln, false)
defer rcv.srv.GracefulStop()
// Wait until one of the conditions below triggers.
select {
case <-ctx.Done():
t.Fail()
case <-done:
require.NoError(t, ctx.Err())
}
cancel()
}
func TestSendTracesOnResourceExhaustion(t *testing.T) {
ln, err := net.Listen("tcp", "localhost:")
require.NoError(t, err)
rcv, _ := otlpTracesReceiverOnGRPCServer(ln, false)
rcv.setExportError(status.Error(codes.ResourceExhausted, "resource exhausted"))
defer rcv.srv.GracefulStop()
factory := NewFactory()
cfg := factory.CreateDefaultConfig().(*Config)
cfg.RetryConfig.InitialInterval = 0
cfg.ClientConfig = configgrpc.ClientConfig{
Endpoint: ln.Addr().String(),
TLS: configtls.ClientConfig{
Insecure: true,
},
}
set := exportertest.NewNopSettings(factory.Type())
exp, err := factory.CreateTraces(context.Background(), set, cfg)
require.NoError(t, err)
require.NotNil(t, exp)
defer func() {
assert.NoError(t, exp.Shutdown(context.Background()))
}()
host := componenttest.NewNopHost()
require.NoError(t, exp.Start(context.Background(), host))
assert.EqualValues(t, 0, rcv.requestCount.Load())
td := ptrace.NewTraces()
require.NoError(t, exp.ConsumeTraces(context.Background(), td))
assert.Never(t, func() bool {
return rcv.requestCount.Load() > 1
}, 1*time.Second, 5*time.Millisecond, "Should not retry if RetryInfo is not included into status details by the server.")
rcv.requestCount.Swap(0)
st := status.New(codes.ResourceExhausted, "resource exhausted")
st, _ = st.WithDetails(&errdetails.RetryInfo{
RetryDelay: durationpb.New(100 * time.Millisecond),
})
rcv.setExportError(st.Err())
require.NoError(t, exp.ConsumeTraces(context.Background(), td))
assert.Eventually(t, func() bool {
return rcv.requestCount.Load() > 1
}, 10*time.Second, 5*time.Millisecond, "Should retry if RetryInfo is included into status details by the server.")
}
func startServerAndMakeRequest(t *testing.T, exp exporter.Traces, td ptrace.Traces, ln net.Listener) {
rcv, _ := otlpTracesReceiverOnGRPCServer(ln, false)
defer rcv.srv.GracefulStop()
// Ensure that initially there is no data in the receiver.
assert.EqualValues(t, 0, rcv.requestCount.Load())
// Clone the request and store as expected.
expectedData := ptrace.NewTraces()
td.CopyTo(expectedData)
// Resend the request, this should succeed.
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
require.NoError(t, exp.ConsumeTraces(ctx, td))
cancel()
// Wait until it is received.
assert.Eventually(t, func() bool {
return rcv.requestCount.Load() > 0
}, 10*time.Second, 5*time.Millisecond)
// Verify received span.
assert.EqualValues(t, 2, rcv.totalItems.Load())
assert.Equal(t, expectedData, 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)
// Disable queuing to ensure that we execute the request when calling ConsumeLogs
// otherwise we will not see any errors.
cfg.QueueConfig = configoptional.None[exporterhelper.QueueBatchConfig]()
cfg.ClientConfig = configgrpc.ClientConfig{
Endpoint: ln.Addr().String(),
TLS: configtls.ClientConfig{
Insecure: true,
},
}
set := exportertest.NewNopSettings(factory.Type())
set.BuildInfo.Description = "Collector"
set.BuildInfo.Version = "1.2.3test"
// For testing the "Partial success" warning.
logger, observed := observer.New(zap.DebugLevel)
set.Logger = zap.New(logger)
exp, err := factory.CreateLogs(context.Background(), set, cfg)
require.NoError(t, err)
require.NotNil(t, exp)
defer func() {
assert.NoError(t, exp.Shutdown(context.Background()))
}()
host := componenttest.NewNopHost()
require.NoError(t, exp.Start(context.Background(), host))
// Ensure that initially there is no data in the receiver.
assert.EqualValues(t, 0, rcv.requestCount.Load())
// Send empty request.
ld := plog.NewLogs()
require.NoError(t, exp.ConsumeLogs(context.Background(), ld))
// Wait until it is received.
assert.Eventually(t, func() bool {
return rcv.requestCount.Load() > 0
}, 10*time.Second, 5*time.Millisecond)
// Ensure it was received empty.
assert.EqualValues(t, 0, rcv.totalItems.Load())
// A request with 2 log entries.
ld = testdata.GenerateLogs(2)
err = exp.ConsumeLogs(context.Background(), ld)
require.NoError(t, err)
// Wait until it is received.
assert.Eventually(t, func() bool {
return rcv.requestCount.Load() > 1
}, 10*time.Second, 5*time.Millisecond)
// Verify received logs.
assert.EqualValues(t, 2, rcv.requestCount.Load())
assert.EqualValues(t, 2, rcv.totalItems.Load())
assert.Equal(t, ld, rcv.getLastRequest())
md := rcv.getMetadata()
require.Len(t, md.Get("User-Agent"), 1)
require.Contains(t, md.Get("User-Agent")[0], "Collector/1.2.3test")
st := status.New(codes.InvalidArgument, "Invalid argument")
rcv.setExportError(st.Err())
// A request with 2 log entries.
ld = testdata.GenerateLogs(2)
err = exp.ConsumeLogs(context.Background(), ld)
require.Error(t, err)
rcv.setExportError(nil)
// Return partial success
rcv.setExportResponse(func() plogotlp.ExportResponse {
response := plogotlp.NewExportResponse()
partialSuccess := response.PartialSuccess()
partialSuccess.SetErrorMessage("Some log records were not ingested")
partialSuccess.SetRejectedLogRecords(1)
return response
})
// A request with 2 log entries.
ld = testdata.GenerateLogs(2)
err = exp.ConsumeLogs(context.Background(), ld)
require.NoError(t, err)
assert.Len(t, observed.FilterLevelExact(zap.WarnLevel).All(), 1)
assert.Contains(t, observed.FilterLevelExact(zap.WarnLevel).All()[0].Message, "Partial success")
}
func TestSendProfiles(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, _ := otlpProfilesReceiverOnGRPCServer(ln, false)
// Also closes the connection.
defer rcv.srv.GracefulStop()
// 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 ConsumeProfiles
// otherwise we will not see any errors.
cfg.QueueConfig = configoptional.None[exporterhelper.QueueBatchConfig]()
cfg.ClientConfig = configgrpc.ClientConfig{
Endpoint: ln.Addr().String(),
TLS: configtls.ClientConfig{
Insecure: true,
},
Headers: configopaque.MapList{
{Name: "header", Value: "header-value"},
},
}
set := exportertest.NewNopSettings(factory.Type())
set.BuildInfo.Description = "Collector"
set.BuildInfo.Version = "1.2.3test"
// For testing the "Partial success" warning.
logger, observed := observer.New(zap.DebugLevel)
set.Logger = zap.New(logger)
exp, err := factory.(xexporter.Factory).CreateProfiles(context.Background(), set, cfg)
require.NoError(t, err)
require.NotNil(t, exp)
defer func() {
require.NoError(t, exp.Shutdown(context.Background()))
}()
host := componenttest.NewNopHost()
require.NoError(t, exp.Start(context.Background(), host))
// Ensure that initially there is no data in the receiver.
assert.EqualValues(t, 0, rcv.requestCount.Load())
// Send empty profile.
td := pprofile.NewProfiles()
require.NoError(t, exp.ConsumeProfiles(context.Background(), td))
// Wait until it is received.
assert.Eventually(t, func() bool {
return rcv.requestCount.Load() > 0
}, 10*time.Second, 5*time.Millisecond)
// Ensure it was received empty.
assert.EqualValues(t, 0, rcv.totalItems.Load())
// A request with 2 profiles.
td = testdata.GenerateProfiles(2)
err = exp.ConsumeProfiles(context.Background(), td)
require.NoError(t, err)
// Wait until it is received.
assert.Eventually(t, func() bool {
return rcv.requestCount.Load() > 1
}, 10*time.Second, 5*time.Millisecond)
expectedHeader := []string{"header-value"}
// Verify received span.
assert.EqualValues(t, 2, rcv.totalItems.Load())
assert.EqualValues(t, 2, rcv.requestCount.Load())
assert.Equal(t, td, rcv.getLastRequest())
md := rcv.getMetadata()
require.Equal(t, expectedHeader, md.Get("header"))
require.Len(t, md.Get("User-Agent"), 1)
require.Contains(t, md.Get("User-Agent")[0], "Collector/1.2.3test")
// Return partial success
rcv.setExportResponse(func() pprofileotlp.ExportResponse {
response := pprofileotlp.NewExportResponse()
partialSuccess := response.PartialSuccess()
partialSuccess.SetErrorMessage("Some spans were not ingested")
partialSuccess.SetRejectedProfiles(1)
return response
})
// A request with 2 Profile entries.
td = testdata.GenerateProfiles(2)
err = exp.ConsumeProfiles(context.Background(), td)
require.NoError(t, err)
assert.Len(t, observed.FilterLevelExact(zap.WarnLevel).All(), 1)
assert.Contains(t, observed.FilterLevelExact(zap.WarnLevel).All()[0].Message, "Partial success")
}
func TestPushTracesBeforeStart(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
set := exportertest.NewNopSettings(factory.Type())
exp := newExporter(cfg, set)
err := exp.pushTraces(context.Background(), ptrace.NewTraces())
require.Error(t, err)
assert.Contains(t, err.Error(), "not started")
}
func TestPushMetricsBeforeStart(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
set := exportertest.NewNopSettings(factory.Type())
exp := newExporter(cfg, set)
err := exp.pushMetrics(context.Background(), pmetric.NewMetrics())
require.Error(t, err)
assert.Contains(t, err.Error(), "not started")
}
func TestPushLogsBeforeStart(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
set := exportertest.NewNopSettings(factory.Type())
exp := newExporter(cfg, set)
err := exp.pushLogs(context.Background(), plog.NewLogs())
require.Error(t, err)
assert.Contains(t, err.Error(), "not started")
}
func TestPushProfilesBeforeStart(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
set := exportertest.NewNopSettings(factory.Type())
exp := newExporter(cfg, set)
err := exp.pushProfiles(context.Background(), pprofile.NewProfiles())
require.Error(t, err)
assert.Contains(t, err.Error(), "not started")
}
func TestSendProfilesWhenEndpointHasHTTPScheme(t *testing.T) {
tests := []struct {
name string
useTLS bool
scheme string
gRPCClientSettings configgrpc.ClientConfig
}{
{
name: "Use https scheme",
useTLS: true,
scheme: "https://",
gRPCClientSettings: configgrpc.ClientConfig{},
},
{
name: "Use http scheme",
useTLS: false,
scheme: "http://",
gRPCClientSettings: configgrpc.ClientConfig{
TLS: configtls.ClientConfig{
Insecure: true,
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(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, err := otlpProfilesReceiverOnGRPCServer(ln, test.useTLS)
require.NoError(t, err, "Failed to start mock OTLP receiver")
// Also closes the connection.
defer rcv.srv.GracefulStop()
// Start an OTLP exporter and point to the receiver.
factory := NewFactory()
cfg := factory.CreateDefaultConfig().(*Config)
cfg.ClientConfig = test.gRPCClientSettings
cfg.ClientConfig.Endpoint = test.scheme + ln.Addr().String()
if test.useTLS {
cfg.ClientConfig.TLS.InsecureSkipVerify = true
}
set := exportertest.NewNopSettings(factory.Type())
exp, err := factory.(xexporter.Factory).CreateProfiles(context.Background(), set, cfg)
require.NoError(t, err)
require.NotNil(t, exp)
defer func() {
require.NoError(t, exp.Shutdown(context.Background()))
}()
host := componenttest.NewNopHost()
require.NoError(t, exp.Start(context.Background(), host))
// Ensure that initially there is no data in the receiver.
assert.EqualValues(t, 0, rcv.requestCount.Load())
// Send empty profile.
td := pprofile.NewProfiles()
require.NoError(t, exp.ConsumeProfiles(context.Background(), td))
// Wait until it is received.
assert.Eventually(t, func() bool {
return rcv.requestCount.Load() > 0
}, 10*time.Second, 5*time.Millisecond)
// Ensure it was received empty.
assert.EqualValues(t, 0, rcv.totalItems.Load())
})
}
}
================================================
FILE: exporter/otlpexporter/testdata/config.yaml
================================================
endpoint: "1.2.3.4:1234"
compression: "gzip"
tls:
ca_file: /var/lib/mycert.pem
timeout: 10s
sending_queue:
enabled: true
sizer: "items"
num_consumers: 2
queue_size: 100000
batch:
flush_timeout: 200ms
min_size: 1000
max_size: 10000
retry_on_failure:
enabled: true
initial_interval: 10s
randomization_factor: 0.7
multiplier: 1.3
max_interval: 60s
max_elapsed_time: 10m
auth:
authenticator: nop
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"
================================================
FILE: exporter/otlpexporter/testdata/default-batch.yaml
================================================
endpoint: "1.2.3.4:1234"
timeout: 10s
sending_queue:
batch:
================================================
FILE: exporter/otlpexporter/testdata/invalid_configs.yaml
================================================
no_endpoint:
timeout: 10s
sending_queue:
enabled: true
num_consumers: 2
queue_size: 10
retry_on_failure:
enabled: true
initial_interval: 10s
randomization_factor: 0.7
multiplier: 1.3
max_interval: 60s
max_elapsed_time: 10m
https_endpoint:
endpoint: https://
http_endpoint:
endpoint: http://
timeout: 10s
sending_queue:
enabled: true
num_consumers: 2
queue_size: 10
retry_on_failure:
enabled: true
initial_interval: 10s
randomization_factor: 0.7
multiplier: 1.3
max_interval: 60s
max_elapsed_time: 10m
invalid_timeout:
endpoint: example.com:443
timeout: -5s
sending_queue:
enabled: true
num_consumers: 2
queue_size: 10
retry_on_failure:
enabled: true
initial_interval: 10s
randomization_factor: 0.7
multiplier: 1.3
max_interval: 60s
max_elapsed_time: 10m
invalid_retry:
endpoint: example.com:443
timeout: 30s
sending_queue:
enabled: true
num_consumers: 2
queue_size: 10
retry_on_failure:
enabled: true
initial_interval: 10s
randomization_factor: -5
multiplier: 1.3
max_interval: 60s
max_elapsed_time: 10m
invalid_tls:
tls:
min_version: asd
endpoint: example.com:443
timeout: 10s
sending_queue:
enabled: true
num_consumers: 2
queue_size: 10
retry_on_failure:
enabled: true
initial_interval: 10s
randomization_factor: 0.7
multiplier: 1.3
max_interval: 60s
max_elapsed_time: 10m
missing_port:
endpoint: example.com
timeout: 10s
sending_queue:
enabled: true
num_consumers: 2
queue_size: 10
retry_on_failure:
enabled: true
initial_interval: 10s
randomization_factor: 0.7
multiplier: 1.3
max_interval: 60s
max_elapsed_time: 10m
invalid_port:
endpoint: example.com:port
timeout: 10s
sending_queue:
enabled: true
num_consumers: 2
queue_size: 10
retry_on_failure:
enabled: true
initial_interval: 10s
randomization_factor: 0.7
multiplier: 1.3
max_interval: 60s
max_elapsed_time: 10m
invalid_unix_socket:
endpoint: unix://
timeout: 10s
sending_queue:
enabled: true
num_consumers: 2
queue_size: 10
retry_on_failure:
enabled: true
initial_interval: 10s
randomization_factor: 0.7
multiplier: 1.3
max_interval: 60s
max_elapsed_time: 10m
================================================
FILE: exporter/otlpexporter/testdata/test_cert.pem
================================================
-----BEGIN CERTIFICATE-----
MIICpDCCAYwCCQC5oaFsqLW3GTANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAls
b2NhbGhvc3QwHhcNMjEwNzE0MDAxMzU2WhcNMzEwNzEyMDAxMzU2WjAUMRIwEAYD
VQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDO
mKaE1qg5VLMwaUnSzufT23rRJFbuy/HDXwsH63yZVSsISQkGjkBYBgrqAMtVnsI/
l4gXtBWkZtJFs68Sbo9ps3W0PdB5+d12R5NUNA1rkZtx3jtEN33dpGhifug/TIZe
7Zr0G1z6gNoaEezk0Jpg4KsH7QpIeHPRhIZMyWeqddgD/qL4/ukaU4NOORuF3WoT
oo2LpI3jUq66mz2N2Inq0V/OX7BYB4Ur6EtjWh2baiUuw9fq+oLUlgZd6ypnugC/
+YfgYqvWtRntmEr0Z+O4Kz81P2IpH/0h1RFhWyK6thVGa9cx6aseCp3V2cMXfGfc
z4n3Uvz87v+bZvGbcse/AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAAlvNBNoqXUQ
ohR0eozIHGeJ94U7WK5zXf2NSvmRlwHzHXvUq6GKd+8Bv1foMjI6OpSOZmjtRGsc
rWET1WjSyQddRfqYazhWp1IyYu5LfATwPS+RXJAkWixKVfG+Ta2x6u+aT/bSZwEg
NwRerc6pyqv5UG8Z7Pe1kAxbgOwZv5KXAewIgTSbEkmIp1Dg8GhGeWD5pjYNCkJV
Na2KMAUWP3PeQzdSBKmBNpsRUALuSTxb5u7pl+PA7FLInTtDeyZn8xpO1GPBhbJE
trDbmTbj5YexOXEaQtGtZ6fwRw2jnUm8nqtXozxIomnVTBO8vLmZAUgyJ71trRw0
gE9tH5Ndlug=
-----END CERTIFICATE-----
================================================
FILE: exporter/otlpexporter/testdata/test_key.pem
================================================
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDOmKaE1qg5VLMw
aUnSzufT23rRJFbuy/HDXwsH63yZVSsISQkGjkBYBgrqAMtVnsI/l4gXtBWkZtJF
s68Sbo9ps3W0PdB5+d12R5NUNA1rkZtx3jtEN33dpGhifug/TIZe7Zr0G1z6gNoa
Eezk0Jpg4KsH7QpIeHPRhIZMyWeqddgD/qL4/ukaU4NOORuF3WoToo2LpI3jUq66
mz2N2Inq0V/OX7BYB4Ur6EtjWh2baiUuw9fq+oLUlgZd6ypnugC/+YfgYqvWtRnt
mEr0Z+O4Kz81P2IpH/0h1RFhWyK6thVGa9cx6aseCp3V2cMXfGfcz4n3Uvz87v+b
ZvGbcse/AgMBAAECggEADeR39iDVKR3H+u5pl3JwZm+w35V4/w/ZzxB6FmtAcrMm
dKUspTM1onWtkDTDd5t4ZnxTG3zxo5+Cbkt571xd6na16Ivrk/g4aza+8n+Zk200
LcEK7ThqD1h56H2uMmt78bA6pkWcx/+YKv6flndsmi0hcyP+eAcZirJFsa4teWna
P6rhI9zThc9OcecqGZIlmzJQ4cLbIO86QqkWW6yjKYg6riOb2g+i3e97ZngMCTcV
lni+sksLlXBNKPqh1AkiUFe4pInRBh4LGQ5rNSYswEqlQY0iW0u4Hs3HNou0On+8
1T8m5wzKQ+23AN+vVRJ/MHssQiB/TPK92jXVgEz6eQKBgQD2GEb7NzDIxsAQZBQo
tt3jYitNcAEqMWeT7wxCMMue4wIrT6Fp6NuG5NMVqLglzx72m6TXg7YzZxPrAnlH
jblWI4sxwVC8BjjYyGud7qMuhUIZmI8aS9HuYW0ODSxkcpVVXd4HDUYKg7PafAkl
cj745E5KGD+qW44KASTTQ1SwRQKBgQDW6WLp/nPVPO5YEK4nzS7b1RRC8ypHiKd6
LzhA2izgcsmO3F3Y5ZZ5rzeFbjgZiGFTUB/r1mgomI8kZyIGP1AN6o8oY9I89gHY
/DEEagIsFK5jAEoMeN0qbgqasOXpi+uUHCNidWa7OWOL9Rsh7dyVT54xcqMC2Qak
Vpoy5miiMwKBgQDuOHH9nF9M+5fQRhB9mQcRpWXlgBagkVKCkVR8fl+dXoIrCtpl
e1OGMNtki/42G1kNv3zCYm1tNMrDI5HjAf32tFF5yHguipdcwiXqq6aq0bQ6ssNT
4TFGYGkAwR/H3GNST5stmFvEsdjYFlmENiNfKyHd97spXZcReCn9l5/TQQKBgDRG
PpYWG4zBrmPjYskxonU8ZhpG1YDi34Hb3H4B06qgoSBLv9QTPD/K++FLxv+G6c1/
DtSpqVo+iYrcPy1v1wQbisjTRv8nA5oI9c9SDcc1HJneJyTTfVBlxdSMtM/TBfFX
ys+XKO7fbbRMYVYmamIzJJJ4hOgba/8rRYSeANN7AoGBAMDdrT+ig3aDMratbAvY
lqsfN3AtxoZ+ZVQYyUbzTSZPZ/to9eNuBzhRKcQ3QfG95nrHb7OnWHa7+1kc4p/Q
jMgzJgRpajlES+F3CCMPgJIJg7Ev+yiSCJLP9ZOsC+E96bK265hUcDyCXwb3Wzmg
4L9sc1QsQW80QO/RnaEzGO51
-----END PRIVATE KEY-----
================================================
FILE: exporter/otlphttpexporter/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: exporter/otlphttpexporter/README.md
================================================
# OTLP HTTP Exporter
| Status | |
| ------------- |-----------|
| Stability | [alpha]: profiles |
| | [stable]: traces, metrics, logs |
| Distributions | [core], [contrib], [k8s], [otlp] |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Aexporter%2Fotlphttp) [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aclosed+is%3Aissue+label%3Aexporter%2Fotlphttp) |
[alpha]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#alpha
[stable]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#stable
[core]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
[k8s]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-k8s
[otlp]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-otlp
The `otlp_http` exporter sends logs, metrics, profiles and traces via HTTP using [OTLP](
https://github.com/open-telemetry/opentelemetry-proto/blob/main/docs/specification.md)
format.
The `otlphttp` deprecated alias exists for the component name. It will be removed in a future version.
If you use the deprecated alias `otlphttp` in your configuration, change it to `otlp_http`.
The following settings are required:
- `endpoint` (no default): The target base URL to send data to (e.g.: https://example.com:4318).
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:4318/v1/traces).
If this setting is present the `endpoint` setting is ignored for traces.
- `metrics_endpoint` (no default): The target URL to send metric data to (e.g.: https://example.com:4318/v1/metrics).
If this setting is present the `endpoint` setting is ignored for metrics.
- `logs_endpoint` (no default): The target URL to send log data to (e.g.: https://example.com:4318/v1/logs).
- `profiles_endpoint` (no default): The target URL to send profile data to (e.g.: https://example.com:4318/v1development/profiles).
If this setting is present the `endpoint` setting is ignored for logs.
- `tls`: see [TLS Configuration Settings](../../config/configtls/README.md) for the full set of available options.
- `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.
- `encoding` (default = proto): The encoding to use for the messages (valid options: `proto`, `json`)
- `retry_on_failure`: see [Retry on Failure](../exporterhelper/README.md#retry-on-failure) for the full set of available options.
- `sending_queue`: see [Sending Queue](../exporterhelper/README.md#sending-queue) for the full set of available options.
Example:
```yaml
exporters:
otlp_http:
endpoint: https://example.com:4318
```
By default `gzip` compression is enabled. See [compression comparison](../../config/configgrpc/README.md#compression-comparison) for details benchmark information. To disable, configure as follows:
```yaml
exporters:
otlp_http:
...
compression: none
```
By default `proto` encoding is used, to change the content encoding of the message configure it as follows:
```yaml
exporters:
otlp_http:
...
encoding: json
```
The full list of settings exposed for this exporter are documented [here](./config.go)
with detailed sample configurations [here](./testdata/config.yaml).
================================================
FILE: exporter/otlphttpexporter/config.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlphttpexporter // import "go.opentelemetry.io/collector/exporter/otlphttpexporter"
import (
"encoding"
"errors"
"fmt"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/config/configretry"
"go.opentelemetry.io/collector/exporter/exporterhelper"
)
// EncodingType defines the type for content encoding
type EncodingType string
const (
EncodingProto EncodingType = "proto"
EncodingJSON EncodingType = "json"
)
var _ encoding.TextUnmarshaler = (*EncodingType)(nil)
// UnmarshalText unmarshalls text to an EncodingType.
func (e *EncodingType) UnmarshalText(text []byte) error {
if e == nil {
return errors.New("cannot unmarshal to a nil *EncodingType")
}
str := string(text)
switch str {
case string(EncodingProto):
*e = EncodingProto
case string(EncodingJSON):
*e = EncodingJSON
default:
return fmt.Errorf("invalid encoding type: %s", str)
}
return nil
}
// Config defines configuration for OTLP/HTTP exporter.
type Config struct {
ClientConfig confighttp.ClientConfig `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct.
QueueConfig configoptional.Optional[exporterhelper.QueueBatchConfig] `mapstructure:"sending_queue"`
RetryConfig configretry.BackOffConfig `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"`
// The URL to send profiles to. If omitted the Endpoint + "/v1development/profiles" will be used.
ProfilesEndpoint string `mapstructure:"profiles_endpoint"`
// The encoding to export telemetry (default: "proto")
Encoding EncodingType `mapstructure:"encoding"`
}
var _ component.Config = (*Config)(nil)
// Validate checks if the exporter configuration is valid
func (cfg *Config) Validate() error {
if cfg.ClientConfig.Endpoint == "" && cfg.TracesEndpoint == "" && cfg.MetricsEndpoint == "" && cfg.LogsEndpoint == "" && cfg.ProfilesEndpoint == "" {
return errors.New("at least one endpoint must be specified")
}
return nil
}
================================================
FILE: exporter/otlphttpexporter/config_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlphttpexporter
import (
"net/http"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/config/configopaque"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/config/configretry"
"go.opentelemetry.io/collector/config/configtls"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/confmaptest"
"go.opentelemetry.io/collector/confmap/xconfmap"
"go.opentelemetry.io/collector/exporter/exporterhelper"
)
func TestUnmarshalDefaultConfig(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
require.NoError(t, confmap.New().Unmarshal(&cfg))
assert.Equal(t, factory.CreateDefaultConfig(), cfg)
// Default/Empty config is invalid.
assert.Error(t, xconfmap.Validate(cfg))
}
func TestUnmarshalConfig(t *testing.T) {
defaultMaxIdleConns := http.DefaultTransport.(*http.Transport).MaxIdleConns
defaultMaxIdleConnsPerHost := http.DefaultTransport.(*http.Transport).MaxIdleConnsPerHost
defaultMaxConnsPerHost := http.DefaultTransport.(*http.Transport).MaxConnsPerHost
defaultIdleConnTimeout := http.DefaultTransport.(*http.Transport).IdleConnTimeout
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
require.NoError(t, err)
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
require.NoError(t, cm.Unmarshal(&cfg))
assert.Equal(t,
&Config{
RetryConfig: configretry.BackOffConfig{
Enabled: true,
InitialInterval: 10 * time.Second,
RandomizationFactor: 0.7,
Multiplier: 1.3,
MaxInterval: 1 * time.Minute,
MaxElapsedTime: 10 * time.Minute,
},
QueueConfig: configoptional.Some(exporterhelper.QueueBatchConfig{
Sizer: exporterhelper.RequestSizerTypeRequests,
NumConsumers: 2,
QueueSize: 10,
Batch: configoptional.Default(exporterhelper.BatchConfig{
Sizer: exporterhelper.RequestSizerTypeItems,
FlushTimeout: 200 * time.Millisecond,
MinSize: 8192,
}),
}),
Encoding: EncodingProto,
ClientConfig: confighttp.ClientConfig{
Headers: configopaque.MapList{
{Name: "another", Value: "somevalue"},
{Name: "can you have a . here?", Value: "F0000000-0000-0000-0000-000000000000"},
{Name: "header1", Value: "234"},
},
Endpoint: "https://1.2.3.4:1234",
TLS: configtls.ClientConfig{
Config: configtls.Config{
CAFile: "/var/lib/mycert.pem",
CertFile: "certfile",
KeyFile: "keyfile",
},
Insecure: true,
},
ReadBufferSize: 123,
WriteBufferSize: 345,
Timeout: time.Second * 10,
Compression: "gzip",
MaxIdleConns: defaultMaxIdleConns,
MaxIdleConnsPerHost: defaultMaxIdleConnsPerHost,
MaxConnsPerHost: defaultMaxConnsPerHost,
IdleConnTimeout: defaultIdleConnTimeout,
ForceAttemptHTTP2: true,
},
ProfilesEndpoint: "https://custom.profiles.endpoint:8080/v1development/profiles",
}, cfg)
}
func TestUnmarshalConfigInvalidEncoding(t *testing.T) {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "bad_invalid_encoding.yaml"))
require.NoError(t, err)
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
assert.Error(t, cm.Unmarshal(&cfg))
}
func TestUnmarshalEncoding(t *testing.T) {
tests := []struct {
name string
encodingBytes []byte
expected EncodingType
shouldError bool
}{
{
name: "UnmarshalEncodingProto",
encodingBytes: []byte("proto"),
expected: EncodingProto,
shouldError: false,
},
{
name: "UnmarshalEncodingJson",
encodingBytes: []byte("json"),
expected: EncodingJSON,
shouldError: false,
},
{
name: "UnmarshalEmptyEncoding",
encodingBytes: []byte(""),
shouldError: true,
},
{
name: "UnmarshalInvalidEncoding",
encodingBytes: []byte("invalid"),
shouldError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var encoding EncodingType
err := encoding.UnmarshalText(tt.encodingBytes)
if tt.shouldError {
assert.Error(t, err)
} else {
require.NoError(t, err)
assert.Equal(t, tt.expected, encoding)
}
})
}
}
func TestConfigValidate(t *testing.T) {
tests := []struct {
name string
cfg *Config
wantErr bool
}{
{
name: "no endpoints specified",
cfg: &Config{
ClientConfig: confighttp.ClientConfig{},
},
wantErr: true,
},
{
name: "main endpoint specified",
cfg: &Config{
ClientConfig: confighttp.ClientConfig{
Endpoint: "http://localhost:4318",
},
},
wantErr: false,
},
{
name: "only traces endpoint specified",
cfg: &Config{
ClientConfig: confighttp.ClientConfig{},
TracesEndpoint: "http://localhost:4318/v1/traces",
},
wantErr: false,
},
{
name: "only profiles endpoint specified",
cfg: &Config{
ClientConfig: confighttp.ClientConfig{},
ProfilesEndpoint: "http://localhost:4318/v1development/profiles",
},
wantErr: false,
},
{
name: "multiple endpoints specified",
cfg: &Config{
ClientConfig: confighttp.ClientConfig{},
TracesEndpoint: "http://localhost:4318/v1/traces",
MetricsEndpoint: "http://localhost:4318/v1/metrics",
LogsEndpoint: "http://localhost:4318/v1/logs",
ProfilesEndpoint: "http://localhost:4318/v1development/profiles",
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.cfg.Validate()
if tt.wantErr {
require.Error(t, err)
assert.Contains(t, err.Error(), "at least one endpoint must be specified")
} else {
assert.NoError(t, err)
}
})
}
}
================================================
FILE: exporter/otlphttpexporter/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:generate mdatagen metadata.yaml
// Package otlphttpexporter exports data by using the OTLP format to an HTTP endpoint.
package otlphttpexporter // import "go.opentelemetry.io/collector/exporter/otlphttpexporter"
================================================
FILE: exporter/otlphttpexporter/factory.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlphttpexporter // import "go.opentelemetry.io/collector/exporter/otlphttpexporter"
import (
"context"
"fmt"
"net/url"
"strings"
"time"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configcompression"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/config/configretry"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exporterhelper"
"go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper"
"go.opentelemetry.io/collector/exporter/otlphttpexporter/internal/metadata"
"go.opentelemetry.io/collector/exporter/xexporter"
)
// NewFactory creates a factory for OTLP exporter.
func NewFactory() exporter.Factory {
return xexporter.NewFactory(
metadata.Type,
createDefaultConfig,
xexporter.WithDeprecatedTypeAlias(metadata.DeprecatedType),
xexporter.WithTraces(createTraces, metadata.TracesStability),
xexporter.WithMetrics(createMetrics, metadata.MetricsStability),
xexporter.WithLogs(createLogs, metadata.LogsStability),
xexporter.WithProfiles(createProfiles, metadata.ProfilesStability),
)
}
func createDefaultConfig() component.Config {
clientConfig := confighttp.NewDefaultClientConfig()
clientConfig.Timeout = 30 * time.Second
// Default to gzip compression
clientConfig.Compression = configcompression.TypeGzip
// We almost read 0 bytes, so no need to tune ReadBufferSize.
clientConfig.WriteBufferSize = 512 * 1024
return &Config{
RetryConfig: configretry.NewDefaultBackOffConfig(),
QueueConfig: configoptional.Some(exporterhelper.NewDefaultQueueConfig()),
Encoding: EncodingProto,
ClientConfig: clientConfig,
}
}
// composeSignalURL composes the final URL for the signal (traces, metrics, logs) based on the configuration.
// oCfg is the configuration of the exporter.
// signalOverrideURL is the URL specified in the signal specific configuration (empty if not specified).
// signalName is the name of the signal, e.g. "traces", "metrics", "logs".
// signalVersion is the version of the signal, e.g. "v1" or "v1development".
func composeSignalURL(oCfg *Config, signalOverrideURL, signalName, signalVersion 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.ClientConfig.Endpoint == "":
return "", fmt.Errorf("either endpoint or %s_endpoint must be specified", signalName)
default:
if strings.HasSuffix(oCfg.ClientConfig.Endpoint, "/") {
return oCfg.ClientConfig.Endpoint + signalVersion + "/" + signalName, nil
}
return oCfg.ClientConfig.Endpoint + "/" + signalVersion + "/" + signalName, nil
}
}
func createTraces(
ctx context.Context,
set exporter.Settings,
cfg component.Config,
) (exporter.Traces, error) {
oce, err := newExporter(cfg, set)
if err != nil {
return nil, err
}
oCfg := cfg.(*Config)
oce.tracesURL, err = composeSignalURL(oCfg, oCfg.TracesEndpoint, "traces", "v1")
if err != nil {
return nil, err
}
return exporterhelper.NewTraces(ctx, set, cfg,
oce.pushTraces,
exporterhelper.WithStart(oce.start),
exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}),
// explicitly disable since we rely on http.Client timeout logic.
exporterhelper.WithTimeout(exporterhelper.TimeoutConfig{Timeout: 0}),
exporterhelper.WithRetry(oCfg.RetryConfig),
exporterhelper.WithQueue(oCfg.QueueConfig))
}
func createMetrics(
ctx context.Context,
set exporter.Settings,
cfg component.Config,
) (exporter.Metrics, error) {
oce, err := newExporter(cfg, set)
if err != nil {
return nil, err
}
oCfg := cfg.(*Config)
oce.metricsURL, err = composeSignalURL(oCfg, oCfg.MetricsEndpoint, "metrics", "v1")
if err != nil {
return nil, err
}
return exporterhelper.NewMetrics(ctx, set, cfg,
oce.pushMetrics,
exporterhelper.WithStart(oce.start),
exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}),
// explicitly disable since we rely on http.Client timeout logic.
exporterhelper.WithTimeout(exporterhelper.TimeoutConfig{Timeout: 0}),
exporterhelper.WithRetry(oCfg.RetryConfig),
exporterhelper.WithQueue(oCfg.QueueConfig))
}
func createLogs(
ctx context.Context,
set exporter.Settings,
cfg component.Config,
) (exporter.Logs, error) {
oce, err := newExporter(cfg, set)
if err != nil {
return nil, err
}
oCfg := cfg.(*Config)
oce.logsURL, err = composeSignalURL(oCfg, oCfg.LogsEndpoint, "logs", "v1")
if err != nil {
return nil, err
}
return exporterhelper.NewLogs(ctx, set, cfg,
oce.pushLogs,
exporterhelper.WithStart(oce.start),
exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}),
// explicitly disable since we rely on http.Client timeout logic.
exporterhelper.WithTimeout(exporterhelper.TimeoutConfig{Timeout: 0}),
exporterhelper.WithRetry(oCfg.RetryConfig),
exporterhelper.WithQueue(oCfg.QueueConfig))
}
func createProfiles(
ctx context.Context,
set exporter.Settings,
cfg component.Config,
) (xexporter.Profiles, error) {
oce, err := newExporter(cfg, set)
if err != nil {
return nil, err
}
oCfg := cfg.(*Config)
oce.profilesURL, err = composeSignalURL(oCfg, oCfg.ProfilesEndpoint, "profiles", "v1development")
if err != nil {
return nil, err
}
return xexporterhelper.NewProfiles(ctx, set, cfg,
oce.pushProfiles,
exporterhelper.WithStart(oce.start),
exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}),
// explicitly disable since we rely on http.Client timeout logic.
exporterhelper.WithTimeout(exporterhelper.TimeoutConfig{Timeout: 0}),
exporterhelper.WithRetry(oCfg.RetryConfig),
exporterhelper.WithQueue(oCfg.QueueConfig))
}
================================================
FILE: exporter/otlphttpexporter/factory_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlphttpexporter
import (
"context"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/config/configcompression"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/config/configopaque"
"go.opentelemetry.io/collector/config/configtls"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/exporter/xexporter"
"go.opentelemetry.io/collector/internal/testutil"
)
func TestCreateDefaultConfig(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
assert.NotNil(t, cfg, "failed to create default config")
require.NoError(t, componenttest.CheckConfigStruct(cfg))
ocfg, ok := factory.CreateDefaultConfig().(*Config)
assert.True(t, ok)
assert.Empty(t, ocfg.ClientConfig.Endpoint)
assert.Equal(t, 30*time.Second, ocfg.ClientConfig.Timeout, "default timeout is 30 second")
assert.True(t, ocfg.RetryConfig.Enabled, "default retry is enabled")
assert.Equal(t, 300*time.Second, ocfg.RetryConfig.MaxElapsedTime, "default retry MaxElapsedTime")
assert.Equal(t, 5*time.Second, ocfg.RetryConfig.InitialInterval, "default retry InitialInterval")
assert.Equal(t, 30*time.Second, ocfg.RetryConfig.MaxInterval, "default retry MaxInterval")
assert.True(t, ocfg.QueueConfig.HasValue(), "default sending queue is enabled")
assert.Equal(t, EncodingProto, ocfg.Encoding)
assert.Equal(t, configcompression.TypeGzip, ocfg.ClientConfig.Compression)
}
func TestCreateMetrics(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig().(*Config)
cfg.ClientConfig.Endpoint = "http://" + testutil.GetAvailableLocalAddress(t)
set := exportertest.NewNopSettings(factory.Type())
oexp, err := factory.CreateMetrics(context.Background(), set, cfg)
require.NoError(t, err)
require.NotNil(t, oexp)
}
func clientConfig(endpoint string, headers configopaque.MapList, tlsSetting configtls.ClientConfig, compression configcompression.Type) confighttp.ClientConfig {
clientConfig := confighttp.NewDefaultClientConfig()
clientConfig.TLS = tlsSetting
clientConfig.Compression = compression
if endpoint != "" {
clientConfig.Endpoint = endpoint
}
if headers != nil {
clientConfig.Headers = headers
}
return clientConfig
}
func TestCreateTraces(t *testing.T) {
var configCompression configcompression.Type
endpoint := "http://" + testutil.GetAvailableLocalAddress(t)
tests := []struct {
name string
config *Config
mustFailOnCreate bool
mustFailOnStart bool
}{
{
name: "NoEndpoint",
config: &Config{
ClientConfig: clientConfig("", nil, configtls.ClientConfig{}, configCompression),
},
mustFailOnCreate: true,
},
{
name: "UseSecure",
config: &Config{
ClientConfig: clientConfig(endpoint, nil, configtls.ClientConfig{
Insecure: false,
}, configCompression),
},
},
{
name: "Headers",
config: &Config{
ClientConfig: clientConfig(endpoint, configopaque.MapList{
{Name: "hdr1", Value: "val1"},
{Name: "hdr2", Value: "val2"},
}, configtls.ClientConfig{}, configCompression),
},
},
{
name: "CaCert",
config: &Config{
ClientConfig: clientConfig(endpoint, nil, configtls.ClientConfig{
Config: configtls.Config{
CAFile: filepath.Join("testdata", "test_cert.pem"),
},
}, configCompression),
},
},
{
name: "CertPemFileError",
config: &Config{
ClientConfig: clientConfig(endpoint, nil, configtls.ClientConfig{
Config: configtls.Config{
CAFile: "nosuchfile",
},
},
configCompression),
},
mustFailOnCreate: false,
mustFailOnStart: true,
},
{
name: "NoneCompression",
config: &Config{
ClientConfig: clientConfig(endpoint, nil, configtls.ClientConfig{}, "none"),
},
},
{
name: "GzipCompression",
config: &Config{
ClientConfig: clientConfig(endpoint, nil, configtls.ClientConfig{}, configcompression.TypeGzip),
},
},
{
name: "SnappyCompression",
config: &Config{
ClientConfig: clientConfig(endpoint, nil, configtls.ClientConfig{}, configcompression.TypeSnappy),
},
},
{
name: "ZstdCompression",
config: &Config{
ClientConfig: clientConfig(endpoint, nil, configtls.ClientConfig{}, configcompression.TypeZstd),
},
},
{
name: "ProtoEncoding",
config: &Config{
Encoding: EncodingProto,
ClientConfig: clientConfig(endpoint, nil, configtls.ClientConfig{}, configCompression),
},
},
{
name: "JSONEncoding",
config: &Config{
Encoding: EncodingJSON,
ClientConfig: clientConfig(endpoint, nil, configtls.ClientConfig{}, configCompression),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
factory := NewFactory()
set := exportertest.NewNopSettings(factory.Type())
consumer, err := factory.CreateTraces(context.Background(), set, tt.config)
if tt.mustFailOnCreate {
assert.Error(t, err)
return
}
require.NoError(t, err)
assert.NotNil(t, consumer)
err = consumer.Start(context.Background(), componenttest.NewNopHost())
if tt.mustFailOnStart {
require.Error(t, err)
}
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, "rpc error: code = Canceled desc = grpc: the client connection is closing", err.Error())
}
})
}
}
func TestCreateLogs(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig().(*Config)
cfg.ClientConfig.Endpoint = "http://" + testutil.GetAvailableLocalAddress(t)
set := exportertest.NewNopSettings(factory.Type())
oexp, err := factory.CreateLogs(context.Background(), set, cfg)
require.NoError(t, err)
require.NotNil(t, oexp)
}
func TestCreateProfiles(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig().(*Config)
cfg.ClientConfig.Endpoint = "http://" + testutil.GetAvailableLocalAddress(t)
set := exportertest.NewNopSettings(factory.Type())
oexp, err := factory.(xexporter.Factory).CreateProfiles(context.Background(), set, cfg)
require.NoError(t, err)
require.NotNil(t, oexp)
}
func TestCreateProfilesWithCustomEndpoint(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig().(*Config)
cfg.ProfilesEndpoint = "http://" + testutil.GetAvailableLocalAddress(t) + "/custom/profiles"
set := exportertest.NewNopSettings(factory.Type())
oexp, err := factory.(xexporter.Factory).CreateProfiles(context.Background(), set, cfg)
require.NoError(t, err)
require.NotNil(t, oexp)
}
func TestComposeSignalURL(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig().(*Config)
// Has slash at end
cfg.ClientConfig.Endpoint = "http://localhost:4318/"
url, err := composeSignalURL(cfg, "", "traces", "v1")
require.NoError(t, err)
assert.Equal(t, "http://localhost:4318/v1/traces", url)
// No slash at end
cfg.ClientConfig.Endpoint = "http://localhost:4318"
url, err = composeSignalURL(cfg, "", "traces", "v1")
require.NoError(t, err)
assert.Equal(t, "http://localhost:4318/v1/traces", url)
// Different version
cfg.ClientConfig.Endpoint = "http://localhost:4318"
url, err = composeSignalURL(cfg, "", "traces", "v2")
require.NoError(t, err)
assert.Equal(t, "http://localhost:4318/v2/traces", url)
// Test profiles endpoint with v1development
cfg.ClientConfig.Endpoint = "http://localhost:4318"
url, err = composeSignalURL(cfg, "", "profiles", "v1development")
require.NoError(t, err)
assert.Equal(t, "http://localhost:4318/v1development/profiles", url)
// Test with custom profiles endpoint override
cfg.ClientConfig.Endpoint = "http://localhost:4318"
url, err = composeSignalURL(cfg, "http://custom:9090/profiles", "profiles", "v1development")
require.NoError(t, err)
assert.Equal(t, "http://custom:9090/profiles", url)
}
================================================
FILE: exporter/otlphttpexporter/generated_component_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package otlphttpexporter
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/confmap/confmaptest"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
)
var typ = component.MustNewType("otlp_http")
func TestComponentFactoryType(t *testing.T) {
require.Equal(t, typ, NewFactory().Type())
}
func TestComponentConfigStruct(t *testing.T) {
require.NoError(t, componenttest.CheckConfigStruct(NewFactory().CreateDefaultConfig()))
}
func TestComponentLifecycle(t *testing.T) {
factory := NewFactory()
tests := []struct {
createFn func(ctx context.Context, set exporter.Settings, cfg component.Config) (component.Component, error)
name string
}{
{
name: "logs",
createFn: func(ctx context.Context, set exporter.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateLogs(ctx, set, cfg)
},
},
{
name: "metrics",
createFn: func(ctx context.Context, set exporter.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateMetrics(ctx, set, cfg)
},
},
{
name: "traces",
createFn: func(ctx context.Context, set exporter.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateTraces(ctx, set, cfg)
},
},
}
cm, err := confmaptest.LoadConf("metadata.yaml")
require.NoError(t, err)
cfg := factory.CreateDefaultConfig()
sub, err := cm.Sub("tests::config")
require.NoError(t, err)
require.NoError(t, sub.Unmarshal(&cfg))
for _, tt := range tests {
t.Run(tt.name+"-shutdown", func(t *testing.T) {
c, err := tt.createFn(context.Background(), exportertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
err = c.Shutdown(context.Background())
require.NoError(t, err)
})
t.Run(tt.name+"-lifecycle", func(t *testing.T) {
c, err := tt.createFn(context.Background(), exportertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
host := newMdatagenNopHost()
err = c.Start(context.Background(), host)
require.NoError(t, err)
require.NotPanics(t, func() {
switch tt.name {
case "logs":
e, ok := c.(exporter.Logs)
require.True(t, ok)
logs := generateLifecycleTestLogs()
if !e.Capabilities().MutatesData {
logs.MarkReadOnly()
}
err = e.ConsumeLogs(context.Background(), logs)
case "metrics":
e, ok := c.(exporter.Metrics)
require.True(t, ok)
metrics := generateLifecycleTestMetrics()
if !e.Capabilities().MutatesData {
metrics.MarkReadOnly()
}
err = e.ConsumeMetrics(context.Background(), metrics)
case "traces":
e, ok := c.(exporter.Traces)
require.True(t, ok)
traces := generateLifecycleTestTraces()
if !e.Capabilities().MutatesData {
traces.MarkReadOnly()
}
err = e.ConsumeTraces(context.Background(), traces)
}
})
require.NoError(t, err)
err = c.Shutdown(context.Background())
require.NoError(t, err)
})
}
}
func generateLifecycleTestLogs() plog.Logs {
logs := plog.NewLogs()
rl := logs.ResourceLogs().AppendEmpty()
rl.Resource().Attributes().PutStr("resource", "R1")
l := rl.ScopeLogs().AppendEmpty().LogRecords().AppendEmpty()
l.Body().SetStr("test log message")
l.SetTimestamp(pcommon.NewTimestampFromTime(time.Now()))
return logs
}
func generateLifecycleTestMetrics() pmetric.Metrics {
metrics := pmetric.NewMetrics()
rm := metrics.ResourceMetrics().AppendEmpty()
rm.Resource().Attributes().PutStr("resource", "R1")
m := rm.ScopeMetrics().AppendEmpty().Metrics().AppendEmpty()
m.SetName("test_metric")
dp := m.SetEmptyGauge().DataPoints().AppendEmpty()
dp.Attributes().PutStr("test_attr", "value_1")
dp.SetIntValue(123)
dp.SetTimestamp(pcommon.NewTimestampFromTime(time.Now()))
return metrics
}
func generateLifecycleTestTraces() ptrace.Traces {
traces := ptrace.NewTraces()
rs := traces.ResourceSpans().AppendEmpty()
rs.Resource().Attributes().PutStr("resource", "R1")
span := rs.ScopeSpans().AppendEmpty().Spans().AppendEmpty()
span.Attributes().PutStr("test_attr", "value_1")
span.SetName("test_span")
span.SetStartTimestamp(pcommon.NewTimestampFromTime(time.Now().Add(-1 * time.Second)))
span.SetEndTimestamp(pcommon.NewTimestampFromTime(time.Now()))
return traces
}
var _ component.Host = (*mdatagenNopHost)(nil)
type mdatagenNopHost struct{}
func newMdatagenNopHost() component.Host {
return &mdatagenNopHost{}
}
func (mnh *mdatagenNopHost) GetExtensions() map[component.ID]component.Component {
return nil
}
func (mnh *mdatagenNopHost) GetFactory(_ component.Kind, _ component.Type) component.Factory {
return nil
}
================================================
FILE: exporter/otlphttpexporter/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package otlphttpexporter
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: exporter/otlphttpexporter/go.mod
================================================
module go.opentelemetry.io/collector/exporter/otlphttpexporter
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector v0.148.0
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.opentelemetry.io/collector/config/configcompression v1.54.0
go.opentelemetry.io/collector/config/confighttp v0.148.0
go.opentelemetry.io/collector/config/configopaque v1.54.0
go.opentelemetry.io/collector/config/configoptional v1.54.0
go.opentelemetry.io/collector/config/configretry v1.54.0
go.opentelemetry.io/collector/config/configtls v1.54.0
go.opentelemetry.io/collector/confmap v1.54.0
go.opentelemetry.io/collector/confmap/xconfmap v0.148.0
go.opentelemetry.io/collector/consumer v1.54.0
go.opentelemetry.io/collector/consumer/consumererror v0.148.0
go.opentelemetry.io/collector/exporter v1.54.0
go.opentelemetry.io/collector/exporter/exporterhelper v0.148.0
go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper v0.148.0
go.opentelemetry.io/collector/exporter/exportertest v0.148.0
go.opentelemetry.io/collector/exporter/xexporter v0.148.0
go.opentelemetry.io/collector/internal/testutil v0.148.0
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/collector/pdata/pprofile v0.148.0
go.uber.org/goleak v1.3.0
go.uber.org/zap v1.27.1
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b
google.golang.org/grpc v1.79.3
google.golang.org/protobuf v1.36.11
)
require (
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/go-tpm v0.9.8 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.18.4 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pierrec/lz4/v4 v4.1.26 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rs/cors v1.11.1 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/client v1.54.0 // indirect
go.opentelemetry.io/collector/config/configauth v1.54.0 // indirect
go.opentelemetry.io/collector/config/configmiddleware v1.54.0 // indirect
go.opentelemetry.io/collector/config/confignet v1.54.0 // indirect
go.opentelemetry.io/collector/consumer/consumererror/xconsumererror v0.148.0 // indirect
go.opentelemetry.io/collector/consumer/consumertest v0.148.0 // indirect
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0 // indirect
go.opentelemetry.io/collector/extension v1.54.0 // indirect
go.opentelemetry.io/collector/extension/extensionauth v1.54.0 // indirect
go.opentelemetry.io/collector/extension/extensionmiddleware v0.148.0 // indirect
go.opentelemetry.io/collector/extension/xextension v0.148.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
go.opentelemetry.io/collector/pdata/xpdata v0.148.0 // indirect
go.opentelemetry.io/collector/pipeline v1.54.0 // indirect
go.opentelemetry.io/collector/pipeline/xpipeline v0.148.0 // indirect
go.opentelemetry.io/collector/receiver v1.54.0 // indirect
go.opentelemetry.io/collector/receiver/receivertest v0.148.0 // indirect
go.opentelemetry.io/collector/receiver/xreceiver v0.148.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector => ../../
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest
replace go.opentelemetry.io/collector/config/configauth => ../../config/configauth
replace go.opentelemetry.io/collector/config/configcompression => ../../config/configcompression
replace go.opentelemetry.io/collector/config/confighttp => ../../config/confighttp
replace go.opentelemetry.io/collector/config/configopaque => ../../config/configopaque
replace go.opentelemetry.io/collector/config/configoptional => ../../config/configoptional
replace go.opentelemetry.io/collector/config/configtls => ../../config/configtls
replace go.opentelemetry.io/collector/confmap => ../../confmap
replace go.opentelemetry.io/collector/confmap/xconfmap => ../../confmap/xconfmap
replace go.opentelemetry.io/collector/exporter => ../
replace go.opentelemetry.io/collector/extension => ../../extension
replace go.opentelemetry.io/collector/extension/extensionauth => ../../extension/extensionauth
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/pdata/testdata => ../../pdata/testdata
replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile
replace go.opentelemetry.io/collector/receiver => ../../receiver
replace go.opentelemetry.io/collector/consumer => ../../consumer
replace go.opentelemetry.io/collector/config/configretry => ../../config/configretry
replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer
replace go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest
replace go.opentelemetry.io/collector/client => ../../client
replace go.opentelemetry.io/collector/receiver/xreceiver => ../../receiver/xreceiver
replace go.opentelemetry.io/collector/receiver/receivertest => ../../receiver/receivertest
replace go.opentelemetry.io/collector/pipeline/xpipeline => ../../pipeline/xpipeline
replace go.opentelemetry.io/collector/consumer/consumererror/xconsumererror => ../../consumer/consumererror/xconsumererror
replace go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper => ../exporterhelper/xexporterhelper
replace go.opentelemetry.io/collector/exporter/xexporter => ../xexporter
replace go.opentelemetry.io/collector/pipeline => ../../pipeline
replace go.opentelemetry.io/collector/consumer/consumererror => ../../consumer/consumererror
retract (
v0.76.0 // Depends on retracted pdata v1.0.0-rc10 module, use v0.76.1
v0.69.0 // Release failed, use v0.69.1
)
replace go.opentelemetry.io/collector/exporter/exportertest => ../exportertest
replace go.opentelemetry.io/collector/extension/extensiontest => ../../extension/extensiontest
replace go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest => ../../extension/extensionauth/extensionauthtest
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/extension/xextension => ../../extension/xextension
replace go.opentelemetry.io/collector/config/configmiddleware => ../../config/configmiddleware
replace go.opentelemetry.io/collector/extension/extensionmiddleware => ../../extension/extensionmiddleware
replace go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest => ../../extension/extensionmiddleware/extensionmiddlewaretest
replace go.opentelemetry.io/collector/pdata/xpdata => ../../pdata/xpdata
replace go.opentelemetry.io/collector/exporter/exporterhelper => ../exporterhelper
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
replace go.opentelemetry.io/collector/config/confignet => ../../config/confignet
================================================
FILE: exporter/otlphttpexporter/go.sum
================================================
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f h1:RJ+BDPLSHQO7cSjKBqjPJSbi1qfk9WcsjQDtZiw3dZw=
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f/go.mod h1:VHbbch/X4roIY22jL1s3qRbZhCiRIgUAF/PdSUcx2io=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/go-tpm-tools v0.4.7 h1:J3ycC8umYxM9A4eF73EofRZu4BxY0jjQnUnkhIBbvws=
github.com/google/go-tpm-tools v0.4.7/go.mod h1:gSyXTZHe3fgbzb6WEGd90QucmsnT1SRdlye82gH8QjQ=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY=
github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: exporter/otlphttpexporter/internal/metadata/generated_status.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/component"
)
var (
Type = component.MustNewType("otlp_http")
DeprecatedType = component.MustNewType("otlphttp")
ScopeName = "go.opentelemetry.io/collector/exporter/otlphttpexporter"
)
const (
ProfilesStability = component.StabilityLevelAlpha
TracesStability = component.StabilityLevelStable
MetricsStability = component.StabilityLevelStable
LogsStability = component.StabilityLevelStable
)
================================================
FILE: exporter/otlphttpexporter/metadata.yaml
================================================
display_name: OTLP HTTP Exporter
type: otlp_http
deprecated_type: otlphttp
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: exporter
stability:
stable: [traces, metrics, logs]
alpha: [profiles]
distributions: [core, contrib, k8s, otlp]
tests:
config:
# use an endpoint that does not resolve, to ensure
# connection attempts fail quickly in tests
endpoint: "https://testing.invalid:1234"
================================================
FILE: exporter/otlphttpexporter/otlp.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlphttpexporter // import "go.opentelemetry.io/collector/exporter/otlphttpexporter"
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"runtime"
"strconv"
"time"
"go.uber.org/zap"
"google.golang.org/genproto/googleapis/rpc/status"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exporterhelper"
"go.opentelemetry.io/collector/internal/statusutil"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/plog/plogotlp"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/pmetric/pmetricotlp"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/pprofile/pprofileotlp"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp"
)
type baseExporter struct {
// Input configuration.
config *Config
client *http.Client
tracesURL string
metricsURL string
logsURL string
profilesURL string
logger *zap.Logger
settings component.TelemetrySettings
// Default user-agent header.
userAgent string
}
const (
headerRetryAfter = "Retry-After"
maxHTTPResponseReadBytes = 64 * 1024
jsonContentType = "application/json"
protobufContentType = "application/x-protobuf"
)
// Create new exporter.
func newExporter(cfg component.Config, set exporter.Settings) (*baseExporter, error) {
oCfg := cfg.(*Config)
if oCfg.ClientConfig.Endpoint != "" {
_, err := url.Parse(oCfg.ClientConfig.Endpoint)
if err != nil {
return nil, errors.New("endpoint must be a valid URL")
}
}
userAgent := fmt.Sprintf("%s/%s (%s/%s)",
set.BuildInfo.Description, set.BuildInfo.Version, runtime.GOOS, runtime.GOARCH)
// client construction is deferred to start
return &baseExporter{
config: oCfg,
logger: set.Logger,
userAgent: userAgent,
settings: set.TelemetrySettings,
}, nil
}
// start actually creates the HTTP client. The client construction is deferred till this point as this
// is the only place we get hold of Extensions which are required to construct auth round tripper.
func (e *baseExporter) start(ctx context.Context, host component.Host) error {
client, err := e.config.ClientConfig.ToClient(ctx, host.GetExtensions(), e.settings)
if err != nil {
return err
}
e.client = client
return nil
}
func (e *baseExporter) pushTraces(ctx context.Context, td ptrace.Traces) error {
tr := ptraceotlp.NewExportRequestFromTraces(td)
var err error
var request []byte
switch e.config.Encoding {
case EncodingJSON:
request, err = tr.MarshalJSON()
case EncodingProto:
request, err = tr.MarshalProto()
default:
err = fmt.Errorf("invalid encoding: %s", e.config.Encoding)
}
if err != nil {
return consumererror.NewPermanent(err)
}
return e.export(ctx, e.tracesURL, request, e.tracesPartialSuccessHandler)
}
func (e *baseExporter) pushMetrics(ctx context.Context, md pmetric.Metrics) error {
tr := pmetricotlp.NewExportRequestFromMetrics(md)
var err error
var request []byte
switch e.config.Encoding {
case EncodingJSON:
request, err = tr.MarshalJSON()
case EncodingProto:
request, err = tr.MarshalProto()
default:
err = fmt.Errorf("invalid encoding: %s", e.config.Encoding)
}
if err != nil {
return consumererror.NewPermanent(err)
}
return e.export(ctx, e.metricsURL, request, e.metricsPartialSuccessHandler)
}
func (e *baseExporter) pushLogs(ctx context.Context, ld plog.Logs) error {
tr := plogotlp.NewExportRequestFromLogs(ld)
var err error
var request []byte
switch e.config.Encoding {
case EncodingJSON:
request, err = tr.MarshalJSON()
case EncodingProto:
request, err = tr.MarshalProto()
default:
err = fmt.Errorf("invalid encoding: %s", e.config.Encoding)
}
if err != nil {
return consumererror.NewPermanent(err)
}
return e.export(ctx, e.logsURL, request, e.logsPartialSuccessHandler)
}
func (e *baseExporter) pushProfiles(ctx context.Context, td pprofile.Profiles) error {
tr := pprofileotlp.NewExportRequestFromProfiles(td)
var err error
var request []byte
switch e.config.Encoding {
case EncodingJSON:
request, err = tr.MarshalJSON()
case EncodingProto:
request, err = tr.MarshalProto()
default:
err = fmt.Errorf("invalid encoding: %s", e.config.Encoding)
}
if err != nil {
return consumererror.NewPermanent(err)
}
return e.export(ctx, e.profilesURL, request, e.profilesPartialSuccessHandler)
}
func (e *baseExporter) export(ctx context.Context, requestURL string, request []byte, partialSuccessHandler partialSuccessHandler) error {
e.logger.Debug("Preparing to make HTTP request", zap.String("url", requestURL))
req, err := http.NewRequestWithContext(ctx, http.MethodPost, requestURL, bytes.NewReader(request))
if err != nil {
return consumererror.NewPermanent(err)
}
switch e.config.Encoding {
case EncodingJSON:
req.Header.Set("Content-Type", jsonContentType)
case EncodingProto:
req.Header.Set("Content-Type", protobufContentType)
default:
return fmt.Errorf("invalid encoding: %s", e.config.Encoding)
}
req.Header.Set("User-Agent", e.userAgent)
resp, err := e.client.Do(req)
if err != nil {
var urlErr *url.Error
if errors.As(err, &urlErr) {
urlErr.URL = req.URL.String()
}
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(io.Discard, resp.Body, maxHTTPResponseReadBytes)
resp.Body.Close()
}()
if resp.StatusCode >= 200 && resp.StatusCode <= 299 {
return handlePartialSuccessResponse(resp, partialSuccessHandler)
}
respStatus := readResponseStatus(resp)
// Format the error message. Use the status if it is present in the response.
var errString string
var formattedErr error
if respStatus != nil {
errString = fmt.Sprintf(
"error exporting items, request to %s responded with HTTP Status Code %d, Message=%s, Details=%v",
requestURL, resp.StatusCode, respStatus.Message, respStatus.Details)
} else {
errString = fmt.Sprintf(
"error exporting items, request to %s responded with HTTP Status Code %d",
requestURL, resp.StatusCode)
}
formattedErr = statusutil.NewStatusFromMsgAndHTTPCode(errString, resp.StatusCode).Err()
if !isRetryableStatusCode(resp.StatusCode) {
return consumererror.NewPermanent(formattedErr)
}
// Check if the server is overwhelmed.
// See spec https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/otlp.md#otlphttp-throttling
isThrottleError := resp.StatusCode == http.StatusTooManyRequests || resp.StatusCode == http.StatusServiceUnavailable
if isThrottleError {
// Use Values to check if the header is present, and if present even if it is empty return ThrottleRetry.
values := resp.Header.Values(headerRetryAfter)
if len(values) == 0 {
return formattedErr
}
// The value of Retry-After field can be either an HTTP-date or a number of
// seconds to delay after the response is received. See https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.3
//
// Retry-After = HTTP-date / delay-seconds
//
// First try to parse delay-seconds, since that is what the receiver will send.
if seconds, err := strconv.Atoi(values[0]); err == nil {
return exporterhelper.NewThrottleRetry(formattedErr, time.Duration(seconds)*time.Second)
}
if date, err := time.Parse(time.RFC1123, values[0]); err == nil {
return exporterhelper.NewThrottleRetry(formattedErr, time.Until(date))
}
}
return formattedErr
}
// Determine if the status code is retryable according to the specification.
// For more, see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/otlp.md#failures-1
func isRetryableStatusCode(code int) bool {
switch code {
case http.StatusTooManyRequests:
return true
case http.StatusBadGateway:
return true
case http.StatusServiceUnavailable:
return true
case http.StatusGatewayTimeout:
return true
default:
return false
}
}
func readResponseBody(resp *http.Response) ([]byte, error) {
if resp.ContentLength == 0 {
return nil, nil
}
maxRead := resp.ContentLength
// if maxRead == -1, the ContentLength header has not been sent, so read up to
// the maximum permitted body size. If it is larger than the permitted body
// size, still try to read from the body in case the value is an error. If the
// body is larger than the maximum size, proto unmarshaling will likely fail.
if maxRead == -1 || maxRead > maxHTTPResponseReadBytes {
maxRead = maxHTTPResponseReadBytes
}
protoBytes := make([]byte, maxRead)
n, err := io.ReadFull(resp.Body, protoBytes)
// No bytes read and an EOF error indicates there is no body to read.
if n == 0 && (err == nil || errors.Is(err, io.EOF)) {
return nil, nil
}
// io.ReadFull will return io.ErrorUnexpectedEOF if the Content-Length header
// wasn't set, since we will try to read past the length of the body. If this
// is the case, the body will still have the full message in it, so we want to
// ignore the error and parse the message.
if err != nil && !errors.Is(err, io.ErrUnexpectedEOF) {
return nil, err
}
return protoBytes[:n], nil
}
// Read the response and decode the status.Status from the body.
// Returns nil if the response is empty or cannot be decoded.
func readResponseStatus(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."
respBytes, err := readResponseBody(resp)
if err != nil {
return nil
}
// Decode it as Status struct. See https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/otlp.md#failures
respStatus = &status.Status{}
err = proto.Unmarshal(respBytes, respStatus)
if err != nil {
return nil
}
}
return respStatus
}
func handlePartialSuccessResponse(resp *http.Response, partialSuccessHandler partialSuccessHandler) error {
bodyBytes, err := readResponseBody(resp)
if err != nil {
return err
}
return partialSuccessHandler(bodyBytes, resp.Header.Get("Content-Type"))
}
type partialSuccessHandler func(bytes []byte, contentType string) error
func (e *baseExporter) tracesPartialSuccessHandler(protoBytes []byte, contentType string) error {
if protoBytes == nil {
return nil
}
exportResponse := ptraceotlp.NewExportResponse()
switch contentType {
case protobufContentType:
err := exportResponse.UnmarshalProto(protoBytes)
if err != nil {
return fmt.Errorf("error parsing protobuf response: %w", err)
}
case jsonContentType:
err := exportResponse.UnmarshalJSON(protoBytes)
if err != nil {
return fmt.Errorf("error parsing json response: %w", err)
}
default:
return nil
}
partialSuccess := exportResponse.PartialSuccess()
if partialSuccess.ErrorMessage() != "" || partialSuccess.RejectedSpans() != 0 {
e.logger.Warn("Partial success response",
zap.String("message", exportResponse.PartialSuccess().ErrorMessage()),
zap.Int64("dropped_spans", exportResponse.PartialSuccess().RejectedSpans()),
)
}
return nil
}
func (e *baseExporter) metricsPartialSuccessHandler(protoBytes []byte, contentType string) error {
if protoBytes == nil {
return nil
}
exportResponse := pmetricotlp.NewExportResponse()
switch contentType {
case protobufContentType:
err := exportResponse.UnmarshalProto(protoBytes)
if err != nil {
return fmt.Errorf("error parsing protobuf response: %w", err)
}
case jsonContentType:
err := exportResponse.UnmarshalJSON(protoBytes)
if err != nil {
return fmt.Errorf("error parsing json response: %w", err)
}
default:
return nil
}
partialSuccess := exportResponse.PartialSuccess()
if partialSuccess.ErrorMessage() != "" || partialSuccess.RejectedDataPoints() != 0 {
e.logger.Warn("Partial success response",
zap.String("message", exportResponse.PartialSuccess().ErrorMessage()),
zap.Int64("dropped_data_points", exportResponse.PartialSuccess().RejectedDataPoints()),
)
}
return nil
}
func (e *baseExporter) logsPartialSuccessHandler(protoBytes []byte, contentType string) error {
if protoBytes == nil {
return nil
}
exportResponse := plogotlp.NewExportResponse()
switch contentType {
case protobufContentType:
err := exportResponse.UnmarshalProto(protoBytes)
if err != nil {
return fmt.Errorf("error parsing protobuf response: %w", err)
}
case jsonContentType:
err := exportResponse.UnmarshalJSON(protoBytes)
if err != nil {
return fmt.Errorf("error parsing json response: %w", err)
}
default:
return nil
}
partialSuccess := exportResponse.PartialSuccess()
if partialSuccess.ErrorMessage() != "" || partialSuccess.RejectedLogRecords() != 0 {
e.logger.Warn("Partial success response",
zap.String("message", exportResponse.PartialSuccess().ErrorMessage()),
zap.Int64("dropped_log_records", exportResponse.PartialSuccess().RejectedLogRecords()),
)
}
return nil
}
func (e *baseExporter) profilesPartialSuccessHandler(protoBytes []byte, contentType string) error {
if protoBytes == nil {
return nil
}
exportResponse := pprofileotlp.NewExportResponse()
switch contentType {
case protobufContentType:
err := exportResponse.UnmarshalProto(protoBytes)
if err != nil {
return fmt.Errorf("error parsing protobuf response: %w", err)
}
case jsonContentType:
err := exportResponse.UnmarshalJSON(protoBytes)
if err != nil {
return fmt.Errorf("error parsing json response: %w", err)
}
default:
return nil
}
partialSuccess := exportResponse.PartialSuccess()
if partialSuccess.ErrorMessage() != "" || partialSuccess.RejectedProfiles() != 0 {
e.logger.Warn("Partial success response",
zap.String("message", exportResponse.PartialSuccess().ErrorMessage()),
zap.Int64("dropped_samples", exportResponse.PartialSuccess().RejectedProfiles()),
)
}
return nil
}
================================================
FILE: exporter/otlphttpexporter/otlp_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlphttpexporter
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/config/configopaque"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/exporter/exporterhelper"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/exporter/otlphttpexporter/internal/metadata"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/plog/plogotlp"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/pmetric/pmetricotlp"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/pprofile/pprofileotlp"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp"
)
const (
tracesTelemetryType = "traces"
metricsTelemetryType = "metrics"
logsTelemetryType = "logs"
profilesTelemetryType = "profiles"
)
type responseSerializer interface {
MarshalJSON() ([]byte, error)
MarshalProto() ([]byte, error)
}
type responseSerializerProvider = func() responseSerializer
func provideTracesResponseSerializer() responseSerializer {
response := ptraceotlp.NewExportResponse()
partial := response.PartialSuccess()
partial.SetErrorMessage("hello")
partial.SetRejectedSpans(1)
return response
}
func provideMetricsResponseSerializer() responseSerializer {
response := pmetricotlp.NewExportResponse()
partial := response.PartialSuccess()
partial.SetErrorMessage("hello")
partial.SetRejectedDataPoints(1)
return response
}
func provideLogsResponseSerializer() responseSerializer {
response := plogotlp.NewExportResponse()
partial := response.PartialSuccess()
partial.SetErrorMessage("hello")
partial.SetRejectedLogRecords(1)
return response
}
func provideProfilesResponseSerializer() responseSerializer {
response := pprofileotlp.NewExportResponse()
partial := response.PartialSuccess()
partial.SetErrorMessage("hello")
partial.SetRejectedProfiles(1)
return response
}
func TestErrorResponses(t *testing.T) {
errMsgPrefix := func(srv *httptest.Server) string {
return fmt.Sprintf("error exporting items, request to %s/v1/traces responded with HTTP Status Code ", srv.URL)
}
tests := []struct {
name string
responseStatus int
responseBody *status.Status
checkErr func(t *testing.T, err error, srv *httptest.Server)
headers map[string]string
}{
{
name: "400",
responseStatus: http.StatusBadRequest,
responseBody: status.New(codes.InvalidArgument, "Bad field"),
checkErr: func(t *testing.T, err error, _ *httptest.Server) {
assert.True(t, consumererror.IsPermanent(err))
},
},
{
name: "402",
responseStatus: http.StatusPaymentRequired,
responseBody: status.New(codes.InvalidArgument, "Bad field"),
checkErr: func(t *testing.T, err error, _ *httptest.Server) {
assert.True(t, consumererror.IsPermanent(err))
},
},
{
name: "404",
responseStatus: http.StatusNotFound,
responseBody: status.New(codes.InvalidArgument, "Bad field"),
checkErr: func(t *testing.T, err error, _ *httptest.Server) {
assert.True(t, consumererror.IsPermanent(err))
},
},
{
name: "405",
responseStatus: http.StatusMethodNotAllowed,
responseBody: status.New(codes.InvalidArgument, "Bad field"),
checkErr: func(t *testing.T, err error, _ *httptest.Server) {
assert.True(t, consumererror.IsPermanent(err))
},
},
{
name: "413",
responseStatus: http.StatusRequestEntityTooLarge,
responseBody: status.New(codes.InvalidArgument, "Bad field"),
checkErr: func(t *testing.T, err error, _ *httptest.Server) {
assert.True(t, consumererror.IsPermanent(err))
},
},
{
name: "414",
responseStatus: http.StatusRequestURITooLong,
responseBody: status.New(codes.InvalidArgument, "Bad field"),
checkErr: func(t *testing.T, err error, _ *httptest.Server) {
assert.True(t, consumererror.IsPermanent(err))
},
},
{
name: "431",
responseStatus: http.StatusRequestHeaderFieldsTooLarge,
responseBody: status.New(codes.InvalidArgument, "Bad field"),
checkErr: func(t *testing.T, err error, _ *httptest.Server) {
assert.True(t, consumererror.IsPermanent(err))
},
},
{
name: "429",
responseStatus: http.StatusTooManyRequests,
responseBody: status.New(codes.ResourceExhausted, "Quota exceeded"),
checkErr: func(t *testing.T, err error, srv *httptest.Server) {
require.EqualError(t, err, status.New(codes.ResourceExhausted, errMsgPrefix(srv)+"429, Message=Quota exceeded, Details=[]").String())
},
},
{
name: "429-Retry-After",
responseStatus: http.StatusTooManyRequests,
responseBody: status.New(codes.InvalidArgument, "Quota exceeded"),
headers: map[string]string{"Retry-After": "Mon, 09 Feb 2025 15:04:05 GMT"},
checkErr: func(t *testing.T, err error, srv *httptest.Server) {
// Cannot test for the delay part since it depends on now. Check first part (which has a negative duration) and last part:
require.ErrorContains(t, err, "Throttle (-")
require.ErrorContains(t, err, "), error: "+status.New(codes.ResourceExhausted, errMsgPrefix(srv)+"429, Message=Quota exceeded, Details=[]").String())
},
},
{
name: "429-Retry-After-Malformed",
responseStatus: http.StatusTooManyRequests,
responseBody: status.New(codes.InvalidArgument, "Quota exceeded"),
headers: map[string]string{"Retry-After": "Malformed"},
checkErr: func(t *testing.T, err error, srv *httptest.Server) {
// Cannot test for the delay part since it depends on now. Check first part (which has a negative duration) and last part:
require.EqualError(t, err, status.New(codes.ResourceExhausted, errMsgPrefix(srv)+"429, Message=Quota exceeded, Details=[]").String())
},
},
{
name: "500",
responseStatus: http.StatusInternalServerError,
responseBody: status.New(codes.InvalidArgument, "Internal server error"),
checkErr: func(t *testing.T, err error, _ *httptest.Server) {
assert.True(t, consumererror.IsPermanent(err))
},
},
{
name: "502",
responseStatus: http.StatusBadGateway,
responseBody: status.New(codes.InvalidArgument, "Bad gateway"),
checkErr: func(t *testing.T, err error, srv *httptest.Server) {
require.EqualError(t, err, status.New(codes.Unavailable, errMsgPrefix(srv)+"502, Message=Bad gateway, Details=[]").String())
},
},
{
name: "503",
responseStatus: http.StatusServiceUnavailable,
responseBody: status.New(codes.InvalidArgument, "Server overloaded"),
checkErr: func(t *testing.T, err error, srv *httptest.Server) {
require.EqualError(t, err, status.New(codes.Unavailable, errMsgPrefix(srv)+"503, Message=Server overloaded, Details=[]").String())
},
},
{
name: "503-Retry-After",
responseStatus: http.StatusServiceUnavailable,
responseBody: status.New(codes.InvalidArgument, "Server overloaded"),
headers: map[string]string{"Retry-After": "30"},
checkErr: func(t *testing.T, err error, srv *httptest.Server) {
require.EqualError(t, err, exporterhelper.NewThrottleRetry(
status.New(codes.Unavailable, errMsgPrefix(srv)+"503, Message=Server overloaded, Details=[]").Err(),
time.Duration(30)*time.Second).Error())
},
},
{
name: "504",
responseStatus: http.StatusGatewayTimeout,
responseBody: status.New(codes.InvalidArgument, "Gateway timeout"),
checkErr: func(t *testing.T, err error, srv *httptest.Server) {
require.EqualError(t, err, status.New(codes.Unavailable, errMsgPrefix(srv)+"504, Message=Gateway timeout, Details=[]").String())
},
},
{
name: "Bad response payload",
responseStatus: http.StatusServiceUnavailable,
responseBody: status.New(codes.InvalidArgument, strings.Repeat("a", maxHTTPResponseReadBytes+1)),
checkErr: func(t *testing.T, err error, srv *httptest.Server) {
require.EqualError(t, err, status.New(codes.Unavailable, errMsgPrefix(srv)+"503").String())
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
srv := createBackend("/v1/traces", func(writer http.ResponseWriter, _ *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())
assert.NoError(t, err)
_, err = writer.Write(msg)
assert.NoError(t, err)
}
})
defer srv.Close()
cfg := &Config{
Encoding: EncodingProto,
TracesEndpoint: srv.URL + "/v1/traces",
// Create without QueueConfig and RetryConfig so that ConsumeTraces
// returns the errors that we want to check immediately.
}
exp, err := createTraces(context.Background(), exportertest.NewNopSettings(metadata.Type), cfg)
require.NoError(t, err)
// start the exporter
err = exp.Start(context.Background(), componenttest.NewNopHost())
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, exp.Shutdown(context.Background()))
})
// generate traces
traces := ptrace.NewTraces()
err = exp.ConsumeTraces(context.Background(), traces)
require.Error(t, err)
test.checkErr(t, err, srv)
})
}
}
func TestErrorResponseInvalidResponseBody(t *testing.T) {
resp := &http.Response{
StatusCode: http.StatusBadRequest,
Body: io.NopCloser(badReader{}),
ContentLength: 100,
}
assert.Nil(t, readResponseStatus(resp))
}
func TestUserAgent(t *testing.T) {
set := exportertest.NewNopSettings(metadata.Type)
set.BuildInfo.Description = "Collector"
set.BuildInfo.Version = "1.2.3test"
tests := []struct {
name string
headers configopaque.MapList
expectedUA string
}{
{
name: "default_user_agent",
expectedUA: "Collector/1.2.3test",
},
{
name: "custom_user_agent",
headers: configopaque.MapList{{Name: "User-Agent", Value: "My Custom Agent"}},
expectedUA: "My Custom Agent",
},
{
name: "custom_user_agent_lowercase",
headers: configopaque.MapList{{Name: "user-agent", Value: "My Custom Agent"}},
expectedUA: "My Custom Agent",
},
}
t.Run("traces", func(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
srv := createBackend("/v1/traces", func(writer http.ResponseWriter, request *http.Request) {
assert.Contains(t, request.Header.Get("user-agent"), tt.expectedUA)
writer.WriteHeader(http.StatusOK)
})
defer srv.Close()
cfg := &Config{
Encoding: EncodingProto,
TracesEndpoint: srv.URL + "/v1/traces",
ClientConfig: confighttp.ClientConfig{
Headers: tt.headers,
},
}
exp, err := createTraces(context.Background(), set, cfg)
require.NoError(t, err)
// start the exporter
err = exp.Start(context.Background(), componenttest.NewNopHost())
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, exp.Shutdown(context.Background()))
})
// generate data
traces := ptrace.NewTraces()
err = exp.ConsumeTraces(context.Background(), traces)
require.NoError(t, err)
})
}
})
t.Run("metrics", func(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
srv := createBackend("/v1/metrics", func(writer http.ResponseWriter, request *http.Request) {
assert.Contains(t, request.Header.Get("user-agent"), tt.expectedUA)
writer.WriteHeader(http.StatusOK)
})
defer srv.Close()
cfg := &Config{
Encoding: EncodingProto,
MetricsEndpoint: srv.URL + "/v1/metrics",
ClientConfig: confighttp.ClientConfig{
Headers: tt.headers,
},
}
exp, err := createMetrics(context.Background(), set, cfg)
require.NoError(t, err)
// start the exporter
err = exp.Start(context.Background(), componenttest.NewNopHost())
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, exp.Shutdown(context.Background()))
})
// generate data
metrics := pmetric.NewMetrics()
err = exp.ConsumeMetrics(context.Background(), metrics)
require.NoError(t, err)
})
}
})
t.Run("logs", func(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
srv := createBackend("/v1/logs", func(writer http.ResponseWriter, request *http.Request) {
assert.Contains(t, request.Header.Get("user-agent"), tt.expectedUA)
writer.WriteHeader(http.StatusOK)
})
defer srv.Close()
cfg := &Config{
Encoding: EncodingProto,
LogsEndpoint: srv.URL + "/v1/logs",
ClientConfig: confighttp.ClientConfig{
Headers: tt.headers,
},
}
exp, err := createLogs(context.Background(), set, cfg)
require.NoError(t, err)
// start the exporter
err = exp.Start(context.Background(), componenttest.NewNopHost())
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, exp.Shutdown(context.Background()))
})
// generate data
logs := plog.NewLogs()
err = exp.ConsumeLogs(context.Background(), logs)
require.NoError(t, err)
srv.Close()
})
}
})
t.Run("profiles", func(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
srv := createBackend("/v1development/profiles", func(writer http.ResponseWriter, request *http.Request) {
assert.Contains(t, request.Header.Get("user-agent"), test.expectedUA)
writer.WriteHeader(http.StatusOK)
})
defer srv.Close()
cfg := &Config{
Encoding: EncodingProto,
ClientConfig: confighttp.ClientConfig{
Endpoint: srv.URL,
Headers: test.headers,
},
}
exp, err := createProfiles(context.Background(), set, cfg)
require.NoError(t, err)
// start the exporter
err = exp.Start(context.Background(), componenttest.NewNopHost())
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, exp.Shutdown(context.Background()))
})
// generate data
profiles := pprofile.NewProfiles()
err = exp.ConsumeProfiles(context.Background(), profiles)
require.NoError(t, err)
})
}
})
}
func TestPartialSuccessInvalidBody(t *testing.T) {
cfg := createDefaultConfig()
set := exportertest.NewNopSettings(metadata.Type)
exp, err := newExporter(cfg, set)
require.NoError(t, err)
invalidBodyCases := []struct {
telemetryType string
handler partialSuccessHandler
}{
{
telemetryType: "traces",
handler: exp.tracesPartialSuccessHandler,
},
{
telemetryType: "metrics",
handler: exp.metricsPartialSuccessHandler,
},
{
telemetryType: "logs",
handler: exp.logsPartialSuccessHandler,
},
{
telemetryType: "profiles",
handler: exp.profilesPartialSuccessHandler,
},
}
for _, tt := range invalidBodyCases {
t.Run("Invalid response body_"+tt.telemetryType, func(t *testing.T) {
err := tt.handler([]byte{1}, "application/x-protobuf")
assert.ErrorContains(t, err, "error parsing protobuf response:")
})
}
}
func TestPartialSuccessUnsupportedContentType(t *testing.T) {
cfg := createDefaultConfig()
set := exportertest.NewNopSettings(metadata.Type)
exp, err := newExporter(cfg, set)
require.NoError(t, err)
unsupportedContentTypeCases := []struct {
contentType string
}{
{
contentType: "text/plain",
},
{
contentType: "application/octet-stream",
},
}
for _, telemetryType := range []string{"logs", "metrics", "traces", "profiles"} {
for _, tt := range unsupportedContentTypeCases {
t.Run("Unsupported content type "+tt.contentType+" "+telemetryType, func(t *testing.T) {
var handler func(b []byte, contentType string) error
switch telemetryType {
case "logs":
handler = exp.logsPartialSuccessHandler
case "metrics":
handler = exp.metricsPartialSuccessHandler
case "traces":
handler = exp.tracesPartialSuccessHandler
case "profiles":
handler = exp.profilesPartialSuccessHandler
default:
panic(telemetryType)
}
exportResponse := ptraceotlp.NewExportResponse()
exportResponse.PartialSuccess().SetErrorMessage("foo")
exportResponse.PartialSuccess().SetRejectedSpans(42)
b, err := exportResponse.MarshalProto()
require.NoError(t, err)
err = handler(b, tt.contentType)
assert.NoError(t, err)
})
}
}
}
func TestPartialSuccess_logs(t *testing.T) {
srv := createBackend("/v1/logs", func(writer http.ResponseWriter, _ *http.Request) {
response := plogotlp.NewExportResponse()
partial := response.PartialSuccess()
partial.SetErrorMessage("hello")
partial.SetRejectedLogRecords(1)
b, err := response.MarshalProto()
assert.NoError(t, err)
writer.Header().Set("Content-Type", "application/x-protobuf")
_, err = writer.Write(b)
assert.NoError(t, err)
})
defer srv.Close()
cfg := &Config{
Encoding: EncodingProto,
LogsEndpoint: srv.URL + "/v1/logs",
ClientConfig: confighttp.ClientConfig{},
}
set := exportertest.NewNopSettings(metadata.Type)
logger, observed := observer.New(zap.DebugLevel)
set.Logger = zap.New(logger)
exp, err := createLogs(context.Background(), set, cfg)
require.NoError(t, err)
// start the exporter
err = exp.Start(context.Background(), componenttest.NewNopHost())
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, exp.Shutdown(context.Background()))
})
// generate data
logs := plog.NewLogs()
err = exp.ConsumeLogs(context.Background(), logs)
require.NoError(t, err)
require.Len(t, observed.FilterLevelExact(zap.WarnLevel).All(), 1)
require.Contains(t, observed.FilterLevelExact(zap.WarnLevel).All()[0].Message, "Partial success")
}
func TestPartialResponse_missingHeaderButHasBody(t *testing.T) {
cfg := createDefaultConfig()
set := exportertest.NewNopSettings(metadata.Type)
exp, err := newExporter(cfg, set)
require.NoError(t, err)
contentTypes := []struct {
contentType string
}{
{contentType: protobufContentType},
{contentType: jsonContentType},
}
telemetryTypes := []struct {
telemetryType string
handler partialSuccessHandler
serializer responseSerializerProvider
}{
{
telemetryType: tracesTelemetryType,
handler: exp.tracesPartialSuccessHandler,
serializer: provideTracesResponseSerializer,
},
{
telemetryType: metricsTelemetryType,
handler: exp.metricsPartialSuccessHandler,
serializer: provideMetricsResponseSerializer,
},
{
telemetryType: logsTelemetryType,
handler: exp.logsPartialSuccessHandler,
serializer: provideLogsResponseSerializer,
},
{
telemetryType: profilesTelemetryType,
handler: exp.profilesPartialSuccessHandler,
serializer: provideProfilesResponseSerializer,
},
}
for _, ct := range contentTypes {
for _, tt := range telemetryTypes {
t.Run(tt.telemetryType+" "+ct.contentType, func(t *testing.T) {
serializer := tt.serializer()
var data []byte
var err error
switch ct.contentType {
case jsonContentType:
data, err = serializer.MarshalJSON()
case protobufContentType:
data, err = serializer.MarshalProto()
default:
require.Failf(t, "unsupported content type: %s", ct.contentType)
}
require.NoError(t, err)
resp := &http.Response{
// `-1` indicates a missing Content-Length header in the Go http standard library
ContentLength: -1,
Body: io.NopCloser(bytes.NewReader(data)),
Header: map[string][]string{
"Content-Type": {ct.contentType},
},
}
err = handlePartialSuccessResponse(resp, tt.handler)
assert.NoError(t, err)
})
}
}
}
func TestPartialResponse_missingHeaderAndBody(t *testing.T) {
cfg := createDefaultConfig()
set := exportertest.NewNopSettings(metadata.Type)
exp, err := newExporter(cfg, set)
require.NoError(t, err)
contentTypes := []struct {
contentType string
}{
{contentType: protobufContentType},
{contentType: jsonContentType},
}
telemetryTypes := []struct {
telemetryType string
handler partialSuccessHandler
}{
{
telemetryType: tracesTelemetryType,
handler: exp.tracesPartialSuccessHandler,
},
{
telemetryType: metricsTelemetryType,
handler: exp.metricsPartialSuccessHandler,
},
{
telemetryType: logsTelemetryType,
handler: exp.logsPartialSuccessHandler,
},
{
telemetryType: profilesTelemetryType,
handler: exp.profilesPartialSuccessHandler,
},
}
for _, ct := range contentTypes {
for _, tt := range telemetryTypes {
t.Run(tt.telemetryType+" "+ct.contentType, func(t *testing.T) {
resp := &http.Response{
// `-1` indicates a missing Content-Length header in the Go http standard library
ContentLength: -1,
Body: io.NopCloser(bytes.NewReader([]byte{})),
Header: map[string][]string{
"Content-Type": {ct.contentType},
},
}
err = handlePartialSuccessResponse(resp, tt.handler)
assert.NoError(t, err)
})
}
}
}
func TestPartialResponse_nonErrUnexpectedEOFError(t *testing.T) {
cfg := createDefaultConfig()
set := exportertest.NewNopSettings(metadata.Type)
exp, err := newExporter(cfg, set)
require.NoError(t, err)
resp := &http.Response{
// `-1` indicates a missing Content-Length header in the Go http standard library
ContentLength: -1,
Body: io.NopCloser(badReader{}),
}
err = handlePartialSuccessResponse(resp, exp.tracesPartialSuccessHandler)
assert.Error(t, err)
}
func TestPartialSuccess_shortContentLengthHeader(t *testing.T) {
cfg := createDefaultConfig()
set := exportertest.NewNopSettings(metadata.Type)
exp, err := newExporter(cfg, set)
require.NoError(t, err)
contentTypes := []struct {
contentType string
}{
{contentType: protobufContentType},
{contentType: jsonContentType},
}
telemetryTypes := []struct {
telemetryType string
handler partialSuccessHandler
serializer responseSerializerProvider
}{
{
telemetryType: tracesTelemetryType,
handler: exp.tracesPartialSuccessHandler,
serializer: provideTracesResponseSerializer,
},
{
telemetryType: metricsTelemetryType,
handler: exp.metricsPartialSuccessHandler,
serializer: provideMetricsResponseSerializer,
},
{
telemetryType: logsTelemetryType,
handler: exp.logsPartialSuccessHandler,
serializer: provideLogsResponseSerializer,
},
{
telemetryType: profilesTelemetryType,
handler: exp.profilesPartialSuccessHandler,
serializer: provideProfilesResponseSerializer,
},
}
for _, ct := range contentTypes {
for _, tt := range telemetryTypes {
t.Run(tt.telemetryType+" "+ct.contentType, func(t *testing.T) {
serializer := tt.serializer()
var data []byte
var err error
switch ct.contentType {
case jsonContentType:
data, err = serializer.MarshalJSON()
case protobufContentType:
data, err = serializer.MarshalProto()
default:
require.Failf(t, "unsupported content type: %s", ct.contentType)
}
require.NoError(t, err)
resp := &http.Response{
ContentLength: 3,
Body: io.NopCloser(bytes.NewReader(data)),
Header: map[string][]string{
"Content-Type": {ct.contentType},
},
}
// For short content-length, a real error happens.
err = handlePartialSuccessResponse(resp, tt.handler)
assert.Error(t, err)
})
}
}
}
func TestPartialSuccess_longContentLengthHeader(t *testing.T) {
contentTypes := []struct {
contentType string
}{
{contentType: protobufContentType},
{contentType: jsonContentType},
}
telemetryTypes := []struct {
telemetryType string
serializer responseSerializerProvider
}{
{
telemetryType: tracesTelemetryType,
serializer: provideTracesResponseSerializer,
},
{
telemetryType: metricsTelemetryType,
serializer: provideMetricsResponseSerializer,
},
{
telemetryType: logsTelemetryType,
serializer: provideLogsResponseSerializer,
},
{
telemetryType: profilesTelemetryType,
serializer: provideProfilesResponseSerializer,
},
}
for _, ct := range contentTypes {
for _, tt := range telemetryTypes {
t.Run(tt.telemetryType+" "+ct.contentType, func(t *testing.T) {
cfg := createDefaultConfig()
set := exportertest.NewNopSettings(metadata.Type)
logger, observed := observer.New(zap.DebugLevel)
set.Logger = zap.New(logger)
exp, err := newExporter(cfg, set)
require.NoError(t, err)
serializer := tt.serializer()
var handler partialSuccessHandler
switch tt.telemetryType {
case tracesTelemetryType:
handler = exp.tracesPartialSuccessHandler
case metricsTelemetryType:
handler = exp.metricsPartialSuccessHandler
case logsTelemetryType:
handler = exp.logsPartialSuccessHandler
case profilesTelemetryType:
handler = exp.profilesPartialSuccessHandler
default:
require.Failf(t, "unsupported telemetry type: %s", ct.contentType)
}
var data []byte
switch ct.contentType {
case jsonContentType:
data, err = serializer.MarshalJSON()
case protobufContentType:
data, err = serializer.MarshalProto()
default:
require.Failf(t, "unsupported content type: %s", ct.contentType)
}
require.NoError(t, err)
resp := &http.Response{
ContentLength: 4096,
Body: io.NopCloser(bytes.NewReader(data)),
Header: map[string][]string{
"Content-Type": {ct.contentType},
},
}
// No real error happens for long content length, so the partial
// success is handled as success with a warning.
err = handlePartialSuccessResponse(resp, handler)
require.NoError(t, err)
assert.Len(t, observed.FilterLevelExact(zap.WarnLevel).All(), 1)
assert.Contains(t, observed.FilterLevelExact(zap.WarnLevel).All()[0].Message, "Partial success")
})
}
}
}
func TestPartialSuccessInvalidResponseBody(t *testing.T) {
cfg := createDefaultConfig()
set := exportertest.NewNopSettings(metadata.Type)
exp, err := newExporter(cfg, set)
require.NoError(t, err)
resp := &http.Response{
Body: io.NopCloser(badReader{}),
ContentLength: 100,
Header: map[string][]string{
"Content-Type": {protobufContentType},
},
}
err = handlePartialSuccessResponse(resp, exp.tracesPartialSuccessHandler)
assert.Error(t, err)
}
func TestPartialSuccess_traces(t *testing.T) {
srv := createBackend("/v1/traces", func(writer http.ResponseWriter, _ *http.Request) {
response := ptraceotlp.NewExportResponse()
partial := response.PartialSuccess()
partial.SetErrorMessage("hello")
partial.SetRejectedSpans(1)
bytes, err := response.MarshalProto()
assert.NoError(t, err)
writer.Header().Set("Content-Type", "application/x-protobuf")
_, err = writer.Write(bytes)
assert.NoError(t, err)
})
defer srv.Close()
cfg := &Config{
Encoding: EncodingProto,
TracesEndpoint: srv.URL + "/v1/traces",
ClientConfig: confighttp.ClientConfig{},
}
set := exportertest.NewNopSettings(metadata.Type)
logger, observed := observer.New(zap.DebugLevel)
set.Logger = zap.New(logger)
exp, err := createTraces(context.Background(), set, cfg)
require.NoError(t, err)
// start the exporter
err = exp.Start(context.Background(), componenttest.NewNopHost())
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, exp.Shutdown(context.Background()))
})
// generate data
traces := ptrace.NewTraces()
err = exp.ConsumeTraces(context.Background(), traces)
require.NoError(t, err)
require.Len(t, observed.FilterLevelExact(zap.WarnLevel).All(), 1)
require.Contains(t, observed.FilterLevelExact(zap.WarnLevel).All()[0].Message, "Partial success")
}
func TestPartialSuccess_metrics(t *testing.T) {
srv := createBackend("/v1/metrics", func(writer http.ResponseWriter, _ *http.Request) {
response := pmetricotlp.NewExportResponse()
partial := response.PartialSuccess()
partial.SetErrorMessage("hello")
partial.SetRejectedDataPoints(1)
bytes, err := response.MarshalProto()
assert.NoError(t, err)
writer.Header().Set("Content-Type", "application/x-protobuf")
_, err = writer.Write(bytes)
assert.NoError(t, err)
})
defer srv.Close()
cfg := &Config{
Encoding: EncodingProto,
MetricsEndpoint: srv.URL + "/v1/metrics",
ClientConfig: confighttp.ClientConfig{},
}
set := exportertest.NewNopSettings(metadata.Type)
logger, observed := observer.New(zap.DebugLevel)
set.Logger = zap.New(logger)
exp, err := createMetrics(context.Background(), set, cfg)
require.NoError(t, err)
// start the exporter
err = exp.Start(context.Background(), componenttest.NewNopHost())
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, exp.Shutdown(context.Background()))
})
// generate data
metrics := pmetric.NewMetrics()
err = exp.ConsumeMetrics(context.Background(), metrics)
require.NoError(t, err)
require.Len(t, observed.FilterLevelExact(zap.WarnLevel).All(), 1)
require.Contains(t, observed.FilterLevelExact(zap.WarnLevel).All()[0].Message, "Partial success")
}
func TestPartialSuccess_profiles(t *testing.T) {
srv := createBackend("/v1development/profiles", func(writer http.ResponseWriter, _ *http.Request) {
response := pprofileotlp.NewExportResponse()
partial := response.PartialSuccess()
partial.SetErrorMessage("hello")
partial.SetRejectedProfiles(1)
bytes, err := response.MarshalProto()
assert.NoError(t, err)
writer.Header().Set("Content-Type", "application/x-protobuf")
_, err = writer.Write(bytes)
assert.NoError(t, err)
})
defer srv.Close()
cfg := &Config{
Encoding: EncodingProto,
ClientConfig: confighttp.ClientConfig{
Endpoint: srv.URL,
},
}
set := exportertest.NewNopSettings(metadata.Type)
logger, observed := observer.New(zap.DebugLevel)
set.Logger = zap.New(logger)
exp, err := createProfiles(context.Background(), set, cfg)
require.NoError(t, err)
// start the exporter
err = exp.Start(context.Background(), componenttest.NewNopHost())
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, exp.Shutdown(context.Background()))
})
// generate data
profiles := pprofile.NewProfiles()
err = exp.ConsumeProfiles(context.Background(), profiles)
require.NoError(t, err)
require.Len(t, observed.FilterLevelExact(zap.WarnLevel).All(), 1)
require.Contains(t, observed.FilterLevelExact(zap.WarnLevel).All()[0].Message, "Partial success")
}
func TestEncoding(t *testing.T) {
set := exportertest.NewNopSettings(metadata.Type)
set.BuildInfo.Description = "Collector"
set.BuildInfo.Version = "1.2.3test"
tests := []struct {
name string
encoding EncodingType
expectedEncoding EncodingType
}{
{
name: "proto_encoding",
encoding: EncodingProto,
expectedEncoding: "application/x-protobuf",
},
{
name: "json_encoding",
encoding: EncodingJSON,
expectedEncoding: "application/json",
},
}
t.Run("traces", func(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
srv := createBackend("/v1/traces", func(writer http.ResponseWriter, request *http.Request) {
assert.Contains(t, request.Header.Get("content-type"), tt.expectedEncoding)
writer.WriteHeader(http.StatusOK)
})
defer srv.Close()
cfg := &Config{
TracesEndpoint: srv.URL + "/v1/traces",
Encoding: tt.encoding,
}
exp, err := createTraces(context.Background(), set, cfg)
require.NoError(t, err)
// start the exporter
err = exp.Start(context.Background(), componenttest.NewNopHost())
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, exp.Shutdown(context.Background()))
})
// generate data
traces := ptrace.NewTraces()
err = exp.ConsumeTraces(context.Background(), traces)
require.NoError(t, err)
})
}
})
t.Run("metrics", func(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
srv := createBackend("/v1/metrics", func(writer http.ResponseWriter, request *http.Request) {
assert.Contains(t, request.Header.Get("content-type"), tt.expectedEncoding)
writer.WriteHeader(http.StatusOK)
})
defer srv.Close()
cfg := &Config{
MetricsEndpoint: srv.URL + "/v1/metrics",
Encoding: tt.encoding,
}
exp, err := createMetrics(context.Background(), set, cfg)
require.NoError(t, err)
// start the exporter
err = exp.Start(context.Background(), componenttest.NewNopHost())
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, exp.Shutdown(context.Background()))
})
// generate data
metrics := pmetric.NewMetrics()
err = exp.ConsumeMetrics(context.Background(), metrics)
require.NoError(t, err)
})
}
})
t.Run("logs", func(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
srv := createBackend("/v1/logs", func(writer http.ResponseWriter, request *http.Request) {
assert.Contains(t, request.Header.Get("content-type"), tt.expectedEncoding)
writer.WriteHeader(http.StatusOK)
})
defer srv.Close()
cfg := &Config{
LogsEndpoint: srv.URL + "/v1/logs",
Encoding: tt.encoding,
}
exp, err := createLogs(context.Background(), set, cfg)
require.NoError(t, err)
// start the exporter
err = exp.Start(context.Background(), componenttest.NewNopHost())
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, exp.Shutdown(context.Background()))
})
// generate data
logs := plog.NewLogs()
err = exp.ConsumeLogs(context.Background(), logs)
require.NoError(t, err)
srv.Close()
})
}
})
t.Run("profiles", func(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
srv := createBackend("/v1development/profiles", func(writer http.ResponseWriter, request *http.Request) {
assert.Contains(t, request.Header.Get("content-type"), test.expectedEncoding)
writer.WriteHeader(http.StatusOK)
})
defer srv.Close()
cfg := &Config{
ClientConfig: confighttp.ClientConfig{
Endpoint: srv.URL,
},
Encoding: test.encoding,
}
exp, err := createProfiles(context.Background(), set, cfg)
require.NoError(t, err)
// start the exporter
err = exp.Start(context.Background(), componenttest.NewNopHost())
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, exp.Shutdown(context.Background()))
})
// generate data
profiles := pprofile.NewProfiles()
err = exp.ConsumeProfiles(context.Background(), profiles)
require.NoError(t, err)
})
}
})
}
func createBackend(endpoint string, handler func(writer http.ResponseWriter, request *http.Request)) *httptest.Server {
mux := http.NewServeMux()
mux.HandleFunc(endpoint, handler)
srv := httptest.NewServer(mux)
return srv
}
type badReader struct{}
func (b badReader) Read([]byte) (int, error) {
return 0, errors.New("Bad read")
}
type mockTransport struct {
roundTripFunc func(req *http.Request) (*http.Response, error)
}
func (t *mockTransport) RoundTrip(req *http.Request) (*http.Response, error) {
return t.roundTripFunc(req)
}
func TestExport_ErrorShowsModifiedURL(t *testing.T) {
originalURL := "http://localhost:4318/v1/logs"
modifiedURL := "https://actual-destination.example.com/v1/logs"
transport := &mockTransport{
roundTripFunc: func(req *http.Request) (*http.Response, error) {
parsedModified, err := url.Parse(modifiedURL)
require.NoError(t, err)
req.URL = parsedModified
return nil, errors.New("we need an error logged")
},
}
client := &http.Client{Transport: transport}
logger, _ := observer.New(zap.DebugLevel)
exp := &baseExporter{
client: client,
logger: zap.New(logger),
config: &Config{
Encoding: EncodingProto,
},
}
err := exp.export(context.Background(), originalURL, []byte("test data"), nil)
require.Error(t, err)
assert.Contains(t, err.Error(), modifiedURL, "Error message should contain the modified destination URL")
assert.NotContains(t, err.Error(), originalURL, "Error message should NOT contain the original placeholder URL")
}
================================================
FILE: exporter/otlphttpexporter/testdata/bad_empty_config.yaml
================================================
receivers:
nop:
processors:
nop:
exporters:
otlp_http:
service:
pipelines:
traces:
receivers: [nop]
processors: [nop]
exporters: [otlp_http]
================================================
FILE: exporter/otlphttpexporter/testdata/bad_invalid_encoding.yaml
================================================
encoding: invalid
================================================
FILE: exporter/otlphttpexporter/testdata/config.yaml
================================================
endpoint: "https://1.2.3.4:1234"
tls:
ca_file: /var/lib/mycert.pem
cert_file: certfile
key_file: keyfile
insecure: true
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
randomization_factor: 0.7
multiplier: 1.3
max_interval: 60s
max_elapsed_time: 10m
headers:
"can you have a . here?": "F0000000-0000-0000-0000-000000000000"
header1: "234"
another: "somevalue"
compression: gzip
profiles_endpoint: "https://custom.profiles.endpoint:8080/v1development/profiles"
================================================
FILE: exporter/otlphttpexporter/testdata/test_cert.pem
================================================
-----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-----
================================================
FILE: exporter/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package exporter
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: exporter/xexporter/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: exporter/xexporter/exporter.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xexporter // import "go.opentelemetry.io/collector/exporter/xexporter"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/internal/componentalias"
"go.opentelemetry.io/collector/pipeline"
)
// Profiles is an exporter that can consume profiles.
type Profiles interface {
component.Component
xconsumer.Profiles
}
type Factory interface {
exporter.Factory
// CreateProfiles creates a Profiles exporter based on this config.
// If the exporter type does not support tracing,
// this function returns the error [pipeline.ErrSignalNotSupported].
CreateProfiles(ctx context.Context, set exporter.Settings, cfg component.Config) (Profiles, error)
// ProfilesStability gets the stability level of the Profiles exporter.
ProfilesStability() component.StabilityLevel
}
// FactoryOption apply changes to ReceiverOptions.
type FactoryOption interface {
// applyOption applies the option.
applyOption(o *factory)
}
// factoryOptionFunc is an ReceiverFactoryOption created through a function.
type factoryOptionFunc func(*factory)
func (f factoryOptionFunc) applyOption(o *factory) {
f(o)
}
// CreateProfilesFunc is the equivalent of Factory.CreateProfiles.
type CreateProfilesFunc func(context.Context, exporter.Settings, component.Config) (Profiles, error)
// WithTraces overrides the default "error not supported" implementation for CreateTraces and the default "undefined" stability level.
func WithTraces(createTraces exporter.CreateTracesFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.opts = append(o.opts, exporter.WithTraces(createTraces, sl))
})
}
// WithMetrics overrides the default "error not supported" implementation for CreateMetrics and the default "undefined" stability level.
func WithMetrics(createMetrics exporter.CreateMetricsFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.opts = append(o.opts, exporter.WithMetrics(createMetrics, sl))
})
}
// WithLogs overrides the default "error not supported" implementation for CreateLogs and the default "undefined" stability level.
func WithLogs(createLogs exporter.CreateLogsFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.opts = append(o.opts, exporter.WithLogs(createLogs, sl))
})
}
// WithProfiles overrides the default "error not supported" implementation for CreateProfilesExporter and the default "undefined" stability level.
func WithProfiles(createProfiles CreateProfilesFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.profilesStabilityLevel = sl
o.createProfilesFunc = createProfiles
})
}
// WithDeprecatedTypeAlias configures a deprecated type alias for the exporter. Only one alias is supported per exporter.
// When the alias is used in configuration, a deprecation warning is automatically logged.
func WithDeprecatedTypeAlias(alias component.Type) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.SetDeprecatedAlias(alias)
})
}
type factory struct {
exporter.Factory
componentalias.TypeAliasHolder
opts []exporter.FactoryOption
createProfilesFunc CreateProfilesFunc
profilesStabilityLevel component.StabilityLevel
}
func (f *factory) ProfilesStability() component.StabilityLevel {
return f.profilesStabilityLevel
}
func (f *factory) CreateProfiles(ctx context.Context, set exporter.Settings, cfg component.Config) (Profiles, error) {
if f.createProfilesFunc == nil {
return nil, pipeline.ErrSignalNotSupported
}
if err := componentalias.ValidateComponentType(f.Factory, set.ID); err != nil {
return nil, err
}
return f.createProfilesFunc(ctx, set, cfg)
}
// NewFactory creates a wrapped exporter.Factory with experimental capabilities.
func NewFactory(cfgType component.Type, createDefaultConfig component.CreateDefaultConfigFunc, options ...FactoryOption) Factory {
f := &factory{TypeAliasHolder: componentalias.NewTypeAliasHolder()}
for _, opt := range options {
opt.applyOption(f)
}
f.Factory = exporter.NewFactory(cfgType, createDefaultConfig, f.opts...)
f.Factory.(componentalias.TypeAliasHolder).SetDeprecatedAlias(f.DeprecatedAlias())
return f
}
================================================
FILE: exporter/xexporter/exporter_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xexporter
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/internal/experr"
"go.opentelemetry.io/collector/internal/componentalias"
)
var testID = component.MustNewID("test")
func TestNewFactoryWithProfiles(t *testing.T) {
testType := component.MustNewType("test")
defaultCfg := struct{}{}
factory := NewFactory(
testType,
func() component.Config { return &defaultCfg },
WithProfiles(createProfiles, component.StabilityLevelDevelopment),
)
assert.Equal(t, testType, factory.Type())
assert.EqualValues(t, &defaultCfg, factory.CreateDefaultConfig())
assert.Equal(t, component.StabilityLevelDevelopment, factory.ProfilesStability())
_, err := factory.CreateProfiles(context.Background(), exporter.Settings{ID: testID}, &defaultCfg)
require.NoError(t, err)
wrongID := component.MustNewID("wrong")
wrongIDErrStr := experr.ErrIDMismatch(wrongID, testType).Error()
_, err = factory.CreateProfiles(context.Background(), exporter.Settings{ID: wrongID}, &defaultCfg)
assert.EqualError(t, err, wrongIDErrStr)
}
var nopInstance = &nop{
Consumer: consumertest.NewNop(),
}
// nop stores consumed profiles for testing purposes.
type nop struct {
component.StartFunc
component.ShutdownFunc
consumertest.Consumer
}
func createProfiles(context.Context, exporter.Settings, component.Config) (Profiles, error) {
return nopInstance, nil
}
func TestNewFactoryWithDeprecatedAlias(t *testing.T) {
testType := component.MustNewType("newname")
aliasType := component.MustNewType("oldname")
defaultCfg := struct{}{}
f := NewFactory(
testType,
func() component.Config { return &defaultCfg },
WithProfiles(createProfiles, component.StabilityLevelAlpha),
WithDeprecatedTypeAlias(aliasType),
)
assert.Equal(t, testType, f.Type())
assert.Equal(t, aliasType, f.(*factory).Factory.(componentalias.TypeAliasHolder).DeprecatedAlias())
assert.EqualValues(t, &defaultCfg, f.CreateDefaultConfig())
_, err := f.CreateProfiles(context.Background(), exporter.Settings{ID: component.MustNewID("newname")}, &defaultCfg)
require.NoError(t, err)
_, err = f.CreateProfiles(context.Background(), exporter.Settings{ID: component.MustNewID("oldname")}, &defaultCfg)
require.NoError(t, err)
_, err = f.CreateProfiles(context.Background(), exporter.Settings{ID: component.MustNewID("wrongname")}, &defaultCfg)
require.Error(t, err)
}
================================================
FILE: exporter/xexporter/go.mod
================================================
module go.opentelemetry.io/collector/exporter/xexporter
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/consumer/consumertest v0.148.0
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0
go.opentelemetry.io/collector/exporter v1.54.0
go.opentelemetry.io/collector/internal/componentalias v0.148.0
go.opentelemetry.io/collector/pipeline v1.54.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/collector/consumer v1.54.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/pdata v1.54.0 // indirect
go.opentelemetry.io/collector/pdata/pprofile v0.148.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer
replace go.opentelemetry.io/collector/extension => ../../extension
replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile
replace go.opentelemetry.io/collector/config/configretry => ../../config/configretry
replace go.opentelemetry.io/collector/pdata/testdata => ../../pdata/testdata
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/receiver => ../../receiver
replace go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest
replace go.opentelemetry.io/collector/consumer => ../../consumer
replace go.opentelemetry.io/collector/exporter => ../
replace go.opentelemetry.io/collector/receiver/xreceiver => ../../receiver/xreceiver
replace go.opentelemetry.io/collector/pipeline => ../../pipeline
replace go.opentelemetry.io/collector/exporter/exportertest => ../exportertest
replace go.opentelemetry.io/collector/consumer/consumererror => ../../consumer/consumererror
replace go.opentelemetry.io/collector/receiver/receivertest => ../../receiver/receivertest
replace go.opentelemetry.io/collector/extension/extensiontest => ../../extension/extensiontest
replace go.opentelemetry.io/collector/extension/xextension => ../../extension/xextension
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/client => ../../client
replace go.opentelemetry.io/collector/pdata/xpdata => ../../pdata/xpdata
replace go.opentelemetry.io/collector/config/configoptional => ../../config/configoptional
replace go.opentelemetry.io/collector/confmap => ../../confmap
replace go.opentelemetry.io/collector/confmap/xconfmap => ../../confmap/xconfmap
replace go.opentelemetry.io/collector/exporter/exporterhelper => ../exporterhelper
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
replace go.opentelemetry.io/collector/pipeline/xpipeline => ../../pipeline/xpipeline
================================================
FILE: exporter/xexporter/go.sum
================================================
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: exporter/xexporter/metadata.yaml
================================================
type: xexporter
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
codeowners:
active:
- mx-psi
- dmathieu
stability:
alpha: [profiles]
================================================
FILE: extension/Makefile
================================================
include ../Makefile.Common
================================================
FILE: extension/README.md
================================================
# 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: Memory Limiter extension that prevents
out of memory situations or zPages extension that provides live data for
debugging different components.
Supported service extensions (sorted alphabetically):
- [Memory Limiter](memorylimiterextension/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: [extension1, extension2]
```
================================================
FILE: extension/extension.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package extension // import "go.opentelemetry.io/collector/extension"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/internal/componentalias"
)
// Extension 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 Extension interface {
component.Component
}
// Settings is passed to Factory.Create(...) function.
type Settings struct {
// ID returns the ID of the component that will be created.
ID component.ID
component.TelemetrySettings
// BuildInfo can be used by components for informational purposes
BuildInfo component.BuildInfo
// prevent unkeyed literal initialization
_ struct{}
}
// CreateFunc is the equivalent of Factory.Create(...) function.
type CreateFunc func(context.Context, Settings, component.Config) (Extension, error)
type Factory interface {
component.Factory
// Create an extension based on the given config.
Create(ctx context.Context, set Settings, cfg component.Config) (Extension, error)
// Stability gets the stability level of the Extension.
Stability() component.StabilityLevel
unexportedFactoryFunc()
}
type factory struct {
cfgType component.Type
component.CreateDefaultConfigFunc
componentalias.TypeAliasHolder
createFunc CreateFunc
extensionStability component.StabilityLevel
}
func (f *factory) Type() component.Type {
return f.cfgType
}
func (f *factory) unexportedFactoryFunc() {}
func (f *factory) Stability() component.StabilityLevel {
return f.extensionStability
}
func (f *factory) Create(ctx context.Context, set Settings, cfg component.Config) (Extension, error) {
if err := componentalias.ValidateComponentType(f, set.ID); err != nil {
return nil, err
}
return f.createFunc(ctx, set, cfg)
}
// NewFactory returns a new Factory based on this configuration.
func NewFactory(
cfgType component.Type,
createDefaultConfig component.CreateDefaultConfigFunc,
createServiceExtension CreateFunc,
sl component.StabilityLevel,
) Factory {
return &factory{
cfgType: cfgType,
CreateDefaultConfigFunc: createDefaultConfig,
TypeAliasHolder: componentalias.NewTypeAliasHolder(),
createFunc: createServiceExtension,
extensionStability: sl,
}
}
================================================
FILE: extension/extension_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package extension
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
)
type nopExtension struct {
component.StartFunc
component.ShutdownFunc
Settings
}
func TestNewFactory(t *testing.T) {
testType := component.MustNewType("test")
defaultCfg := struct{}{}
nopExtensionInstance := new(nopExtension)
factory := NewFactory(
testType,
func() component.Config { return &defaultCfg },
func(context.Context, Settings, component.Config) (Extension, error) {
return nopExtensionInstance, nil
},
component.StabilityLevelDevelopment)
assert.Equal(t, testType, factory.Type())
assert.EqualValues(t, &defaultCfg, factory.CreateDefaultConfig())
assert.Equal(t, component.StabilityLevelDevelopment, factory.Stability())
ext, err := factory.Create(context.Background(), Settings{ID: component.NewID(testType)}, &defaultCfg)
require.NoError(t, err)
assert.Same(t, nopExtensionInstance, ext)
_, err = factory.Create(context.Background(), Settings{ID: component.NewID(component.MustNewType("mismatch"))}, &defaultCfg)
require.Error(t, err)
}
================================================
FILE: extension/extensionauth/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: extension/extensionauth/client.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package extensionauth // import "go.opentelemetry.io/collector/extension/extensionauth"
import (
"net/http"
"google.golang.org/grpc/credentials"
)
// HTTPClient is an optional Extension interface that can be used as an HTTP authenticator for the configauth.Config option.
// Authenticators are then included as part of OpenTelemetry Collector builds and can be referenced by their
// names from the [configauth.Config] configuration.
type HTTPClient interface {
// RoundTripper returns a RoundTripper that can be used to authenticate HTTP requests.
RoundTripper(base http.RoundTripper) (http.RoundTripper, error)
}
// GRPCClient is an optional Extension interface that can be used as a gRPC authenticator for the configauth.Config option.
// Authenticators are then included as part of OpenTelemetry Collector builds and can be referenced by their
// names from the [configauth.Config] configuration.
type GRPCClient interface {
// PerRPCCredentials returns a PerRPCCredentials that can be used to authenticate gRPC requests.
PerRPCCredentials() (credentials.PerRPCCredentials, error)
}
var _ HTTPClient = (*ClientRoundTripperFunc)(nil)
// ClientRoundTripperFunc specifies the function that returns a RoundTripper that can be used to authenticate HTTP requests.
type ClientRoundTripperFunc func(base http.RoundTripper) (http.RoundTripper, error)
func (f ClientRoundTripperFunc) RoundTripper(base http.RoundTripper) (http.RoundTripper, error) {
if f == nil {
return base, nil
}
return f(base)
}
var _ GRPCClient = (*ClientPerRPCCredentialsFunc)(nil)
// ClientPerRPCCredentialsFunc specifies the function that returns a PerRPCCredentials that can be used to authenticate gRPC requests.
type ClientPerRPCCredentialsFunc func() (credentials.PerRPCCredentials, error)
func (f ClientPerRPCCredentialsFunc) PerRPCCredentials() (credentials.PerRPCCredentials, error) {
if f == nil {
return nil, nil
}
return f()
}
================================================
FILE: extension/extensionauth/client_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package extensionauth
import (
"context"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/credentials"
)
func TestRoundTripperFunc(t *testing.T) {
var called bool
var httpClient HTTPClient = ClientRoundTripperFunc(func(base http.RoundTripper) (http.RoundTripper, error) {
called = true
return base, nil
})
rt, err := httpClient.RoundTripper(http.DefaultTransport)
require.NoError(t, err)
assert.True(t, called)
assert.Equal(t, http.DefaultTransport, rt)
}
type customPerRPCCredentials struct{}
var _ credentials.PerRPCCredentials = (*customPerRPCCredentials)(nil)
func (c *customPerRPCCredentials) GetRequestMetadata(context.Context, ...string) (map[string]string, error) {
return nil, nil
}
func (c *customPerRPCCredentials) RequireTransportSecurity() bool {
return true
}
func TestWithPerRPCCredentialsFunc(t *testing.T) {
var called bool
var grpcClient GRPCClient = ClientPerRPCCredentialsFunc(func() (credentials.PerRPCCredentials, error) {
called = true
return &customPerRPCCredentials{}, nil
})
creds, err := grpcClient.PerRPCCredentials()
require.NoError(t, err)
assert.True(t, called)
assert.NotNil(t, creds)
}
================================================
FILE: extension/extensionauth/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package auth implements the configuration settings to
// ensure authentication on incoming requests, and allows
// exporters to add authentication on outgoing requests.
package extensionauth // import "go.opentelemetry.io/collector/extension/extensionauth"
================================================
FILE: extension/extensionauth/extensionauthtest/Makefile
================================================
include ../../../Makefile.Common
================================================
FILE: extension/extensionauth/extensionauthtest/err.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package extensionauthtest // import "go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest"
import (
"context"
"net/http"
"google.golang.org/grpc/credentials"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/extension"
"go.opentelemetry.io/collector/extension/extensionauth"
)
var (
_ extension.Extension = (*errClient)(nil)
_ extensionauth.HTTPClient = (*errClient)(nil)
_ extensionauth.GRPCClient = (*errClient)(nil)
)
type errClient struct {
component.StartFunc
component.ShutdownFunc
extensionauth.ClientPerRPCCredentialsFunc
extensionauth.ClientRoundTripperFunc
extensionauth.ServerAuthenticateFunc
}
// NewErr returns a new [extension.Extension] that implements all
// extensionauth interface and always returns an error.
func NewErr(err error) extension.Extension {
return &errClient{
ClientRoundTripperFunc: func(http.RoundTripper) (http.RoundTripper, error) {
return nil, err
},
ClientPerRPCCredentialsFunc: func() (credentials.PerRPCCredentials, error) {
return nil, err
},
ServerAuthenticateFunc: func(ctx context.Context, _ map[string][]string) (context.Context, error) { return ctx, err },
}
}
================================================
FILE: extension/extensionauth/extensionauthtest/err_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package extensionauthtest
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/extension/extensionauth"
)
func TestErrorClient(t *testing.T) {
client := NewErr(errors.New("error"))
httpClient, ok := client.(extensionauth.HTTPClient)
require.True(t, ok)
_, err := httpClient.RoundTripper(nil)
require.Error(t, err)
grpcClient, ok := client.(extensionauth.GRPCClient)
require.True(t, ok)
_, err = grpcClient.PerRPCCredentials()
require.Error(t, err)
server, ok := client.(extensionauth.Server)
require.True(t, ok)
_, err = server.Authenticate(context.Background(), map[string][]string{})
require.Error(t, err)
}
================================================
FILE: extension/extensionauth/extensionauthtest/go.mod
================================================
module go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/extension v1.54.0
go.opentelemetry.io/collector/extension/extensionauth v1.54.0
go.uber.org/goleak v1.3.0
google.golang.org/grpc v1.79.3
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
go.opentelemetry.io/collector/pdata v1.54.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/extension/extensionauth => ..
replace go.opentelemetry.io/collector/component => ../../../component
replace go.opentelemetry.io/collector/pdata => ../../../pdata
replace go.opentelemetry.io/collector/extension => ../..
replace go.opentelemetry.io/collector/featuregate => ../../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../../internal/componentalias
================================================
FILE: extension/extensionauth/extensionauthtest/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: extension/extensionauth/extensionauthtest/metadata.yaml
================================================
type: extensionauth/extensionauthtest
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: extension/extensionauth/extensionauthtest/nop_client.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package extensionauthtest // import "go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest"
import (
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/extension"
"go.opentelemetry.io/collector/extension/extensionauth"
)
var (
_ extension.Extension = (*nopClient)(nil)
_ extensionauth.HTTPClient = (*nopClient)(nil)
_ extensionauth.GRPCClient = (*nopClient)(nil)
)
type nopClient struct {
component.StartFunc
component.ShutdownFunc
extensionauth.ClientRoundTripperFunc
extensionauth.ClientPerRPCCredentialsFunc
}
// NewNopClient returns a new [extension.Extension] that implements the [extensionauth.HTTPClient] and [extensionauth.GRPCClient].
// For HTTP requests it returns the base RoundTripper and for gRPC requests it returns a nil [credentials.PerRPCCredentials].
func NewNopClient() extension.Extension {
return &nopClient{}
}
================================================
FILE: extension/extensionauth/extensionauthtest/nop_client_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package extensionauthtest
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/extension/extensionauth"
)
func TestNopClient(t *testing.T) {
client := NewNopClient()
httpClient, ok := client.(extensionauth.HTTPClient)
require.True(t, ok)
rt, err := httpClient.RoundTripper(nil)
require.NoError(t, err)
assert.Nil(t, rt)
grpcClient, ok := client.(extensionauth.GRPCClient)
require.True(t, ok)
grpcAuth, err := grpcClient.PerRPCCredentials()
require.NoError(t, err)
assert.Nil(t, grpcAuth)
}
================================================
FILE: extension/extensionauth/extensionauthtest/nop_server.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package extensionauthtest // import "go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/extension"
"go.opentelemetry.io/collector/extension/extensionauth"
)
var (
_ extension.Extension = (*nopServer)(nil)
_ extensionauth.Server = (*nopServer)(nil)
)
type nopServer struct {
component.StartFunc
component.ShutdownFunc
}
// Authenticate implements extensionauth.Server.
func (n *nopServer) Authenticate(ctx context.Context, _ map[string][]string) (context.Context, error) {
return ctx, nil
}
// NewNopServer returns a new extension.Extension that implements the extensionauth.Server.
func NewNopServer() extension.Extension {
return &nopServer{}
}
================================================
FILE: extension/extensionauth/extensionauthtest/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package extensionauthtest
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: extension/extensionauth/go.mod
================================================
module go.opentelemetry.io/collector/extension/extensionauth
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.uber.org/goleak v1.3.0
google.golang.org/grpc v1.79.3
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
golang.org/x/net v0.51.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
================================================
FILE: extension/extensionauth/go.sum
================================================
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
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/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: extension/extensionauth/metadata.yaml
================================================
type: extensionauth
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: extension/extensionauth/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package extensionauth
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: extension/extensionauth/server.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package extensionauth // import "go.opentelemetry.io/collector/extension/extensionauth"
import (
"context"
)
// Server is an optional Extension interface that can be used as an authenticator for the configauth.Config option.
// Authenticators are then included as part of OpenTelemetry Collector builds and can be referenced by their
// names from the [configauth.Config] configuration. Each Server is free to define its own behavior and configuration options,
// but note that the expectations that come as part of Extensions exist here as well. For instance, multiple instances of the same
// authenticator should be possible to exist under different names.
type Server interface {
// Authenticate checks whether the given map contains valid auth data. Successfully authenticated calls will always return a nil error.
// When the authentication fails, an error must be returned and the caller must not retry. This function is typically called from interceptors,
// on behalf of receivers, but receivers can still call this directly if the usage of interceptors isn't suitable.
// The deadline and cancellation given to this function must be respected, but note that authentication data has to be part of the map, not context.
// The resulting context should contain the authentication data, such as the principal/username, group membership (if available), and the raw
// authentication data (if possible). This will allow other components in the pipeline to make decisions based on that data, such as routing based
// on tenancy as determined by the group membership, or passing through the authentication data to the next collector/backend.
// The context keys to be used are not defined yet.
Authenticate(ctx context.Context, sources map[string][]string) (context.Context, error)
}
// ServerAuthenticateFunc defines the signature for the function responsible for performing the authentication based
// on the given sources map. See Server.Authenticate.
type ServerAuthenticateFunc func(ctx context.Context, sources map[string][]string) (context.Context, error)
func (f ServerAuthenticateFunc) Authenticate(ctx context.Context, sources map[string][]string) (context.Context, error) {
if f == nil {
return ctx, nil
}
return f(ctx, sources)
}
================================================
FILE: extension/extensionauth/server_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package extensionauth
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestServerAuthenticateFunc(t *testing.T) {
var called bool
var server Server = ServerAuthenticateFunc(func(ctx context.Context, _ map[string][]string) (context.Context, error) {
called = true
return ctx, nil
})
ctx, err := server.Authenticate(context.Background(), nil)
require.NoError(t, err)
assert.True(t, called)
assert.NotNil(t, ctx)
}
================================================
FILE: extension/extensioncapabilities/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: extension/extensioncapabilities/go.mod
================================================
module go.opentelemetry.io/collector/extension/extensioncapabilities
go 1.25.0
require (
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/confmap v1.54.0
go.opentelemetry.io/collector/extension v1.54.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
go.opentelemetry.io/collector/pdata v1.54.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
)
replace go.opentelemetry.io/collector/extension => ../
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/confmap => ../../confmap
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
================================================
FILE: extension/extensioncapabilities/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: extension/extensioncapabilities/interfaces.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package extensioncapabilities provides interfaces that can be implemented by extensions
// to provide additional capabilities.
package extensioncapabilities // import "go.opentelemetry.io/collector/extension/extensioncapabilities"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/extension"
)
// Dependent is an optional interface that can be implemented by extensions
// that depend on other extensions and must be started only after their dependencies.
// See https://github.com/open-telemetry/opentelemetry-collector/pull/8768 for examples.
type Dependent interface {
extension.Extension
Dependencies() []component.ID
}
// PipelineWatcher is an extra interface for Extension 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 Extension that all pipelines were built and the
// receivers were started, i.e.: the service is ready to receive data
// (note that it may already have received data when this method is called).
Ready() error
// NotReady notifies the Extension 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 Extension can take any
// appropriate actions before that happens.
NotReady() error
}
// ConfigWatcher is an interface that should be implemented by an extension that
// wishes to be notified of the Collector's effective configuration.
type ConfigWatcher interface {
// NotifyConfig notifies the extension of the Collector's current effective configuration.
// The extension owns the `confmap.Conf`. Callers must ensure that it's safe for
// extensions to store the `conf` pointer and use it concurrently with any other
// instances of `conf`.
NotifyConfig(ctx context.Context, conf *confmap.Conf) error
}
================================================
FILE: extension/extensioncapabilities/metadata.yaml
================================================
type: extensioncapabilities
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: extension/extensionmiddleware/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: extension/extensionmiddleware/README.md
================================================
# OpenTelemetry Collector Middleware Extension API
This package implements interfaces for injecting middleware behavior
in OpenTelemetry Collector exporters and receivers. See the
[associated `configmiddleware` package](../../config/configmiddleware/README.md)
for referring to middleware
extensions in component configurations.
## Overview
Middleware extensions can be configured on gRPC and HTTP connections,
on both the client and server side. The term "middleware" is defined
broadly to cover many ways of intercepting, acting on, and observing
requests as they enter and exit and RPC system.
Middleware details and capabilities are specific to each protocol. In
some cases, these interfaces permit configuring behavior other than
middleware. Users have to place a trust in the extensions they
configure, since they are capable of subverting security and other RPC
configuration.
Middleware is generally configured at a level in the code where:
1. the identity of the calling component is not known, because
`confighttp` and `configgrpc` interfaces likewise are not configured
with the identify of the calling component.
2. the signal type in use is not known, because a single connection
serves multiple signals.
## Interfaces
Each interface has a single function to configure middleware for a
protocol on the client or server side. An error is returned if the
extension cannot be configured.
New protocols and new ways to configure middleware can be introduced
by adding new interfaces. Note that for each interface, there is a
corresponding method to locate a named middleware extension that
satisfies the interface in
[the `configmiddleware` package](../../config/configmiddleware/README.md) .
### HTTP
Interface methods are called once per request to construct a client-
or server-side middleware object.
- **HTTPClient**: The extension returns a function to create new `http.RoundTripper`s.
- **HTTPServer**: The extension returns a function to create new `http.Handler`s.
### GRPC
Interface methods are called once at setup to configure the client- or
server-side middleware object.
- **GRPCClient**: The extension returns `[]grpc.DialOption`.
- **GRPCServer**: The extension returns `[]grpc.ServerOption`.
================================================
FILE: extension/extensionmiddleware/client.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package extensionmiddleware // import "go.opentelemetry.io/collector/extension/extensionmiddleware"
import (
"context"
"net/http"
"google.golang.org/grpc"
)
// HTTPClient is an interface for HTTP client middleware extensions.
type HTTPClient interface {
// GetHTTPRoundTripper initializes a client HTTP RoundTripper
// wrapper, this typically called when the Collector
// starts. If there is an error this returns a nil
// function. If the error is nil, the WrapHTTPRoundTripperFunc
// will never be nil.
GetHTTPRoundTripper(context.Context) (WrapHTTPRoundTripperFunc, error)
}
// GRPCClient is an interface for gRPC client middleware extensions.
type GRPCClient interface {
// GetGRPCClientOptions returns the gRPC dial options to use for client connections.
GetGRPCClientOptions(context.Context) ([]grpc.DialOption, error)
}
var _ HTTPClient = (*GetHTTPRoundTripperFunc)(nil)
// GetHTTPRoundTripperFunc is a function that implements HTTPClient.
type GetHTTPRoundTripperFunc func(context.Context) (WrapHTTPRoundTripperFunc, error)
func (f GetHTTPRoundTripperFunc) GetHTTPRoundTripper(ctx context.Context) (WrapHTTPRoundTripperFunc, error) {
if f == nil {
return func(_ context.Context, rt http.RoundTripper) (http.RoundTripper, error) { return rt, nil }, nil
}
return f(ctx)
}
var _ GRPCClient = (*GetGRPCClientOptionsFunc)(nil)
// GetGRPCClientOptionsFunc is a function that implements GRPCClient.
type GetGRPCClientOptionsFunc func(context.Context) ([]grpc.DialOption, error)
func (f GetGRPCClientOptionsFunc) GetGRPCClientOptions(ctx context.Context) ([]grpc.DialOption, error) {
if f == nil {
return nil, nil
}
return f(ctx)
}
// WrapHTTPRoundTripperFunc is called to initialize a new instance of
// HTTP client middleware.
type WrapHTTPRoundTripperFunc = func(context.Context, http.RoundTripper) (http.RoundTripper, error)
================================================
FILE: extension/extensionmiddleware/client_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package extensionmiddleware
import (
"context"
"errors"
"net/http"
"testing"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
)
func TestGetHTTPRoundTripperFunc(t *testing.T) {
// Create a base round tripper for testing
baseRT := http.DefaultTransport
testctx := context.Background()
t.Run("nil function", func(t *testing.T) {
var nilFunc GetHTTPRoundTripperFunc
rtfunc, err := nilFunc.GetHTTPRoundTripper(testctx)
require.NoError(t, err)
rt, err := rtfunc(testctx, baseRT)
require.NoError(t, err)
require.Equal(t, baseRT, rt)
})
t.Run("identity function", func(t *testing.T) {
identityFunc := GetHTTPRoundTripperFunc(nil)
rtfunc, err := identityFunc.GetHTTPRoundTripper(testctx)
require.NoError(t, err)
rt, err := rtfunc(testctx, baseRT)
require.NoError(t, err)
require.Equal(t, baseRT, rt)
})
t.Run("error function", func(t *testing.T) {
expectedErr := errors.New("round tripper error")
errorFunc := GetHTTPRoundTripperFunc(func(_ context.Context) (WrapHTTPRoundTripperFunc, error) {
return nil, expectedErr
})
rtfunc, err := errorFunc.GetHTTPRoundTripper(testctx)
require.Error(t, err)
require.Equal(t, expectedErr, err)
require.Nil(t, rtfunc)
})
}
func TestGetGRPCClientOptionsFunc(t *testing.T) {
type testCtx struct{}
var (
key = testCtx{}
value = "testval"
)
testctx := context.WithValue(context.Background(), key, value)
t.Run("nil function", func(t *testing.T) {
var nilFunc GetGRPCClientOptionsFunc
options, err := nilFunc.GetGRPCClientOptions(testctx)
require.NoError(t, err)
require.Nil(t, options)
})
t.Run("options function", func(t *testing.T) {
dialOpt1 := grpc.WithAuthority("test-authority")
dialOpt2 := grpc.WithDisableRetry()
optionsFunc := GetGRPCClientOptionsFunc(func(ctx context.Context) ([]grpc.DialOption, error) {
require.Equal(t, ctx.Value(key), value)
return []grpc.DialOption{dialOpt1, dialOpt2}, nil
})
options, err := optionsFunc.GetGRPCClientOptions(testctx)
require.NoError(t, err)
require.Len(t, options, 2)
})
t.Run("error function", func(t *testing.T) {
expectedErr := errors.New("grpc options error")
errorFunc := GetGRPCClientOptionsFunc(func(ctx context.Context) ([]grpc.DialOption, error) {
require.Equal(t, ctx.Value(key), value)
return nil, expectedErr
})
options, err := errorFunc.GetGRPCClientOptions(testctx)
require.Error(t, err)
require.Equal(t, expectedErr, err)
require.Nil(t, options)
})
}
================================================
FILE: extension/extensionmiddleware/extensionmiddlewaretest/Makefile
================================================
include ../../../Makefile.Common
================================================
FILE: extension/extensionmiddleware/extensionmiddlewaretest/err.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package extensionmiddlewaretest // import "go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest"
import (
"context"
"google.golang.org/grpc"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/extension"
"go.opentelemetry.io/collector/extension/extensionmiddleware"
)
var (
_ extension.Extension = (*baseExtension)(nil)
_ extensionmiddleware.HTTPClient = (*baseExtension)(nil)
_ extensionmiddleware.GRPCClient = (*baseExtension)(nil)
_ extensionmiddleware.HTTPServer = (*baseExtension)(nil)
_ extensionmiddleware.GRPCServer = (*baseExtension)(nil)
)
type baseExtension struct {
component.StartFunc
component.ShutdownFunc
extensionmiddleware.GetHTTPHandlerFunc
extensionmiddleware.GetGRPCServerOptionsFunc
extensionmiddleware.GetHTTPRoundTripperFunc
extensionmiddleware.GetGRPCClientOptionsFunc
}
// NewErr returns a new [extension.Extension] that implements all
// extensionmiddleware interface and always returns an error.
func NewErr(err error) extension.Extension {
return &baseExtension{
GetHTTPRoundTripperFunc: func(context.Context) (extensionmiddleware.WrapHTTPRoundTripperFunc, error) {
return nil, err
},
GetGRPCClientOptionsFunc: func(context.Context) ([]grpc.DialOption, error) {
return nil, err
},
GetHTTPHandlerFunc: func(context.Context) (extensionmiddleware.WrapHTTPHandlerFunc, error) {
return nil, err
},
GetGRPCServerOptionsFunc: func(context.Context) ([]grpc.ServerOption, error) {
return nil, err
},
}
}
================================================
FILE: extension/extensionmiddleware/extensionmiddlewaretest/err_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package extensionmiddlewaretest
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/extension/extensionmiddleware"
)
func TestErrClient(t *testing.T) {
client := NewErr(errors.New("error"))
httpClient, ok := client.(extensionmiddleware.HTTPClient)
require.True(t, ok)
_, err := httpClient.GetHTTPRoundTripper(context.Background())
require.Error(t, err)
grpcClient, ok := client.(extensionmiddleware.GRPCClient)
require.True(t, ok)
_, err = grpcClient.GetGRPCClientOptions(context.Background())
require.Error(t, err)
}
func TestErrServer(t *testing.T) {
server := NewErr(errors.New("error"))
testctx := context.Background()
httpServer, ok := server.(extensionmiddleware.HTTPServer)
require.True(t, ok)
_, err := httpServer.GetHTTPHandler(testctx)
require.Error(t, err)
grpcServer, ok := server.(extensionmiddleware.GRPCServer)
require.True(t, ok)
_, err = grpcServer.GetGRPCServerOptions(context.Background())
require.Error(t, err)
}
================================================
FILE: extension/extensionmiddleware/extensionmiddlewaretest/go.mod
================================================
module go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/extension v1.54.0
go.opentelemetry.io/collector/extension/extensionmiddleware v0.148.0
google.golang.org/grpc v1.79.3
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
go.opentelemetry.io/collector/pdata v1.54.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/extension/extensionmiddleware => ..
replace go.opentelemetry.io/collector/component => ../../../component
replace go.opentelemetry.io/collector/pdata => ../../../pdata
replace go.opentelemetry.io/collector/extension => ../..
replace go.opentelemetry.io/collector/featuregate => ../../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../../internal/componentalias
================================================
FILE: extension/extensionmiddleware/extensionmiddlewaretest/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: extension/extensionmiddleware/extensionmiddlewaretest/metadata.yaml
================================================
type: extensionmiddleware/extensionmiddlewaretest
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: extension/extensionmiddleware/extensionmiddlewaretest/nop.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package extensionmiddlewaretest // import "go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest"
import (
"net/http"
"go.opentelemetry.io/collector/extension"
)
// NewNop returns a new [extension.Extension] that implements
// the all the extensionmiddleware interfaces. For HTTP requests it
// returns the base RoundTripper and for gRPC requests it returns an
// empty slice of options.
func NewNop() extension.Extension {
return &baseExtension{}
}
// RoundTripperFunc implements an HTTP client middleware function. This
// is the equivalent of net/http.HandlerFunc for creating a
// net/http.RoundTripper from a function.
type RoundTripperFunc func(*http.Request) (*http.Response, error)
func (f RoundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return f(req)
}
================================================
FILE: extension/extensionmiddleware/extensionmiddlewaretest/nop_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package extensionmiddlewaretest
import (
"context"
"net/http"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/extension/extensionmiddleware"
)
func TestNopClient(t *testing.T) {
testctx := context.Background()
client := NewNop()
httpClient, ok := client.(extensionmiddleware.HTTPClient)
require.True(t, ok)
rtfunc, err := httpClient.GetHTTPRoundTripper(context.Background())
require.NoError(t, err)
rt, err := rtfunc(testctx, nil)
require.NoError(t, err)
require.Nil(t, rt)
grpcClient, ok := client.(extensionmiddleware.GRPCClient)
require.True(t, ok)
grpcOpts, err := grpcClient.GetGRPCClientOptions(context.Background())
require.NoError(t, err)
require.Nil(t, grpcOpts)
}
func TestNopServer(t *testing.T) {
client := NewNop()
testctx := context.Background()
httpServer, ok := client.(extensionmiddleware.HTTPServer)
require.True(t, ok)
hfunc, err := httpServer.GetHTTPHandler(testctx)
require.NoError(t, err)
handler, err := hfunc(testctx, nil)
require.NoError(t, err)
require.Nil(t, handler)
grpcServer, ok := client.(extensionmiddleware.GRPCServer)
require.True(t, ok)
grpcOpts, err := grpcServer.GetGRPCServerOptions(context.Background())
require.NoError(t, err)
require.Nil(t, grpcOpts)
}
func TestRoundTripperFunc(t *testing.T) {
called := false
req := &http.Request{}
resp := &http.Response{}
f := RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
require.Equal(t, r, req)
called = true
return resp, nil
})
result, _ := f.RoundTrip(req)
require.True(t, called)
require.Equal(t, resp, result)
}
================================================
FILE: extension/extensionmiddleware/go.mod
================================================
module go.opentelemetry.io/collector/extension/extensionmiddleware
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
google.golang.org/grpc v1.79.3
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
================================================
FILE: extension/extensionmiddleware/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: extension/extensionmiddleware/metadata.yaml
================================================
type: extensionmiddleware
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: extension/extensionmiddleware/server.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package extensionmiddleware // import "go.opentelemetry.io/collector/extension/extensionmiddleware"
import (
"context"
"net/http"
"google.golang.org/grpc"
)
// HTTPServer defines the interface for HTTP server middleware extensions.
type HTTPServer interface {
// GetHTTPHandler wraps the provided base http.Handler.
GetHTTPHandler(_ context.Context) (WrapHTTPHandlerFunc, error)
}
// GRPCServer defines the interface for gRPC server middleware extensions.
type GRPCServer interface {
// GetGRPCServerOptions returns options for a gRPC server.
GetGRPCServerOptions(context.Context) ([]grpc.ServerOption, error)
}
var _ HTTPServer = (*GetHTTPHandlerFunc)(nil)
// GetHTTPHandlerFunc is a function that implements HTTPServer.
type GetHTTPHandlerFunc func(_ context.Context) (WrapHTTPHandlerFunc, error)
func (f GetHTTPHandlerFunc) GetHTTPHandler(ctx context.Context) (WrapHTTPHandlerFunc, error) {
if f == nil {
return func(_ context.Context, h http.Handler) (http.Handler, error) {
return h, nil
}, nil
}
return f(ctx)
}
var _ GRPCServer = (*GetGRPCServerOptionsFunc)(nil)
// GetGRPCServerOptionsFunc is a function that implements GRPCServer.
type GetGRPCServerOptionsFunc func(context.Context) ([]grpc.ServerOption, error)
func (f GetGRPCServerOptionsFunc) GetGRPCServerOptions(ctx context.Context) ([]grpc.ServerOption, error) {
if f == nil {
return nil, nil
}
return f(ctx)
}
// WrapHTTPHandlerFunc is called to initialize a new instance of
// HTTP server middleware at runtime.
type WrapHTTPHandlerFunc = func(context.Context, http.Handler) (http.Handler, error)
================================================
FILE: extension/extensionmiddleware/server_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package extensionmiddleware
import (
"context"
"errors"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
)
func TestGetHTTPHandlerFunc(t *testing.T) {
testctx := context.Background()
t.Run("nil_function", func(t *testing.T) {
var f GetHTTPHandlerFunc
baseHandler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusNoContent)
})
hfunc, err := f.GetHTTPHandler(testctx)
require.NoError(t, err)
handler, err := hfunc(testctx, baseHandler)
require.NoError(t, err)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/", http.NoBody))
require.Equal(t, http.StatusNoContent, rr.Code)
})
t.Run("returns_wrapped_handler", func(t *testing.T) {
called := false
baseHandler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
})
f := GetHTTPHandlerFunc(func(_ context.Context) (WrapHTTPHandlerFunc, error) {
return func(_ context.Context, base http.Handler) (http.Handler, error) {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
called = true
base.ServeHTTP(w, r)
}), nil
}, nil
})
hfunc, err := f.GetHTTPHandler(testctx)
require.NoError(t, err)
require.NotNil(t, hfunc)
handler, err := hfunc(testctx, baseHandler)
require.NoError(t, err)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/", http.NoBody))
require.True(t, called)
require.Equal(t, http.StatusOK, rr.Code)
})
t.Run("returns_error", func(t *testing.T) {
expectedErr := errors.New("test error")
f := GetHTTPHandlerFunc(func(context.Context) (WrapHTTPHandlerFunc, error) {
return nil, expectedErr
})
hfunc, err := f.GetHTTPHandler(testctx)
require.Equal(t, expectedErr, err)
require.Nil(t, hfunc)
})
}
func TestGetGRPCServerOptionsFunc(t *testing.T) {
type testCtx struct{}
var (
key = testCtx{}
value = "testval"
)
testctx := context.WithValue(context.Background(), key, value)
t.Run("nil_function", func(t *testing.T) {
var f GetGRPCServerOptionsFunc
opts, err := f.GetGRPCServerOptions(testctx)
require.NoError(t, err)
require.Nil(t, opts)
})
t.Run("returns_server_options", func(t *testing.T) {
var interceptor grpc.UnaryServerInterceptor = func(
context.Context,
any,
*grpc.UnaryServerInfo,
grpc.UnaryHandler,
) (resp any, err error) {
return nil, nil
}
expectedOpts := []grpc.ServerOption{grpc.UnaryInterceptor(interceptor)}
f := GetGRPCServerOptionsFunc(func(ctx context.Context) ([]grpc.ServerOption, error) {
require.Equal(t, ctx.Value(key), value)
return expectedOpts, nil
})
opts, err := f.GetGRPCServerOptions(testctx)
require.NoError(t, err)
require.Equal(t, expectedOpts, opts)
})
t.Run("returns_error", func(t *testing.T) {
expectedErr := errors.New("test error")
f := GetGRPCServerOptionsFunc(func(ctx context.Context) ([]grpc.ServerOption, error) {
require.Equal(t, ctx.Value(key), value)
return nil, expectedErr
})
opts, err := f.GetGRPCServerOptions(testctx)
require.Equal(t, expectedErr, err)
require.Nil(t, opts)
})
}
================================================
FILE: extension/extensiontest/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: extension/extensiontest/go.mod
================================================
module go.opentelemetry.io/collector/extension/extensiontest
go 1.25.0
replace go.opentelemetry.io/collector/extension => ..
require (
github.com/google/uuid v1.6.0
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.opentelemetry.io/collector/extension v1.54.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
go.opentelemetry.io/collector/pdata v1.54.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
golang.org/x/sys v0.41.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
================================================
FILE: extension/extensiontest/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: extension/extensiontest/metadata.yaml
================================================
type: extension/extensiontest
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: extension/extensiontest/nop_extension.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package extensiontest // import "go.opentelemetry.io/collector/extension/extensiontest"
import (
"context"
"github.com/google/uuid"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/extension"
)
// NopType is the type of the nop extension.
var NopType = component.MustNewType("nop")
// NewNopSettings returns a new nop settings for extension.Factory Create* functions with the given type.
func NewNopSettings(typ component.Type) extension.Settings {
return extension.Settings{
ID: component.NewIDWithName(typ, uuid.NewString()),
TelemetrySettings: componenttest.NewNopTelemetrySettings(),
BuildInfo: component.NewDefaultBuildInfo(),
}
}
// NewNopFactory returns an extension.Factory that constructs nop extensions.
func NewNopFactory() extension.Factory {
return extension.NewFactory(
NopType,
func() component.Config {
return &nopConfig{}
},
func(context.Context, extension.Settings, component.Config) (extension.Extension, error) {
return nopInstance, nil
},
component.StabilityLevelStable)
}
type nopConfig struct{}
var nopInstance = &nopExtension{}
// nopExtension acts as an extension for testing purposes.
type nopExtension struct {
component.StartFunc
component.ShutdownFunc
}
================================================
FILE: extension/extensiontest/nop_extension_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package extensiontest
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
)
func TestNewNopFactory(t *testing.T) {
factory := NewNopFactory()
require.NotNil(t, factory)
assert.Equal(t, component.MustNewType("nop"), factory.Type())
cfg := factory.CreateDefaultConfig()
assert.Equal(t, &nopConfig{}, cfg)
traces, err := factory.Create(context.Background(), NewNopSettings(NopType), cfg)
require.NoError(t, err)
assert.NoError(t, traces.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, traces.Shutdown(context.Background()))
}
================================================
FILE: extension/go.mod
================================================
module go.opentelemetry.io/collector/extension
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/internal/componentalias v0.148.0
go.uber.org/goleak v1.3.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/pdata v1.54.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/component => ../component
replace go.opentelemetry.io/collector/pdata => ../pdata
replace go.opentelemetry.io/collector/featuregate => ../featuregate
replace go.opentelemetry.io/collector/internal/componentalias => ../internal/componentalias
replace go.opentelemetry.io/collector/internal/testutil => ../internal/testutil
================================================
FILE: extension/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: extension/memorylimiterextension/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: extension/memorylimiterextension/README.md
================================================
# Memory Limiter Extension
| Status | |
| ------------- |-----------|
| Stability | [development] |
| Distributions | [] |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Aextension%2Fmemorylimiter) [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aclosed+is%3Aissue+label%3Aextension%2Fmemorylimiter) |
[development]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#development
The memory limiter extension is used to prevent out of memory situations on
the collector. The extension will potentially replace the Memory Limiter Processor.
It provides better guarantees from running out of memory as it will be used by the
receivers to reject requests before converting them into OTLP. All the configurations
are the same as Memory Limiter Processor.
This extension can be used as an extension for all HTTP and gRPC receivers that
are configured through the standard `confighttp` and `configgrpc` libraries. For
example, to configure this extension in the OTLP receiver:
```
receivers:
otlp:
protocols:
grpc:
middlewares:
- id: memory_limiter
http:
middlewares:
- id: memory_limiter
extensions:
memory_limiter:
check_interval: 1s
limit_percentage: 1
spike_limit_percentage: 0.05
```
see [memorylimiterprocessor](../../processor/memorylimiterprocessor/README.md) for additional details
================================================
FILE: extension/memorylimiterextension/config.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package memorylimiterextension // import "go.opentelemetry.io/collector/extension/memorylimiterextension"
import (
"go.opentelemetry.io/collector/internal/memorylimiter"
)
type Config = memorylimiter.Config
================================================
FILE: extension/memorylimiterextension/factory.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package memorylimiterextension // import "go.opentelemetry.io/collector/extension/memorylimiterextension"
//go:generate mdatagen metadata.yaml
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/extension"
"go.opentelemetry.io/collector/extension/memorylimiterextension/internal/metadata"
"go.opentelemetry.io/collector/internal/memorylimiter"
)
// NewFactory returns a new factory for the Memory Limiter extension.
func NewFactory() extension.Factory {
return extension.NewFactory(
metadata.Type,
createDefaultConfig,
create,
metadata.ExtensionStability)
}
// CreateDefaultConfig creates the default configuration for extension. Notice
// that the default configuration is expected to fail for this extension.
func createDefaultConfig() component.Config {
return memorylimiter.NewDefaultConfig()
}
func create(_ context.Context, set extension.Settings, cfg component.Config) (extension.Extension, error) {
return newMemoryLimiter(cfg.(*Config), set.Logger)
}
================================================
FILE: extension/memorylimiterextension/factory_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package memorylimiterextension
import (
"context"
"testing"
"time"
"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/extension/extensiontest"
)
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, componenttest.CheckConfigStruct(cfg))
}
func TestCreate(t *testing.T) {
factory := NewFactory()
require.NotNil(t, factory)
cfg := factory.CreateDefaultConfig()
// Create extension with a valid config.
pCfg := cfg.(*Config)
pCfg.MemoryLimitMiB = 5722
pCfg.MemorySpikeLimitMiB = 1907
pCfg.CheckInterval = 100 * time.Millisecond
set := extensiontest.NewNopSettings(factory.Type())
set.ID = component.NewID(factory.Type())
tp, err := factory.Create(context.Background(), set, cfg)
require.NoError(t, err)
assert.NotNil(t, tp)
// test if we can shutdown a monitoring routine that has not started
require.NoError(t, tp.Shutdown(context.Background()))
assert.NoError(t, tp.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, tp.Shutdown(context.Background()))
// verify that shutdown twice works:
assert.NoError(t, tp.Shutdown(context.Background()))
}
================================================
FILE: extension/memorylimiterextension/generated_component_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package memorylimiterextension
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/confmap/confmaptest"
"go.opentelemetry.io/collector/extension/extensiontest"
)
var typ = component.MustNewType("memory_limiter")
func TestComponentFactoryType(t *testing.T) {
require.Equal(t, typ, NewFactory().Type())
}
func TestComponentConfigStruct(t *testing.T) {
require.NoError(t, componenttest.CheckConfigStruct(NewFactory().CreateDefaultConfig()))
}
func TestComponentLifecycle(t *testing.T) {
factory := NewFactory()
cm, err := confmaptest.LoadConf("metadata.yaml")
require.NoError(t, err)
cfg := factory.CreateDefaultConfig()
sub, err := cm.Sub("tests::config")
require.NoError(t, err)
require.NoError(t, sub.Unmarshal(&cfg))
t.Run("shutdown", func(t *testing.T) {
e, err := factory.Create(context.Background(), extensiontest.NewNopSettings(typ), cfg)
require.NoError(t, err)
err = e.Shutdown(context.Background())
require.NoError(t, err)
})
t.Run("lifecycle", func(t *testing.T) {
firstExt, err := factory.Create(context.Background(), extensiontest.NewNopSettings(typ), cfg)
require.NoError(t, err)
require.NoError(t, firstExt.Start(context.Background(), newMdatagenNopHost()))
require.NoError(t, firstExt.Shutdown(context.Background()))
secondExt, err := factory.Create(context.Background(), extensiontest.NewNopSettings(typ), cfg)
require.NoError(t, err)
require.NoError(t, secondExt.Start(context.Background(), newMdatagenNopHost()))
require.NoError(t, secondExt.Shutdown(context.Background()))
})
}
var _ component.Host = (*mdatagenNopHost)(nil)
type mdatagenNopHost struct{}
func newMdatagenNopHost() component.Host {
return &mdatagenNopHost{}
}
func (mnh *mdatagenNopHost) GetExtensions() map[component.ID]component.Component {
return nil
}
func (mnh *mdatagenNopHost) GetFactory(_ component.Kind, _ component.Type) component.Factory {
return nil
}
================================================
FILE: extension/memorylimiterextension/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package memorylimiterextension
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: extension/memorylimiterextension/go.mod
================================================
module go.opentelemetry.io/collector/extension/memorylimiterextension
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.opentelemetry.io/collector/confmap v1.54.0
go.opentelemetry.io/collector/extension v1.54.0
go.opentelemetry.io/collector/extension/extensionmiddleware v0.148.0
go.opentelemetry.io/collector/extension/extensiontest v0.148.0
go.opentelemetry.io/collector/internal/memorylimiter v0.148.0
go.uber.org/goleak v1.3.0
go.uber.org/zap v1.27.1
google.golang.org/grpc v1.79.3
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/ebitengine/purego v0.10.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/shirou/gopsutil/v4 v4.26.2 // indirect
github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.11.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
go.opentelemetry.io/collector/pdata v1.54.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest
replace go.opentelemetry.io/collector/confmap => ../../confmap
replace go.opentelemetry.io/collector/extension => ../../extension
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/internal/memorylimiter => ../../internal/memorylimiter
replace go.opentelemetry.io/collector/extension/extensiontest => ../../extension/extensiontest
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/extension/extensionmiddleware => ../../extension/extensionmiddleware
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
================================================
FILE: extension/memorylimiterextension/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/shirou/gopsutil/v4 v4.26.2 h1:X8i6sicvUFih4BmYIGT1m2wwgw2VG9YgrDTi7cIRGUI=
github.com/shirou/gopsutil/v4 v4.26.2/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: extension/memorylimiterextension/internal/metadata/generated_status.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/component"
)
var (
Type = component.MustNewType("memory_limiter")
ScopeName = "go.opentelemetry.io/collector/extension/memorylimiterextension"
)
const (
ExtensionStability = component.StabilityLevelDevelopment
)
================================================
FILE: extension/memorylimiterextension/memorylimiter.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package memorylimiterextension // import "go.opentelemetry.io/collector/extension/memorylimiterextension"
import (
"context"
"net/http"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/extension/extensionmiddleware"
"go.opentelemetry.io/collector/internal/memorylimiter"
)
var (
_ extensionmiddleware.GRPCServer = (*memoryLimiterExtension)(nil)
_ extensionmiddleware.HTTPServer = (*memoryLimiterExtension)(nil)
)
type memoryLimiterExtension struct {
memLimiter *memorylimiter.MemoryLimiter
}
// newMemoryLimiter returns a new memorylimiter extension.
func newMemoryLimiter(cfg *Config, logger *zap.Logger) (*memoryLimiterExtension, error) {
ml, err := memorylimiter.NewMemoryLimiter(cfg, logger)
if err != nil {
return nil, err
}
return &memoryLimiterExtension{memLimiter: ml}, nil
}
func (ml *memoryLimiterExtension) Start(ctx context.Context, host component.Host) error {
return ml.memLimiter.Start(ctx, host)
}
func (ml *memoryLimiterExtension) Shutdown(ctx context.Context) error {
return ml.memLimiter.Shutdown(ctx)
}
// MustRefuse returns if the caller should deny because memory has reached it's configured limits
func (ml *memoryLimiterExtension) MustRefuse() bool {
return ml.memLimiter.MustRefuse()
}
// GetHTTPHandler implements extensionmiddleware.HTTPServer
func (ml *memoryLimiterExtension) GetHTTPHandler(_ context.Context) (extensionmiddleware.WrapHTTPHandlerFunc, error) {
return ml.wrapHTTPHandler, nil
}
func (ml *memoryLimiterExtension) wrapHTTPHandler(_ context.Context, base http.Handler) (http.Handler, error) {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
if ml.MustRefuse() {
http.Error(resp, http.StatusText(http.StatusTooManyRequests), http.StatusTooManyRequests)
return
}
base.ServeHTTP(resp, req)
}), nil
}
func (ml *memoryLimiterExtension) GetGRPCServerOptions(_ context.Context) ([]grpc.ServerOption, error) {
return []grpc.ServerOption{
grpc.ChainUnaryInterceptor(
func(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
if ml.MustRefuse() {
return nil, status.Errorf(codes.ResourceExhausted, "RESOURCE_EXHAUSTED")
}
return handler(ctx, req)
}),
grpc.ChainStreamInterceptor(
func(srv any, ss grpc.ServerStream, _ *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
if ml.MustRefuse() {
return status.Errorf(codes.ResourceExhausted, "RESOURCE_EXHAUSTED")
}
return handler(srv, ss)
}),
}, nil
}
================================================
FILE: extension/memorylimiterextension/memorylimiter_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package memorylimiterextension
import (
"context"
"runtime"
"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/internal/memorylimiter"
"go.opentelemetry.io/collector/internal/memorylimiter/iruntime"
)
func TestMemoryPressureResponse(t *testing.T) {
ctx := context.Background()
tests := []struct {
name string
mlCfg *Config
memAlloc uint64
expectError bool
}{
{
name: "Below memAllocLimit",
mlCfg: &Config{
CheckInterval: time.Second,
MemoryLimitPercentage: 50,
MemorySpikePercentage: 1,
},
memAlloc: 800,
expectError: false,
},
{
name: "Above memAllocLimit",
mlCfg: &Config{
CheckInterval: time.Second,
MemoryLimitPercentage: 50,
MemorySpikePercentage: 1,
},
memAlloc: 1800,
expectError: true,
},
{
name: "Below memSpikeLimit",
mlCfg: &Config{
CheckInterval: time.Second,
MemoryLimitPercentage: 50,
MemorySpikePercentage: 10,
},
memAlloc: 800,
expectError: false,
},
{
name: "Above memSpikeLimit",
mlCfg: &Config{
CheckInterval: time.Second,
MemoryLimitPercentage: 50,
MemorySpikePercentage: 11,
},
memAlloc: 800,
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
memorylimiter.GetMemoryFn = func() (uint64, error) {
return uint64(2048), nil
}
memorylimiter.ReadMemStatsFn = func(ms *runtime.MemStats) {
ms.Alloc = tt.memAlloc
}
t.Cleanup(func() {
memorylimiter.GetMemoryFn = iruntime.TotalMemory
memorylimiter.ReadMemStatsFn = runtime.ReadMemStats
})
ml, err := newMemoryLimiter(tt.mlCfg, zap.NewNop())
assert.NoError(t, err)
assert.NoError(t, ml.Start(ctx, componenttest.NewNopHost()))
ml.memLimiter.CheckMemLimits()
mustRefuse := ml.MustRefuse()
if tt.expectError {
assert.True(t, mustRefuse)
} else {
require.NoError(t, err)
}
assert.NoError(t, ml.Shutdown(ctx))
})
}
}
================================================
FILE: extension/memorylimiterextension/metadata.yaml
================================================
display_name: Memory Limiter Extension
type: memory_limiter
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: extension
stability:
development: [extension]
distributions: []
tests:
config:
check_interval: 5s
limit_mib: 400
spike_limit_mib: 50
================================================
FILE: extension/memorylimiterextension/testdata/config.yaml
================================================
# 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
# the maximum amount of memory, in %, targeted to be allocated by the process
limit_percentage: 0
# the maximum, in percents against the total memory, spike expected between the measurements of memory usage.
spike_limit_percentage: 0
================================================
FILE: extension/metadata.yaml
================================================
type: extension
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: extension/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package extension
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: extension/xextension/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: extension/xextension/extension.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xextension // import "go.opentelemetry.io/collector/extension/xextension"
import (
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/extension"
"go.opentelemetry.io/collector/internal/componentalias"
)
type Factory interface {
extension.Factory
}
type FactoryOption interface {
applyOption(o *factory)
}
type factoryOptionFunc func(*factory)
func (f factoryOptionFunc) applyOption(o *factory) {
f(o)
}
type factory struct {
extension.Factory
componentalias.TypeAliasHolder
}
func WithDeprecatedTypeAlias(alias component.Type) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.SetDeprecatedAlias(alias)
})
}
func NewFactory(
cfgType component.Type,
createDefaultConfig component.CreateDefaultConfigFunc,
createServiceExtension extension.CreateFunc,
sl component.StabilityLevel,
options ...FactoryOption,
) Factory {
f := &factory{TypeAliasHolder: componentalias.NewTypeAliasHolder()}
for _, opt := range options {
opt.applyOption(f)
}
f.Factory = extension.NewFactory(cfgType, createDefaultConfig, createServiceExtension, sl)
f.Factory.(componentalias.TypeAliasHolder).SetDeprecatedAlias(f.DeprecatedAlias())
return f
}
================================================
FILE: extension/xextension/extension_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xextension
import (
"context"
"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/extension"
)
type nopExtension struct {
component.StartFunc
component.ShutdownFunc
}
func TestWithDeprecatedTypeAlias(t *testing.T) {
originalType := component.MustNewType("original")
aliasType := component.MustNewType("alias")
nopExtensionInstance := new(nopExtension)
factory := NewFactory(
originalType,
func() component.Config { return &struct{}{} },
func(context.Context, extension.Settings, component.Config) (extension.Extension, error) {
return nopExtensionInstance, nil
},
component.StabilityLevelAlpha,
WithDeprecatedTypeAlias(aliasType),
)
assert.Equal(t, originalType, factory.Type())
ext, err := factory.Create(context.Background(), extension.Settings{
ID: component.NewID(originalType),
TelemetrySettings: componenttest.NewNopTelemetrySettings(),
}, factory.CreateDefaultConfig())
require.NoError(t, err)
require.NotNil(t, ext)
ext, err = factory.Create(context.Background(), extension.Settings{
ID: component.NewID(aliasType),
TelemetrySettings: componenttest.NewNopTelemetrySettings(),
}, factory.CreateDefaultConfig())
require.NoError(t, err)
require.NotNil(t, ext)
ext, err = factory.Create(context.Background(), extension.Settings{
ID: component.NewID(component.MustNewType("wrong")),
TelemetrySettings: componenttest.NewNopTelemetrySettings(),
}, factory.CreateDefaultConfig())
require.Error(t, err)
require.Nil(t, ext)
}
================================================
FILE: extension/xextension/go.mod
================================================
module go.opentelemetry.io/collector/extension/xextension
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.opentelemetry.io/collector/extension v1.54.0
go.opentelemetry.io/collector/internal/componentalias v0.148.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/pdata v1.54.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
golang.org/x/sys v0.41.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/extension => ../
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
replace go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest
================================================
FILE: extension/xextension/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: extension/xextension/metadata.yaml
================================================
type: xextension
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
codeowners:
stability:
alpha: [profiles]
================================================
FILE: extension/xextension/storage/README.md
================================================
# Storage
**Status: under development; This is currently just the interface**
A storage extension persists state beyond the collector process. Other components can request a storage client from the storage extension and use it to manage state.
The `storage.Extension` interface extends `component.Extension` by adding the following method:
```
GetClient(context.Context, component.Kind, component.ID, string) (Client, error)
```
The `storage.Client` interface contains the following methods:
```
Get(context.Context, string) ([]byte, error)
Set(context.Context, string, []byte) error
Delete(context.Context, string) error
Close(context.Context) error
```
It is possible to execute several operations in a single transaction via `Batch`. The method takes a collection of
`Operation` arguments (each of which contains `Key`, `Value` and `Type` properties):
```
Batch(context.Context, ...Operation) error
```
The elements itself can be created using:
```
SetOperation(string, []byte) Operation
GetOperation(string) Operation
DeleteOperation(string) Operation
```
Get operation results are stored in-place into the given Operation and can be retrieved using its `Value` property.
Note: All methods should return error only if a problem occurred. (For example, if a file is no longer accessible, or if a remote service is unavailable.)
Note: It is the responsibility of each component to `Close` a storage client that it has requested.
================================================
FILE: extension/xextension/storage/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package storage implements an extension that can
// persist state beyond the collector process.
package storage // import "go.opentelemetry.io/collector/extension/xextension/storage"
================================================
FILE: extension/xextension/storage/metadata.yaml
================================================
type: xextension/storage
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
codeowners:
active:
- swiatekm
stability:
alpha: [profiles]
================================================
FILE: extension/xextension/storage/nop_client.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package storage // import "go.opentelemetry.io/collector/extension/xextension/storage"
import "context"
type nopClient struct{}
var nopClientInstance Client = &nopClient{}
// NewNopClient returns a nop client
func NewNopClient() Client {
return nopClientInstance
}
// Get does nothing, and returns nil, nil
func (c nopClient) Get(context.Context, string) ([]byte, error) {
return nil, nil // no result, but no problem
}
// Set does nothing and returns nil
func (c nopClient) Set(context.Context, string, []byte) error {
return nil // no problem
}
// Delete does nothing and returns nil
func (c nopClient) Delete(context.Context, string) error {
return nil // no problem
}
// Close does nothing and returns nil
func (c nopClient) Close(context.Context) error {
return nil
}
// Batch does nothing, and returns nil, nil
func (c nopClient) Batch(context.Context, ...*Operation) error {
return nil // no result, but no problem
}
================================================
FILE: extension/xextension/storage/storage.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package storage // import "go.opentelemetry.io/collector/extension/xextension/storage"
import (
"context"
"errors"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/extension"
)
// Extension is the interface that storage extensions must implement
type Extension interface {
extension.Extension
// GetClient will create a client for use by the specified component.
// Each component can have multiple storages (e.g. one for each signal),
// which can be identified using storageName parameter.
// The component can use the client to manage state
GetClient(ctx context.Context, kind component.Kind, id component.ID, storageName string) (Client, error)
}
// Client is the interface that storage clients must implement
// All methods should return error only if a problem occurred.
// This mirrors the behavior of a golang map:
// - Set doesn't error if a key already exists - it just overwrites the value.
// - Get doesn't error if a key is not found - it just returns nil.
// - Delete doesn't error if the key doesn't exist - it just no-ops.
//
// Similarly:
// - Batch doesn't error if any of the above happens for either retrieved or updated keys
//
// This also provides a way to differentiate data operations
//
// [overwrite | not-found | no-op] from "real" problems
type Client interface {
// Get will retrieve data from storage that corresponds to the
// specified key. It should return (nil, nil) if not found
Get(ctx context.Context, key string) ([]byte, error)
// Set will store data. The data can be retrieved by the same
// component after a process restart, using the same key
Set(ctx context.Context, key string, value []byte) error
// Delete will delete data associated with the specified key
Delete(ctx context.Context, key string) error
// Batch handles specified operations in batch. Get operation results are put in-place
Batch(ctx context.Context, ops ...*Operation) error
// Close will release any resources held by the client
Close(ctx context.Context) error
}
type OpType int
const (
Get OpType = iota
Set
Delete
)
type Operation struct {
// Key specifies key which is going to be get/set/deleted
Key string
// Value specifies value that is going to be set or holds result of get operation
Value []byte
// Type describes the operation type
Type OpType
}
func SetOperation(key string, value []byte) *Operation {
return &Operation{
Key: key,
Value: value,
Type: Set,
}
}
func GetOperation(key string) *Operation {
return &Operation{
Key: key,
Type: Get,
}
}
func DeleteOperation(key string) *Operation {
return &Operation{
Key: key,
Type: Delete,
}
}
var ErrStorageFull = errors.New("the storage extension has run out of available space")
================================================
FILE: extension/zpagesextension/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: extension/zpagesextension/README.md
================================================
# zPages Extension
| Status | |
| ------------- |-----------|
| Stability | [beta] |
| Distributions | [core], [contrib], [k8s] |
| Warnings | [The zPages extension is incompatible with `service::telemetry::traces::level` set to `none`](#warnings) |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Aextension%2Fzpages) [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aclosed+is%3Aissue+label%3Aextension%2Fzpages) |
[beta]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#beta
[core]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
[k8s]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-k8s
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.
zPages are useful for in-process diagnostics without having to depend on any
backend to examine traces or metrics.
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.
The following settings can be optionally configured:
- `expvar`
- `enabled` (default = false): Enable the expvar services. For detail see [ExpvarZ](#expvarz).
Example:
```yaml
extensions:
zpages:
```
The full list of settings exposed for this extension are documented [here](./config.go)
with detailed sample configurations [here](./testdata/config.yaml).
## Exposed zPages routes
The collector exposes the following zPage routes:
### ServiceZ
ServiceZ gives an overview of the collector services and quick access to the
`pipelinez`, `extensionz`, and `featurez` zPages. The page also provides build
and runtime information.
Example URL: http://localhost:55679/debug/servicez
### PipelineZ
PipelineZ brings insight on the running pipelines running in the collector. You can
find information on type, if data is mutated and the receivers, processors and exporters
that are used for each pipeline.
Example URL: http://localhost:55679/debug/pipelinez
### ExtensionZ
ExtensionZ shows the extensions that are active in the collector.
Example URL: http://localhost:55679/debug/extensionz
### FeatureZ
FeatureZ lists the feature gates available along with their current status
and description.
Example URL: http://localhost:55679/debug/featurez
### TraceZ
The TraceZ route is available to examine and bucketize spans by latency buckets for
example
(0us, 10us, 100us, 1ms, 10ms, 100ms, 1s, 10s, 1m]
They also allow you to quickly examine error samples
Example URL: http://localhost:55679/debug/tracez
### ExpvarZ
The ExpvarZ exposes the useful information about Go runtime, OTel components could leverage [expvar](https://pkg.go.dev/expvar) library to expose their own state.
Example URL: http://localhost:55679/debug/expvarz
## Warnings
This extension registers a SpanProcessor to record all the spans created inside
the Collector. This depends on a TracerProvider that supports
the SDK methods RegisterSpanProcessor and UnregisterSpanProcessor. Setting
`service::telemetry::traces::level` to `none` configures a No-Op
TracerProvider that does not support these methods, and therefore the zPages
extension cannot work in this mode.
================================================
FILE: extension/zpagesextension/config.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package zpagesextension // import "go.opentelemetry.io/collector/extension/zpagesextension"
import (
"errors"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/confighttp"
)
// Config has the configuration for the extension enabling the zPages extension.
type Config struct {
confighttp.ServerConfig `mapstructure:",squash"`
Expvar ExpvarConfig `mapstructure:"expvar"`
// prevent unkeyed literal initialization
_ struct{}
}
// ExpvarConfig has the configuration for the expvar service.
type ExpvarConfig struct {
// Enabled indicates whether to enable expvar service.
// (default = false)
Enabled bool `mapstructure:"enabled"`
// prevent unkeyed literal initialization
_ struct{}
}
var _ component.Config = (*Config)(nil)
// Validate checks if the extension configuration is valid
func (cfg *Config) Validate() error {
if cfg.NetAddr.Endpoint == "" {
return errors.New("\"endpoint\" is required when using the \"zpages\" extension")
}
return nil
}
================================================
FILE: extension/zpagesextension/config_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package zpagesextension
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/confmaptest"
)
func TestUnmarshalDefaultConfig(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
require.NoError(t, confmap.New().Unmarshal(&cfg))
assert.Equal(t, factory.CreateDefaultConfig(), cfg)
}
func TestInvalidConfig(t *testing.T) {
assert.Error(t, (&Config{}).Validate())
}
func TestUnmarshalConfig(t *testing.T) {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
require.NoError(t, err)
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
require.NoError(t, cm.Unmarshal(&cfg))
expectedServerConfig := confighttp.NewDefaultServerConfig()
expectedServerConfig.NetAddr.Endpoint = "localhost:56888"
assert.Equal(t, &Config{ServerConfig: expectedServerConfig}, cfg)
}
================================================
FILE: extension/zpagesextension/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:generate mdatagen metadata.yaml
// Package zpagesextension implements an extension that exposes zPages of
// properly instrumented components.
package zpagesextension // import "go.opentelemetry.io/collector/extension/zpagesextension"
================================================
FILE: extension/zpagesextension/factory.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package zpagesextension // import "go.opentelemetry.io/collector/extension/zpagesextension"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/extension"
"go.opentelemetry.io/collector/extension/zpagesextension/internal/metadata"
)
const (
defaultEndpoint = "localhost:55679"
)
// NewFactory creates a factory for Z-Pages extension.
func NewFactory() extension.Factory {
return extension.NewFactory(metadata.Type, createDefaultConfig, create, metadata.ExtensionStability)
}
func createDefaultConfig() component.Config {
serverConfig := confighttp.NewDefaultServerConfig()
serverConfig.NetAddr.Endpoint = defaultEndpoint
return &Config{
ServerConfig: serverConfig,
}
}
// create creates the extension based on this config.
func create(_ context.Context, set extension.Settings, cfg component.Config) (extension.Extension, error) {
return newServer(cfg.(*Config), set.TelemetrySettings), nil
}
================================================
FILE: extension/zpagesextension/factory_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package zpagesextension
import (
"context"
"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/confighttp"
"go.opentelemetry.io/collector/extension/extensiontest"
"go.opentelemetry.io/collector/extension/zpagesextension/internal/metadata"
"go.opentelemetry.io/collector/internal/testutil"
)
func TestFactory_CreateDefaultConfig(t *testing.T) {
expectedServerConfig := confighttp.NewDefaultServerConfig()
expectedServerConfig.NetAddr.Endpoint = "localhost:55679"
cfg := createDefaultConfig()
assert.Equal(t, &Config{ServerConfig: expectedServerConfig}, cfg)
require.NoError(t, componenttest.CheckConfigStruct(cfg))
ext, err := create(context.Background(), extensiontest.NewNopSettings(metadata.Type), cfg)
require.NoError(t, err)
require.NotNil(t, ext)
}
func TestFactoryCreate(t *testing.T) {
cfg := createDefaultConfig().(*Config)
cfg.NetAddr.Endpoint = testutil.GetAvailableLocalAddress(t)
set := extensiontest.NewNopSettings(extensiontest.NopType)
set.ID = component.NewID(NewFactory().Type())
ext, err := create(context.Background(), set, cfg)
require.NoError(t, err)
require.NotNil(t, ext)
}
================================================
FILE: extension/zpagesextension/generated_component_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package zpagesextension
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/confmap/confmaptest"
"go.opentelemetry.io/collector/extension/extensiontest"
)
var typ = component.MustNewType("zpages")
func TestComponentFactoryType(t *testing.T) {
require.Equal(t, typ, NewFactory().Type())
}
func TestComponentConfigStruct(t *testing.T) {
require.NoError(t, componenttest.CheckConfigStruct(NewFactory().CreateDefaultConfig()))
}
func TestComponentLifecycle(t *testing.T) {
factory := NewFactory()
cm, err := confmaptest.LoadConf("metadata.yaml")
require.NoError(t, err)
cfg := factory.CreateDefaultConfig()
sub, err := cm.Sub("tests::config")
require.NoError(t, err)
require.NoError(t, sub.Unmarshal(&cfg))
t.Run("shutdown", func(t *testing.T) {
e, err := factory.Create(context.Background(), extensiontest.NewNopSettings(typ), cfg)
require.NoError(t, err)
err = e.Shutdown(context.Background())
require.NoError(t, err)
})
t.Run("lifecycle", func(t *testing.T) {
firstExt, err := factory.Create(context.Background(), extensiontest.NewNopSettings(typ), cfg)
require.NoError(t, err)
require.NoError(t, firstExt.Start(context.Background(), newMdatagenNopHost()))
require.NoError(t, firstExt.Shutdown(context.Background()))
secondExt, err := factory.Create(context.Background(), extensiontest.NewNopSettings(typ), cfg)
require.NoError(t, err)
require.NoError(t, secondExt.Start(context.Background(), newMdatagenNopHost()))
require.NoError(t, secondExt.Shutdown(context.Background()))
})
}
var _ component.Host = (*mdatagenNopHost)(nil)
type mdatagenNopHost struct{}
func newMdatagenNopHost() component.Host {
return &mdatagenNopHost{}
}
func (mnh *mdatagenNopHost) GetExtensions() map[component.ID]component.Component {
return nil
}
func (mnh *mdatagenNopHost) GetFactory(_ component.Kind, _ component.Type) component.Factory {
return nil
}
================================================
FILE: extension/zpagesextension/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package zpagesextension
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: extension/zpagesextension/go.mod
================================================
module go.opentelemetry.io/collector/extension/zpagesextension
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componentstatus v0.148.0
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.opentelemetry.io/collector/config/configauth v1.54.0
go.opentelemetry.io/collector/config/confighttp v0.148.0
go.opentelemetry.io/collector/config/confignet v1.54.0
go.opentelemetry.io/collector/config/configoptional v1.54.0
go.opentelemetry.io/collector/confmap v1.54.0
go.opentelemetry.io/collector/extension v1.54.0
go.opentelemetry.io/collector/extension/extensiontest v0.148.0
go.opentelemetry.io/collector/internal/testutil v0.148.0
go.opentelemetry.io/contrib/zpages v0.67.0
go.opentelemetry.io/otel/sdk v1.42.0
go.opentelemetry.io/otel/trace v1.42.0
go.uber.org/goleak v1.3.0
go.uber.org/zap v1.27.1
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/go-tpm v0.9.8 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.18.4 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pierrec/lz4/v4 v4.1.26 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rs/cors v1.11.1 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/client v1.54.0 // indirect
go.opentelemetry.io/collector/config/configcompression v1.54.0 // indirect
go.opentelemetry.io/collector/config/configmiddleware v1.54.0 // indirect
go.opentelemetry.io/collector/config/configopaque v1.54.0 // indirect
go.opentelemetry.io/collector/config/configtls v1.54.0 // indirect
go.opentelemetry.io/collector/confmap/xconfmap v0.148.0 // indirect
go.opentelemetry.io/collector/extension/extensionauth v1.54.0 // indirect
go.opentelemetry.io/collector/extension/extensionmiddleware v0.148.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
go.opentelemetry.io/collector/pdata v1.54.0 // indirect
go.opentelemetry.io/collector/pipeline v1.54.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest
replace go.opentelemetry.io/collector/confmap => ../../confmap
replace go.opentelemetry.io/collector/extension => ../
replace go.opentelemetry.io/collector/extension/extensiontest => ../extensiontest
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/consumer => ../../consumer
replace go.opentelemetry.io/collector/config/configopaque => ../../config/configopaque
replace go.opentelemetry.io/collector/config/configoptional => ../../config/configoptional
replace go.opentelemetry.io/collector/config/configtls => ../../config/configtls
replace go.opentelemetry.io/collector/config/configcompression => ../../config/configcompression
replace go.opentelemetry.io/collector/config/configauth => ../../config/configauth
replace go.opentelemetry.io/collector/extension/extensionauth => ../extensionauth
replace go.opentelemetry.io/collector/config/confighttp => ../../config/confighttp
replace go.opentelemetry.io/collector/config/confignet => ../../config/confignet
replace go.opentelemetry.io/collector/client => ../../client
replace go.opentelemetry.io/collector/component/componentstatus => ../../component/componentstatus
replace go.opentelemetry.io/collector/pipeline => ../../pipeline
retract (
v0.76.0 // Depends on retracted pdata v1.0.0-rc10 module, use v0.76.1
v0.69.0 // Release failed, use v0.69.1
)
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest => ../../extension/extensionauth/extensionauthtest
replace go.opentelemetry.io/collector/extension/extensionmiddleware => ../extensionmiddleware
replace go.opentelemetry.io/collector/config/configmiddleware => ../../config/configmiddleware
replace go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest => ../extensionmiddleware/extensionmiddlewaretest
replace go.opentelemetry.io/collector/confmap/xconfmap => ../../confmap/xconfmap
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
================================================
FILE: extension/zpagesextension/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f h1:RJ+BDPLSHQO7cSjKBqjPJSbi1qfk9WcsjQDtZiw3dZw=
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f/go.mod h1:VHbbch/X4roIY22jL1s3qRbZhCiRIgUAF/PdSUcx2io=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/go-tpm-tools v0.4.7 h1:J3ycC8umYxM9A4eF73EofRZu4BxY0jjQnUnkhIBbvws=
github.com/google/go-tpm-tools v0.4.7/go.mod h1:gSyXTZHe3fgbzb6WEGd90QucmsnT1SRdlye82gH8QjQ=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY=
github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
go.opentelemetry.io/contrib/zpages v0.67.0 h1:cIUwWSVDovuLEbDIKreptjdxMuIhGiqwq0uL8YNaq1c=
go.opentelemetry.io/contrib/zpages v0.67.0/go.mod h1:vK8fsYHgPYg4Z/XDbFSEvItSGZDbjWTvjBOu8+AiDhc=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: extension/zpagesextension/internal/metadata/generated_status.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/component"
)
var (
Type = component.MustNewType("zpages")
ScopeName = "go.opentelemetry.io/collector/extension/zpagesextension"
)
const (
ExtensionStability = component.StabilityLevelBeta
)
================================================
FILE: extension/zpagesextension/metadata.yaml
================================================
display_name: zPages Extension
type: zpages
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: extension
stability:
beta: [extension]
distributions: [core, contrib, k8s]
warnings:
- The zPages extension is incompatible with `service::telemetry::traces::level` set to `none`
================================================
FILE: extension/zpagesextension/testdata/config.yaml
================================================
endpoint: "localhost:56888"
transport: tcp
================================================
FILE: extension/zpagesextension/zpagesextension.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package zpagesextension // import "go.opentelemetry.io/collector/extension/zpagesextension"
import (
"context"
"errors"
"expvar"
"net/http"
"path"
"go.opentelemetry.io/contrib/zpages"
traceSdk "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componentstatus"
)
const (
tracezPath = "tracez"
expvarzPath = "expvarz"
)
type zpagesExtension struct {
config *Config
telemetry component.TelemetrySettings
zpagesSpanProcessor *zpages.SpanProcessor
server *http.Server
stopCh chan struct{}
}
// registerableTracerProvider is a tracer that supports
// the SDK methods RegisterSpanProcessor and UnregisterSpanProcessor.
//
// We use an interface instead of casting to the SDK tracer type to support tracer providers
// that extend the SDK.
type registerableTracerProvider interface {
// RegisterSpanProcessor adds the given SpanProcessor to the list of SpanProcessors.
// https://pkg.go.dev/go.opentelemetry.io/otel/sdk/trace#TracerProvider.RegisterSpanProcessor.
RegisterSpanProcessor(SpanProcessor traceSdk.SpanProcessor)
// UnregisterSpanProcessor removes the given SpanProcessor from the list of SpanProcessors.
// https://pkg.go.dev/go.opentelemetry.io/otel/sdk/trace#TracerProvider.UnregisterSpanProcessor.
UnregisterSpanProcessor(SpanProcessor traceSdk.SpanProcessor)
}
func (zpe *zpagesExtension) Start(ctx context.Context, host component.Host) error {
zPagesMux := http.NewServeMux()
tp := zpe.telemetry.TracerProvider
// If the TracerProvider was wrapped by the service implementation, access the underlying SDK provider
for {
wrapped, ok := tp.(interface{ Unwrap() trace.TracerProvider })
if !ok {
break
}
tp = wrapped.Unwrap()
}
sdktracer, ok := tp.(registerableTracerProvider)
if ok {
sdktracer.RegisterSpanProcessor(zpe.zpagesSpanProcessor)
zPagesMux.Handle(path.Join("/debug", tracezPath), zpages.NewTracezHandler(zpe.zpagesSpanProcessor))
zpe.telemetry.Logger.Info("Registered zPages span processor on tracer provider")
} else {
zpe.telemetry.Logger.Warn("zPages span processor registration is not available")
}
if zpe.config.Expvar.Enabled {
zPagesMux.Handle(path.Join("/debug", expvarzPath), expvar.Handler())
zpe.telemetry.Logger.Info("Registered zPages expvar handler")
}
hostZPages, ok := host.(interface {
RegisterZPages(mux *http.ServeMux, pathPrefix string)
})
if ok {
hostZPages.RegisterZPages(zPagesMux, "/debug")
zpe.telemetry.Logger.Info("Registered Host's zPages")
} else {
zpe.telemetry.Logger.Warn("Host's zPages not available")
}
// Start the listener here so we can have earlier failure if port is
// already in use.
ln, err := zpe.config.ToListener(ctx)
if err != nil {
return err
}
zpe.telemetry.Logger.Info("Starting zPages extension", zap.Any("config", zpe.config))
zpe.server, err = zpe.config.ToServer(ctx, host.GetExtensions(), zpe.telemetry, zPagesMux)
if err != nil {
return err
}
zpe.stopCh = make(chan struct{})
go func() {
defer close(zpe.stopCh)
if errHTTP := zpe.server.Serve(ln); errHTTP != nil && !errors.Is(errHTTP, http.ErrServerClosed) {
componentstatus.ReportStatus(host, componentstatus.NewFatalErrorEvent(errHTTP))
}
}()
return nil
}
func (zpe *zpagesExtension) Shutdown(context.Context) error {
if zpe.server == nil {
return nil
}
err := zpe.server.Close()
if zpe.stopCh != nil {
<-zpe.stopCh
}
sdktracer, ok := zpe.telemetry.TracerProvider.(registerableTracerProvider)
if ok {
sdktracer.UnregisterSpanProcessor(zpe.zpagesSpanProcessor)
zpe.telemetry.Logger.Info("Unregistered zPages span processor on tracer provider")
} else {
zpe.telemetry.Logger.Warn("zPages span processor registration is not available")
}
return err
}
func newServer(config *Config, telemetry component.TelemetrySettings) *zpagesExtension {
return &zpagesExtension{
config: config,
telemetry: telemetry,
zpagesSpanProcessor: zpages.NewSpanProcessor(),
}
}
================================================
FILE: extension/zpagesextension/zpagesextension_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package zpagesextension
import (
"context"
"net"
"net/http"
"runtime"
"testing"
"github.com/stretchr/testify/require"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/config/configauth"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/config/confignet"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/internal/testutil"
)
type zpagesHost struct {
component.Host
}
func newZPagesHost() *zpagesHost {
return &zpagesHost{Host: componenttest.NewNopHost()}
}
func (*zpagesHost) RegisterZPages(*http.ServeMux, string) {}
var (
_ registerableTracerProvider = (*registerableProvider)(nil)
_ registerableTracerProvider = sdktrace.NewTracerProvider()
)
type registerableProvider struct {
trace.TracerProvider
}
func (*registerableProvider) RegisterSpanProcessor(sdktrace.SpanProcessor) {}
func (*registerableProvider) UnregisterSpanProcessor(sdktrace.SpanProcessor) {}
func newZpagesTelemetrySettings() component.TelemetrySettings {
set := componenttest.NewNopTelemetrySettings()
set.TracerProvider = ®isterableProvider{set.TracerProvider}
return set
}
func TestZPagesExtensionUsage(t *testing.T) {
addr := testutil.GetAvailableLocalAddress(t)
cfg := &Config{
ServerConfig: confighttp.ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: addr,
Transport: confignet.TransportTypeTCP,
},
},
}
zpagesExt := newServer(cfg, newZpagesTelemetrySettings())
require.NotNil(t, zpagesExt)
require.NoError(t, zpagesExt.Start(context.Background(), newZPagesHost()))
t.Cleanup(func() { require.NoError(t, zpagesExt.Shutdown(context.Background())) })
// Give a chance for the server goroutine to run.
runtime.Gosched()
client := &http.Client{}
resp, err := client.Get("http://" + addr + "/debug/tracez")
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
}
func TestZPagesExtensionBadAuthExtension(t *testing.T) {
cfg := &Config{
ServerConfig: confighttp.ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:0",
Transport: confignet.TransportTypeTCP,
},
Auth: configoptional.Some(confighttp.AuthConfig{
Config: configauth.Config{
AuthenticatorID: component.MustNewIDWithName("foo", "bar"),
},
}),
},
}
zpagesExt := newServer(cfg, newZpagesTelemetrySettings())
require.EqualError(t, zpagesExt.Start(context.Background(), componenttest.NewNopHost()), `failed to resolve authenticator "foo/bar": authenticator not found`)
}
func TestZPagesExtensionPortAlreadyInUse(t *testing.T) {
endpoint := testutil.GetAvailableLocalAddress(t)
ln, err := net.Listen("tcp", endpoint)
require.NoError(t, err)
defer ln.Close()
cfg := &Config{
ServerConfig: confighttp.ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: endpoint,
Transport: confignet.TransportTypeTCP,
},
},
}
zpagesExt := newServer(cfg, newZpagesTelemetrySettings())
require.NotNil(t, zpagesExt)
require.Error(t, zpagesExt.Start(context.Background(), componenttest.NewNopHost()))
}
func TestZPagesMultipleStarts(t *testing.T) {
cfg := &Config{
ServerConfig: confighttp.ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: testutil.GetAvailableLocalAddress(t),
Transport: confignet.TransportTypeTCP,
},
},
}
zpagesExt := newServer(cfg, newZpagesTelemetrySettings())
require.NotNil(t, zpagesExt)
require.NoError(t, zpagesExt.Start(context.Background(), componenttest.NewNopHost()))
t.Cleanup(func() { require.NoError(t, 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) {
cfg := &Config{
ServerConfig: confighttp.ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: testutil.GetAvailableLocalAddress(t),
Transport: confignet.TransportTypeTCP,
},
},
}
zpagesExt := newServer(cfg, newZpagesTelemetrySettings())
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) {
cfg := &Config{
ServerConfig: confighttp.ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: testutil.GetAvailableLocalAddress(t),
Transport: confignet.TransportTypeTCP,
},
},
}
zpagesExt := newServer(cfg, newZpagesTelemetrySettings())
require.NotNil(t, zpagesExt)
require.NoError(t, zpagesExt.Shutdown(context.Background()))
}
func TestZPagesEnableExpvar(t *testing.T) {
addr := testutil.GetAvailableLocalAddress(t)
cfg := &Config{
ServerConfig: confighttp.ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: addr,
Transport: confignet.TransportTypeTCP,
},
},
Expvar: ExpvarConfig{
Enabled: true,
},
}
zpagesExt := newServer(cfg, newZpagesTelemetrySettings())
require.NotNil(t, zpagesExt)
require.NoError(t, zpagesExt.Start(context.Background(), newZPagesHost()))
t.Cleanup(func() { require.NoError(t, zpagesExt.Shutdown(context.Background())) })
// Give a chance for the server goroutine to run.
runtime.Gosched()
client := &http.Client{}
resp, err := client.Get("http://" + addr + "/debug/expvarz")
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
}
================================================
FILE: featuregate/Makefile
================================================
include ../Makefile.Common
================================================
FILE: featuregate/README.md
================================================
# Collector Feature Gates
This package provides a mechanism that allows operators to enable and disable
experimental or transitional features at deployment time. These flags should
be able to govern the behavior of the application starting as early as possible
and should be available to every component such that decisions may be made
based on flags at the component level.
## Usage
### With mdatagen
In components that use mdatagen, feature gates should be defined in the
component's `metadata.yml`.
```yaml
feature_gates:
- id: namespaced.uniqueIdentifier
description: A brief description of what the gate controls
stage: stable
from_version: 'v0.65.0'
reference_url: 'https://github.com/open-telemetry/opentelemetry-collector/issues/6167'
```
Running the mdatagen code generator with this configuration will initialize the
feature flag in the `internal/metadata` submodule.
The status of the gate can later be checked by calling that submodule:
```go
if metadata.NamespacedUniqueIdentifierFeatureGate.IsEnabled() {
setupNewFeature()
}
```
### In code
In components that don't use mdatagen, feature gates can be defined and
registered with the global registry in an `init()` function. This makes the
`Gate` available to be configured and queried with the defined
[`Stage`](#feature-lifecycle) default value.
A `Gate` can have a list of associated issues that allow users to refer to
the issue and report any additional problems or understand the context of the `Gate`.
Once a `Gate` has been marked as `Stable`, it must have a `RemovalVersion` set.
```go
var myFeatureGate = featuregate.GlobalRegistry().MustRegister(
"namespaced.uniqueIdentifier",
featuregate.Stable,
featuregate.WithRegisterFromVersion("v0.65.0")
featuregate.WithRegisterDescription("A brief description of what the gate controls"),
featuregate.WithRegisterReferenceURL("https://github.com/open-telemetry/opentelemetry-collector/issues/6167"),
featuregate.WithRegisterToVersion("v0.70.0"))
```
The status of the gate may later be checked by interrogating the global
feature gate registry:
```go
if myFeatureGate.IsEnabled() {
setupNewFeature()
}
```
Note that querying the registry takes a read lock and accesses a map, so it
should be done once and the result cached for local use if repeated checks
are required. Avoid querying the registry in a loop.
## Controlling Gates
Feature gates can be enabled or disabled via the CLI, with the
`--feature-gates` flag. When using the CLI flag, gate
identifiers must be presented as a comma-delimited list. Gate identifiers
prefixed with `-` will disable the gate and prefixing with `+` or with no
prefix will enable the gate.
```shell
otelcol --config=config.yaml --feature-gates=gate1,-gate2,+gate3
```
This will enable `gate1` and `gate3` and disable `gate2`.
## Feature Lifecycle
Features controlled by a `Gate` should follow a three-stage lifecycle,
modeled after the [system used by Kubernetes](https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/#feature-stages):
1. An `alpha` stage where the feature is disabled by default and must be enabled
through a `Gate`.
2. A `beta` stage where the feature has been well tested and is enabled by
default but can be disabled through a `Gate`.
3. A generally available or `stable` stage where the feature is permanently enabled. At this stage
the gate should no longer be explicitly used. Disabling the gate will produce an error and
explicitly enabling will produce a warning log.
4. A `stable` feature gate will be removed in the version specified by its `ToVersion` value.
Features that prove unworkable in the `alpha` stage may be discontinued
without proceeding to the `beta` stage. Instead, they will proceed to the
`deprecated` stage, which will feature is permanently disabled. A feature gate will
be removed once it has been `deprecated` for at least 2 releases of the collector.
Features that make it to the `beta` stage are intended to reach general availability but may still be discontinued.
If, after wider use, it is determined that the gate should be discontinued it will be reverted to the `alpha` stage
for 2 releases and then proceed to the `deprecated` stage. If instead it is ready for general availability it will
proceed to the `stable` stage.
================================================
FILE: featuregate/examples_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package featuregate_test
import (
"flag"
"fmt"
"go.opentelemetry.io/collector/featuregate"
)
func ExampleRegistry_Register() {
reg := featuregate.NewRegistry()
gate := reg.MustRegister(
"featuregate.example.gate",
featuregate.StageAlpha,
featuregate.WithRegisterDescription("Example gate"),
)
// By default an alpha feature gate is disabled.
fmt.Println(gate.IsEnabled())
if err := reg.Set(gate.ID(), true); err != nil {
fmt.Println("error:", err)
return
}
// Alpha feature gates can be modified so the gate is now enabled.
fmt.Println(gate.IsEnabled())
// Output:
// false
// true
}
func ExampleRegistry_RegisterFlags() {
reg := featuregate.NewRegistry()
alphaGate := reg.MustRegister("featuregate.example.alpha", featuregate.StageAlpha)
betaGate := reg.MustRegister("featuregate.example.beta", featuregate.StageBeta)
fs := flag.NewFlagSet("example", flag.ContinueOnError)
// RegisterFlags registers the `--feature-gates` flag on the FlagSet.
reg.RegisterFlags(fs)
if err := fs.Parse([]string{"--feature-gates=featuregate.example.alpha,-featuregate.example.beta"}); err != nil {
fmt.Println("error:", err)
return
}
fmt.Printf("featuregate.example.alpha=%v\n", alphaGate.IsEnabled())
fmt.Printf("featuregate.example.beta=%v\n", betaGate.IsEnabled())
// Output:
// featuregate.example.alpha=true
// featuregate.example.beta=false
}
================================================
FILE: featuregate/flag.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package featuregate // import "go.opentelemetry.io/collector/featuregate"
import (
"flag"
"strings"
"go.uber.org/multierr"
)
const (
featureGatesFlag = "feature-gates"
featureGatesFlagDescription = "Comma-delimited list of feature gate identifiers. Prefix with '-' to disable the feature. '+' or no prefix will enable the feature."
)
// RegisterFlagsOption is an option for RegisterFlags.
type RegisterFlagsOption interface {
private()
}
// RegisterFlags that directly applies feature gate statuses to a Registry.
func (r *Registry) RegisterFlags(flagSet *flag.FlagSet, _ ...RegisterFlagsOption) {
flagSet.Var(&flagValue{reg: r}, featureGatesFlag, featureGatesFlagDescription)
}
// flagValue implements the flag.Value interface and directly applies feature gate statuses to a Registry.
type flagValue struct {
reg *Registry
}
func (f *flagValue) String() string {
// This function can be called by isZeroValue https://github.com/golang/go/blob/go1.23.3/src/flag/flag.go#L630
// which creates an instance of flagValue using reflect.New. In this case, the field `reg` is nil.
if f.reg == nil {
return ""
}
var ids []string
f.reg.VisitAll(func(g *Gate) {
id := g.ID()
if !g.IsEnabled() {
id = "-" + id
}
ids = append(ids, id)
})
return strings.Join(ids, ",")
}
func (f *flagValue) Set(s string) error {
if s == "" {
return nil
}
var errs error
ids := strings.Split(s, ",")
for i := range ids {
id := ids[i]
val := true
switch id[0] {
case '-':
id = id[1:]
val = false
case '+':
id = id[1:]
}
errs = multierr.Append(errs, f.reg.Set(id, val))
}
return errs
}
================================================
FILE: featuregate/flag_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package featuregate
import (
"flag"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewFlag(t *testing.T) {
for _, tt := range []struct {
name string
input string
expectedSetErr bool
expected map[string]bool
expectedStr string
}{
{
name: "empty item",
input: "",
expected: map[string]bool{"alpha": false, "beta": true, "deprecated": false, "stable": true},
expectedStr: "-alpha,beta,-deprecated,stable",
},
{
name: "simple enable alpha",
input: "alpha",
expected: map[string]bool{"alpha": true, "beta": true, "deprecated": false, "stable": true},
expectedStr: "alpha,beta,-deprecated,stable",
},
{
name: "plus enable alpha",
input: "+alpha",
expected: map[string]bool{"alpha": true, "beta": true, "deprecated": false, "stable": true},
expectedStr: "alpha,beta,-deprecated,stable",
},
{
name: "disabled beta",
input: "-beta",
expected: map[string]bool{"alpha": false, "beta": false, "deprecated": false, "stable": true},
expectedStr: "-alpha,-beta,-deprecated,stable",
},
{
name: "multiple items",
input: "-beta,alpha",
expected: map[string]bool{"alpha": true, "beta": false, "deprecated": false, "stable": true},
expectedStr: "alpha,-beta,-deprecated,stable",
},
{
name: "multiple items with plus",
input: "-beta,+alpha",
expected: map[string]bool{"alpha": true, "beta": false, "deprecated": false, "stable": true},
expectedStr: "alpha,-beta,-deprecated,stable",
},
{
name: "repeated items",
input: "alpha,-beta,-alpha",
expected: map[string]bool{"alpha": false, "beta": false, "deprecated": false, "stable": true},
expectedStr: "-alpha,-beta,-deprecated,stable",
},
{
name: "multiple plus items",
input: "+alpha,+beta",
expected: map[string]bool{"alpha": true, "beta": true, "deprecated": false, "stable": true},
expectedStr: "alpha,beta,-deprecated,stable",
},
{
name: "enable stable",
input: "stable",
expected: map[string]bool{"alpha": false, "beta": true, "deprecated": false, "stable": true},
expectedStr: "-alpha,beta,-deprecated,stable",
},
{
name: "disable stable",
input: "-stable",
expectedSetErr: true,
expected: map[string]bool{"alpha": false, "beta": true, "deprecated": false, "stable": true},
expectedStr: "-alpha,beta,-deprecated,stable",
},
{
name: "enable deprecated",
input: "deprecated",
expectedSetErr: true,
expected: map[string]bool{"alpha": false, "beta": true, "deprecated": false, "stable": true},
expectedStr: "-alpha,beta,-deprecated,stable",
},
{
name: "disable deprecated",
input: "-deprecated",
expected: map[string]bool{"alpha": false, "beta": true, "deprecated": false, "stable": true},
expectedStr: "-alpha,beta,-deprecated,stable",
},
{
name: "enable missing",
input: "missing",
expectedSetErr: true,
expected: map[string]bool{"alpha": false, "beta": true, "deprecated": false, "stable": true},
expectedStr: "-alpha,beta,-deprecated,stable",
},
{
name: "disable missing",
input: "missing",
expectedSetErr: true,
expected: map[string]bool{"alpha": false, "beta": true, "deprecated": false, "stable": true},
expectedStr: "-alpha,beta,-deprecated,stable",
},
} {
t.Run(tt.name, func(t *testing.T) {
reg := NewRegistry()
reg.MustRegister("alpha", StageAlpha)
reg.MustRegister("beta", StageBeta)
reg.MustRegister("deprecated", StageDeprecated, WithRegisterToVersion("1.0.0"))
reg.MustRegister("stable", StageStable, WithRegisterToVersion("1.0.0"))
fs := flag.NewFlagSet("test", flag.ContinueOnError)
reg.RegisterFlags(fs)
registrationFlag := fs.Lookup(featureGatesFlag)
require.NotNil(t, registrationFlag)
if tt.expectedSetErr {
require.Error(t, registrationFlag.Value.Set(tt.input))
} else {
require.NoError(t, registrationFlag.Value.Set(tt.input))
}
got := map[string]bool{}
reg.VisitAll(func(g *Gate) {
got[g.ID()] = g.IsEnabled()
})
assert.Equal(t, tt.expected, got)
assert.Equal(t, tt.expectedStr, registrationFlag.Value.String())
})
}
}
func TestFlagStringNotInitialize(t *testing.T) {
flag := &flagValue{}
assert.Empty(t, flag.String())
}
================================================
FILE: featuregate/gate.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package featuregate // import "go.opentelemetry.io/collector/featuregate"
import (
"fmt"
"sync/atomic"
"github.com/hashicorp/go-version"
)
// Gate is an immutable object that is owned by the Registry and represents an individual feature that
// may be enabled or disabled based on the lifecycle state of the feature and CLI flags specified by the user.
type Gate struct {
id string
description string
referenceURL string
fromVersion *version.Version
toVersion *version.Version
stage Stage
enabled *atomic.Bool
}
// ID returns the id of the Gate.
func (g *Gate) ID() string {
return g.id
}
// IsEnabled returns true if the feature described by the Gate is enabled.
func (g *Gate) IsEnabled() bool {
return g.enabled.Load()
}
// Description returns the description for the Gate.
func (g *Gate) Description() string {
return g.description
}
// Stage returns the Gate's lifecycle stage.
func (g *Gate) Stage() Stage {
return g.stage
}
// ReferenceURL returns the URL to the contextual information about the Gate.
func (g *Gate) ReferenceURL() string {
return g.referenceURL
}
// FromVersion returns the version information when the Gate's was added.
func (g *Gate) FromVersion() string {
return fmt.Sprintf("v%s", g.fromVersion)
}
// ToVersion returns the version information when Gate's in StageStable.
func (g *Gate) ToVersion() string {
return fmt.Sprintf("v%s", g.toVersion)
}
================================================
FILE: featuregate/gate_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package featuregate
import (
"sync/atomic"
"testing"
"github.com/hashicorp/go-version"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGate(t *testing.T) {
enabled := &atomic.Bool{}
enabled.Store(true)
from, err := version.NewVersion("v0.61.0")
require.NoError(t, err)
to, err := version.NewVersion("v0.64.0")
require.NoError(t, err)
g := &Gate{
id: "test",
description: "test gate",
enabled: enabled,
stage: StageAlpha,
referenceURL: "http://example.com",
fromVersion: from,
toVersion: to,
}
assert.Equal(t, "test", g.ID())
assert.Equal(t, "test gate", g.Description())
assert.True(t, g.IsEnabled())
assert.Equal(t, StageAlpha, g.Stage())
assert.Equal(t, "http://example.com", g.ReferenceURL())
assert.Equal(t, "v0.61.0", g.FromVersion())
assert.Equal(t, "v0.64.0", g.ToVersion())
}
================================================
FILE: featuregate/go.mod
================================================
module go.opentelemetry.io/collector/featuregate
go 1.25.0
require (
github.com/hashicorp/go-version v1.8.0
github.com/stretchr/testify v1.11.1
go.uber.org/goleak v1.3.0
go.uber.org/multierr v1.11.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
retract (
v0.76.0 // Depends on retracted pdata v1.0.0-rc10 module, use v0.76.1
v0.69.0 // Release failed, use v0.69.1
)
================================================
FILE: featuregate/go.sum
================================================
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
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/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: featuregate/metadata.yaml
================================================
type: featuregate
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: featuregate/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package featuregate
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: featuregate/registry.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package featuregate // import "go.opentelemetry.io/collector/featuregate"
import (
"errors"
"fmt"
"net/url"
"regexp"
"sort"
"sync"
"sync/atomic"
"github.com/hashicorp/go-version"
)
var (
globalRegistry = NewRegistry()
// idRegexp is used to validate the ID of a Gate.
// IDs' characters must be alphanumeric or dots.
idRegexp = regexp.MustCompile(`^[0-9a-zA-Z.]*$`)
)
// ErrAlreadyRegistered is returned when adding a Gate that is already registered.
var ErrAlreadyRegistered = errors.New("gate is already registered")
// GlobalRegistry returns the global Registry.
func GlobalRegistry() *Registry {
return globalRegistry
}
type Registry struct {
gates sync.Map
}
// NewRegistry returns a new empty Registry.
func NewRegistry() *Registry {
return &Registry{}
}
// RegisterOption allows to configure additional information about a Gate during registration.
type RegisterOption interface {
apply(g *Gate) error
}
type registerOptionFunc func(g *Gate) error
func (ro registerOptionFunc) apply(g *Gate) error {
return ro(g)
}
// WithRegisterDescription adds description for the Gate.
func WithRegisterDescription(description string) RegisterOption {
return registerOptionFunc(func(g *Gate) error {
g.description = description
return nil
})
}
// WithRegisterReferenceURL adds a URL that has all the contextual information about the Gate.
// referenceURL must be a valid URL as defined by `net/url.Parse`.
func WithRegisterReferenceURL(referenceURL string) RegisterOption {
return registerOptionFunc(func(g *Gate) error {
if _, err := url.Parse(referenceURL); err != nil {
return fmt.Errorf("WithRegisterReferenceURL: invalid reference URL %q: %w", referenceURL, err)
}
g.referenceURL = referenceURL
return nil
})
}
// WithRegisterFromVersion is used to set the Gate "FromVersion".
// The "FromVersion" contains the Collector release when a feature is introduced.
// fromVersion must be a valid version string: it may start with 'v' and must be in the format Major.Minor.Patch[-PreRelease].
// PreRelease is optional and may have dashes, tildes and ASCII alphanumeric characters.
func WithRegisterFromVersion(fromVersion string) RegisterOption {
return registerOptionFunc(func(g *Gate) error {
from, err := version.NewVersion(fromVersion)
if err != nil {
return fmt.Errorf("WithRegisterFromVersion: invalid version %q: %w", fromVersion, err)
}
g.fromVersion = from
return nil
})
}
// WithRegisterToVersion is used to set the Gate "ToVersion".
// The "ToVersion", if not empty, contains the last Collector release in which you can still use a feature gate.
// If the feature stage is either "Deprecated" or "Stable", the "ToVersion" is the Collector release when the feature is removed.
// toVersion must be a valid version string: it may start with 'v' and must be in the format Major.Minor.Patch[-PreRelease].
// PreRelease is optional and may have dashes, tildes and ASCII alphanumeric characters.
func WithRegisterToVersion(toVersion string) RegisterOption {
return registerOptionFunc(func(g *Gate) error {
to, err := version.NewVersion(toVersion)
if err != nil {
return fmt.Errorf("WithRegisterToVersion: invalid version %q: %w", toVersion, err)
}
g.toVersion = to
return nil
})
}
// MustRegister like Register but panics if an invalid ID or gate options are provided.
func (r *Registry) MustRegister(id string, stage Stage, opts ...RegisterOption) *Gate {
g, err := r.Register(id, stage, opts...)
if err != nil {
panic(err)
}
return g
}
func validateID(id string) error {
if id == "" {
return errors.New("empty ID")
}
if !idRegexp.MatchString(id) {
return errors.New("invalid character(s) in ID")
}
return nil
}
// Register a Gate and return it. The returned Gate can be used to check if is enabled or not.
// id must be an ASCII alphanumeric nonempty string. Dots are allowed for namespacing.
func (r *Registry) Register(id string, stage Stage, opts ...RegisterOption) (*Gate, error) {
if err := validateID(id); err != nil {
return nil, fmt.Errorf("invalid ID %q: %w", id, err)
}
g := &Gate{
id: id,
stage: stage,
}
for _, opt := range opts {
err := opt.apply(g)
if err != nil {
return nil, fmt.Errorf("failed to apply option: %w", err)
}
}
switch g.stage {
case StageAlpha, StageDeprecated:
g.enabled = &atomic.Bool{}
case StageBeta, StageStable:
enabled := &atomic.Bool{}
enabled.Store(true)
g.enabled = enabled
default:
return nil, fmt.Errorf("unknown stage value %q for gate %q", stage, id)
}
if (g.stage == StageStable || g.stage == StageDeprecated) && g.toVersion == nil {
return nil, fmt.Errorf("no removal version set for %v gate %q", g.stage.String(), id)
}
if g.fromVersion != nil && g.toVersion != nil && g.toVersion.LessThan(g.fromVersion) {
return nil, fmt.Errorf("toVersion %q is before fromVersion %q", g.toVersion, g.fromVersion)
}
if _, loaded := r.gates.LoadOrStore(id, g); loaded {
return nil, fmt.Errorf("failed to register %q: %w", id, ErrAlreadyRegistered)
}
return g, nil
}
// Set the enabled valued for a Gate identified by the given id.
func (r *Registry) Set(id string, enabled bool) error {
v, ok := r.gates.Load(id)
if !ok {
validGates := []string{}
r.VisitAll(func(g *Gate) {
validGates = append(validGates, g.ID())
})
return fmt.Errorf("no such feature gate %q. valid gates: %v", id, validGates)
}
g := v.(*Gate)
switch g.stage {
case StageStable:
if !enabled {
return fmt.Errorf("feature gate %q is stable, can not be disabled", id)
}
fmt.Printf("Feature gate %q is stable and already enabled. It will be removed in version %v and continued use of the gate after version %v will result in an error.\n", id, g.toVersion, g.toVersion)
case StageDeprecated:
if enabled {
return fmt.Errorf("feature gate %q is deprecated, can not be enabled", id)
}
fmt.Printf("Feature gate %q is deprecated and already disabled. It will be removed in version %v and continued use of the gate after version %v will result in an error.\n", id, g.toVersion, g.toVersion)
default:
g.enabled.Store(enabled)
}
return nil
}
// VisitAll visits all the gates in lexicographical order, calling fn for each.
func (r *Registry) VisitAll(fn func(*Gate)) {
var gates []*Gate
r.gates.Range(func(_, value any) bool {
gates = append(gates, value.(*Gate))
return true
})
sort.Slice(gates, func(i, j int) bool {
return gates[i].ID() < gates[j].ID()
})
for i := range gates {
fn(gates[i])
}
}
================================================
FILE: featuregate/registry_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package featuregate
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGlobalRegistry(t *testing.T) {
assert.Same(t, globalRegistry, GlobalRegistry())
}
func TestRegistry(t *testing.T) {
r := NewRegistry()
// Expect that no gates to visit.
r.VisitAll(func(*Gate) {
t.FailNow()
})
const id = "foo"
g, err := r.Register(id, StageBeta, WithRegisterDescription("Test Gate"))
require.NoError(t, err)
r.VisitAll(func(gate *Gate) {
assert.Equal(t, id, gate.ID())
})
assert.True(t, g.IsEnabled())
require.NoError(t, r.Set(id, false))
assert.False(t, g.IsEnabled())
_, err = r.Register(id, StageBeta)
require.ErrorIs(t, err, ErrAlreadyRegistered)
assert.Panics(t, func() {
r.MustRegister(id, StageBeta)
})
}
func TestRegistryApplyError(t *testing.T) {
r := NewRegistry()
require.Error(t, r.Set("foo", true))
r.MustRegister("bar", StageAlpha)
require.Error(t, r.Set("foo", true))
_, err := r.Register("foo", StageStable)
require.Error(t, err)
require.Error(t, r.Set("foo", true))
r.MustRegister("foo", StageStable, WithRegisterToVersion("v1.0.0"))
require.Error(t, r.Set("foo", false))
require.Error(t, r.Set("deprecated", true))
_, err = r.Register("deprecated", StageDeprecated)
require.Error(t, err)
require.Error(t, r.Set("deprecated", true))
r.MustRegister("deprecated", StageDeprecated, WithRegisterToVersion("v1.0.0"))
assert.Error(t, r.Set("deprecated", true))
}
func TestRegistryApply(t *testing.T) {
r := NewRegistry()
fooGate := r.MustRegister("foo", StageAlpha, WithRegisterDescription("Test Gate"))
assert.False(t, fooGate.IsEnabled())
require.NoError(t, r.Set(fooGate.ID(), true))
assert.True(t, fooGate.IsEnabled())
}
func TestRegisterGateLifecycle(t *testing.T) {
for _, tc := range []struct {
name string
id string
stage Stage
opts []RegisterOption
enabled bool
shouldErr bool
}{
{
name: "StageAlpha Flag",
id: "test.gate",
stage: StageAlpha,
enabled: false,
shouldErr: false,
},
{
name: "StageAlpha Flag with all options",
id: "test.gate",
stage: StageAlpha,
opts: []RegisterOption{
WithRegisterDescription("test.gate"),
WithRegisterReferenceURL("http://example.com/issue/1"),
WithRegisterToVersion("v0.88.0"),
},
enabled: false,
shouldErr: false,
},
{
name: "StageBeta Flag",
id: "test.gate",
stage: StageBeta,
enabled: true,
shouldErr: false,
},
{
name: "StageStable Flag",
id: "test.gate",
stage: StageStable,
opts: []RegisterOption{
WithRegisterToVersion("v1.0.0-rcv.0014"),
},
enabled: true,
shouldErr: false,
},
{
name: "StageDeprecated Flag",
id: "test.gate",
stage: StageDeprecated,
opts: []RegisterOption{
WithRegisterToVersion("v0.89.0"),
},
enabled: false,
shouldErr: false,
},
{
name: "Invalid stage",
id: "test.gate",
stage: Stage(-1),
shouldErr: true,
},
{
name: "StageStable gate missing removal version",
id: "test.gate",
stage: StageStable,
shouldErr: true,
},
{
name: "StageDeprecated gate missing removal version",
id: "test.gate",
stage: StageDeprecated,
shouldErr: true,
},
{
name: "Duplicate gate",
id: "existing.gate",
stage: StageStable,
shouldErr: true,
},
{
name: "Invalid gate name",
id: "+invalid.gate.name",
stage: StageAlpha,
shouldErr: true,
},
{
name: "Invalid empty gate",
id: "",
stage: StageAlpha,
shouldErr: true,
},
{
name: "Invalid gate to version",
id: "invalid.gate.to.version",
stage: StageAlpha,
opts: []RegisterOption{WithRegisterToVersion("invalid-version")},
shouldErr: true,
},
{
name: "Invalid gate from version",
id: "invalid.gate.from.version",
stage: StageAlpha,
opts: []RegisterOption{WithRegisterFromVersion("invalid-version")},
shouldErr: true,
},
{
name: "Invalid gate reference URL",
id: "invalid.gate.reference.URL",
stage: StageAlpha,
opts: []RegisterOption{WithRegisterReferenceURL(":invalid-url")},
shouldErr: true,
},
{
name: "Empty version range",
id: "invalid.gate.version.range",
stage: StageAlpha,
opts: []RegisterOption{
WithRegisterFromVersion("v0.88.0"),
WithRegisterToVersion("v0.87.0"),
},
shouldErr: true,
},
} {
t.Run(tc.name, func(t *testing.T) {
r := NewRegistry()
r.MustRegister("existing.gate", StageBeta)
if tc.shouldErr {
_, err := r.Register(tc.id, tc.stage, tc.opts...)
require.Error(t, err)
assert.Panics(t, func() {
r.MustRegister(tc.id, tc.stage, tc.opts...)
})
return
}
g, err := r.Register(tc.id, tc.stage, tc.opts...)
require.NoError(t, err)
assert.Equal(t, tc.enabled, g.IsEnabled())
})
}
}
================================================
FILE: featuregate/stage.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package featuregate // import "go.opentelemetry.io/collector/featuregate"
// Stage represents the Gate's lifecycle and what is the expected state of it.
type Stage int8
const (
// StageAlpha is used when creating a new feature and the Gate must be explicitly enabled
// by the operator.
//
// The Gate will be disabled by default.
StageAlpha Stage = iota
// StageBeta is used when the feature gate is well tested and is enabled by default,
// but can be disabled by a Gate.
//
// The Gate will be enabled by default.
StageBeta
// StageStable is used when feature is permanently enabled and can not be disabled by a Gate.
// This value is used to provide feedback to the user that the gate will be removed in the next versions.
//
// The Gate will be enabled by default and will return an error if disabled.
StageStable
// StageDeprecated is used when feature is permanently disabled and can not be enabled by a Gate.
// This value is used to provide feedback to the user that the gate will be removed in the next versions.
//
// The Gate will be disabled by default and will return an error if modified.
StageDeprecated
)
func (s Stage) String() string {
switch s {
case StageAlpha:
return "Alpha"
case StageBeta:
return "Beta"
case StageStable:
return "Stable"
case StageDeprecated:
return "Deprecated"
}
return "Unknown"
}
================================================
FILE: featuregate/stage_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package featuregate
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestStageString(t *testing.T) {
assert.Equal(t, "Alpha", StageAlpha.String())
assert.Equal(t, "Beta", StageBeta.String())
assert.Equal(t, "Stable", StageStable.String())
assert.Equal(t, "Deprecated", StageDeprecated.String())
assert.Equal(t, "Unknown", Stage(-1).String())
}
================================================
FILE: filter/Makefile
================================================
include ../Makefile.Common
================================================
FILE: filter/config.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package filter // import "go.opentelemetry.io/collector/filter"
import (
"errors"
"regexp"
)
// Config configures the matching behavior of a Filter.
type Config struct {
Strict string `mapstructure:"strict"`
Regex string `mapstructure:"regexp"`
// prevent unkeyed literal initialization
_ struct{}
}
func (c Config) Validate() error {
if c.Strict == "" && c.Regex == "" {
return errors.New("must specify either strict or regex")
}
if c.Strict != "" && c.Regex != "" {
return errors.New("strict and regex cannot be used together")
}
if c.Regex != "" {
_, err := regexp.Compile(c.Regex)
if err != nil {
return err
}
}
return nil
}
type combinedFilter struct {
stricts map[any]struct{}
regexes []*regexp.Regexp
}
// CreateFilter creates a Filter out of a set of Config configuration objects.
func CreateFilter(configs []Config) Filter {
cf := &combinedFilter{
stricts: make(map[any]struct{}),
}
for _, config := range configs {
if config.Strict != "" {
cf.stricts[config.Strict] = struct{}{}
}
if config.Regex != "" {
// Validate() call above ensures that the regex is valid.
re := regexp.MustCompile(config.Regex)
cf.regexes = append(cf.regexes, re)
}
}
return cf
}
func (cf *combinedFilter) Matches(toMatch any) bool {
_, ok := cf.stricts[toMatch]
if ok {
return ok
}
if str, ok := toMatch.(string); ok {
for _, re := range cf.regexes {
if re.MatchString(str) {
return true
}
}
}
return false
}
================================================
FILE: filter/config.schema.yaml
================================================
$defs:
config:
description: Config configures the matching behavior of a Filter.
type: object
properties:
regexp:
type: string
strict:
type: string
================================================
FILE: filter/config_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package filter
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/confmaptest"
)
func readTestdataConfigYamls(t *testing.T, filename string) map[string][]Config {
testFile := filepath.Join("testdata", filename)
v, err := confmaptest.LoadConf(testFile)
require.NoError(t, err)
cfgs := map[string][]Config{}
require.NoErrorf(t, v.Unmarshal(&cfgs, confmap.WithIgnoreUnused()), "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": {
{
Regex: "one|two",
},
},
"strict/default": {
{
Strict: "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)
for _, cfg := range actualCfg {
require.NoError(t, cfg.Validate())
}
fs := CreateFilter(actualCfg)
assert.NotNil(t, fs)
})
}
}
func TestMatches(t *testing.T) {
cfg := []Config{
{
Strict: "a",
},
{
Strict: "b",
},
{
Regex: "a|b|c",
},
}
for _, c := range cfg {
require.NoError(t, c.Validate())
}
fs := CreateFilter(cfg)
assert.True(t, fs.Matches("a"))
assert.True(t, fs.Matches("b"))
assert.True(t, fs.Matches("c"))
}
func TestConfigInvalid(t *testing.T) {
actualConfigs := readTestdataConfigYamls(t, "config_invalid.yaml")
expectedConfigs := map[string][]Config{
"invalid/regexp": {
{
Regex: "(.*[",
},
},
"invalid/config_empty": {
{
Regex: "",
Strict: "",
},
},
"invalid/config_both_set": {
{
Regex: "1",
Strict: "1",
},
},
}
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)
for _, cfg := range actualCfg {
assert.Error(t, cfg.Validate())
}
})
}
}
================================================
FILE: filter/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package filter provides an interface for matching strings against a set of string filters.
package filter // import "go.opentelemetry.io/collector/filter"
================================================
FILE: filter/filter.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package filter // import "go.opentelemetry.io/collector/filter"
// Filter is an interface for matching values against a set of filters.
type Filter interface {
// Matches returns true if the given value matches at least one
// of the filters encapsulated by the Filter.
Matches(any) bool
}
================================================
FILE: filter/go.mod
================================================
module go.opentelemetry.io/collector/filter
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/confmap v1.54.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/confmap => ../confmap
replace go.opentelemetry.io/collector/featuregate => ../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../internal/testutil
================================================
FILE: filter/go.sum
================================================
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/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: filter/metadata.yaml
================================================
type: filter
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: filter/testdata/config.yaml
================================================
# Yaml form of the configuration for Filters
# 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: "one|two"
strict/default:
- strict: "strict"
================================================
FILE: filter/testdata/config_invalid.yaml
================================================
# Yaml form of the configuration for Filters
# 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/regexp:
- regexp: "(.*["
invalid/config_empty:
- regexp: ""
strict: ""
invalid/config_both_set:
- regexp: "1"
strict: "1"
================================================
FILE: go.mod
================================================
module go.opentelemetry.io/collector
// NOTE:
// This go.mod is NOT used to build any official binary.
// To see the builder manifests used for official binaries,
// check https://github.com/open-telemetry/opentelemetry-collector-releases
//
// For the OpenTelemetry Collector Core distribution specifically, see
// https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217
google.golang.org/grpc v1.79.3
google.golang.org/protobuf v1.36.11
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sys v0.41.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
retract (
v0.76.0 // Depends on retracted pdata v1.0.0-rc10 module, use v0.76.1
v0.69.0 // Release failed, use v0.69.1
v0.57.1 // Release failed, use v0.57.2
v0.57.0 // Release failed, use v0.57.2
v0.32.0 // Contains incomplete metrics transition to proto 0.9.0, random components are not working.
)
================================================
FILE: go.sum
================================================
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
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/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: internal/buildscripts/compare-apidiff.sh
================================================
#!/usr/bin/env bash
#
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0
# This script is used to compare API state snapshots to the current package state in order to validate releases are not breaking backwards compatibility.
usage() {
echo "Usage: $0"
echo
echo "-c Check-incompatibility mode. Script will fail if an incompatible change is found. Default: 'false'"
echo "-p Package to generate API state snapshot of. Default: ''"
echo "-d directory where prior states will be read from. Default: './internal/data/apidiff'"
exit 1
}
package=""
input_dir="./internal/data/apidiff"
check_only=false
repo_toplevel="$( git rev-parse --show-toplevel )"
tools_mod_file="${repo_toplevel}/internal/tools/go.mod"
while getopts "cp:d:" o; do
case "${o}" in
c)
check_only=true
;;
p)
package=$OPTARG
;;
d)
input_dir=$OPTARG
;;
*)
usage
;;
esac
done
shift $((OPTIND-1))
if [ -z "$package" ]; then
usage
fi
set -e
if [ -e "$input_dir"/"$package"/apidiff.state ]; then
changes=$(go tool -modfile "${tools_mod_file}" apidiff "$input_dir"/"$package"/apidiff.state "$package")
if [ -n "$changes" ] && [ "$changes" != " " ]; then
SUB='Incompatible changes:'
if [ $check_only = true ] && [[ "$changes" =~ .*"$SUB".* ]]; then
echo "Incompatible Changes Found."
echo "Check the logs in the GitHub Action log group: 'Compare-States'."
exit 1
else
echo "Changes found in $package:"
echo "$changes"
fi
fi
fi
================================================
FILE: internal/buildscripts/gen-apidiff.sh
================================================
#!/usr/bin/env bash
#
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0
# This script is used to create API state snapshots used to validate releases are not breaking backwards compatibility.
usage() {
echo "Usage: $0"
echo
echo "-d Dry-run mode. No project files will not be modified. Default: 'false'"
echo "-p Package to generate API state snapshot of. Default: ''"
echo "-o Output directory where state will be written to. Default: './internal/data/apidiff'"
exit 1
}
dry_run=false
package=""
output_dir="./internal/data/apidiff"
repo_toplevel="$( git rev-parse --show-toplevel )"
tools_mod_file="${repo_toplevel}/internal/tools/go.mod"
while getopts "dp:o:" o; do
case "${o}" in
d)
dry_run=true
;;
p)
package=$OPTARG
;;
o)
output_dir=$OPTARG
;;
*)
usage
;;
esac
done
shift $((OPTIND-1))
if [ -z "$package" ]; then
usage
fi
set -ex
# Create temp dir for generated files.
# Source: https://unix.stackexchange.com/a/84980
tmp_dir=$(mktemp -d 2>/dev/null || mktemp -d -t 'apidiff')
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
mkdir -p "$tmp_dir/$package"
go tool -modfile "${tools_mod_file}" apidiff -w "$tmp_dir"/"$package"/apidiff.state "$package"
# Copy files if not in dry-run mode.
if [ $dry_run = false ]; then
mkdir -p "$output_dir/$package" && \
cp "$tmp_dir/$package/apidiff.state" \
"$output_dir/$package"
fi
================================================
FILE: internal/buildscripts/gen-certs.sh
================================================
#!/usr/bin/env bash
#
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0
# 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"
echo "-s A suffix for the generated certificate. Default: \"\"; an empty string"
exit 1
}
dry_run=false
domain="localhost"
output_dir="."
suffix=""
while getopts "dm:o:s:" o; do
case "${o}" in
d)
dry_run=true
;;
m)
domain=$OPTARG
;;
o)
output_dir=$OPTARG
;;
s)
suffix=$OPTARG
;;
*)
usage
;;
esac
done
shift $((OPTIND-1))
set -ex
# Create temp dir for generated files.
tmp_dir=$(mktemp -d -t certificatesXXX)
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${suffix}.key" 2048
openssl req -new -key "$tmp_dir/ca${suffix}.key" -x509 -days 3650 -out "$tmp_dir/ca${suffix}.crt" -config "$tmp_dir/ssl.conf"
# Create client and server keys.
openssl genrsa -out "$tmp_dir/server${suffix}.key" 2048
openssl genrsa -out "$tmp_dir/client${suffix}.key" 2048
# Create certificate sign request using the above created keys.
openssl req -new -nodes -key "$tmp_dir/server${suffix}.key" -out "$tmp_dir/server${suffix}.csr" -config "$tmp_dir/ssl.conf"
openssl req -new -nodes -key "$tmp_dir/client${suffix}.key" -out "$tmp_dir/client${suffix}.csr" -config "$tmp_dir/ssl.conf"
# Creating the client and server certificates.
openssl x509 -req \
-sha256 \
-days 3650 \
-in "$tmp_dir/server${suffix}.csr" \
-out "$tmp_dir/server${suffix}.crt" \
-extensions req_ext \
-CA "$tmp_dir/ca${suffix}.crt" \
-CAkey "$tmp_dir/ca${suffix}.key" \
-CAcreateserial \
-extfile "$tmp_dir/ssl.conf"
openssl x509 -req \
-sha256 \
-days 3650 \
-in "$tmp_dir/client${suffix}.csr" \
-out "$tmp_dir/client${suffix}.crt" \
-extensions req_ext \
-CA "$tmp_dir/ca${suffix}.crt" \
-CAkey "$tmp_dir/ca${suffix}.key" \
-CAcreateserial \
-extfile "$tmp_dir/ssl.conf"
# Copy files if not in dry-run mode.
if [ $dry_run = false ]; then
cp "$tmp_dir/ca${suffix}.crt" \
"$tmp_dir/client${suffix}.crt" \
"$tmp_dir/client${suffix}.key" \
"$tmp_dir/server${suffix}.crt" \
"$tmp_dir/server${suffix}.key" \
"$output_dir"
fi
================================================
FILE: internal/cmd/pdatagen/Makefile
================================================
include ../../../Makefile.Common
================================================
FILE: internal/cmd/pdatagen/go.mod
================================================
module go.opentelemetry.io/collector/internal/cmd/pdatagen
go 1.25.0
require github.com/ettle/strcase v0.2.0
================================================
FILE: internal/cmd/pdatagen/go.sum
================================================
github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q=
github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A=
================================================
FILE: internal/cmd/pdatagen/internal/pdata/base_slices.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pdata // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/pdata"
import (
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/tmplutil"
)
type baseSlice interface {
getName() string
getHasWrapper() bool
getOriginFullName() string
getElementOriginName() string
getElementNullable() bool
getPackageName() string
}
// messageSlice generates code for a slice of pointer fields. The generated structs cannot be used from other packages.
type messageSlice struct {
structName string
packageName string
elementNullable bool
element *messageStruct
}
func (ss *messageSlice) getProtoMessage() *proto.Message {
return nil
}
func (ss *messageSlice) getName() string {
return ss.structName
}
func (ss *messageSlice) getPackageName() string {
return ss.packageName
}
func (ss *messageSlice) generate(packageInfo *PackageInfo) []byte {
return []byte(tmplutil.Execute(sliceTemplate, ss.templateFields(packageInfo)))
}
func (ss *messageSlice) generateTests(packageInfo *PackageInfo) []byte {
return []byte(tmplutil.Execute(sliceTestTemplate, ss.templateFields(packageInfo)))
}
func (ss *messageSlice) generateInternal(packageInfo *PackageInfo) []byte {
return []byte(tmplutil.Execute(sliceInternalTemplate, ss.templateFields(packageInfo)))
}
func (ss *messageSlice) templateFields(packageInfo *PackageInfo) map[string]any {
hasWrapper := usedByOtherDataTypes(ss.packageName)
return map[string]any{
"hasWrapper": usedByOtherDataTypes(ss.packageName),
"structName": ss.structName,
"elementName": ss.element.getName(),
"elementOriginName": ss.getElementOriginName(),
"elementNullable": ss.elementNullable,
"origAccessor": origAccessor(hasWrapper),
"stateAccessor": stateAccessor(hasWrapper),
"packageName": packageInfo.name,
"imports": packageInfo.imports,
"testImports": packageInfo.testImports,
}
}
func (ss *messageSlice) getOriginName() string {
return ss.element.getOriginName() + "Slice"
}
func (ss *messageSlice) getOriginFullName() string {
return ss.element.getOriginFullName()
}
func (ss *messageSlice) getHasWrapper() bool {
return usedByOtherDataTypes(ss.packageName)
}
func (ss *messageSlice) getHasOnlyInternal() bool {
return false
}
func (ss *messageSlice) getElementOriginName() string {
return ss.element.getOriginName()
}
func (ss *messageSlice) getElementNullable() bool {
return ss.elementNullable
}
var _ baseStruct = (*messageSlice)(nil)
================================================
FILE: internal/cmd/pdatagen/internal/pdata/base_struct.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pdata // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/pdata"
import (
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/tmplutil"
)
type baseStruct interface {
getName() string
getOriginName() string
getOriginFullName() string
getHasWrapper() bool
getHasOnlyInternal() bool
generate(packageInfo *PackageInfo) []byte
generateTests(packageInfo *PackageInfo) []byte
generateInternal(packageInfo *PackageInfo) []byte
getProtoMessage() *proto.Message
}
// messageStruct generates a struct for a proto message. The struct can be generated both as a common struct
// that can be used as a field in struct from other packages and as an isolated struct with depending on a package name.
type messageStruct struct {
structName string
packageName string
description string
protoName string
upstreamProto string
fields []Field
hasWrapper bool
hasOnlyInternal bool
}
func (ms *messageStruct) getName() string {
return ms.structName
}
func (ms *messageStruct) generate(packageInfo *PackageInfo) []byte {
return []byte(tmplutil.Execute(messageTemplate, ms.templateFields(packageInfo)))
}
func (ms *messageStruct) generateTests(packageInfo *PackageInfo) []byte {
return []byte(tmplutil.Execute(messageTestTemplate, ms.templateFields(packageInfo)))
}
func (ms *messageStruct) generateInternal(packageInfo *PackageInfo) []byte {
return []byte(tmplutil.Execute(messageInternalTemplate, ms.templateFields(packageInfo)))
}
func (ms *messageStruct) getProtoMessage() *proto.Message {
fields := make([]proto.FieldInterface, len(ms.fields))
for i := range ms.fields {
fields[i] = ms.fields[i].toProtoField(ms)
}
return &proto.Message{
Name: ms.protoName,
Description: ms.description,
UpstreamMessage: ms.upstreamProto,
Fields: fields,
}
}
func (ms *messageStruct) templateFields(packageInfo *PackageInfo) map[string]any {
hasWrapper := ms.hasWrapper
if !hasWrapper {
hasWrapper = usedByOtherDataTypes(ms.packageName)
}
return map[string]any{
"messageStruct": ms,
"fields": ms.fields,
"structName": ms.getName(),
"protoName": ms.getOriginFullName(),
"originName": ms.getOriginName(),
"description": ms.description,
"hasWrapper": hasWrapper,
"origAccessor": origAccessor(hasWrapper),
"stateAccessor": stateAccessor(hasWrapper),
"packageName": packageInfo.name,
"imports": packageInfo.imports,
"testImports": packageInfo.testImports,
}
}
func (ms *messageStruct) getHasWrapper() bool {
if ms.hasWrapper {
return true
}
if ms.hasOnlyInternal {
return false
}
return usedByOtherDataTypes(ms.packageName)
}
func (ms *messageStruct) getHasOnlyInternal() bool {
return ms.hasOnlyInternal
}
func (ms *messageStruct) getOriginName() string {
return ms.protoName
}
func (ms *messageStruct) getOriginFullName() string {
return ms.protoName
}
var _ baseStruct = (*messageStruct)(nil)
================================================
FILE: internal/cmd/pdatagen/internal/pdata/field.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pdata // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/pdata"
import (
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
)
type Field interface {
GenerateAccessors(ms *messageStruct) string
GenerateAccessorsTest(ms *messageStruct) string
GenerateTestValue(ms *messageStruct) string
toProtoField(ms *messageStruct) proto.FieldInterface
}
func origAccessor(hasWrapper bool) string {
if hasWrapper {
return "getOrig()"
}
return "orig"
}
func stateAccessor(hasWrapper bool) string {
if hasWrapper {
return "getState()"
}
return "state"
}
================================================
FILE: internal/cmd/pdatagen/internal/pdata/message_field.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pdata // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/pdata"
import (
"strings"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/tmplutil"
)
const messageAccessorsTemplate = `// {{ .fieldName }} returns the {{ .lowerFieldName }} associated with this {{ .structName }}.
func (ms {{ .structName }}) {{ .fieldName }}() {{ .packageName }}{{ .returnType }} {
{{- if .messageHasWrapper }}
return {{ .packageName }}{{ .returnType }}(internal.New{{ .returnType }}Wrapper(&ms.{{ .origAccessor }}.{{ .fieldOriginFullName }}, ms.{{ .stateAccessor }}))
{{- else }}
return new{{ .returnType }}(&ms.{{ .origAccessor }}.{{ .fieldOriginFullName }}, ms.{{ .stateAccessor }})
{{- end }}
}`
const messageAccessorsTestTemplate = `func Test{{ .structName }}_{{ .fieldName }}(t *testing.T) {
ms := New{{ .structName }}()
assert.Equal(t, {{ .packageName }}New{{ .returnType }}{{- if eq .returnType "Value" }}Empty{{- end }}(), ms.{{ .fieldName }}())
ms.{{ .origAccessor }}.{{ .fieldOriginFullName }} = *internal.GenTest{{ .fieldOriginName }}()
{{- if .messageHasWrapper }}
assert.Equal(t, {{ .packageName }}{{ .returnType }}(internal.GenTest{{ .returnType }}Wrapper()), ms.{{ .fieldName }}())
{{- else }}
assert.Equal(t, generateTest{{ .returnType }}(), ms.{{ .fieldName }}())
{{- end }}
}`
const messageSetTestTemplate = `orig.{{ .fieldOriginFullName }} = *GenTest{{ .fieldOriginName }}()`
type MessageField struct {
fieldName string
protoID uint32
nullable bool
returnMessage *messageStruct
}
func (mf *MessageField) GenerateAccessors(ms *messageStruct) string {
t := tmplutil.Parse("messageAccessorsTemplate", []byte(messageAccessorsTemplate))
return tmplutil.Execute(t, mf.templateFields(ms))
}
func (mf *MessageField) GenerateAccessorsTest(ms *messageStruct) string {
t := tmplutil.Parse("messageAccessorsTestTemplate", []byte(messageAccessorsTestTemplate))
return tmplutil.Execute(t, mf.templateFields(ms))
}
func (mf *MessageField) GenerateTestValue(ms *messageStruct) string {
t := tmplutil.Parse("messageSetTestTemplate", []byte(messageSetTestTemplate))
return tmplutil.Execute(t, mf.templateFields(ms))
}
func (mf *MessageField) toProtoField(ms *messageStruct) proto.FieldInterface {
pt := proto.TypeMessage
if mf.returnMessage.getName() == "TraceState" {
pt = proto.TypeString
}
return &proto.Field{
Type: pt,
ID: mf.protoID,
Name: mf.fieldName,
MessageName: mf.returnMessage.getOriginName(),
ParentMessageName: ms.protoName,
Nullable: mf.nullable,
}
}
func (mf *MessageField) templateFields(ms *messageStruct) map[string]any {
return map[string]any{
"messageHasWrapper": usedByOtherDataTypes(mf.returnMessage.packageName),
"structName": ms.getName(),
"fieldName": mf.fieldName,
"fieldOriginFullName": mf.fieldName,
"fieldOriginName": mf.returnMessage.getOriginName(),
"lowerFieldName": strings.ToLower(mf.fieldName),
"returnType": mf.returnMessage.getName(),
"packageName": func() string {
if mf.returnMessage.packageName != ms.packageName {
return mf.returnMessage.packageName + "."
}
return ""
}(),
"origAccessor": origAccessor(ms.getHasWrapper()),
"stateAccessor": stateAccessor(ms.getHasWrapper()),
}
}
var _ Field = (*MessageField)(nil)
================================================
FILE: internal/cmd/pdatagen/internal/pdata/one_of_field.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pdata // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/pdata"
import (
"strings"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/tmplutil"
)
const oneOfAccessorTemplate = `// {{ .typeFuncName }} returns the type of the {{ .lowerOriginFieldName }} for this {{ .structName }}.
// Calling this function on zero-initialized {{ .structName }} will cause a panic.
func (ms {{ .structName }}) {{ .typeFuncName }}() {{ .typeName }} {
switch ms.{{ .origAccessor }}.{{ .originFieldName }}.(type) {
{{- range .values }}
{{ .GenerateType $.baseStruct $.OneOfField }}
{{- end }}
}
return {{ .typeName }}Empty
}
{{ range .values -}}
{{ .GenerateAccessors $.baseStruct $.OneOfField }}
{{- end }}`
const oneOfAccessorTestTemplate = `func Test{{ .structName }}_{{ .typeFuncName }}(t *testing.T) {
tv := New{{ .structName }}()
assert.Equal(t, {{ .typeName }}Empty, tv.{{ .typeFuncName }}())
}
{{ range .values -}}
{{ .GenerateTests $.baseStruct $.OneOfField }}
{{- end }}
`
const oneOfTestFailingUnmarshalProtoValuesTemplate = `
{{ range .fields -}}
{{ .GenTestFailingUnmarshalProtoValues }}
{{- end }}`
const oneOfTestValuesTemplate = `
{{ range .fields -}}
{{ .GenTestEncodingValues }}
{{- end }}`
const oneOfPoolOrigTemplate = `
{{ range .fields -}}
{{ .GenPool }}
{{- end }}`
const oneOfMessageOrigTemplate = `
func (m *{{ .protoName }}) Get{{ .originFieldName }}() any {
if m != nil {
return m.{{ .originFieldName }}
}
return nil
}
{{ range .fields -}}
{{ .GenOneOfMessages }}
{{- end }}`
const oneOfDeleteOrigTemplate = `switch ov := orig.{{ .originFieldName }}.(type) {
{{ range .fields -}}
case *{{ $.protoName }}_{{ .GetName }}:
{{ .GenDelete }}
{{ end -}}
}`
const oneOfCopyOrigTemplate = `switch t := src.{{ .originFieldName }}.(type) {
{{ range .fields -}}
case *{{ $.protoName }}_{{ .GetName }}:
{{ .GenCopy }}
{{ end -}}
default:
dest.{{ .originFieldName }} = nil
}`
const oneOfMarshalJSONTemplate = `switch orig := orig.{{ .originFieldName }}.(type) {
{{ range .fields -}}
case *{{ $.protoName }}_{{ .GetName }}:
{{ .GenMarshalJSON }}
{{ end -}}
}`
const oneOfUnmarshalJSONTemplate = `
{{ range .fields -}}
{{ .GenUnmarshalJSON }}
{{- end }}`
const oneOfSizeProtoTemplate = `switch orig := orig.{{ .originFieldName }}.(type) {
case nil:
_ = orig
break
{{ range .fields -}}
case *{{ $.protoName }}_{{ .GetName }}:
{{ .GenSizeProto }}
{{ end -}}
}`
const oneOfMarshalProtoTemplate = `switch orig := orig.{{ .originFieldName }}.(type) {
{{ range .fields -}}
case *{{ $.protoName }}_{{ .GetName }}:
{{ .GenMarshalProto }}
{{ end -}}
}`
const oneOfUnmarshalProtoTemplate = `
{{- range .fields }}
{{ .GenUnmarshalProto }}
{{ end }}`
type OneOfField struct {
originFieldName string
typeName string
testValueIdx int
values []oneOfValue
omitOriginFieldNameInNames bool
}
func (of *OneOfField) GenerateAccessors(ms *messageStruct) string {
return tmplutil.Execute(tmplutil.Parse("oneOfAccessorTemplate", []byte(oneOfAccessorTemplate)), of.templateFields(ms))
}
func (of *OneOfField) typeFuncName() string {
const typeSuffix = "Type"
if of.omitOriginFieldNameInNames {
return typeSuffix
}
return of.originFieldName + typeSuffix
}
func (of *OneOfField) GenerateAccessorsTest(ms *messageStruct) string {
return tmplutil.Execute(tmplutil.Parse("oneOfAccessorTestTemplate", []byte(oneOfAccessorTestTemplate)), of.templateFields(ms))
}
func (of *OneOfField) GenerateTestValue(ms *messageStruct) string {
return of.values[of.testValueIdx].GenerateTestValue(ms, of)
}
func (of *OneOfField) toProtoField(ms *messageStruct) proto.FieldInterface {
fields := make([]proto.FieldInterface, len(of.values))
for i := range of.values {
fields[i] = of.values[i].toProtoField(ms, of)
}
return &oneOfProtoField{
originFieldName: of.originFieldName,
protoName: ms.protoName,
fields: fields,
}
}
type oneOfProtoField struct {
originFieldName string
protoName string
fields []proto.FieldInterface
}
func (of *oneOfProtoField) GenMessageField() string {
return of.originFieldName + " any"
}
func (of *oneOfProtoField) GenOneOfMessages() string {
return tmplutil.Execute(tmplutil.Parse("oneOfMessageOrigTemplate", []byte(oneOfMessageOrigTemplate)), of.templateFields())
}
func (of *oneOfProtoField) GetName() string {
return of.originFieldName
}
func (of *oneOfProtoField) GoType() string {
panic("implement me")
}
func (of *oneOfProtoField) DefaultValue() string {
panic("implement me")
}
func (of *oneOfProtoField) GenTest() string {
return "orig." + of.GetName() + " = " + of.TestValue()
}
func (of *oneOfProtoField) TestValue() string {
return "&" + of.protoName + "_" + of.fields[0].GetName() + "{" + of.fields[0].GetName() + ": " + of.fields[0].TestValue() + "}"
}
func (of *oneOfProtoField) GenTestFailingUnmarshalProtoValues() string {
return tmplutil.Execute(tmplutil.Parse("oneOfTestFailingUnmarshalProtoValuesTemplate", []byte(oneOfTestFailingUnmarshalProtoValuesTemplate)), of.templateFields())
}
func (of *oneOfProtoField) GenTestEncodingValues() string {
return tmplutil.Execute(tmplutil.Parse("oneOfTestValuesTemplate", []byte(oneOfTestValuesTemplate)), of.templateFields())
}
func (of *oneOfProtoField) GenPool() string {
return tmplutil.Execute(tmplutil.Parse("oneOfPoolOrigTemplate", []byte(oneOfPoolOrigTemplate)), of.templateFields())
}
func (of *oneOfProtoField) GenDelete() string {
return tmplutil.Execute(tmplutil.Parse("oneOfDeleteOrigTemplate", []byte(oneOfDeleteOrigTemplate)), of.templateFields())
}
func (of *oneOfProtoField) GenCopy() string {
return tmplutil.Execute(tmplutil.Parse("oneOfCopyOrigTemplate", []byte(oneOfCopyOrigTemplate)), of.templateFields())
}
func (of *oneOfProtoField) GenMarshalJSON() string {
return tmplutil.Execute(tmplutil.Parse("oneOfMarshalJSONTemplate", []byte(oneOfMarshalJSONTemplate)), of.templateFields())
}
func (of *oneOfProtoField) GenUnmarshalJSON() string {
return tmplutil.Execute(tmplutil.Parse("oneOfUnmarshalJSONTemplate", []byte(oneOfUnmarshalJSONTemplate)), of.templateFields())
}
func (of *oneOfProtoField) GenSizeProto() string {
t := tmplutil.Parse("oneOfSizeProtoTemplate", []byte(oneOfSizeProtoTemplate))
return tmplutil.Execute(t, of.templateFields())
}
func (of *oneOfProtoField) GenMarshalProto() string {
t := tmplutil.Parse("oneOfMarshalProtoTemplate", []byte(oneOfMarshalProtoTemplate))
return tmplutil.Execute(t, of.templateFields())
}
func (of *oneOfProtoField) GenUnmarshalProto() string {
t := tmplutil.Parse("oneOfUnmarshalProtoTemplate", []byte(oneOfUnmarshalProtoTemplate))
return tmplutil.Execute(t, of.templateFields())
}
func (of *oneOfProtoField) templateFields() map[string]any {
return map[string]any{
"originFieldName": of.originFieldName,
"fields": of.fields,
"protoName": of.protoName,
}
}
func (of *OneOfField) templateFields(ms *messageStruct) map[string]any {
return map[string]any{
"baseStruct": ms,
"OneOfField": of,
"packageName": "",
"structName": ms.getName(),
"typeFuncName": of.typeFuncName(),
"typeName": of.typeName,
"originName": ms.getOriginName(),
"originFieldName": of.originFieldName,
"lowerOriginFieldName": strings.ToLower(of.originFieldName),
"origAccessor": origAccessor(ms.getHasWrapper()),
"stateAccessor": stateAccessor(ms.getHasWrapper()),
"values": of.values,
}
}
var _ Field = (*OneOfField)(nil)
type oneOfValue interface {
GetOriginFieldName() string
GenerateAccessors(ms *messageStruct, of *OneOfField) string
GenerateType(ms *messageStruct, of *OneOfField) string
GenerateTests(ms *messageStruct, of *OneOfField) string
GenerateTestValue(ms *messageStruct, of *OneOfField) string
toProtoField(ms *messageStruct, of *OneOfField) proto.FieldInterface
}
================================================
FILE: internal/cmd/pdatagen/internal/pdata/one_of_message_value.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pdata // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/pdata"
import (
"strings"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/tmplutil"
)
const oneOfMessageAccessorsTemplate = `// {{ .fieldName }} returns the {{ .lowerFieldName }} associated with this {{ .structName }}.
//
// Calling this function when {{ .originOneOfTypeFuncName }}() != {{ .typeName }} returns an invalid
// zero-initialized instance of {{ .returnType }}. Note that using such {{ .returnType }} instance can cause panic.
//
// Calling this function on zero-initialized {{ .structName }} will cause a panic.
func (ms {{ .structName }}) {{ .fieldName }}() {{ .returnType }} {
v, ok := ms.orig.Get{{ .originOneOfFieldName }}().(*internal.{{ .originStructType }})
if !ok {
return {{ .returnType }}{}
}
return new{{ .returnType }}(v.{{ .fieldName }}, ms.state)
}
// SetEmpty{{ .fieldName }} sets an empty {{ .lowerFieldName }} to this {{ .structName }}.
//
// After this, {{ .originOneOfTypeFuncName }}() function will return {{ .typeName }}".
//
// Calling this function on zero-initialized {{ .structName }} will cause a panic.
func (ms {{ .structName }}) SetEmpty{{ .fieldName }}() {{ .returnType }} {
ms.state.AssertMutable()
var ov *internal.{{ .originStructType }}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &internal.{{ .originStructType }}{}
} else {
ov = internal.ProtoPool{{ .oneOfName }}.Get().(*internal.{{ .originStructType }})
}
ov.{{ .fieldName }} = internal.New{{ .fieldOriginName }}()
ms.orig.{{ .originOneOfFieldName }} = ov
return new{{ .returnType }}(ov.{{ .fieldName }}, ms.state)
}`
const oneOfMessageAccessorsTestTemplate = `func Test{{ .structName }}_{{ .fieldName }}(t *testing.T) {
ms := New{{ .structName }}()
ms.SetEmpty{{ .fieldName }}()
assert.Equal(t, New{{ .returnType }}(), ms.{{ .fieldName }}())
ms.orig.Get{{ .originOneOfFieldName }}().(*internal.{{ .originStructType }}).{{ .fieldName }} = internal.GenTest{{ .returnType }}()
assert.Equal(t, {{ .typeName }}, ms.{{ .originOneOfTypeFuncName }}())
assert.Equal(t, generateTest{{ .returnType }}(), ms.{{ .fieldName }}())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { new{{ .structName }}(internal.New{{ .originStructName }}(), sharedState).SetEmpty{{ .fieldName }}() })
}
`
const oneOfMessageSetTestTemplate = `orig.{{ .originOneOfFieldName }} = &internal.{{ .originStructType }}{
{{- .fieldName }}: GenTest{{ .fieldOriginName }}() }`
const oneOfMessageTypeTemplate = `case *internal.{{ .originStructType }}:
return {{ .typeName }}`
type OneOfMessageValue struct {
fieldName string
protoID uint32
returnMessage *messageStruct
}
func (omv *OneOfMessageValue) GetOriginFieldName() string {
return omv.fieldName
}
func (omv *OneOfMessageValue) GenerateAccessors(ms *messageStruct, of *OneOfField) string {
t := tmplutil.Parse("oneOfMessageAccessorsTemplate", []byte(oneOfMessageAccessorsTemplate))
return tmplutil.Execute(t, omv.templateFields(ms, of))
}
func (omv *OneOfMessageValue) GenerateTests(ms *messageStruct, of *OneOfField) string {
t := tmplutil.Parse("oneOfMessageAccessorsTestTemplate", []byte(oneOfMessageAccessorsTestTemplate))
return tmplutil.Execute(t, omv.templateFields(ms, of))
}
func (omv *OneOfMessageValue) GenerateTestValue(ms *messageStruct, of *OneOfField) string {
t := tmplutil.Parse("oneOfMessageSetTestTemplate", []byte(oneOfMessageSetTestTemplate))
return tmplutil.Execute(t, omv.templateFields(ms, of))
}
func (omv *OneOfMessageValue) GenerateType(ms *messageStruct, of *OneOfField) string {
t := tmplutil.Parse("oneOfMessageTypeTemplate", []byte(oneOfMessageTypeTemplate))
return tmplutil.Execute(t, omv.templateFields(ms, of))
}
func (omv *OneOfMessageValue) toProtoField(ms *messageStruct, of *OneOfField) proto.FieldInterface {
return &proto.Field{
Type: proto.TypeMessage,
ID: omv.protoID,
OneOfGroup: of.originFieldName,
OneOfMessageName: ms.protoName + "_" + omv.fieldName,
Name: omv.fieldName,
MessageName: omv.returnMessage.getOriginFullName(),
ParentMessageName: ms.protoName,
Nullable: true,
}
}
func (omv *OneOfMessageValue) templateFields(ms *messageStruct, of *OneOfField) map[string]any {
return map[string]any{
"fieldName": omv.fieldName,
"originOneOfFieldName": of.originFieldName,
"fieldOriginName": omv.returnMessage.getOriginName(),
"typeName": of.typeName + omv.fieldName,
"structName": ms.getName(),
"returnType": omv.returnMessage.getName(),
"originOneOfTypeFuncName": of.typeFuncName(),
"lowerFieldName": strings.ToLower(omv.fieldName),
"originStructName": ms.protoName,
"originStructType": ms.protoName + "_" + omv.fieldName,
"oneOfName": proto.ExtractNameFromFull(ms.protoName + "_" + omv.fieldName),
}
}
var _ oneOfValue = (*OneOfMessageValue)(nil)
================================================
FILE: internal/cmd/pdatagen/internal/pdata/one_of_primitive_value.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pdata // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/pdata"
import (
"strings"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/tmplutil"
)
const oneOfPrimitiveAccessorsTemplate = `// {{ .accessorFieldName }} returns the {{ .lowerFieldName }} associated with this {{ .structName }}.
func (ms {{ .structName }}) {{ .accessorFieldName }}() {{ .returnType }} {
return ms.orig.Get{{ .originFieldName }}()
}
// Set{{ .accessorFieldName }} replaces the {{ .lowerFieldName }} associated with this {{ .structName }}.
func (ms {{ .structName }}) Set{{ .accessorFieldName }}(v {{ .returnType }}) {
ms.state.AssertMutable()
var ov *internal.{{ .originStructType }}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &internal.{{ .originStructType }}{}
} else {
ov = internal.ProtoPool{{ .oneOfName }}.Get().(*internal.{{ .originStructType }})
}
ov.{{ .originFieldName }} = v
ms.orig.{{ .originOneOfFieldName }} = ov
}`
const oneOfPrimitiveAccessorTestTemplate = `func Test{{ .structName }}_{{ .accessorFieldName }}(t *testing.T) {
ms := New{{ .structName }}()
{{- if eq .returnType "float64"}}
assert.InDelta(t, {{ .defaultVal }}, ms.{{ .accessorFieldName }}(), 0.01)
{{- else if and (eq .returnType "string") (eq .defaultVal "\"\"") }}
assert.Empty(t, ms.{{ .accessorFieldName }}())
{{- else }}
assert.Equal(t, {{ .defaultVal }}, ms.{{ .accessorFieldName }}())
{{- end }}
ms.Set{{ .accessorFieldName }}({{ .testValue }})
{{- if eq .returnType "float64" }}
assert.InDelta(t, {{ .testValue }}, ms.{{ .accessorFieldName }}(), 0.01)
{{- else if and (eq .returnType "string") (eq .testValue "\"\"") }}
assert.Empty(t, ms.{{ .accessorFieldName }}())
{{- else }}
assert.Equal(t, {{ .testValue }}, ms.{{ .accessorFieldName }}())
{{- end }}
assert.Equal(t, {{ .typeName }}, ms.{{ .originOneOfTypeFuncName }}())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { new{{ .structName }}(internal.New{{ .originStructName }}(), sharedState).Set{{ .accessorFieldName }}({{ .testValue }}) })
}
`
const oneOfPrimitiveSetTestTemplate = `orig.{{ .originOneOfFieldName }} = &internal.{{ .originStructType }}{
{{- .originFieldName }}: {{ .testValue }}}`
const oneOfPrimitiveTypeTemplate = `case *internal.{{ .originStructType }}:
return {{ .typeName }}`
type OneOfPrimitiveValue struct {
fieldName string
protoID uint32
protoType proto.Type
originFieldName string
}
func (opv *OneOfPrimitiveValue) GetOriginFieldName() string {
return opv.originFieldName
}
func (opv *OneOfPrimitiveValue) GenerateAccessors(ms *messageStruct, of *OneOfField) string {
t := tmplutil.Parse("oneOfPrimitiveAccessorsTemplate", []byte(oneOfPrimitiveAccessorsTemplate))
return tmplutil.Execute(t, opv.templateFields(ms, of))
}
func (opv *OneOfPrimitiveValue) GenerateTests(ms *messageStruct, of *OneOfField) string {
t := tmplutil.Parse("oneOfPrimitiveAccessorTestTemplate", []byte(oneOfPrimitiveAccessorTestTemplate))
return tmplutil.Execute(t, opv.templateFields(ms, of))
}
func (opv *OneOfPrimitiveValue) GenerateTestValue(ms *messageStruct, of *OneOfField) string {
t := tmplutil.Parse("oneOfPrimitiveSetTestTemplate", []byte(oneOfPrimitiveSetTestTemplate))
return tmplutil.Execute(t, opv.templateFields(ms, of))
}
func (opv *OneOfPrimitiveValue) GenerateType(ms *messageStruct, of *OneOfField) string {
t := tmplutil.Parse("oneOfPrimitiveCopyOrigTemplate", []byte(oneOfPrimitiveTypeTemplate))
return tmplutil.Execute(t, opv.templateFields(ms, of))
}
func (opv *OneOfPrimitiveValue) toProtoField(ms *messageStruct, of *OneOfField) proto.FieldInterface {
pf := &proto.Field{
Type: opv.protoType,
ID: opv.protoID,
OneOfGroup: of.originFieldName,
Name: opv.originFieldName,
OneOfMessageName: ms.protoName + "_" + opv.originFieldName,
ParentMessageName: ms.protoName,
Nullable: true,
}
return pf
}
func (opv *OneOfPrimitiveValue) templateFields(ms *messageStruct, of *OneOfField) map[string]any {
pf := opv.toProtoField(ms, of)
return map[string]any{
"structName": ms.getName(),
"defaultVal": pf.DefaultValue(),
"packageName": "",
"accessorFieldName": opv.getAccessorFieldName(of),
"testValue": pf.TestValue(),
"originOneOfTypeFuncName": of.typeFuncName(),
"typeName": of.typeName + opv.fieldName,
"lowerFieldName": strings.ToLower(opv.fieldName),
"returnType": pf.GoType(),
"originFieldName": opv.originFieldName,
"originOneOfFieldName": of.originFieldName,
"originStructName": ms.protoName,
"originStructType": ms.protoName + "_" + opv.originFieldName,
"oneOfName": proto.ExtractNameFromFull(ms.protoName + "_" + opv.originFieldName),
}
}
func (opv *OneOfPrimitiveValue) getAccessorFieldName(of *OneOfField) string {
if of.omitOriginFieldNameInNames {
return opv.fieldName
}
return opv.fieldName + of.originFieldName
}
var _ oneOfValue = (*OneOfPrimitiveValue)(nil)
================================================
FILE: internal/cmd/pdatagen/internal/pdata/optional_primitive_field.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pdata // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/pdata"
import (
"strings"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/tmplutil"
)
const optionalPrimitiveAccessorsTemplate = `// {{ .fieldName }} returns the {{ .lowerFieldName }} associated with this {{ .structName }}.
func (ms {{ .structName }}) {{ .fieldName }}() {{ .returnType }} {
return ms.orig.{{ .fieldName }}
}
// Has{{ .fieldName }} returns true if the {{ .structName }} contains a
// {{ .fieldName }} value otherwise.
func (ms {{ .structName }}) Has{{ .fieldName }}() bool {
return ms.orig.Has{{ .fieldName }}()
}
// Set{{ .fieldName }} replaces the {{ .lowerFieldName }} associated with this {{ .structName }}.
func (ms {{ .structName }}) Set{{ .fieldName }}(v {{ .returnType }}) {
ms.state.AssertMutable()
ms.orig.Set{{ .fieldName }}(v)
}
// Remove{{ .fieldName }} removes the {{ .lowerFieldName }} associated with this {{ .structName }}.
func (ms {{ .structName }}) Remove{{ .fieldName }}() {
ms.state.AssertMutable()
ms.orig.Remove{{ .fieldName }}()
}`
const optionalPrimitiveAccessorsTestTemplate = `func Test{{ .structName }}_{{ .fieldName }}(t *testing.T) {
ms := New{{ .structName }}()
{{- if eq .returnType "float64" }}
assert.InDelta(t, {{ .defaultVal }}, ms.{{ .fieldName }}() , 0.01)
{{- else }}
assert.Equal(t, {{ .defaultVal }}, ms.{{ .fieldName }}())
{{- end }}
ms.Set{{ .fieldName }}({{ .testValue }})
assert.True(t, ms.Has{{ .fieldName }}())
{{- if eq .returnType "float64" }}
assert.InDelta(t, {{.testValue }}, ms.{{ .fieldName }}(), 0.01)
{{- else }}
assert.Equal(t, {{ .testValue }}, ms.{{ .fieldName }}())
{{- end }}
ms.Remove{{ .fieldName }}()
assert.False(t, ms.Has{{ .fieldName }}())
dest := New{{ .structName }}()
dest.Set{{ .fieldName }}({{ .testValue }})
ms.CopyTo(dest)
assert.False(t, dest.Has{{ .fieldName }}())
}`
const optionalPrimitiveSetTestTemplate = `orig.{{ .fieldName }}_ = &internal.{{ .originStructType }}{
{{- .fieldName }}: {{ .testValue }}}`
type OptionalPrimitiveField struct {
fieldName string
protoID uint32
protoType proto.Type
}
func (opv *OptionalPrimitiveField) GenerateAccessors(ms *messageStruct) string {
return tmplutil.Execute(tmplutil.Parse("optionalPrimitiveAccessorsTemplate", []byte(optionalPrimitiveAccessorsTemplate)), opv.templateFields(ms))
}
func (opv *OptionalPrimitiveField) GenerateAccessorsTest(ms *messageStruct) string {
return tmplutil.Execute(tmplutil.Parse("optionalPrimitiveAccessorsTestTemplate", []byte(optionalPrimitiveAccessorsTestTemplate)), opv.templateFields(ms))
}
func (opv *OptionalPrimitiveField) GenerateTestValue(ms *messageStruct) string {
return tmplutil.Execute(tmplutil.Parse("optionalPrimitiveSetTestTemplate", []byte(optionalPrimitiveSetTestTemplate)), opv.templateFields(ms))
}
func (opv *OptionalPrimitiveField) toProtoField(ms *messageStruct) proto.FieldInterface {
return &proto.Field{
Type: opv.protoType,
ID: opv.protoID,
Name: opv.fieldName,
ParentMessageName: ms.protoName,
Nullable: true,
}
}
func (opv *OptionalPrimitiveField) templateFields(ms *messageStruct) map[string]any {
pf := opv.toProtoField(ms).(*proto.Field)
return map[string]any{
"structName": ms.getName(),
"defaultVal": pf.DefaultValue(),
"fieldName": opv.fieldName,
"lowerFieldName": strings.ToLower(opv.fieldName),
"testValue": pf.TestValue(),
"returnType": pf.GoType(),
"originName": ms.getOriginName(),
"originStructName": ms.getOriginFullName(),
"originStructType": ms.getOriginFullName() + "_" + opv.fieldName,
}
}
var _ Field = (*OptionalPrimitiveField)(nil)
================================================
FILE: internal/cmd/pdatagen/internal/pdata/packages.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pdata // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/pdata"
import (
"fmt"
"os"
"path/filepath"
"slices"
"strings"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
)
var nonInternalDeps = []string{
`"go.opentelemetry.io/collector/pdata/internal"`,
`"go.opentelemetry.io/collector/pdata/pcommon"`,
`"go.opentelemetry.io/collector/pdata/plog"`,
`"go.opentelemetry.io/collector/pdata/pmetric"`,
`"go.opentelemetry.io/collector/pdata/pprofile"`,
`"go.opentelemetry.io/collector/pdata/ptrace"`,
`"go.opentelemetry.io/collector/pdata/xpdata"`,
}
// AllPackages is a list of all packages that needs to be generated.
var AllPackages = []*Package{
pcommon,
plog,
plogotlp,
pmetric,
pmetricotlp,
ptrace,
ptraceotlp,
pprofile,
pprofileotlp,
xpdataEntity,
prequest,
}
// Package is a struct used to generate files.
type Package struct {
info *PackageInfo
// Can be any of sliceStruct, sliceOfValues, messageStruct.
structs []baseStruct
enums []*proto.Enum
}
type PackageInfo struct {
name string
path string
imports []string
testImports []string
}
// Path returns the package path for file generation.
func (p *Package) Path() string {
return p.info.path
}
// DeleteGeneratedFiles removes all generated files matching the pattern in the given directory.
func DeleteGeneratedFiles(dir string) error {
matches, err := filepath.Glob(filepath.Join(dir, "generated_*.go"))
if err != nil {
return err
}
for _, match := range matches {
if err := os.Remove(match); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("failed to remove %s: %w", match, err)
}
}
return nil
}
// GenerateFiles generates files with the configured data structures for this Package.
func (p *Package) GenerateFiles() error {
for _, s := range p.structs {
if s.getHasOnlyInternal() {
continue
}
path := filepath.Join("pdata", p.info.path, "generated_"+strings.ToLower(s.getName())+".go")
if err := os.WriteFile(path, s.generate(p.info), 0o600); err != nil {
return err
}
}
return nil
}
// GenerateTestFiles generates files with tests for the configured data structures for this Package.
func (p *Package) GenerateTestFiles() error {
for _, s := range p.structs {
if s.getHasOnlyInternal() {
continue
}
path := filepath.Join("pdata", p.info.path, "generated_"+strings.ToLower(s.getName())+"_test.go")
if err := os.WriteFile(path, s.generateTests(p.info), 0o600); err != nil {
return err
}
}
return nil
}
// GenerateInternalFiles generates files with internal structs for this Package.
func (p *Package) GenerateInternalFiles() error {
for _, s := range p.structs {
if !s.getHasWrapper() {
continue
}
path := filepath.Join("pdata", "internal", "generated_wrapper_"+strings.ToLower(s.getOriginName())+".go")
saveImports := slices.Clone(p.info.imports)
p.info.imports = slices.DeleteFunc(p.info.imports, func(s string) bool {
return slices.Contains(nonInternalDeps, s)
})
if err := os.WriteFile(path, s.generateInternal(p.info), 0o600); err != nil {
return err
}
p.info.imports = saveImports
}
return nil
}
// GenerateProtoMessageFiles generates files with proto messages for this Package.
func (p *Package) GenerateProtoMessageFiles() error {
for _, s := range p.structs {
pm := s.getProtoMessage()
if pm == nil {
continue
}
saveTestImports := slices.Clone(p.info.testImports)
p.info.testImports = slices.DeleteFunc(p.info.testImports, func(s string) bool {
return slices.Contains(nonInternalDeps, s)
})
path := filepath.Join("pdata", "internal", "generated_proto_"+strings.ToLower(s.getOriginName())+".go")
if err := os.WriteFile(path, pm.GenerateMessage(p.info.imports, p.info.testImports), 0o600); err != nil {
return err
}
p.info.testImports = saveTestImports
}
return nil
}
// GenerateProtoMessageTestsFiles generates files with proto messages tests for this Package.
func (p *Package) GenerateProtoMessageTestsFiles() error {
for _, s := range p.structs {
pm := s.getProtoMessage()
if pm == nil {
continue
}
saveTestImports := slices.Clone(p.info.testImports)
p.info.testImports = slices.DeleteFunc(p.info.testImports, func(s string) bool {
return slices.Contains(nonInternalDeps, s)
})
path := filepath.Join("pdata", "internal", "generated_proto_"+strings.ToLower(pm.Name)+"_test.go")
if err := os.WriteFile(path, pm.GenerateMessageTests(p.info.imports, p.info.testImports), 0o600); err != nil {
return err
}
p.info.testImports = saveTestImports
}
return nil
}
// GenerateProtoEnumFiles generates files with proto messages for this Package.
func (p *Package) GenerateProtoEnumFiles() error {
for _, s := range p.enums {
path := filepath.Join("pdata", "internal", "generated_enum_"+strings.ToLower(s.Name)+".go")
if err := os.WriteFile(path, s.GenerateEnum(), 0o600); err != nil {
return err
}
}
return nil
}
// usedByOtherDataTypes defines if the package is used by other data types and orig fields of the package's structs
// need to be accessible from other pdata packages.
func usedByOtherDataTypes(packageName string) bool {
return packageName == "pcommon" || packageName == "entity"
}
================================================
FILE: internal/cmd/pdatagen/internal/pdata/pcommon_package.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pdata // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/pdata"
import (
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
)
var pcommon = &Package{
info: &PackageInfo{
name: "pcommon",
path: "pcommon",
imports: []string{
`"encoding/binary"`,
`"fmt"`,
`"iter"`,
`"math"`,
`"sort"`,
`"sync"`,
``,
`"go.opentelemetry.io/collector/pdata/internal"`,
`"go.opentelemetry.io/collector/pdata/internal/json"`,
`"go.opentelemetry.io/collector/pdata/internal/proto"`,
},
testImports: []string{
`"strconv"`,
`"testing"`,
``,
`"github.com/stretchr/testify/assert"`,
`"github.com/stretchr/testify/require"`,
`"google.golang.org/protobuf/proto"`,
`gootlpcommon "go.opentelemetry.io/proto/slim/otlp/common/v1"`,
`gootlpresource "go.opentelemetry.io/proto/slim/otlp/resource/v1"`,
``,
`"go.opentelemetry.io/collector/internal/testutil"`,
`"go.opentelemetry.io/collector/pdata/internal"`,
`"go.opentelemetry.io/collector/pdata/internal/json"`,
},
},
structs: []baseStruct{
anyValueStruct,
arrayValueStruct,
keyValueStruct,
keyValueListStruct,
anyValueSlice,
scope,
resource,
byteSlice,
float64Slice,
uInt64Slice,
int64Slice,
int32Slice,
stringSlice,
},
}
var scope = &messageStruct{
structName: "InstrumentationScope",
packageName: "pcommon",
description: "// InstrumentationScope is a message representing the instrumentation scope information.",
protoName: "InstrumentationScope",
upstreamProto: "gootlpcommon.InstrumentationScope",
fields: []Field{
&PrimitiveField{
fieldName: "Name",
protoID: 1,
protoType: proto.TypeString,
},
&PrimitiveField{
fieldName: "Version",
protoID: 2,
protoType: proto.TypeString,
},
&SliceField{
fieldName: "Attributes",
protoType: proto.TypeMessage,
protoID: 3,
returnSlice: mapStruct,
},
&PrimitiveField{
fieldName: "DroppedAttributesCount",
protoID: 4,
protoType: proto.TypeUint32,
},
},
}
// This will not be generated by this class.
// Defined here just to be available as returned message for the fields.
var mapStruct = &messageSlice{
structName: "Map",
packageName: "pcommon",
elementNullable: false,
element: keyValueStruct,
}
var keyValueStruct = &messageStruct{
structName: "KeyValue",
protoName: "KeyValue",
upstreamProto: "gootlpcommon.KeyValue",
fields: []Field{
&PrimitiveField{
fieldName: "Key",
protoID: 1,
protoType: proto.TypeString,
},
&MessageField{
fieldName: "Value",
protoID: 2,
returnMessage: anyValueClone,
},
&PrimitiveField{
fieldName: "KeyStrindex",
protoID: 3,
protoType: proto.TypeInt32,
},
},
hasOnlyInternal: true,
}
var anyValueClone = &messageStruct{
structName: "Value",
protoName: "AnyValue",
}
// anyValueStruct needs to be different from anyValue because otherwise we cause initialization circular deps with mapStruct.
var anyValueStruct = &messageStruct{
structName: "Value",
packageName: "pcommon",
protoName: "AnyValue",
upstreamProto: "gootlpcommon.AnyValue",
fields: []Field{
&OneOfField{
typeName: "ValueType",
originFieldName: "Value",
testValueIdx: 1, //
omitOriginFieldNameInNames: true,
values: []oneOfValue{
&OneOfPrimitiveValue{
fieldName: "StringValue",
protoID: 1,
originFieldName: "StringValue",
protoType: proto.TypeString,
},
&OneOfPrimitiveValue{
fieldName: "BoolValue",
protoID: 2,
originFieldName: "BoolValue",
protoType: proto.TypeBool,
},
&OneOfPrimitiveValue{
fieldName: "IntValue",
protoID: 3,
originFieldName: "IntValue",
protoType: proto.TypeInt64,
},
&OneOfPrimitiveValue{
fieldName: "DoubleValue",
protoID: 4,
originFieldName: "DoubleValue",
protoType: proto.TypeDouble,
},
&OneOfMessageValue{
fieldName: "ArrayValue",
protoID: 5,
returnMessage: arrayValueStruct,
},
&OneOfMessageValue{
fieldName: "KvlistValue",
protoID: 6,
returnMessage: keyValueListStruct,
},
&OneOfPrimitiveValue{
fieldName: "BytesValue",
protoID: 7,
originFieldName: "BytesValue",
protoType: proto.TypeBytes,
},
&OneOfPrimitiveValue{
fieldName: "StringValueStrindex",
protoID: 8,
originFieldName: "StringValueStrindex",
protoType: proto.TypeInt32,
},
},
},
},
hasOnlyInternal: true,
}
var keyValueListStruct = &messageStruct{
structName: "KeyValueList",
description: "// KeyValueList is a list of KeyValue messages. We need KeyValueList as a message since oneof in AnyValue does not allow repeated fields.",
protoName: "KeyValueList",
upstreamProto: "gootlpcommon.KeyValueList",
fields: []Field{
&SliceField{
fieldName: "Values",
protoID: 1,
protoType: proto.TypeMessage,
returnSlice: mapStruct,
},
},
hasOnlyInternal: true,
}
var arrayValueStruct = &messageStruct{
structName: "ArrayValue",
description: "// ArrayValue is a list of AnyValue messages. We need ArrayValue as a message since oneof in AnyValue does not allow repeated fields.",
protoName: "ArrayValue",
upstreamProto: "gootlpcommon.ArrayValue",
fields: []Field{
&SliceField{
fieldName: "Values",
protoID: 1,
protoType: proto.TypeMessage,
returnSlice: anyValueSlice,
},
},
hasOnlyInternal: true,
}
var anyValueSlice = &messageSlice{
structName: "Slice",
packageName: "pcommon",
elementNullable: false,
element: anyValueClone,
}
var traceState = &messageStruct{
structName: "TraceState",
packageName: "pcommon",
protoName: "TraceState", // Fake name to generate correct CopyOrig* name.
}
var timestampType = &TypedType{
structName: "Timestamp",
packageName: "pcommon",
protoType: proto.TypeFixed64,
defaultVal: "0",
testVal: "1234567890",
}
var traceIDType = &TypedType{
structName: "TraceID",
packageName: "pcommon",
protoType: proto.TypeMessage,
messageName: "TraceID",
defaultVal: "TraceID([16]byte{})",
testVal: "TraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1})",
}
var spanIDType = &TypedType{
structName: "SpanID",
packageName: "pcommon",
protoType: proto.TypeMessage,
messageName: "SpanID",
defaultVal: "SpanID([8]byte{})",
testVal: "SpanID([8]byte{8, 7, 6, 5, 4, 3, 2, 1})",
}
var resource = &messageStruct{
structName: "Resource",
packageName: "pcommon",
description: "// Resource is a message representing the resource information.",
protoName: "Resource",
upstreamProto: "gootlpresource.Resource",
fields: []Field{
&SliceField{
fieldName: "Attributes",
protoType: proto.TypeMessage,
protoID: 1,
returnSlice: mapStruct,
},
&PrimitiveField{
fieldName: "DroppedAttributesCount",
protoID: 2,
protoType: proto.TypeUint32,
},
&SliceField{
fieldName: "EntityRefs",
protoType: proto.TypeMessage,
protoID: 3,
returnSlice: entityRefSlice,
// Hide accessors for this field from 1.x public API since the proto field is experimental.
// It's available via the xpdata/entity.ResourceEntityRefs.
hideAccessors: true,
},
},
}
var byteSlice = &primitiveSliceStruct{
structName: "ByteSlice",
packageName: "pcommon",
itemType: "byte",
testOrigVal: "1, 2, 3",
testInterfaceOrigVal: []any{1, 2, 3},
testSetVal: "5",
testNewVal: "1, 5, 3",
}
var float64Slice = &primitiveSliceStruct{
structName: "Float64Slice",
packageName: "pcommon",
itemType: "float64",
testOrigVal: "1.1, 2.2, 3.3",
testInterfaceOrigVal: []any{1.1, 2.2, 3.3},
testSetVal: "5.5",
testNewVal: "1.1, 5.5, 3.3",
}
var uInt64Slice = &primitiveSliceStruct{
structName: "UInt64Slice",
packageName: "pcommon",
itemType: "uint64",
testOrigVal: "1, 2, 3",
testInterfaceOrigVal: []any{1, 2, 3},
testSetVal: "5",
testNewVal: "1, 5, 3",
}
var int64Slice = &primitiveSliceStruct{
structName: "Int64Slice",
packageName: "pcommon",
itemType: "int64",
testOrigVal: "1, 2, 3",
testInterfaceOrigVal: []any{1, 2, 3},
testSetVal: "5",
testNewVal: "1, 5, 3",
}
var int32Slice = &primitiveSliceStruct{
structName: "Int32Slice",
packageName: "pcommon",
itemType: "int32",
testOrigVal: "1, 2, 3",
testInterfaceOrigVal: []any{1, 2, 3},
testSetVal: "5",
testNewVal: "1, 5, 3",
}
var stringSlice = &primitiveSliceStruct{
structName: "StringSlice",
packageName: "pcommon",
itemType: "string",
testOrigVal: `"a", "b", "c"`,
testInterfaceOrigVal: []any{`"a"`, `"b"`, `"c"`},
testSetVal: `"d"`,
testNewVal: `"a", "d", "c"`,
}
================================================
FILE: internal/cmd/pdatagen/internal/pdata/plog_package.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pdata // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/pdata"
import (
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
)
var plog = &Package{
info: &PackageInfo{
name: "plog",
path: "plog",
imports: []string{
`"encoding/binary"`,
`"fmt"`,
`"iter"`,
`"math"`,
`"sort"`,
`"sync"`,
``,
`"go.opentelemetry.io/collector/pdata/internal"`,
`"go.opentelemetry.io/collector/pdata/internal/json"`,
`"go.opentelemetry.io/collector/pdata/internal/proto"`,
`"go.opentelemetry.io/collector/pdata/pcommon"`,
},
testImports: []string{
`"strconv"`,
`"testing"`,
`"unsafe"`,
``,
`"github.com/stretchr/testify/assert"`,
`"github.com/stretchr/testify/require"`,
`"google.golang.org/protobuf/proto"`,
`gootlpcollectorlogs "go.opentelemetry.io/proto/slim/otlp/collector/logs/v1"`,
`gootlplogs "go.opentelemetry.io/proto/slim/otlp/logs/v1"`,
``,
`"go.opentelemetry.io/collector/pdata/internal"`,
`"go.opentelemetry.io/collector/pdata/internal/json"`,
`"go.opentelemetry.io/collector/pdata/pcommon"`,
},
},
structs: []baseStruct{
logs,
logsData,
resourceLogsSlice,
resourceLogs,
scopeLogsSlice,
scopeLogs,
logSlice,
logRecord,
},
enums: []*proto.Enum{
severityNumberEnum,
},
}
var logs = &messageStruct{
structName: "Logs",
description: "// Logs is the top-level struct that is propagated through the logs pipeline.\n// Use NewLogs to create new instance, zero-initialized instance is not valid for use.",
protoName: "ExportLogsServiceRequest",
upstreamProto: "gootlpcollectorlogs.ExportLogsServiceRequest",
fields: []Field{
&SliceField{
fieldName: "ResourceLogs",
protoID: 1,
protoType: proto.TypeMessage,
returnSlice: resourceLogsSlice,
},
},
hasWrapper: true,
}
var logsData = &messageStruct{
structName: "LogsData",
description: "// LogsData represents the logs data that can be stored in a persistent storage,\n// OR can be embedded by other protocols that transfer OTLP logs data but do not\n// implement the OTLP protocol.",
protoName: "LogsData",
upstreamProto: "gootlplogs.LogsData",
fields: []Field{
&SliceField{
fieldName: "ResourceLogs",
protoID: 1,
protoType: proto.TypeMessage,
returnSlice: resourceLogsSlice,
},
},
hasOnlyInternal: true,
}
var resourceLogsSlice = &messageSlice{
structName: "ResourceLogsSlice",
elementNullable: true,
element: resourceLogs,
}
var resourceLogs = &messageStruct{
structName: "ResourceLogs",
description: "// ResourceLogs is a collection of logs from a Resource.",
protoName: "ResourceLogs",
upstreamProto: "gootlplogs.ResourceLogs",
fields: []Field{
&MessageField{
fieldName: "Resource",
protoID: 1,
returnMessage: resource,
},
&SliceField{
fieldName: "ScopeLogs",
protoID: 2,
protoType: proto.TypeMessage,
returnSlice: scopeLogsSlice,
},
&PrimitiveField{
fieldName: "SchemaUrl",
protoID: 3,
protoType: proto.TypeString,
},
&SliceField{
fieldName: "DeprecatedScopeLogs",
protoType: proto.TypeMessage,
protoID: 1000,
returnSlice: scopeLogsSlice,
// Hide accessors for this field because it is a HACK:
// Workaround for istio 1.15 / envoy 1.23.1 mistakenly emitting deprecated field.
hideAccessors: true,
},
},
}
var scopeLogsSlice = &messageSlice{
structName: "ScopeLogsSlice",
elementNullable: true,
element: scopeLogs,
}
var scopeLogs = &messageStruct{
structName: "ScopeLogs",
description: "// ScopeLogs is a collection of logs from a LibraryInstrumentation.",
protoName: "ScopeLogs",
upstreamProto: "gootlplogs.ScopeLogs",
fields: []Field{
&MessageField{
fieldName: "Scope",
protoID: 1,
returnMessage: scope,
},
&SliceField{
fieldName: "LogRecords",
protoID: 2,
protoType: proto.TypeMessage,
returnSlice: logSlice,
},
&PrimitiveField{
fieldName: "SchemaUrl",
protoID: 3,
protoType: proto.TypeString,
},
},
}
var logSlice = &messageSlice{
structName: "LogRecordSlice",
elementNullable: true,
element: logRecord,
}
var logRecord = &messageStruct{
structName: "LogRecord",
description: "// LogRecord are experimental implementation of OpenTelemetry Log Data Model.\n",
protoName: "LogRecord",
upstreamProto: "gootlplogs.LogRecord",
fields: []Field{
&TypedField{
fieldName: "Timestamp",
protoID: 1,
originFieldName: "TimeUnixNano",
returnType: timestampType,
},
&TypedField{
fieldName: "ObservedTimestamp",
protoID: 11,
originFieldName: "ObservedTimeUnixNano",
returnType: timestampType,
},
&TypedField{
fieldName: "SeverityNumber",
protoID: 2,
returnType: &TypedType{
structName: "SeverityNumber",
protoType: proto.TypeEnum,
messageName: "SeverityNumber",
defaultVal: `SeverityNumber_SEVERITY_NUMBER_UNSPECIFIED`,
testVal: `SeverityNumber_SEVERITY_NUMBER_DEBUG`,
},
},
&PrimitiveField{
fieldName: "SeverityText",
protoID: 3,
protoType: proto.TypeString,
},
&MessageField{
fieldName: "Body",
protoID: 5,
returnMessage: anyValueStruct,
},
&SliceField{
fieldName: "Attributes",
protoID: 6,
protoType: proto.TypeMessage,
returnSlice: mapStruct,
},
&PrimitiveField{
fieldName: "DroppedAttributesCount",
protoID: 7,
protoType: proto.TypeUint32,
},
&TypedField{
fieldName: "Flags",
protoID: 8,
returnType: &TypedType{
structName: "LogRecordFlags",
protoType: proto.TypeFixed32,
defaultVal: "0",
testVal: "1",
},
},
&TypedField{
fieldName: "TraceID",
originFieldName: "TraceId",
protoID: 9,
returnType: traceIDType,
},
&TypedField{
fieldName: "SpanID",
originFieldName: "SpanId",
protoID: 10,
returnType: spanIDType,
},
&PrimitiveField{
fieldName: "EventName",
protoID: 12,
protoType: proto.TypeString,
},
},
}
var severityNumberEnum = &proto.Enum{
Name: "SeverityNumber",
Description: "// SeverityNumber represent possible values for LogRecord.SeverityNumber",
Fields: []*proto.EnumField{
{Name: "SEVERITY_NUMBER_UNSPECIFIED", Value: 0},
{Name: "SEVERITY_NUMBER_TRACE ", Value: 1},
{Name: "SEVERITY_NUMBER_TRACE2", Value: 2},
{Name: "SEVERITY_NUMBER_TRACE3", Value: 3},
{Name: "SEVERITY_NUMBER_TRACE4", Value: 4},
{Name: "SEVERITY_NUMBER_DEBUG", Value: 5},
{Name: "SEVERITY_NUMBER_DEBUG2", Value: 6},
{Name: "SEVERITY_NUMBER_DEBUG3", Value: 7},
{Name: "SEVERITY_NUMBER_DEBUG4", Value: 8},
{Name: "SEVERITY_NUMBER_INFO", Value: 9},
{Name: "SEVERITY_NUMBER_INFO2", Value: 10},
{Name: "SEVERITY_NUMBER_INFO3", Value: 11},
{Name: "SEVERITY_NUMBER_INFO4", Value: 12},
{Name: "SEVERITY_NUMBER_WARN", Value: 13},
{Name: "SEVERITY_NUMBER_WARN2", Value: 14},
{Name: "SEVERITY_NUMBER_WARN3", Value: 15},
{Name: "SEVERITY_NUMBER_WARN4", Value: 16},
{Name: "SEVERITY_NUMBER_ERROR", Value: 17},
{Name: "SEVERITY_NUMBER_ERROR2", Value: 18},
{Name: "SEVERITY_NUMBER_ERROR3", Value: 19},
{Name: "SEVERITY_NUMBER_ERROR4", Value: 20},
{Name: "SEVERITY_NUMBER_FATAL", Value: 21},
{Name: "SEVERITY_NUMBER_FATAL2", Value: 22},
{Name: "SEVERITY_NUMBER_FATAL3", Value: 23},
{Name: "SEVERITY_NUMBER_FATAL4", Value: 24},
},
}
================================================
FILE: internal/cmd/pdatagen/internal/pdata/plogotlp_package.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pdata // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/pdata"
import (
"path/filepath"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
)
var plogotlp = &Package{
info: &PackageInfo{
name: "plogotlp",
path: filepath.Join("plog", "plogotlp"),
imports: []string{
`"encoding/binary"`,
`"fmt"`,
`"iter"`,
`"math"`,
`"sort"`,
`"sync"`,
``,
`"go.opentelemetry.io/collector/pdata/internal"`,
},
testImports: []string{
`"strconv"`,
`"testing"`,
``,
`"github.com/stretchr/testify/assert"`,
`"github.com/stretchr/testify/require"`,
`"google.golang.org/protobuf/proto"`,
`gootlpcollectorlogs "go.opentelemetry.io/proto/slim/otlp/collector/logs/v1"`,
``,
`"go.opentelemetry.io/collector/pdata/internal"`,
},
},
structs: []baseStruct{
exportLogsResponse,
exportLogsPartialSuccess,
},
}
var exportLogsResponse = &messageStruct{
structName: "ExportResponse",
description: "// ExportResponse represents the response for gRPC/HTTP client/server.",
protoName: "ExportLogsServiceResponse",
upstreamProto: "gootlpcollectorlogs.ExportLogsServiceResponse",
fields: []Field{
&MessageField{
fieldName: "PartialSuccess",
protoID: 1,
returnMessage: exportLogsPartialSuccess,
},
},
}
var exportLogsPartialSuccess = &messageStruct{
structName: "ExportPartialSuccess",
description: "// ExportPartialSuccess represents the details of a partially successful export request.",
protoName: "ExportLogsPartialSuccess",
upstreamProto: "gootlpcollectorlogs.ExportLogsPartialSuccess",
fields: []Field{
&PrimitiveField{
fieldName: "RejectedLogRecords",
protoID: 1,
protoType: proto.TypeInt64,
},
&PrimitiveField{
fieldName: "ErrorMessage",
protoID: 2,
protoType: proto.TypeString,
},
},
}
================================================
FILE: internal/cmd/pdatagen/internal/pdata/pmetric_package.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pdata // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/pdata"
import (
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
)
var pmetric = &Package{
info: &PackageInfo{
name: "pmetric",
path: "pmetric",
imports: []string{
`"encoding/binary"`,
`"fmt"`,
`"iter"`,
`"math"`,
`"sort"`,
`"sync"`,
``,
`"go.opentelemetry.io/collector/pdata/internal"`,
`"go.opentelemetry.io/collector/pdata/internal/json"`,
`"go.opentelemetry.io/collector/pdata/internal/proto"`,
`"go.opentelemetry.io/collector/pdata/pcommon"`,
},
testImports: []string{
`"strconv"`,
`"testing"`,
`"unsafe"`,
``,
`"github.com/stretchr/testify/assert"`,
`"github.com/stretchr/testify/require"`,
`"google.golang.org/protobuf/proto"`,
`gootlpcollectormetrics "go.opentelemetry.io/proto/slim/otlp/collector/metrics/v1"`,
`gootlpmetrics "go.opentelemetry.io/proto/slim/otlp/metrics/v1"`,
``,
`"go.opentelemetry.io/collector/pdata/internal"`,
`"go.opentelemetry.io/collector/pdata/internal/json"`,
`"go.opentelemetry.io/collector/pdata/pcommon"`,
},
},
structs: []baseStruct{
metrics,
metricsData,
resourceMetricsSlice,
resourceMetrics,
scopeMetricsSlice,
scopeMetrics,
metricSlice,
metric,
gauge,
sum,
histogram,
exponentialHistogram,
summary,
numberDataPointSlice,
numberDataPoint,
histogramDataPointSlice,
histogramDataPoint,
exponentialHistogramDataPointSlice,
exponentialHistogramDataPoint,
bucketsValues,
summaryDataPointSlice,
summaryDataPoint,
quantileValuesSlice,
quantileValues,
exemplarSlice,
exemplar,
},
enums: []*proto.Enum{
aggregationTemporalityEnum,
},
}
var metrics = &messageStruct{
structName: "Metrics",
description: "// Metrics is the top-level struct that is propagated through the metrics pipeline.\n// Use NewMetrics to create new instance, zero-initialized instance is not valid for use.",
protoName: "ExportMetricsServiceRequest",
upstreamProto: "gootlpcollectormetrics.ExportMetricsServiceRequest",
fields: []Field{
&SliceField{
fieldName: "ResourceMetrics",
protoID: 1,
protoType: proto.TypeMessage,
returnSlice: resourceMetricsSlice,
},
},
hasWrapper: true,
}
var metricsData = &messageStruct{
structName: "MetricsData",
description: "// MetricsData represents the metrics data that can be stored in a persistent storage,\n// OR can be embedded by other protocols that transfer OTLP metrics data but do not\n// implement the OTLP protocol..",
protoName: "MetricsData",
upstreamProto: "gootlpmetrics.MetricsData",
fields: []Field{
&SliceField{
fieldName: "ResourceMetrics",
protoID: 1,
protoType: proto.TypeMessage,
returnSlice: resourceMetricsSlice,
},
},
hasOnlyInternal: true,
}
var resourceMetricsSlice = &messageSlice{
structName: "ResourceMetricsSlice",
elementNullable: true,
element: resourceMetrics,
}
var resourceMetrics = &messageStruct{
structName: "ResourceMetrics",
description: "// ResourceMetrics is a collection of metrics from a Resource.",
protoName: "ResourceMetrics",
upstreamProto: "gootlpmetrics.ResourceMetrics",
fields: []Field{
&MessageField{
fieldName: "Resource",
protoID: 1,
returnMessage: resource,
},
&SliceField{
fieldName: "ScopeMetrics",
protoID: 2,
protoType: proto.TypeMessage,
returnSlice: scopeMetricsSlice,
},
&PrimitiveField{
fieldName: "SchemaUrl",
protoID: 3,
protoType: proto.TypeString,
},
&SliceField{
fieldName: "DeprecatedScopeMetrics",
protoType: proto.TypeMessage,
protoID: 1000,
returnSlice: scopeMetricsSlice,
// Hide accessors for this field because it is a HACK:
// Workaround for istio 1.15 / envoy 1.23.1 mistakenly emitting deprecated field.
hideAccessors: true,
},
},
}
var scopeMetricsSlice = &messageSlice{
structName: "ScopeMetricsSlice",
elementNullable: true,
element: scopeMetrics,
}
var scopeMetrics = &messageStruct{
structName: "ScopeMetrics",
description: "// ScopeMetrics is a collection of metrics from a LibraryInstrumentation.",
protoName: "ScopeMetrics",
upstreamProto: "gootlpmetrics.ScopeMetrics",
fields: []Field{
&MessageField{
fieldName: "Scope",
protoID: 1,
returnMessage: scope,
},
&SliceField{
fieldName: "Metrics",
protoID: 2,
protoType: proto.TypeMessage,
returnSlice: metricSlice,
},
&PrimitiveField{
fieldName: "SchemaUrl",
protoID: 3,
protoType: proto.TypeString,
},
},
}
var metricSlice = &messageSlice{
structName: "MetricSlice",
elementNullable: true,
element: metric,
}
var metric = &messageStruct{
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/main/opentelemetry/proto/metrics/v1/metrics.proto",
protoName: "Metric",
upstreamProto: "gootlpmetrics.Metric",
fields: []Field{
&PrimitiveField{
fieldName: "Name",
protoID: 1,
protoType: proto.TypeString,
},
&PrimitiveField{
fieldName: "Description",
protoID: 2,
protoType: proto.TypeString,
},
&PrimitiveField{
fieldName: "Unit",
protoID: 3,
protoType: proto.TypeString,
},
&OneOfField{
typeName: "MetricType",
originFieldName: "Data",
testValueIdx: 1, // Sum
omitOriginFieldNameInNames: true,
values: []oneOfValue{
&OneOfMessageValue{
fieldName: "Gauge",
protoID: 5,
returnMessage: gauge,
},
&OneOfMessageValue{
fieldName: "Sum",
protoID: 7,
returnMessage: sum,
},
&OneOfMessageValue{
fieldName: "Histogram",
protoID: 9,
returnMessage: histogram,
},
&OneOfMessageValue{
fieldName: "ExponentialHistogram",
protoID: 10,
returnMessage: exponentialHistogram,
},
&OneOfMessageValue{
fieldName: "Summary",
protoID: 11,
returnMessage: summary,
},
},
},
&SliceField{
fieldName: "Metadata",
protoID: 12,
protoType: proto.TypeMessage,
returnSlice: mapStruct,
},
},
}
var gauge = &messageStruct{
structName: "Gauge",
description: "// Gauge represents the type of a numeric metric that always exports the \"current value\" for every data point.",
protoName: "Gauge",
upstreamProto: "gootlpmetrics.Gauge",
fields: []Field{
&SliceField{
fieldName: "DataPoints",
protoID: 1,
protoType: proto.TypeMessage,
returnSlice: numberDataPointSlice,
},
},
}
var sum = &messageStruct{
structName: "Sum",
description: "// Sum represents the type of a numeric metric that is calculated as a sum of all reported measurements over a time interval.",
protoName: "Sum",
upstreamProto: "gootlpmetrics.Sum",
fields: []Field{
&SliceField{
fieldName: "DataPoints",
protoID: 1,
protoType: proto.TypeMessage,
returnSlice: numberDataPointSlice,
},
&TypedField{
fieldName: "AggregationTemporality",
protoID: 2,
returnType: aggregationTemporalityType,
},
&PrimitiveField{
fieldName: "IsMonotonic",
protoID: 3,
protoType: proto.TypeBool,
},
},
}
var histogram = &messageStruct{
structName: "Histogram",
description: "// Histogram represents the type of a metric that is calculated by aggregating as a Histogram of all reported measurements over a time interval.",
protoName: "Histogram",
upstreamProto: "gootlpmetrics.Histogram",
fields: []Field{
&SliceField{
fieldName: "DataPoints",
protoID: 1,
protoType: proto.TypeMessage,
returnSlice: histogramDataPointSlice,
},
&TypedField{
fieldName: "AggregationTemporality",
protoID: 2,
returnType: aggregationTemporalityType,
},
},
}
var exponentialHistogram = &messageStruct{
structName: "ExponentialHistogram",
description: `// ExponentialHistogram represents the type of a metric that is calculated by aggregating
// as a ExponentialHistogram of all reported double measurements over a time interval.`,
protoName: "ExponentialHistogram",
upstreamProto: "gootlpmetrics.ExponentialHistogram",
fields: []Field{
&SliceField{
fieldName: "DataPoints",
protoID: 1,
protoType: proto.TypeMessage,
returnSlice: exponentialHistogramDataPointSlice,
},
&TypedField{
fieldName: "AggregationTemporality",
protoID: 2,
returnType: aggregationTemporalityType,
},
},
}
var summary = &messageStruct{
structName: "Summary",
description: "// Summary represents the type of a metric that is calculated by aggregating as a Summary of all reported double measurements over a time interval.",
protoName: "Summary",
upstreamProto: "gootlpmetrics.Summary",
fields: []Field{
&SliceField{
fieldName: "DataPoints",
protoID: 1,
protoType: proto.TypeMessage,
returnSlice: summaryDataPointSlice,
},
},
}
var numberDataPointSlice = &messageSlice{
structName: "NumberDataPointSlice",
elementNullable: true,
element: numberDataPoint,
}
var numberDataPoint = &messageStruct{
structName: "NumberDataPoint",
description: "// NumberDataPoint is a single data point in a timeseries that describes the time-varying value of a number metric.",
protoName: "NumberDataPoint",
upstreamProto: "gootlpmetrics.NumberDataPoint",
fields: []Field{
&SliceField{
fieldName: "Attributes",
protoID: 7,
protoType: proto.TypeMessage,
returnSlice: mapStruct,
},
&TypedField{
fieldName: "StartTimestamp",
originFieldName: "StartTimeUnixNano",
protoID: 2,
returnType: timestampType,
},
&TypedField{
fieldName: "Timestamp",
originFieldName: "TimeUnixNano",
protoID: 3,
returnType: timestampType,
},
&OneOfField{
typeName: "NumberDataPointValueType",
originFieldName: "Value",
testValueIdx: 0, // Double
values: []oneOfValue{
&OneOfPrimitiveValue{
fieldName: "Double",
protoID: 4,
originFieldName: "AsDouble",
protoType: proto.TypeDouble,
},
&OneOfPrimitiveValue{
fieldName: "Int",
protoID: 6,
originFieldName: "AsInt",
protoType: proto.TypeSFixed64,
},
},
},
&SliceField{
fieldName: "Exemplars",
protoID: 5,
protoType: proto.TypeMessage,
returnSlice: exemplarSlice,
},
&TypedField{
fieldName: "Flags",
protoID: 8,
returnType: &TypedType{
structName: "DataPointFlags",
protoType: proto.TypeUint32,
defaultVal: "0",
testVal: "1",
},
},
},
}
var histogramDataPointSlice = &messageSlice{
structName: "HistogramDataPointSlice",
elementNullable: true,
element: histogramDataPoint,
}
var histogramDataPoint = &messageStruct{
structName: "HistogramDataPoint",
description: "// HistogramDataPoint is a single data point in a timeseries that describes the time-varying values of a Histogram of values.",
protoName: "HistogramDataPoint",
upstreamProto: "gootlpmetrics.HistogramDataPoint",
fields: []Field{
&SliceField{
fieldName: "Attributes",
protoID: 9,
protoType: proto.TypeMessage,
returnSlice: mapStruct,
},
&TypedField{
fieldName: "StartTimestamp",
originFieldName: "StartTimeUnixNano",
protoID: 2,
returnType: timestampType,
},
&TypedField{
fieldName: "Timestamp",
originFieldName: "TimeUnixNano",
protoID: 3,
returnType: timestampType,
},
&PrimitiveField{
fieldName: "Count",
protoID: 4,
protoType: proto.TypeFixed64,
},
&OptionalPrimitiveField{
fieldName: "Sum",
protoID: 5,
protoType: proto.TypeDouble,
},
&SliceField{
fieldName: "BucketCounts",
protoID: 6,
protoType: proto.TypeFixed64,
returnSlice: uInt64Slice,
},
&SliceField{
fieldName: "ExplicitBounds",
protoID: 7,
protoType: proto.TypeDouble,
returnSlice: float64Slice,
},
&SliceField{
fieldName: "Exemplars",
protoID: 8,
protoType: proto.TypeMessage,
returnSlice: exemplarSlice,
},
&TypedField{
fieldName: "Flags",
protoID: 10,
returnType: &TypedType{
structName: "DataPointFlags",
protoType: proto.TypeUint32,
defaultVal: "0",
testVal: "1",
},
},
&OptionalPrimitiveField{
fieldName: "Min",
protoID: 11,
protoType: proto.TypeDouble,
},
&OptionalPrimitiveField{
fieldName: "Max",
protoID: 12,
protoType: proto.TypeDouble,
},
},
}
var exponentialHistogramDataPointSlice = &messageSlice{
structName: "ExponentialHistogramDataPointSlice",
elementNullable: true,
element: exponentialHistogramDataPoint,
}
var exponentialHistogramDataPoint = &messageStruct{
structName: "ExponentialHistogramDataPoint",
description: `// ExponentialHistogramDataPoint is a single data point in a timeseries that describes the
// time-varying values of a ExponentialHistogram of double values. A ExponentialHistogram contains
// summary statistics for a population of values, it may optionally contain the
// distribution of those values across a set of buckets.`,
protoName: "ExponentialHistogramDataPoint",
upstreamProto: "gootlpmetrics.ExponentialHistogramDataPoint",
fields: []Field{
&SliceField{
fieldName: "Attributes",
protoID: 1,
protoType: proto.TypeMessage,
returnSlice: mapStruct,
},
&TypedField{
fieldName: "StartTimestamp",
protoID: 2,
originFieldName: "StartTimeUnixNano",
returnType: timestampType,
},
&TypedField{
fieldName: "Timestamp",
protoID: 3,
originFieldName: "TimeUnixNano",
returnType: timestampType,
},
&PrimitiveField{
fieldName: "Count",
protoID: 4,
protoType: proto.TypeFixed64,
},
&OptionalPrimitiveField{
fieldName: "Sum",
protoID: 5,
protoType: proto.TypeDouble,
},
&PrimitiveField{
fieldName: "Scale",
protoID: 6,
protoType: proto.TypeSInt32,
},
&PrimitiveField{
fieldName: "ZeroCount",
protoID: 7,
protoType: proto.TypeFixed64,
},
&MessageField{
fieldName: "Positive",
protoID: 8,
returnMessage: bucketsValues,
},
&MessageField{
fieldName: "Negative",
protoID: 9,
returnMessage: bucketsValues,
},
&TypedField{
fieldName: "Flags",
protoID: 10,
returnType: &TypedType{
structName: "DataPointFlags",
protoType: proto.TypeUint32,
defaultVal: "0",
testVal: "1",
},
},
&SliceField{
fieldName: "Exemplars",
protoID: 11,
protoType: proto.TypeMessage,
returnSlice: exemplarSlice,
},
&OptionalPrimitiveField{
fieldName: "Min",
protoID: 12,
protoType: proto.TypeDouble,
},
&OptionalPrimitiveField{
fieldName: "Max",
protoID: 13,
protoType: proto.TypeDouble,
},
&PrimitiveField{
fieldName: "ZeroThreshold",
protoID: 14,
protoType: proto.TypeDouble,
},
},
}
var bucketsValues = &messageStruct{
structName: "ExponentialHistogramDataPointBuckets",
description: "// ExponentialHistogramDataPointBuckets are a set of bucket counts, encoded in a contiguous array of counts.",
protoName: "ExponentialHistogramDataPointBuckets",
upstreamProto: "gootlpmetrics.ExponentialHistogramDataPoint_Buckets",
fields: []Field{
&PrimitiveField{
fieldName: "Offset",
protoID: 1,
protoType: proto.TypeSInt32,
},
&SliceField{
fieldName: "BucketCounts",
protoID: 2,
protoType: proto.TypeUint64,
returnSlice: uInt64Slice,
},
},
}
var summaryDataPointSlice = &messageSlice{
structName: "SummaryDataPointSlice",
elementNullable: true,
element: summaryDataPoint,
}
var summaryDataPoint = &messageStruct{
structName: "SummaryDataPoint",
description: "// SummaryDataPoint is a single data point in a timeseries that describes the time-varying values of a Summary of double values.",
protoName: "SummaryDataPoint",
upstreamProto: "gootlpmetrics.SummaryDataPoint",
fields: []Field{
&SliceField{
fieldName: "Attributes",
protoID: 7,
protoType: proto.TypeMessage,
returnSlice: mapStruct,
},
&TypedField{
fieldName: "StartTimestamp",
originFieldName: "StartTimeUnixNano",
protoID: 2,
returnType: timestampType,
},
&TypedField{
fieldName: "Timestamp",
originFieldName: "TimeUnixNano",
protoID: 3,
returnType: timestampType,
},
&PrimitiveField{
fieldName: "Count",
protoID: 4,
protoType: proto.TypeFixed64,
},
&PrimitiveField{
fieldName: "Sum",
protoID: 5,
protoType: proto.TypeDouble,
},
&SliceField{
fieldName: "QuantileValues",
protoID: 6,
protoType: proto.TypeMessage,
returnSlice: quantileValuesSlice,
},
&TypedField{
fieldName: "Flags",
protoID: 8,
returnType: &TypedType{
structName: "DataPointFlags",
protoType: proto.TypeUint32,
defaultVal: "0",
testVal: "1",
},
},
},
}
var quantileValuesSlice = &messageSlice{
structName: "SummaryDataPointValueAtQuantileSlice",
elementNullable: true,
element: quantileValues,
}
var quantileValues = &messageStruct{
structName: "SummaryDataPointValueAtQuantile",
description: "// SummaryDataPointValueAtQuantile is a quantile value within a Summary data point.",
protoName: "SummaryDataPointValueAtQuantile",
upstreamProto: "gootlpmetrics.SummaryDataPoint_ValueAtQuantile",
fields: []Field{
&PrimitiveField{
fieldName: "Quantile",
protoID: 1,
protoType: proto.TypeDouble,
},
&PrimitiveField{
fieldName: "Value",
protoID: 2,
protoType: proto.TypeDouble,
},
},
}
var exemplarSlice = &messageSlice{
structName: "ExemplarSlice",
elementNullable: false,
element: exemplar,
}
var exemplar = &messageStruct{
structName: "Exemplar",
description: "// Exemplar 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.",
protoName: "Exemplar",
upstreamProto: "gootlpmetrics.Exemplar",
fields: []Field{
&SliceField{
fieldName: "FilteredAttributes",
protoID: 7,
protoType: proto.TypeMessage,
returnSlice: mapStruct,
},
&TypedField{
fieldName: "Timestamp",
originFieldName: "TimeUnixNano",
protoID: 2,
returnType: timestampType,
},
&OneOfField{
typeName: "ExemplarValueType",
originFieldName: "Value",
testValueIdx: 1, // Int
values: []oneOfValue{
&OneOfPrimitiveValue{
fieldName: "Double",
originFieldName: "AsDouble",
protoID: 3,
protoType: proto.TypeDouble,
},
&OneOfPrimitiveValue{
fieldName: "Int",
originFieldName: "AsInt",
protoID: 6,
protoType: proto.TypeSFixed64,
},
},
},
&TypedField{
fieldName: "TraceID",
originFieldName: "TraceId",
protoID: 5,
returnType: traceIDType,
},
&TypedField{
fieldName: "SpanID",
originFieldName: "SpanId",
protoID: 4,
returnType: spanIDType,
},
},
}
var aggregationTemporalityType = &TypedType{
structName: "AggregationTemporality",
protoType: proto.TypeEnum,
messageName: "AggregationTemporality",
defaultVal: "AggregationTemporality(0)",
testVal: "AggregationTemporality(1)",
}
var aggregationTemporalityEnum = &proto.Enum{
Name: "AggregationTemporality",
Description: "// AggregationTemporality defines how a metric aggregator reports aggregated values.\n// It describes how those values relate to the time interval over which they are aggregated.",
Fields: []*proto.EnumField{
{Name: "AGGREGATION_TEMPORALITY_UNSPECIFIED", Value: 0},
{Name: "AGGREGATION_TEMPORALITY_DELTA", Value: 1},
{Name: "AGGREGATION_TEMPORALITY_CUMULATIVE", Value: 2},
},
}
================================================
FILE: internal/cmd/pdatagen/internal/pdata/pmetricotlp_package.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pdata // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/pdata"
import (
"path/filepath"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
)
var pmetricotlp = &Package{
info: &PackageInfo{
name: "pmetricotlp",
path: filepath.Join("pmetric", "pmetricotlp"),
imports: []string{
`"encoding/binary"`,
`"fmt"`,
`"iter"`,
`"math"`,
`"sort"`,
`"sync"`,
``,
`"go.opentelemetry.io/collector/pdata/internal"`,
},
testImports: []string{
`"strconv"`,
`"testing"`,
``,
`"github.com/stretchr/testify/assert"`,
`"github.com/stretchr/testify/require"`,
`"google.golang.org/protobuf/proto"`,
`gootlpcollectormetrics "go.opentelemetry.io/proto/slim/otlp/collector/metrics/v1"`,
``,
`"go.opentelemetry.io/collector/pdata/internal"`,
},
},
structs: []baseStruct{
exportMetricsResponse,
exportMetricsPartialSuccess,
},
}
var exportMetricsResponse = &messageStruct{
structName: "ExportResponse",
description: "// ExportResponse represents the response for gRPC/HTTP client/server.",
protoName: "ExportMetricsServiceResponse",
upstreamProto: "gootlpcollectormetrics.ExportMetricsServiceResponse",
fields: []Field{
&MessageField{
fieldName: "PartialSuccess",
protoID: 1,
returnMessage: exportMetricsPartialSuccess,
},
},
}
var exportMetricsPartialSuccess = &messageStruct{
structName: "ExportPartialSuccess",
description: "// ExportPartialSuccess represents the details of a partially successful export request.",
protoName: "ExportMetricsPartialSuccess",
upstreamProto: "gootlpcollectormetrics.ExportMetricsPartialSuccess",
fields: []Field{
&PrimitiveField{
fieldName: "RejectedDataPoints",
protoID: 1,
protoType: proto.TypeInt64,
},
&PrimitiveField{
fieldName: "ErrorMessage",
protoID: 2,
protoType: proto.TypeString,
},
},
}
================================================
FILE: internal/cmd/pdatagen/internal/pdata/pprofile_package.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pdata // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/pdata"
import (
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
)
var pprofile = &Package{
info: &PackageInfo{
name: "pprofile",
path: "pprofile",
imports: []string{
`"encoding/binary"`,
`"fmt"`,
`"iter"`,
`"math"`,
`"sort"`,
`"sync"`,
``,
`"go.opentelemetry.io/collector/pdata/internal"`,
`"go.opentelemetry.io/collector/pdata/internal/json"`,
`"go.opentelemetry.io/collector/pdata/internal/proto"`,
`"go.opentelemetry.io/collector/pdata/pcommon"`,
},
testImports: []string{
`"strconv"`,
`"testing"`,
`"unsafe"`,
``,
`"github.com/stretchr/testify/assert"`,
`"github.com/stretchr/testify/require"`,
`"google.golang.org/protobuf/proto"`,
`gootlpcollectorprofiles "go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development"`,
`gootlpcommon "go.opentelemetry.io/proto/slim/otlp/common/v1"`,
`gootlpprofiles "go.opentelemetry.io/proto/slim/otlp/profiles/v1development"`,
``,
`"go.opentelemetry.io/collector/pdata/internal"`,
`"go.opentelemetry.io/collector/pdata/internal/json"`,
`"go.opentelemetry.io/collector/pdata/pcommon"`,
},
},
structs: []baseStruct{
profiles,
profilesData,
resourceProfilesSlice,
resourceProfiles,
profilesDictionary,
scopeProfilesSlice,
scopeProfiles,
profilesSlice,
profile,
valueTypeSlice,
valueType,
sampleSlice,
sample,
mappingSlice,
mapping,
locationSlice,
location,
lineSlice,
line,
functionSlice,
function,
keyValueAndUnitSlice,
keyValueAndUnit,
linkSlice,
link,
stackSlice,
stack,
},
}
var profiles = &messageStruct{
structName: "Profiles",
description: "// Profiles is the top-level struct that is propagated through the profiles pipeline.\n// Use NewProfiles to create new instance, zero-initialized instance is not valid for use.",
protoName: "ExportProfilesServiceRequest",
upstreamProto: "gootlpcollectorprofiles.ExportProfilesServiceRequest",
fields: []Field{
&SliceField{
fieldName: "ResourceProfiles",
protoID: 1,
protoType: proto.TypeMessage,
returnSlice: resourceProfilesSlice,
},
&MessageField{
fieldName: "Dictionary",
protoID: 2,
returnMessage: profilesDictionary,
},
},
hasWrapper: true,
}
var profilesData = &messageStruct{
structName: "ProfilesData",
description: "// ProfilesData represents the profiles data that can be stored in persistent storage,\n// OR can be embedded by other protocols that transfer OTLP profiles data but do not\n// implement the OTLP protocol.",
protoName: "ProfilesData",
upstreamProto: "gootlpprofiles.ProfilesData",
fields: []Field{
&SliceField{
fieldName: "ResourceProfiles",
protoID: 1,
protoType: proto.TypeMessage,
returnSlice: resourceProfilesSlice,
},
&MessageField{
fieldName: "Dictionary",
protoID: 2,
returnMessage: profilesDictionary,
},
},
hasWrapper: true,
}
var resourceProfilesSlice = &messageSlice{
structName: "ResourceProfilesSlice",
elementNullable: true,
element: resourceProfiles,
}
var resourceProfiles = &messageStruct{
structName: "ResourceProfiles",
description: "// ResourceProfiles is a collection of profiles from a Resource.",
protoName: "ResourceProfiles",
upstreamProto: "gootlpprofiles.ResourceProfiles",
fields: []Field{
&MessageField{
fieldName: "Resource",
protoID: 1,
returnMessage: resource,
},
&SliceField{
fieldName: "ScopeProfiles",
protoID: 2,
protoType: proto.TypeMessage,
returnSlice: scopeProfilesSlice,
},
&PrimitiveField{
fieldName: "SchemaUrl",
protoID: 3,
protoType: proto.TypeString,
},
},
}
var profilesDictionary = &messageStruct{
structName: "ProfilesDictionary",
description: "// ProfilesDictionary is the reference table containing all data shared by profiles across the message being sent.",
protoName: "ProfilesDictionary",
upstreamProto: "gootlpprofiles.ProfilesDictionary",
fields: []Field{
&SliceField{
fieldName: "MappingTable",
protoID: 1,
protoType: proto.TypeMessage,
returnSlice: mappingSlice,
},
&SliceField{
fieldName: "LocationTable",
protoID: 2,
protoType: proto.TypeMessage,
returnSlice: locationSlice,
},
&SliceField{
fieldName: "FunctionTable",
protoID: 3,
protoType: proto.TypeMessage,
returnSlice: functionSlice,
},
&SliceField{
fieldName: "LinkTable",
protoID: 4,
protoType: proto.TypeMessage,
returnSlice: linkSlice,
},
&SliceField{
fieldName: "StringTable",
protoID: 5,
protoType: proto.TypeString,
returnSlice: stringSlice,
},
&SliceField{
fieldName: "AttributeTable",
protoID: 6,
protoType: proto.TypeMessage,
returnSlice: keyValueAndUnitSlice,
},
&SliceField{
fieldName: "StackTable",
protoID: 7,
protoType: proto.TypeMessage,
returnSlice: stackSlice,
},
},
}
var scopeProfilesSlice = &messageSlice{
structName: "ScopeProfilesSlice",
elementNullable: true,
element: scopeProfiles,
}
var scopeProfiles = &messageStruct{
structName: "ScopeProfiles",
description: "// ScopeProfiles is a collection of profiles from a LibraryInstrumentation.",
protoName: "ScopeProfiles",
upstreamProto: "gootlpprofiles.ScopeProfiles",
fields: []Field{
&MessageField{
fieldName: "Scope",
protoID: 1,
returnMessage: scope,
},
&SliceField{
fieldName: "Profiles",
protoID: 2,
protoType: proto.TypeMessage,
returnSlice: profilesSlice,
},
&PrimitiveField{
fieldName: "SchemaUrl",
protoID: 3,
protoType: proto.TypeString,
},
},
}
var profilesSlice = &messageSlice{
structName: "ProfilesSlice",
elementNullable: true,
element: profile,
}
var profile = &messageStruct{
structName: "Profile",
description: "// Profile are an implementation of the pprofextended data model.\n",
protoName: "Profile",
upstreamProto: "gootlpprofiles.Profile",
fields: []Field{
&MessageField{
fieldName: "SampleType",
protoID: 1,
returnMessage: valueType,
},
&SliceField{
fieldName: "Samples",
protoID: 2,
protoType: proto.TypeMessage,
returnSlice: sampleSlice,
},
&TypedField{
fieldName: "Time",
originFieldName: "TimeUnixNano",
protoID: 3,
returnType: timestampType,
},
&PrimitiveField{
fieldName: "DurationNano",
protoID: 4,
protoType: proto.TypeUint64,
},
&MessageField{
fieldName: "PeriodType",
protoID: 5,
returnMessage: valueType,
},
&PrimitiveField{
fieldName: "Period",
protoID: 6,
protoType: proto.TypeInt64,
},
&TypedField{
fieldName: "ProfileID",
originFieldName: "ProfileId",
protoID: 7,
returnType: &TypedType{
structName: "ProfileID",
protoType: proto.TypeMessage,
messageName: "ProfileID",
defaultVal: "ProfileID([16]byte{})",
testVal: "ProfileID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1})",
},
},
&PrimitiveField{
fieldName: "DroppedAttributesCount",
protoID: 8,
protoType: proto.TypeUint32,
},
&PrimitiveField{
fieldName: "OriginalPayloadFormat",
protoID: 9,
protoType: proto.TypeString,
},
&SliceField{
fieldName: "OriginalPayload",
protoID: 10,
protoType: proto.TypeBytes,
returnSlice: byteSlice,
},
&SliceField{
fieldName: "AttributeIndices",
protoID: 11,
protoType: proto.TypeInt32,
returnSlice: int32Slice,
},
},
}
var keyValueAndUnitSlice = &messageSlice{
structName: "KeyValueAndUnitSlice",
elementNullable: true,
element: keyValueAndUnit,
}
var keyValueAndUnit = &messageStruct{
structName: "KeyValueAndUnit",
description: `// KeyValueAndUnit represents a custom 'dictionary native'
// style of encoding attributes which is more convenient
// for profiles than opentelemetry.proto.common.v1.KeyValue.`,
protoName: "KeyValueAndUnit",
upstreamProto: "gootlpprofiles.KeyValueAndUnit",
fields: []Field{
&PrimitiveField{
fieldName: "KeyStrindex",
protoID: 1,
protoType: proto.TypeInt32,
},
&MessageField{
fieldName: "Value",
protoID: 2,
returnMessage: anyValueStruct,
},
&PrimitiveField{
fieldName: "UnitStrindex",
protoID: 3,
protoType: proto.TypeInt32,
},
},
}
var linkSlice = &messageSlice{
structName: "LinkSlice",
elementNullable: true,
element: link,
}
var link = &messageStruct{
structName: "Link",
description: "// Link represents a pointer from a profile Sample to a trace Span.",
protoName: "Link",
upstreamProto: "gootlpprofiles.Link",
fields: []Field{
&TypedField{
fieldName: "TraceID",
originFieldName: "TraceId",
protoID: 1,
returnType: traceIDType,
},
&TypedField{
fieldName: "SpanID",
originFieldName: "SpanId",
protoID: 2,
returnType: spanIDType,
},
},
}
var valueTypeSlice = &messageSlice{
structName: "ValueTypeSlice",
elementNullable: true,
element: valueType,
}
var valueType = &messageStruct{
structName: "ValueType",
description: "// ValueType describes the type and units of a value.",
protoName: "ValueType",
upstreamProto: "gootlpprofiles.ValueType",
fields: []Field{
&PrimitiveField{
fieldName: "TypeStrindex",
protoID: 1,
protoType: proto.TypeInt32,
},
&PrimitiveField{
fieldName: "UnitStrindex",
protoID: 2,
protoType: proto.TypeInt32,
},
},
}
var sampleSlice = &messageSlice{
structName: "SampleSlice",
elementNullable: true,
element: sample,
}
var sample = &messageStruct{
structName: "Sample",
description: "// Sample represents each record value encountered within a profiled program.",
protoName: "Sample",
upstreamProto: "gootlpprofiles.Sample",
fields: []Field{
&PrimitiveField{
fieldName: "StackIndex",
protoID: 1,
protoType: proto.TypeInt32,
},
&SliceField{
fieldName: "AttributeIndices",
protoID: 2,
protoType: proto.TypeInt32,
returnSlice: int32Slice,
},
&PrimitiveField{
fieldName: "LinkIndex",
protoID: 3,
protoType: proto.TypeInt32,
},
&SliceField{
fieldName: "Values",
protoID: 4,
protoType: proto.TypeInt64,
returnSlice: int64Slice,
},
&SliceField{
fieldName: "TimestampsUnixNano",
protoID: 5,
protoType: proto.TypeFixed64,
returnSlice: uInt64Slice,
},
},
}
var mappingSlice = &messageSlice{
structName: "MappingSlice",
elementNullable: true,
element: mapping,
}
var mapping = &messageStruct{
structName: "Mapping",
description: "// Mapping describes the mapping of a binary in memory, including its address range, file offset, and metadata like build ID",
protoName: "Mapping",
upstreamProto: "gootlpprofiles.Mapping",
fields: []Field{
&PrimitiveField{
fieldName: "MemoryStart",
protoID: 1,
protoType: proto.TypeUint64,
},
&PrimitiveField{
fieldName: "MemoryLimit",
protoID: 2,
protoType: proto.TypeUint64,
},
&PrimitiveField{
fieldName: "FileOffset",
protoID: 3,
protoType: proto.TypeUint64,
},
&PrimitiveField{
fieldName: "FilenameStrindex",
protoID: 4,
protoType: proto.TypeInt32,
},
&SliceField{
fieldName: "AttributeIndices",
protoID: 5,
protoType: proto.TypeInt32,
returnSlice: int32Slice,
},
},
}
var locationSlice = &messageSlice{
structName: "LocationSlice",
elementNullable: true,
element: location,
}
var location = &messageStruct{
structName: "Location",
description: "// Location describes function and line table debug information.",
protoName: "Location",
upstreamProto: "gootlpprofiles.Location",
fields: []Field{
&PrimitiveField{
fieldName: "MappingIndex",
protoID: 1,
protoType: proto.TypeInt32,
},
&PrimitiveField{
fieldName: "Address",
protoID: 2,
protoType: proto.TypeUint64,
},
&SliceField{
fieldName: "Lines",
protoID: 3,
protoType: proto.TypeMessage,
returnSlice: lineSlice,
},
&SliceField{
fieldName: "AttributeIndices",
protoID: 4,
protoType: proto.TypeInt32,
returnSlice: int32Slice,
},
},
}
var lineSlice = &messageSlice{
structName: "LineSlice",
elementNullable: true,
element: line,
}
var line = &messageStruct{
structName: "Line",
description: "// Line details a specific line in a source code, linked to a function.",
protoName: "Line",
upstreamProto: "gootlpprofiles.Line",
fields: []Field{
&PrimitiveField{
fieldName: "FunctionIndex",
protoID: 1,
protoType: proto.TypeInt32,
},
&PrimitiveField{
fieldName: "Line",
protoID: 2,
protoType: proto.TypeInt64,
},
&PrimitiveField{
fieldName: "Column",
protoID: 3,
protoType: proto.TypeInt64,
},
},
}
var functionSlice = &messageSlice{
structName: "FunctionSlice",
elementNullable: true,
element: function,
}
var function = &messageStruct{
structName: "Function",
description: "// Function describes a function, including its human-readable name, system name, source file, and starting line number in the source.",
protoName: "Function",
upstreamProto: "gootlpprofiles.Function",
fields: []Field{
&PrimitiveField{
fieldName: "NameStrindex",
protoID: 1,
protoType: proto.TypeInt32,
},
&PrimitiveField{
fieldName: "SystemNameStrindex",
protoID: 2,
protoType: proto.TypeInt32,
},
&PrimitiveField{
fieldName: "FilenameStrindex",
protoID: 3,
protoType: proto.TypeInt32,
},
&PrimitiveField{
fieldName: "StartLine",
protoID: 4,
protoType: proto.TypeInt64,
},
},
}
var stackSlice = &messageSlice{
structName: "StackSlice",
elementNullable: true,
element: stack,
}
var stack = &messageStruct{
structName: "Stack",
description: "// Stack represents a stack trace as a list of locations.\n",
protoName: "Stack",
upstreamProto: "gootlpprofiles.Stack",
fields: []Field{
&SliceField{
fieldName: "LocationIndices",
protoID: 1,
protoType: proto.TypeInt32,
returnSlice: int32Slice,
},
},
}
================================================
FILE: internal/cmd/pdatagen/internal/pdata/pprofileotlp_package.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pdata // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/pdata"
import (
"path/filepath"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
)
var pprofileotlp = &Package{
info: &PackageInfo{
name: "pprofileotlp",
path: filepath.Join("pprofile", "pprofileotlp"),
imports: []string{
`"encoding/binary"`,
`"fmt"`,
`"iter"`,
`"math"`,
`"sort"`,
`"sync"`,
``,
`"go.opentelemetry.io/collector/pdata/internal"`,
},
testImports: []string{
`"strconv"`,
`"testing"`,
``,
`"github.com/stretchr/testify/assert"`,
`"github.com/stretchr/testify/require"`,
`"google.golang.org/protobuf/proto"`,
`gootlpcollectorprofiles "go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development"`,
``,
`"go.opentelemetry.io/collector/pdata/internal"`,
},
},
structs: []baseStruct{
exportProfilesResponse,
exportProfilesPartialSuccess,
},
}
var exportProfilesResponse = &messageStruct{
structName: "ExportResponse",
description: "// ExportResponse represents the response for gRPC/HTTP client/server.",
protoName: "ExportProfilesServiceResponse",
upstreamProto: "gootlpcollectorprofiles.ExportProfilesServiceResponse",
fields: []Field{
&MessageField{
fieldName: "PartialSuccess",
protoID: 1,
returnMessage: exportProfilesPartialSuccess,
},
},
}
var exportProfilesPartialSuccess = &messageStruct{
structName: "ExportPartialSuccess",
description: "// ExportPartialSuccess represents the details of a partially successful export request.",
protoName: "ExportProfilesPartialSuccess",
upstreamProto: "gootlpcollectorprofiles.ExportProfilesPartialSuccess",
fields: []Field{
&PrimitiveField{
fieldName: "RejectedProfiles",
protoID: 1,
protoType: proto.TypeInt64,
},
&PrimitiveField{
fieldName: "ErrorMessage",
protoID: 2,
protoType: proto.TypeString,
},
},
}
================================================
FILE: internal/cmd/pdatagen/internal/pdata/primitive_field.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pdata // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/pdata"
import (
"strings"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/tmplutil"
)
const primitiveAccessorsTemplate = `// {{ .fieldName }} returns the {{ .lowerFieldName }} associated with this {{ .structName }}.
func (ms {{ .structName }}) {{ .fieldName }}() {{ .packageName }}{{ .returnType }} {
return ms.{{ .origAccessor }}.{{ .originFieldName }}
}
// Set{{ .fieldName }} replaces the {{ .lowerFieldName }} associated with this {{ .structName }}.
func (ms {{ .structName }}) Set{{ .fieldName }}(v {{ .returnType }}) {
ms.{{ .stateAccessor }}.AssertMutable()
ms.{{ .origAccessor }}.{{ .originFieldName }} = v
}`
const primitiveAccessorsTestTemplate = `func Test{{ .structName }}_{{ .fieldName }}(t *testing.T) {
ms := New{{ .structName }}()
{{- if eq .returnType "bool" }}
assert.{{- if eq .defaultVal "true" }}True{{- else }}False{{- end }}(t, ms.{{ .fieldName }}())
{{- else if eq .returnType "float64" }}
assert.InDelta(t, {{ .defaultVal }}, ms.{{ .fieldName }}(), 0.01)
{{- else if and (eq .returnType "string") (eq .defaultVal "\"\"") }}
assert.Empty(t, ms.{{ .fieldName }}())
{{- else }}
assert.Equal(t, {{ .defaultVal }}, ms.{{ .fieldName }}())
{{- end }}
ms.Set{{ .fieldName }}({{ .testValue }})
{{- if eq .returnType "bool" }}
assert.{{- if eq .testValue "true" }}True{{- else }}False{{- end }}(t, ms.{{ .fieldName }}())
{{- else if eq .returnType "float64"}}
assert.InDelta(t, {{ .testValue }}, ms.{{ .fieldName }}(), 0.01)
{{- else if and (eq .returnType "string") (eq .testValue "\"\"") }}
assert.Empty(t, ms.{{ .fieldName }}())
{{- else }}
assert.Equal(t, {{ .testValue }}, ms.{{ .fieldName }}())
{{- end }}
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { new{{ .structName }}(internal.New{{ .originStructName }}(), sharedState).Set{{ .fieldName }}({{ .testValue }}) })
}`
const primitiveSetTestTemplate = `orig.{{ .originFieldName }} = {{ .testValue }}`
type PrimitiveField struct {
fieldName string
protoType proto.Type
protoID uint32
}
func (pf *PrimitiveField) GenerateAccessors(ms *messageStruct) string {
t := tmplutil.Parse("primitiveAccessorsTemplate", []byte(primitiveAccessorsTemplate))
return tmplutil.Execute(t, pf.templateFields(ms))
}
func (pf *PrimitiveField) GenerateAccessorsTest(ms *messageStruct) string {
t := tmplutil.Parse("primitiveAccessorsTestTemplate", []byte(primitiveAccessorsTestTemplate))
return tmplutil.Execute(t, pf.templateFields(ms))
}
func (pf *PrimitiveField) GenerateTestValue(ms *messageStruct) string {
t := tmplutil.Parse("primitiveSetTestTemplate", []byte(primitiveSetTestTemplate))
return tmplutil.Execute(t, pf.templateFields(ms))
}
func (pf *PrimitiveField) toProtoField(*messageStruct) proto.FieldInterface {
return &proto.Field{
Type: pf.protoType,
ID: pf.protoID,
Name: pf.fieldName,
}
}
func (pf *PrimitiveField) templateFields(ms *messageStruct) map[string]any {
prf := pf.toProtoField(ms)
return map[string]any{
"structName": ms.getName(),
"packageName": "",
"defaultVal": prf.DefaultValue(),
"fieldName": pf.fieldName,
"lowerFieldName": strings.ToLower(pf.fieldName),
"testValue": prf.TestValue(),
"returnType": prf.GoType(),
"origAccessor": origAccessor(ms.getHasWrapper()),
"stateAccessor": stateAccessor(ms.getHasWrapper()),
"originStructName": ms.protoName,
"originFieldName": pf.fieldName,
}
}
var _ Field = (*PrimitiveField)(nil)
================================================
FILE: internal/cmd/pdatagen/internal/pdata/primitive_slice_structs.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pdata // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/pdata"
import (
"strings"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/tmplutil"
)
// primitiveSliceStruct generates a struct for a slice of primitive value elements. The structs are always generated
// in a way that they can be used as fields in structs from other packages (using the internal package).
type primitiveSliceStruct struct {
structName string
packageName string
itemType string
testOrigVal string
testInterfaceOrigVal []any
testSetVal string
testNewVal string
}
func (iss *primitiveSliceStruct) getName() string {
return iss.structName
}
func (iss *primitiveSliceStruct) getPackageName() string {
return iss.packageName
}
func (iss *primitiveSliceStruct) generate(packageInfo *PackageInfo) []byte {
return []byte(tmplutil.Execute(primitiveSliceTemplate, iss.templateFields(packageInfo)))
}
func (iss *primitiveSliceStruct) generateTests(packageInfo *PackageInfo) []byte {
return []byte(tmplutil.Execute(primitiveSliceTestTemplate, iss.templateFields(packageInfo)))
}
func (iss *primitiveSliceStruct) generateInternal(packageInfo *PackageInfo) []byte {
return []byte(tmplutil.Execute(primitiveSliceInternalTemplate, iss.templateFields(packageInfo)))
}
func (iss *primitiveSliceStruct) getOriginName() string {
return iss.getName()
}
func (iss *primitiveSliceStruct) getOriginFullName() string {
return iss.getName()
}
func (iss *primitiveSliceStruct) getHasWrapper() bool {
return usedByOtherDataTypes(iss.packageName)
}
func (iss *primitiveSliceStruct) getHasOnlyInternal() bool {
return false
}
func (iss *primitiveSliceStruct) getElementOriginName() string {
return upperFirst(iss.itemType)
}
func (iss *primitiveSliceStruct) getElementNullable() bool {
return false
}
func (iss *primitiveSliceStruct) getProtoMessage() *proto.Message {
return nil
}
func (iss *primitiveSliceStruct) templateFields(packageInfo *PackageInfo) map[string]any {
return map[string]any{
"structName": iss.getName(),
"itemType": iss.itemType,
"elementOriginName": iss.getElementOriginName(),
"lowerStructName": strings.ToLower(iss.structName[:1]) + iss.structName[1:],
"testOrigVal": iss.testOrigVal,
"testInterfaceOrigVal": iss.testInterfaceOrigVal,
"testSetVal": iss.testSetVal,
"testNewVal": iss.testNewVal,
"packageName": packageInfo.name,
"imports": packageInfo.imports,
"testImports": packageInfo.testImports,
}
}
func upperFirst(s string) string {
return strings.ToUpper(s[0:1]) + s[1:]
}
================================================
FILE: internal/cmd/pdatagen/internal/pdata/ptrace_package.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pdata // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/pdata"
import (
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
)
var ptrace = &Package{
info: &PackageInfo{
name: "ptrace",
path: "ptrace",
imports: []string{
`"encoding/binary"`,
`"fmt"`,
`"iter"`,
`"math"`,
`"sort"`,
`"sync"`,
``,
`"go.opentelemetry.io/collector/pdata/internal"`,
`"go.opentelemetry.io/collector/pdata/internal/json"`,
`"go.opentelemetry.io/collector/pdata/internal/proto"`,
`"go.opentelemetry.io/collector/pdata/pcommon"`,
},
testImports: []string{
`"strconv"`,
`"testing"`,
`"unsafe"`,
``,
`"github.com/stretchr/testify/assert"`,
`"github.com/stretchr/testify/require"`,
`"google.golang.org/protobuf/proto"`,
`gootlpcollectortrace "go.opentelemetry.io/proto/slim/otlp/collector/trace/v1"`,
`gootlptrace "go.opentelemetry.io/proto/slim/otlp/trace/v1"`,
``,
`"go.opentelemetry.io/collector/pdata/internal"`,
`"go.opentelemetry.io/collector/pdata/internal/json"`,
`"go.opentelemetry.io/collector/pdata/pcommon"`,
},
},
structs: []baseStruct{
traces,
tracesData,
resourceSpansSlice,
resourceSpans,
scopeSpansSlice,
scopeSpans,
spanSlice,
span,
spanEventSlice,
spanEvent,
spanLinkSlice,
spanLink,
spanStatus,
},
enums: []*proto.Enum{
spanKindEnum,
statusCodeEnum,
},
}
var traces = &messageStruct{
structName: "Traces",
description: "// Traces is the top-level struct that is propagated through the traces pipeline.\n// Use NewTraces to create new instance, zero-initialized instance is not valid for use.",
protoName: "ExportTraceServiceRequest",
upstreamProto: "gootlpcollectortrace.ExportTraceServiceRequest",
fields: []Field{
&SliceField{
fieldName: "ResourceSpans",
protoID: 1,
protoType: proto.TypeMessage,
returnSlice: resourceSpansSlice,
},
},
hasWrapper: true,
}
var tracesData = &messageStruct{
structName: "TracesData",
description: "// TracesData represents the traces data that can be stored in a persistent storage,\n// OR can be embedded by other protocols that transfer OTLP traces data but do not\n// implement the OTLP protocol.",
protoName: "TracesData",
upstreamProto: "gootlptrace.TracesData",
fields: []Field{
&SliceField{
fieldName: "ResourceSpans",
protoID: 1,
protoType: proto.TypeMessage,
returnSlice: resourceSpansSlice,
},
},
hasOnlyInternal: true,
}
var resourceSpansSlice = &messageSlice{
structName: "ResourceSpansSlice",
elementNullable: true,
element: resourceSpans,
}
var resourceSpans = &messageStruct{
structName: "ResourceSpans",
description: "// ResourceSpans is a collection of spans from a Resource.",
protoName: "ResourceSpans",
upstreamProto: "gootlptrace.ResourceSpans",
fields: []Field{
&MessageField{
fieldName: "Resource",
protoID: 1,
returnMessage: resource,
},
&SliceField{
fieldName: "ScopeSpans",
protoID: 2,
protoType: proto.TypeMessage,
returnSlice: scopeSpansSlice,
},
&PrimitiveField{
fieldName: "SchemaUrl",
protoID: 3,
protoType: proto.TypeString,
},
&SliceField{
fieldName: "DeprecatedScopeSpans",
protoType: proto.TypeMessage,
protoID: 1000,
returnSlice: scopeSpansSlice,
// Hide accessors for this field because it is a HACK:
// Workaround for istio 1.15 / envoy 1.23.1 mistakenly emitting deprecated field.
hideAccessors: true,
},
},
}
var scopeSpansSlice = &messageSlice{
structName: "ScopeSpansSlice",
elementNullable: true,
element: scopeSpans,
}
var scopeSpans = &messageStruct{
structName: "ScopeSpans",
description: "// ScopeSpans is a collection of spans from a LibraryInstrumentation.",
protoName: "ScopeSpans",
upstreamProto: "gootlptrace.ScopeSpans",
fields: []Field{
&MessageField{
fieldName: "Scope",
protoID: 1,
returnMessage: scope,
},
&SliceField{
fieldName: "Spans",
protoID: 2,
protoType: proto.TypeMessage,
returnSlice: spanSlice,
},
&PrimitiveField{
fieldName: "SchemaUrl",
protoID: 3,
protoType: proto.TypeString,
},
},
}
var spanSlice = &messageSlice{
structName: "SpanSlice",
elementNullable: true,
element: span,
}
var span = &messageStruct{
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/main/opentelemetry/proto/trace/v1/trace.proto",
protoName: "Span",
upstreamProto: "gootlptrace.Span",
fields: []Field{
&TypedField{
fieldName: "TraceID",
originFieldName: "TraceId",
protoID: 1,
returnType: traceIDType,
},
&TypedField{
fieldName: "SpanID",
originFieldName: "SpanId",
protoID: 2,
returnType: spanIDType,
},
&MessageField{
fieldName: "TraceState",
protoID: 3,
returnMessage: traceState,
},
&TypedField{
fieldName: "ParentSpanID",
originFieldName: "ParentSpanId",
protoID: 4,
returnType: spanIDType,
},
&PrimitiveField{
fieldName: "Flags",
protoID: 16,
protoType: proto.TypeFixed32,
},
&PrimitiveField{
fieldName: "Name",
protoID: 5,
protoType: proto.TypeString,
},
&TypedField{
fieldName: "Kind",
protoID: 6,
returnType: &TypedType{
structName: "SpanKind",
protoType: proto.TypeEnum,
messageName: "SpanKind",
defaultVal: "SpanKind_SPAN_KIND_UNSPECIFIED",
testVal: "SpanKind_SPAN_KIND_CLIENT",
},
},
&TypedField{
fieldName: "StartTimestamp",
originFieldName: "StartTimeUnixNano",
protoID: 7,
returnType: timestampType,
},
&TypedField{
fieldName: "EndTimestamp",
originFieldName: "EndTimeUnixNano",
protoID: 8,
returnType: timestampType,
},
&SliceField{
fieldName: "Attributes",
protoID: 9,
protoType: proto.TypeMessage,
returnSlice: mapStruct,
},
&PrimitiveField{
fieldName: "DroppedAttributesCount",
protoID: 10,
protoType: proto.TypeUint32,
},
&SliceField{
fieldName: "Events",
protoID: 11,
protoType: proto.TypeMessage,
returnSlice: spanEventSlice,
},
&PrimitiveField{
fieldName: "DroppedEventsCount",
protoID: 12,
protoType: proto.TypeUint32,
},
&SliceField{
fieldName: "Links",
protoID: 13,
protoType: proto.TypeMessage,
returnSlice: spanLinkSlice,
},
&PrimitiveField{
fieldName: "DroppedLinksCount",
protoID: 14,
protoType: proto.TypeUint32,
},
&MessageField{
fieldName: "Status",
protoID: 15,
returnMessage: spanStatus,
},
},
}
var spanEventSlice = &messageSlice{
structName: "SpanEventSlice",
elementNullable: true,
element: spanEvent,
}
var spanEvent = &messageStruct{
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.",
protoName: "SpanEvent",
upstreamProto: "gootlptrace.Span_Event",
fields: []Field{
&TypedField{
fieldName: "Timestamp",
originFieldName: "TimeUnixNano",
protoID: 1,
returnType: timestampType,
},
&PrimitiveField{
fieldName: "Name",
protoID: 2,
protoType: proto.TypeString,
},
&SliceField{
fieldName: "Attributes",
protoID: 3,
protoType: proto.TypeMessage,
returnSlice: mapStruct,
},
&PrimitiveField{
fieldName: "DroppedAttributesCount",
protoID: 4,
protoType: proto.TypeUint32,
},
},
}
var spanLinkSlice = &messageSlice{
structName: "SpanLinkSlice",
elementNullable: true,
element: spanLink,
}
var spanLink = &messageStruct{
structName: "SpanLink",
description: "// SpanLink is a pointer from the current span to another span in the same trace or in a\n" +
"// different trace.\n" +
"// See Link definition in OTLP: https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/trace/v1/trace.proto",
protoName: "SpanLink",
upstreamProto: "gootlptrace.Span_Link",
fields: []Field{
&TypedField{
fieldName: "TraceID",
originFieldName: "TraceId",
protoID: 1,
returnType: traceIDType,
},
&TypedField{
fieldName: "SpanID",
originFieldName: "SpanId",
protoID: 2,
returnType: spanIDType,
},
&MessageField{
fieldName: "TraceState",
protoID: 3,
returnMessage: traceState,
},
&SliceField{
fieldName: "Attributes",
protoID: 4,
protoType: proto.TypeMessage,
returnSlice: mapStruct,
},
&PrimitiveField{
fieldName: "DroppedAttributesCount",
protoID: 5,
protoType: proto.TypeUint32,
},
&PrimitiveField{
fieldName: "Flags",
protoID: 6,
protoType: proto.TypeFixed32,
},
},
}
var spanStatus = &messageStruct{
structName: "Status",
description: "// Status is an optional final status for this span. Semantically, when Status was not\n" +
"// set, that means the span ended without errors and to assume Status.Ok (code = 0).",
protoName: "Status",
upstreamProto: "gootlptrace.Status",
fields: []Field{
&PrimitiveField{
fieldName: "Message",
protoID: 2,
protoType: proto.TypeString,
},
&TypedField{
fieldName: "Code",
protoID: 3,
returnType: &TypedType{
structName: "StatusCode",
protoType: proto.TypeEnum,
messageName: "StatusCode",
defaultVal: "StatusCode_STATUS_CODE_UNSET",
testVal: "StatusCode_STATUS_CODE_OK",
},
},
},
}
var spanKindEnum = &proto.Enum{
Name: "SpanKind",
Description: "// SpanKind is the type of span.\n// Can be used to specify additional relationships between spans in addition to a parent/child relationship.",
Fields: []*proto.EnumField{
{Name: "SPAN_KIND_UNSPECIFIED", Value: 0},
{Name: "SPAN_KIND_INTERNAL", Value: 1},
{Name: "SPAN_KIND_SERVER", Value: 2},
{Name: "SPAN_KIND_CLIENT", Value: 3},
{Name: "SPAN_KIND_PRODUCER", Value: 4},
{Name: "SPAN_KIND_CONSUMER", Value: 5},
},
}
var statusCodeEnum = &proto.Enum{
Name: "StatusCode",
Description: "// StatusCode is the status of the span, for the semantics of codes see\n// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#set-status",
Fields: []*proto.EnumField{
{Name: "STATUS_CODE_UNSET", Value: 0},
{Name: "STATUS_CODE_OK", Value: 1},
{Name: "STATUS_CODE_ERROR", Value: 2},
},
}
================================================
FILE: internal/cmd/pdatagen/internal/pdata/ptraceotlp_package.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pdata // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/pdata"
import (
"path/filepath"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
)
var ptraceotlp = &Package{
info: &PackageInfo{
name: "ptraceotlp",
path: filepath.Join("ptrace", "ptraceotlp"),
imports: []string{
`"encoding/binary"`,
`"fmt"`,
`"iter"`,
`"math"`,
`"sort"`,
`"sync"`,
``,
`"go.opentelemetry.io/collector/pdata/internal"`,
},
testImports: []string{
`"strconv"`,
`"testing"`,
``,
`"github.com/stretchr/testify/assert"`,
`"github.com/stretchr/testify/require"`,
`"google.golang.org/protobuf/proto"`,
`gootlpcollectortrace "go.opentelemetry.io/proto/slim/otlp/collector/trace/v1"`,
``,
`"go.opentelemetry.io/collector/pdata/internal"`,
},
},
structs: []baseStruct{
exportTraceResponse,
exportTracePartialSuccess,
},
}
var exportTraceResponse = &messageStruct{
structName: "ExportResponse",
description: "// ExportResponse represents the response for gRPC/HTTP client/server.",
protoName: "ExportTraceServiceResponse",
upstreamProto: "gootlpcollectortrace.ExportTraceServiceResponse",
fields: []Field{
&MessageField{
fieldName: "PartialSuccess",
protoID: 1,
returnMessage: exportTracePartialSuccess,
},
},
}
var exportTracePartialSuccess = &messageStruct{
structName: "ExportPartialSuccess",
description: "// ExportPartialSuccess represents the details of a partially successful export request.",
protoName: "ExportTracePartialSuccess",
upstreamProto: "gootlpcollectortrace.ExportTracePartialSuccess",
fields: []Field{
&PrimitiveField{
fieldName: "RejectedSpans",
protoID: 1,
protoType: proto.TypeInt64,
},
&PrimitiveField{
fieldName: "ErrorMessage",
protoID: 2,
protoType: proto.TypeString,
},
},
}
================================================
FILE: internal/cmd/pdatagen/internal/pdata/request_package.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pdata // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/pdata"
import (
"path/filepath"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
)
var prequest = &Package{
info: &PackageInfo{
name: "request",
path: filepath.Join("xpdata", "request", "internal"),
imports: []string{
`"encoding/binary"`,
`"fmt"`,
`"iter"`,
`"math"`,
`"sort"`,
``,
`"go.opentelemetry.io/collector/pdata/internal"`,
`"go.opentelemetry.io/collector/pdata/internal/json"`,
`"go.opentelemetry.io/collector/pdata/internal/proto"`,
`"go.opentelemetry.io/collector/pdata/pcommon"`,
},
testImports: []string{
`"testing"`,
``,
`"github.com/stretchr/testify/assert"`,
`"google.golang.org/protobuf/types/known/emptypb"`,
``,
`"go.opentelemetry.io/collector/pdata/internal"`,
`"go.opentelemetry.io/collector/pdata/internal/json"`,
`"go.opentelemetry.io/collector/pdata/pcommon"`,
},
},
structs: []baseStruct{
spanContext,
ipAddr,
tcpAddr,
udpAddr,
unixAddr,
requestContext,
tracesRequest,
metricsRequest,
logsRequest,
profilesRequest,
},
}
var spanContext = &messageStruct{
structName: "SpanContext",
packageName: "request",
protoName: "SpanContext",
upstreamProto: "emptypb.Empty",
fields: []Field{
&TypedField{
fieldName: "TraceID",
protoID: 1,
returnType: traceIDType,
},
&TypedField{
fieldName: "SpanID",
protoID: 2,
returnType: spanIDType,
},
&PrimitiveField{
fieldName: "TraceFlags",
protoID: 3,
protoType: proto.TypeFixed32,
},
&MessageField{
fieldName: "TraceState",
protoID: 4,
returnMessage: traceState,
},
&PrimitiveField{
fieldName: "Remote",
protoID: 5,
protoType: proto.TypeBool,
},
},
hasOnlyInternal: true,
}
var ipAddr = &messageStruct{
structName: "IPAddr",
packageName: "request",
protoName: "IPAddr",
upstreamProto: "emptypb.Empty",
fields: []Field{
&PrimitiveField{
fieldName: "IP",
protoID: 1,
protoType: proto.TypeBytes,
},
&PrimitiveField{
fieldName: "Zone",
protoID: 2,
protoType: proto.TypeString,
},
},
hasOnlyInternal: true,
}
var tcpAddr = &messageStruct{
structName: "TCPAddr",
packageName: "request",
protoName: "TCPAddr",
upstreamProto: "emptypb.Empty",
fields: []Field{
&PrimitiveField{
fieldName: "IP",
protoID: 1,
protoType: proto.TypeBytes,
},
&PrimitiveField{
fieldName: "Port",
protoID: 2,
protoType: proto.TypeInt64,
},
&PrimitiveField{
fieldName: "Zone",
protoID: 3,
protoType: proto.TypeString,
},
},
hasOnlyInternal: true,
}
var udpAddr = &messageStruct{
structName: "UDPAddr",
packageName: "request",
protoName: "UDPAddr",
upstreamProto: "emptypb.Empty",
fields: []Field{
&PrimitiveField{
fieldName: "IP",
protoID: 1,
protoType: proto.TypeBytes,
},
&PrimitiveField{
fieldName: "Port",
protoID: 2,
protoType: proto.TypeInt64,
},
&PrimitiveField{
fieldName: "Zone",
protoID: 3,
protoType: proto.TypeString,
},
},
hasOnlyInternal: true,
}
var unixAddr = &messageStruct{
structName: "UnixAddr",
packageName: "request",
protoName: "UnixAddr",
upstreamProto: "emptypb.Empty",
fields: []Field{
&PrimitiveField{
fieldName: "Name",
protoID: 1,
protoType: proto.TypeString,
},
&PrimitiveField{
fieldName: "Net",
protoID: 2,
protoType: proto.TypeString,
},
},
hasOnlyInternal: true,
}
var requestContext = &messageStruct{
structName: "RequestContext",
packageName: "request",
protoName: "RequestContext",
upstreamProto: "emptypb.Empty",
fields: []Field{
&MessageField{
fieldName: "SpanContext",
protoID: 1,
nullable: true,
returnMessage: spanContext,
},
&SliceField{
fieldName: "ClientMetadata",
protoID: 2,
protoType: proto.TypeMessage,
returnSlice: mapStruct,
},
&OneOfField{
typeName: "ClientAddressType",
originFieldName: "ClientAddress",
testValueIdx: 1, //
omitOriginFieldNameInNames: true,
values: []oneOfValue{
&OneOfMessageValue{
fieldName: "IP",
protoID: 3,
returnMessage: ipAddr,
},
&OneOfMessageValue{
fieldName: "TCP",
protoID: 4,
returnMessage: tcpAddr,
},
&OneOfMessageValue{
fieldName: "UDP",
protoID: 5,
returnMessage: udpAddr,
},
&OneOfMessageValue{
fieldName: "Unix",
protoID: 6,
returnMessage: unixAddr,
},
},
},
},
hasOnlyInternal: true,
}
var tracesRequest = &messageStruct{
structName: "TracesRequest",
packageName: "request",
protoName: "TracesRequest",
upstreamProto: "emptypb.Empty",
fields: []Field{
&MessageField{
fieldName: "RequestContext",
protoID: 2,
nullable: true,
returnMessage: requestContext,
},
&MessageField{
fieldName: "TracesData",
protoID: 3,
returnMessage: tracesData,
},
&PrimitiveField{
fieldName: "FormatVersion",
protoID: 1,
protoType: proto.TypeFixed32,
},
},
hasOnlyInternal: true,
}
var metricsRequest = &messageStruct{
structName: "MetricsRequest",
packageName: "request",
protoName: "MetricsRequest",
upstreamProto: "emptypb.Empty",
fields: []Field{
&MessageField{
fieldName: "RequestContext",
protoID: 2,
nullable: true,
returnMessage: requestContext,
},
&MessageField{
fieldName: "MetricsData",
protoID: 3,
returnMessage: metricsData,
},
&PrimitiveField{
fieldName: "FormatVersion",
protoID: 1,
protoType: proto.TypeFixed32,
},
},
hasOnlyInternal: true,
}
var logsRequest = &messageStruct{
structName: "LogsRequest",
packageName: "request",
protoName: "LogsRequest",
upstreamProto: "emptypb.Empty",
fields: []Field{
&MessageField{
fieldName: "RequestContext",
protoID: 2,
nullable: true,
returnMessage: requestContext,
},
&MessageField{
fieldName: "LogsData",
protoID: 3,
returnMessage: logsData,
},
&PrimitiveField{
fieldName: "FormatVersion",
protoID: 1,
protoType: proto.TypeFixed32,
},
},
hasOnlyInternal: true,
}
var profilesRequest = &messageStruct{
structName: "ProfilesRequest",
packageName: "request",
protoName: "ProfilesRequest",
upstreamProto: "emptypb.Empty",
fields: []Field{
&MessageField{
fieldName: "RequestContext",
protoID: 2,
nullable: true,
returnMessage: requestContext,
},
&MessageField{
fieldName: "ProfilesData",
protoID: 3,
returnMessage: profilesData,
},
&PrimitiveField{
fieldName: "FormatVersion",
protoID: 1,
protoType: proto.TypeFixed32,
},
},
hasOnlyInternal: true,
}
================================================
FILE: internal/cmd/pdatagen/internal/pdata/slice_field.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pdata // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/pdata"
import (
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/tmplutil"
)
const sliceAccessorTemplate = `// {{ .fieldName }} returns the {{ .fieldName }} associated with this {{ .structName }}.
func (ms {{ .structName }}) {{ .fieldName }}() {{ .packageName }}{{ .returnType }} {
{{- if .elementHasWrapper }}
return {{ .packageName }}{{ .returnType }}(internal.New{{ .returnType }}Wrapper(&ms.{{ .origAccessor }}.{{ .originFieldName }}, ms.{{ .stateAccessor }}))
{{- else }}
return new{{ .returnType }}(&ms.{{ .origAccessor }}.{{ .originFieldName }}, ms.{{ .stateAccessor }})
{{- end }}
}`
const sliceAccessorsTestTemplate = `func Test{{ .structName }}_{{ .fieldName }}(t *testing.T) {
ms := New{{ .structName }}()
assert.Equal(t, {{ .packageName }}New{{ .returnType }}(), ms.{{ .fieldName }}())
ms.{{ .origAccessor }}.{{ .originFieldName }} = internal.GenTest{{ .elementOriginName }}{{ if .elementNullable }}Ptr{{ end }}Slice()
{{- if .elementHasWrapper }}
assert.Equal(t, {{ .packageName }}{{ .returnType }}(internal.GenTest{{ .returnType }}Wrapper()), ms.{{ .fieldName }}())
{{- else }}
assert.Equal(t, generateTest{{ .returnType }}(), ms.{{ .fieldName }}())
{{- end }}
}`
const sliceSetTestTemplate = `orig.{{ .originFieldName }} = internal.GenTest{{ .elementOriginName }}{{ if .elementNullable }}Ptr{{ end }}Slice()`
type SliceField struct {
fieldName string
protoType proto.Type
protoID uint32
returnSlice baseSlice
hideAccessors bool
}
func (sf *SliceField) GenerateAccessors(ms *messageStruct) string {
if sf.hideAccessors {
return ""
}
t := tmplutil.Parse("sliceAccessorTemplate", []byte(sliceAccessorTemplate))
return tmplutil.Execute(t, sf.templateFields(ms))
}
func (sf *SliceField) GenerateAccessorsTest(ms *messageStruct) string {
if sf.hideAccessors {
return ""
}
t := tmplutil.Parse("sliceAccessorsTestTemplate", []byte(sliceAccessorsTestTemplate))
return tmplutil.Execute(t, sf.templateFields(ms))
}
func (sf *SliceField) GenerateTestValue(ms *messageStruct) string {
t := tmplutil.Parse("sliceSetTestTemplate", []byte(sliceSetTestTemplate))
return tmplutil.Execute(t, sf.templateFields(ms))
}
func (sf *SliceField) toProtoField(ms *messageStruct) proto.FieldInterface {
return &proto.Field{
Type: sf.protoType,
ID: sf.protoID,
Name: sf.fieldName,
MessageName: sf.returnSlice.getElementOriginName(),
ParentMessageName: ms.protoName,
Repeated: sf.protoType != proto.TypeBytes,
Nullable: sf.returnSlice.getElementNullable(),
}
}
func (sf *SliceField) templateFields(ms *messageStruct) map[string]any {
return map[string]any{
"structName": ms.getName(),
"fieldName": sf.fieldName,
"originFieldName": sf.fieldName,
"elementOriginName": sf.returnSlice.getElementOriginName(),
"packageName": func() string {
if sf.returnSlice.getPackageName() != ms.packageName {
return sf.returnSlice.getPackageName() + "."
}
return ""
}(),
"returnType": sf.returnSlice.getName(),
"origAccessor": origAccessor(ms.getHasWrapper()),
"stateAccessor": stateAccessor(ms.getHasWrapper()),
"elementHasWrapper": sf.returnSlice.getHasWrapper(),
"elementNullable": sf.returnSlice.getElementNullable(),
}
}
var _ Field = (*SliceField)(nil)
================================================
FILE: internal/cmd/pdatagen/internal/pdata/templates/message.go.tmpl
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package {{ .packageName }}
import (
{{ range $index, $element := .imports -}}
{{ $element }}
{{ end }}
)
{{ .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.
{{- if .hasWrapper }}
type {{ .structName }} internal.{{ .structName }}Wrapper
{{- else }}
type {{ .structName }} struct {
orig *internal.{{ .originName }}
state *internal.State
}
{{- end }}
func new{{ .structName }}(orig *internal.{{ .originName }}, state *internal.State) {{ .structName }} {
{{- if .hasWrapper }}
return {{ .structName }}(internal.New{{ .structName }}Wrapper(orig, state))
{{- else }}
return {{ .structName }}{orig: orig, state: state}
{{- end }}
}
// New{{ .structName }} creates a new empty {{ .structName }}.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func New{{ .structName }}() {{ .structName }} {
return new{{ .structName }}(internal.New{{ .originName }}(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms {{ .structName }}) MoveTo(dest {{ .structName }}) {
ms.{{ .stateAccessor }}.AssertMutable()
dest.{{ .stateAccessor }}.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.{{ .origAccessor }} == dest.{{ .origAccessor }} {
return
}
internal.Delete{{ .originName }}(dest.{{ .origAccessor }}, false)
*dest.{{ .origAccessor }}, *ms.{{ .origAccessor }} = *ms.{{ .origAccessor }}, *dest.{{ .origAccessor }}
}
{{ range .fields -}}
{{ .GenerateAccessors $.messageStruct }}
{{ end }}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms {{ .structName }}) CopyTo(dest {{ .structName }}) {
dest.{{ .stateAccessor }}.AssertMutable()
internal.Copy{{ .originName }}(dest.{{ .origAccessor }}, ms.{{ .origAccessor }})
}
{{ if .hasWrapper -}}
func (ms {{ .structName }}) getOrig() *internal.{{ .originName }} {
return internal.Get{{ .structName }}Orig(internal.{{ .structName }}Wrapper(ms))
}
func (ms {{ .structName }}) getState() *internal.State {
return internal.Get{{ .structName }}State(internal.{{ .structName }}Wrapper(ms))
}
{{- end }}
================================================
FILE: internal/cmd/pdatagen/internal/pdata/templates/message_internal.go.tmpl
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
{{ range $index, $element := .imports -}}
{{ $element }}
{{ end }}
)
type {{ .structName }}Wrapper struct {
orig *{{ .originName }}
state *State
}
func Get{{ .structName }}Orig(ms {{ .structName }}Wrapper) *{{ .originName }} {
return ms.orig
}
func Get{{ .structName }}State(ms {{ .structName }}Wrapper) *State {
return ms.state
}
func New{{ .structName }}Wrapper(orig *{{ .originName }}, state *State) {{ .structName }}Wrapper {
return {{ .structName }}Wrapper{orig: orig, state: state}
}
func GenTest{{ .structName }}Wrapper() {{ .structName }}Wrapper {
return New{{ .structName }}Wrapper(GenTest{{ .originName }}(), NewState())
}
================================================
FILE: internal/cmd/pdatagen/internal/pdata/templates/message_test.go.tmpl
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package {{ .packageName }}
import (
{{ range $index, $element := .testImports -}}
{{ $element }}
{{ end }}
)
func Test{{ .structName }}_MoveTo(t *testing.T) {
ms := generateTest{{ .structName }}()
dest := New{{ .structName }}()
ms.MoveTo(dest)
assert.Equal(t, New{{ .structName }}(), ms)
assert.Equal(t, generateTest{{ .structName }}(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTest{{ .structName }}(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(new{{ .structName }}(internal.New{{ .originName }}(), sharedState)) })
assert.Panics(t, func() { new{{ .structName }}(internal.New{{ .originName }}(), sharedState).MoveTo(dest) })
}
func Test{{ .structName }}_CopyTo(t *testing.T) {
ms := New{{ .structName }}()
orig := New{{ .structName }}()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTest{{ .structName }}()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(new{{ .structName }}(internal.New{{ .originName }}(), sharedState)) })
}
{{ range .fields }}
{{ .GenerateAccessorsTest $.messageStruct }}
{{ end }}
func generateTest{{ .structName }}() {{ .structName }} {
return new{{ .structName }}(internal.GenTest{{ .originName }}(), internal.NewState())
}
================================================
FILE: internal/cmd/pdatagen/internal/pdata/templates/primitive_slice.go.tmpl
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package {{ .packageName }}
import (
{{ range $index, $element := .imports -}}
{{ $element }}
{{ end }}
)
// {{ .structName }} represents a []{{ .itemType }} slice.
// The instance of {{ .structName }} can be assigned to multiple objects since it's immutable.
//
// Must use New{{ .structName }} function to create new instances.
// Important: zero-initialized instance is not valid for use.
type {{ .structName }} internal.{{ .structName }}Wrapper
func (ms {{ .structName }}) getOrig() *[]{{ .itemType }} {
return internal.Get{{ .structName }}Orig(internal.{{ .structName }}Wrapper(ms))
}
func (ms {{ .structName }}) getState() *internal.State {
return internal.Get{{ .structName }}State(internal.{{ .structName }}Wrapper(ms))
}
// New{{ .structName }} creates a new empty {{ .structName }}.
func New{{ .structName }}() {{ .structName }} {
orig := []{{ .itemType }}(nil)
return {{ .structName }}(internal.New{{ .structName }}Wrapper(&orig, internal.NewState()))
}
// AsRaw returns a copy of the []{{ .itemType }} slice.
func (ms {{ .structName }}) AsRaw() []{{ .itemType }} {
return copy{{ .elementOriginName }}Slice(nil, *ms.getOrig())
}
// FromRaw copies raw []{{ .itemType }} into the slice {{ .structName }}.
func (ms {{ .structName }}) FromRaw(val []{{ .itemType }}) {
ms.getState().AssertMutable()
*ms.getOrig() = copy{{ .elementOriginName }}Slice(*ms.getOrig(), val)
}
// Len returns length of the []{{ .itemType }} slice value.
// Equivalent of len({{ .lowerStructName }}).
func (ms {{ .structName }}) Len() int {
return len(*ms.getOrig())
}
// At returns an item from particular index.
// Equivalent of {{ .lowerStructName }}[i].
func (ms {{ .structName }}) At(i int) {{ .itemType }} {
return (*ms.getOrig())[i]
}
// All returns an iterator over index-value pairs in the slice.
func (ms {{ .structName }}) All() iter.Seq2[int, {{ .itemType }}] {
return func(yield func(int, {{ .itemType }}) bool) {
for i := 0; i < ms.Len(); i++ {
if !yield(i, ms.At(i)) {
return
}
}
}
}
// SetAt sets {{ .itemType }} item at particular index.
// Equivalent of {{ .lowerStructName }}[i] = val
func (ms {{ .structName }}) SetAt(i int, val {{ .itemType }}) {
ms.getState().AssertMutable()
(*ms.getOrig())[i] = val
}
// EnsureCapacity ensures {{ .structName }} has at least the specified capacity.
// 1. If the newCap <= cap, then is no change in capacity.
// 2. If the newCap > cap, then the slice capacity will be expanded to the provided value which will be equivalent of:
// buf := make([]{{ .itemType }}, len({{ .lowerStructName }}), newCap)
// copy(buf, {{ .lowerStructName }})
// {{ .lowerStructName }} = buf
func (ms {{ .structName }}) EnsureCapacity(newCap int) {
ms.getState().AssertMutable()
oldCap := cap(*ms.getOrig())
if newCap <= oldCap {
return
}
newOrig := make([]{{ .itemType }}, len(*ms.getOrig()), newCap)
copy(newOrig, *ms.getOrig())
*ms.getOrig() = newOrig
}
// Append appends extra elements to {{ .structName }}.
// Equivalent of {{ .lowerStructName }} = append({{ .lowerStructName }}, elms...)
func (ms {{ .structName }}) Append(elms ...{{ .itemType }}) {
ms.getState().AssertMutable()
*ms.getOrig() = append(*ms.getOrig(), elms...)
}
// MoveTo moves all elements from the current slice overriding the destination and
// resetting the current instance to its zero value.
func (ms {{ .structName }}) MoveTo(dest {{ .structName }}) {
ms.getState().AssertMutable()
dest.getState().AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.getOrig() == dest.getOrig() {
return
}
*dest.getOrig() = *ms.getOrig()
*ms.getOrig() = nil
}
// MoveAndAppendTo moves all elements from the current slice and appends them to the dest.
// The current slice will be cleared.
func (ms {{ .structName }}) MoveAndAppendTo(dest {{ .structName }}) {
ms.getState().AssertMutable()
dest.getState().AssertMutable()
if *dest.getOrig() == nil {
// We can simply move the entire vector and avoid any allocations.
*dest.getOrig() = *ms.getOrig()
} else {
*dest.getOrig() = append(*dest.getOrig(), *ms.getOrig()...)
}
*ms.getOrig() = nil
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (ms {{ .structName }}) RemoveIf(f func({{ .itemType }}) bool) {
ms.getState().AssertMutable()
newLen := 0
for i := 0; i < len(*ms.getOrig()); i++ {
if f((*ms.getOrig())[i]) {
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*ms.getOrig())[newLen] = (*ms.getOrig())[i]
var zero {{ .itemType }}
(*ms.getOrig())[i] = zero
newLen++
}
*ms.getOrig() = (*ms.getOrig())[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (ms {{ .structName }}) CopyTo(dest {{ .structName }}) {
dest.getState().AssertMutable()
if ms.getOrig() == dest.getOrig() {
return
}
*dest.getOrig() = copy{{ .elementOriginName }}Slice(*dest.getOrig(), *ms.getOrig())
}
// Equal checks equality with another {{ .structName }}
func (ms {{ .structName }}) Equal(val {{ .structName }}) bool {
return slices.Equal(*ms.getOrig(), *val.getOrig())
}
func copy{{ .elementOriginName }}Slice(dst, src []{{ .itemType }}) []{{ .itemType }} {
return append(dst[:0], src...)
}
================================================
FILE: internal/cmd/pdatagen/internal/pdata/templates/primitive_slice_internal.go.tmpl
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
{{ range $index, $element := .imports -}}
{{ $element }}
{{ end }}
)
type {{ .structName }}Wrapper struct {
orig *[]{{ .itemType }}
state *State
}
func Get{{ .structName }}Orig(ms {{ .structName }}Wrapper) *[]{{ .itemType }} {
return ms.orig
}
func Get{{ .structName }}State(ms {{ .structName }}Wrapper) *State {
return ms.state
}
func New{{ .structName }}Wrapper(orig *[]{{ .itemType }}, state *State) {{ .structName }}Wrapper {
return {{ .structName }}Wrapper{orig: orig, state: state}
}
func GenTest{{ .structName }}Wrapper() {{ .structName }}Wrapper {
orig := []{{ .itemType }}{ {{ .testOrigVal }} }
return New{{ .structName }}Wrapper(&orig, NewState())
}
func GenTest{{ .elementOriginName }}Slice() []{{ .itemType }} {
return []{{ .itemType }}{ {{ .testOrigVal }} }
}
================================================
FILE: internal/cmd/pdatagen/internal/pdata/templates/primitive_slice_test.go.tmpl
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package {{ .packageName }}
import (
{{ range $index, $element := .testImports -}}
{{ $element }}
{{ end }}
)
func TestNew{{ .structName }}(t *testing.T) {
ms := New{{ .structName }}()
assert.Equal(t, 0, ms.Len())
ms.FromRaw([]{{ .itemType }}{ {{ .testOrigVal }} })
assert.Equal(t, 3, ms.Len())
assert.Equal(t, []{{ .itemType }}{ {{ .testOrigVal }} }, ms.AsRaw())
ms.SetAt(1, {{ .itemType }}( {{ .testSetVal }} ))
assert.Equal(t, []{{ .itemType }}{ {{ .testNewVal }} }, ms.AsRaw())
ms.FromRaw([]{{ .itemType }}{ {{ index .testInterfaceOrigVal 2 }} })
assert.Equal(t, 1, ms.Len())
{{- if eq .itemType "float64" }}
assert.InDelta(t, {{ .itemType }}({{ index .testInterfaceOrigVal 2 }}), ms.At(0), 0.01)
{{- else }}
assert.Equal(t, {{ .itemType }}({{ index .testInterfaceOrigVal 2 }}), ms.At(0))
{{- end }}
cp := New{{ .structName }}()
ms.CopyTo(cp)
ms.SetAt(0, {{ .itemType }}( {{ index .testInterfaceOrigVal 1 }} ))
{{- if eq .itemType "float64" }}
assert.InDelta(t, {{ .itemType }}({{ index .testInterfaceOrigVal 1 }}), ms.At(0), 0.01)
{{- else }}
assert.Equal(t, {{ .itemType }}({{ index .testInterfaceOrigVal 1 }}), ms.At(0))
{{- end }}
{{- if eq .itemType "float64" }}
assert.InDelta(t, {{ .itemType }}({{ index .testInterfaceOrigVal 2 }}), cp.At(0), 0.01)
{{- else }}
assert.Equal(t, {{ .itemType }}({{ index .testInterfaceOrigVal 2 }}), cp.At(0))
{{- end }}
ms.CopyTo(cp)
{{- if eq .itemType "float64" }}
assert.InDelta(t, {{ .itemType }}({{ index .testInterfaceOrigVal 1 }}), cp.At(0), 0.01)
{{- else }}
assert.Equal(t, {{ .itemType }}({{ index .testInterfaceOrigVal 1 }}), cp.At(0))
{{- end }}
mv := New{{ .structName }}()
ms.MoveTo(mv)
assert.Equal(t, 0, ms.Len())
assert.Equal(t, 1, mv.Len())
{{- if eq .itemType "float64" }}
assert.InDelta(t, {{ .itemType }}({{index .testInterfaceOrigVal 1 }}), mv.At(0), 0.01)
{{- else }}
assert.Equal(t, {{ .itemType }}({{index .testInterfaceOrigVal 1 }}), mv.At(0))
{{- end }}
ms.FromRaw([]{{ .itemType }}{ {{ .testOrigVal }} })
ms.MoveTo(mv)
assert.Equal(t, 3, mv.Len())
{{- if eq .itemType "float64" }}
assert.InDelta(t, {{ .itemType }}({{index .testInterfaceOrigVal 0 }}), mv.At(0), 0.01)
{{- else }}
assert.Equal(t, {{ .itemType }}({{index .testInterfaceOrigVal 0 }}), mv.At(0))
{{- end }}
mv.MoveTo(mv)
assert.Equal(t, 3, mv.Len())
{{- if eq .itemType "float64" }}
assert.InDelta(t, {{ .itemType }}({{index .testInterfaceOrigVal 0 }}), mv.At(0), 0.01)
{{- else }}
assert.Equal(t, {{ .itemType }}({{index .testInterfaceOrigVal 0 }}), mv.At(0))
{{- end }}
}
func Test{{ .structName }}ReadOnly(t *testing.T) {
raw := []{{ .itemType }}{ {{ .testOrigVal }}}
sharedState := internal.NewState()
sharedState.MarkReadOnly()
ms := {{ .structName }}(internal.New{{ .structName }}Wrapper(&raw, sharedState))
assert.Equal(t, 3, ms.Len())
{{- if eq .itemType "float64" }}
assert.InDelta(t, {{ .itemType }}( {{index .testInterfaceOrigVal 0 }} ), ms.At(0), 0.01)
{{- else }}
assert.Equal(t, {{ .itemType }}({{ index .testInterfaceOrigVal 0 }}), ms.At(0))
{{- end }}
assert.Panics(t, func() { ms.Append({{ index .testInterfaceOrigVal 0 }}) })
assert.Panics(t, func() { ms.EnsureCapacity(2) })
assert.Equal(t, raw, ms.AsRaw())
assert.Panics(t, func() { ms.FromRaw(raw) })
ms2 := New{{ .structName }}()
ms.CopyTo(ms2)
assert.Equal(t, ms.AsRaw(), ms2.AsRaw())
assert.Panics(t, func() { ms2.CopyTo(ms) })
assert.Panics(t, func() { ms.MoveTo(ms2) })
assert.Panics(t, func() { ms2.MoveTo(ms) })
}
func Test{{ .structName }}Append(t *testing.T) {
ms := New{{ .structName }}()
ms.FromRaw([]{{ .itemType }}{ {{ .testOrigVal }} })
ms.Append({{ .testSetVal }}, {{ .testSetVal }})
assert.Equal(t, 5, ms.Len())
{{- if eq .itemType "float64" }}
assert.InDelta(t, {{ .itemType }}({{ .testSetVal }} ), ms.At(4), 0.01)
{{- else }}
assert.Equal(t, {{ .itemType }}({{ .testSetVal }}), ms.At(4))
{{- end }}
}
func Test{{ .structName }}EnsureCapacity(t *testing.T) {
ms := New{{ .structName }}()
ms.EnsureCapacity(4)
assert.Equal(t, 4, cap(*ms.getOrig()))
ms.EnsureCapacity(2)
assert.Equal(t, 4, cap(*ms.getOrig()))
}
func Test{{ .structName }}All(t *testing.T) {
ms := New{{ .structName }}()
ms.FromRaw([]{{ .itemType }}{ {{ .testOrigVal }} })
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func Test{{ .structName }}MoveAndAppendTo(t *testing.T) {
// Test moving from an empty slice
ms := New{{ .structName }}()
ms2 := New{{ .structName }}()
ms.MoveAndAppendTo(ms2)
assert.Equal(t, New{{ .structName }}(), ms2)
assert.Equal(t, ms.Len(), 0)
// Test moving to empty slice
ms.FromRaw([]{{ .itemType }}{ {{ .testOrigVal }} })
ms.MoveAndAppendTo(ms2)
assert.Equal(t, ms2.Len(), 3)
// Test moving to a non empty slice
ms.FromRaw([]{{ .itemType }}{ {{ .testOrigVal }} })
ms.MoveAndAppendTo(ms2)
assert.Equal(t, ms2.Len(), 6)
}
func Test{{ .structName }}RemoveIf(t *testing.T) {
emptySlice := New{{ .structName }}()
emptySlice.RemoveIf(func(el {{ .itemType }}) bool {
t.Fail()
return false
})
ms := New{{ .structName }}()
ms.FromRaw([]{{ .itemType }}{ {{ .testOrigVal }} })
pos := 0
ms.RemoveIf(func(el {{ .itemType }}) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, pos/2, ms.Len())
}
func Test{{ .structName }}RemoveIfAll(t *testing.T) {
ms := New{{ .structName }}()
ms.FromRaw([]{{ .itemType }}{ {{ .testOrigVal }} })
ms.RemoveIf(func(el {{ .itemType }}) bool {
return true
})
assert.Equal(t, 0, ms.Len())
}
func Test{{ .structName }}Equal(t *testing.T) {
ms := New{{ .structName }}()
ms2 := New{{ .structName }}()
assert.True(t, ms.Equal(ms2))
ms.Append({{ .testOrigVal }})
assert.False(t, ms.Equal(ms2))
ms2.Append({{ .testOrigVal }})
assert.True(t, ms.Equal(ms2))
}
func Benchmark{{ .structName }}Equal(b *testing.B) {
testutil.SkipMemoryBench(b)
ms := New{{ .structName }}()
ms.Append({{ .testOrigVal }})
cmp := New{{ .structName }}()
cmp.Append({{ .testOrigVal }})
b.ResetTimer()
b.ReportAllocs()
for n := 0; n < b.N; n++ {
_ = ms.Equal(cmp)
}
}
================================================
FILE: internal/cmd/pdatagen/internal/pdata/templates/slice.go.tmpl
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package {{ .packageName }}
import (
{{ range $index, $element := .imports -}}
{{ $element }}
{{ end }}
)
// {{ .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.
{{- if .hasWrapper }}
type {{ .structName }} internal.{{ .structName }}Wrapper
{{- else }}
type {{ .structName }} struct {
orig *[]{{ if .elementNullable }}*{{ end }}internal.{{ .elementOriginName }}
state *internal.State
}
{{- end }}
func new{{ .structName }}(orig *[]{{ if .elementNullable }}*{{ end }}internal.{{ .elementOriginName }}, state *internal.State) {{ .structName }} {
{{- if .hasWrapper }}
return {{ .structName }}(internal.New{{ .structName }}Wrapper(orig, state))
{{- else }}
return {{ .structName }}{orig: orig, state: state}
{{- end }}
}
// New{{ .structName }} creates a {{ .structName }}Wrapper with 0 elements.
// Can use "EnsureCapacity" to initialize with a given capacity.
func New{{ .structName }}() {{ .structName }} {
orig := []{{ if .elementNullable }}*{{ end }}internal.{{ .elementOriginName }}(nil)
return new{{ .structName }}(&orig, internal.NewState())
}
// 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.{{ .origAccessor }})
}
// 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(i int) {{ .elementName }} {
return new{{ .elementName }}({{ if not .elementNullable }}&{{ end }}(*es.{{ .origAccessor }})[i], es.{{ .stateAccessor }})
}
// All returns an iterator over index-value pairs in the slice.
//
// for i, v := range es.All() {
// ... // Do something with index-value pair
// }
func (es {{ .structName }}) All() iter.Seq2[int, {{ .elementName }}] {
return func(yield func(int, {{ .elementName }}) bool) {
for i := 0; i < es.Len(); i++ {
if !yield(i, es.At(i)) {
return
}
}
}
}
// EnsureCapacity is an operation that ensures the slice has at least the specified capacity.
// 1. If the newCap <= cap then no change in capacity.
// 2. If the newCap > cap then the slice capacity will be expanded to equal newCap.
//
// Here is how a new {{ .structName }} can be initialized:
// es := New{{ .structName }}()
// es.EnsureCapacity(4)
// for i := 0; i < 4; i++ {
// e := es.AppendEmpty()
// // Here should set all the values for e.
// }
func (es {{ .structName }}) EnsureCapacity(newCap int) {
es.{{ .stateAccessor }}.AssertMutable()
oldCap := cap(*es.{{ .origAccessor }})
if newCap <= oldCap {
return
}
newOrig := make([]{{ if .elementNullable }}*{{ end }}internal.{{ .elementOriginName }}, len(*es.{{ .origAccessor }}), newCap)
copy(newOrig, *es.{{ .origAccessor }})
*es.{{ .origAccessor }} = newOrig
}
// AppendEmpty will append to the end of the slice an empty {{ .elementName }}.
// It returns the newly added {{ .elementName }}.
func (es {{ .structName }}) AppendEmpty() {{ .elementName }} {
es.{{ .stateAccessor }}.AssertMutable()
*es.{{ .origAccessor }} = append(*es.{{ .origAccessor }}, {{- if .elementNullable }}internal.New{{ .elementOriginName }}(){{ else }}internal.{{ .elementOriginName }}{}{{ end }})
return es.At(es.Len() - 1)
}
// 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 }}) {
es.{{ .stateAccessor }}.AssertMutable()
dest.{{ .stateAccessor }}.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if es.{{ .origAccessor }} == dest.{{ .origAccessor }} {
return
}
if *dest.{{ .origAccessor }} == nil {
// We can simply move the entire vector and avoid any allocations.
*dest.{{ .origAccessor }} = *es.{{ .origAccessor }}
} else {
*dest.{{ .origAccessor }} = append(*dest.{{ .origAccessor }}, *es.{{ .origAccessor }}...)
}
*es.{{ .origAccessor }} = nil
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (es {{ .structName }}) RemoveIf(f func({{ .elementName }}) bool) {
es.{{ .stateAccessor }}.AssertMutable()
newLen := 0
for i := 0; i < len(*es.{{ .origAccessor }}); i++ {
if f(es.At(i)) {
{{ if .elementNullable -}}
internal.Delete{{ .elementOriginName }}((*es.{{ .origAccessor }})[i], true)
(*es.{{ .origAccessor }})[i] = nil
{{ else -}}
internal.Delete{{ .elementOriginName }}(&(*es.{{ .origAccessor }})[i], false)
{{- end }}
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*es.{{ .origAccessor }})[newLen] = (*es.{{ .origAccessor }})[i]
{{ if .elementNullable -}}
// Cannot delete here since we just move the data(or pointer to data) to a different position in the slice.
(*es.{{ .origAccessor }})[i] = nil{{ else -}}
(*es.{{ .origAccessor }})[i].Reset()
{{- end }}
newLen++
}
*es.{{ .origAccessor }} = (*es.{{ .origAccessor }})[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (es {{ .structName }}) CopyTo(dest {{ .structName }}) {
dest.{{ .stateAccessor }}.AssertMutable()
if es.{{ .origAccessor }} == dest.{{ .origAccessor }} {
return
}
*dest.{{ .origAccessor }} = internal.Copy{{ .elementOriginName }}{{ if .elementNullable }}Ptr{{ end }}Slice(*dest.{{ .origAccessor }}, *es.{{ .origAccessor }})
}
{{ if .elementNullable -}}
// Sort sorts the {{ .elementName }} elements within {{ .structName }} given the
// provided less function so that two instances of {{ .structName }}
// can be compared.
func (es {{ .structName }}) Sort(less func(a, b {{ .elementName }}) bool) {
es.{{ .stateAccessor }}.AssertMutable()
sort.SliceStable(*es.{{ .origAccessor }}, func(i, j int) bool { return less(es.At(i), es.At(j)) })
}
{{- end }}
{{ if .hasWrapper -}}
func (ms {{ .structName }}) getOrig() *[]{{ if .elementNullable }}*{{ end }}internal.{{ .elementOriginName }} {
return internal.Get{{ .structName }}Orig(internal.{{ .structName }}Wrapper(ms))
}
func (ms {{ .structName }}) getState() *internal.State {
return internal.Get{{ .structName }}State(internal.{{ .structName }}Wrapper(ms))
}
{{- end }}
================================================
FILE: internal/cmd/pdatagen/internal/pdata/templates/slice_internal.go.tmpl
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
{{ range $index, $element := .imports -}}
{{ $element }}
{{ end }}
)
type {{ .structName }}Wrapper struct {
orig *[]{{ if .elementNullable }}*{{ end }}{{ .elementOriginName }}
state *State
}
func Get{{ .structName }}Orig(ms {{ .structName }}Wrapper) *[]{{ if .elementNullable }}*{{ end }}{{ .elementOriginName }} {
return ms.orig
}
func Get{{ .structName }}State(ms {{ .structName }}Wrapper) *State {
return ms.state
}
func New{{ .structName }}Wrapper(orig *[]{{ if .elementNullable }}*{{ end }}{{ .elementOriginName }}, state *State) {{ .structName }}Wrapper {
return {{ .structName }}Wrapper{orig: orig, state: state}
}
func GenTest{{ .structName }}Wrapper() {{ .structName }}Wrapper {
orig := GenTest{{ .elementOriginName }}{{ if .elementNullable }}Ptr{{ end }}Slice()
return New{{ .structName }}Wrapper(&orig, NewState())
}
================================================
FILE: internal/cmd/pdatagen/internal/pdata/templates/slice_test.go.tmpl
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package {{ .packageName }}
import (
{{ range $index, $element := .testImports -}}
{{ $element }}
{{ end }}
)
func Test{{ .structName }}(t *testing.T) {
es := New{{ .structName }}()
assert.Equal(t, 0, es.Len())
es = new{{ .structName }}(&[]{{ if .elementNullable }}*{{ end }}internal.{{ .elementOriginName }}{}, internal.NewState())
assert.Equal(t, 0, es.Len())
{{ if eq .elementName "Value" -}}
emptyVal := New{{ .elementName }}Empty()
{{- else }}
emptyVal := New{{ .elementName }}()
{{- end }}
{{- if .hasWrapper }}
testVal := {{ .elementName }}(internal.GenTest{{ .elementName }}Wrapper())
{{- else }}
testVal := generateTest{{ .elementName }}()
{{- end }}
for i := 0; i < 7; i++ {
es.AppendEmpty()
assert.Equal(t, emptyVal, es.At(i))
(*es.{{ .origAccessor }})[i] = {{ if not .elementNullable }}*{{ end }}internal.GenTest{{ .elementOriginName }}()
assert.Equal(t, testVal, es.At(i))
}
assert.Equal(t, 7, es.Len())
}
func Test{{ .structName }}ReadOnly(t *testing.T) {
sharedState := internal.NewState()
sharedState.MarkReadOnly()
es := new{{ .structName }}(&[]{{ if .elementNullable }}*{{ end }}internal.{{ .elementOriginName }}{}, sharedState)
assert.Equal(t, 0, es.Len())
assert.Panics(t, func() { es.AppendEmpty() })
assert.Panics(t, func() { es.EnsureCapacity(2) })
es2 := New{{ .structName }}()
es.CopyTo(es2)
assert.Panics(t, func() { es2.CopyTo(es) })
assert.Panics(t, func() { es.MoveAndAppendTo(es2) })
assert.Panics(t, func() { es2.MoveAndAppendTo(es) })
}
func Test{{ .structName }}_CopyTo(t *testing.T) {
dest := New{{ .structName }}()
src := generateTest{{ .structName }}()
src.CopyTo(dest)
assert.Equal(t, generateTest{{ .structName }}(), dest)
dest.CopyTo(dest)
assert.Equal(t, generateTest{{ .structName }}(), dest)
}
func Test{{ .structName }}_EnsureCapacity(t *testing.T) {
es := generateTest{{ .structName }}()
// Test ensure smaller capacity.
const ensureSmallLen = 4
es.EnsureCapacity(ensureSmallLen)
assert.Less(t, ensureSmallLen, es.Len())
assert.Equal(t, es.Len(), cap(*es.{{ .origAccessor }}))
assert.Equal(t, generateTest{{ .structName }}(), es)
// Test ensure larger capacity
const ensureLargeLen = 9
es.EnsureCapacity(ensureLargeLen)
assert.Less(t, generateTest{{ .structName }}().Len(), ensureLargeLen)
assert.Equal(t, ensureLargeLen, cap(*es.{{ .origAccessor }}))
assert.Equal(t, generateTest{{ .structName }}(), es)
}
func Test{{ .structName }}_MoveAndAppendTo(t *testing.T) {
// Test MoveAndAppendTo to empty
expectedSlice := generateTest{{ .structName }}()
dest := New{{ .structName }}()
src := generateTest{{ .structName }}()
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTest{{ .structName }}(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo empty slice
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTest{{ .structName }}(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo not empty slice
generateTest{{ .structName }}().MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
dest.MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
}
func Test{{ .structName }}_RemoveIf(t *testing.T) {
// Test RemoveIf on empty slice
emptySlice := New{{ .structName }}()
emptySlice.RemoveIf(func(el {{ .elementName }}) bool {
t.Fail()
return false
})
// Test RemoveIf
filtered := generateTest{{ .structName }}()
pos := 0
filtered.RemoveIf(func(el {{ .elementName }}) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, 2, filtered.Len())
}
func Test{{ .structName }}_RemoveIfAll(t *testing.T) {
got := generateTest{{ .structName }}()
got.RemoveIf(func(el {{ .elementName }}) bool {
return true
})
assert.Equal(t, 0, got.Len())
}
func Test{{ .structName }}All(t *testing.T) {
ms := generateTest{{ .structName }}()
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
{{ if .elementNullable -}}
func Test{{ .structName }}_Sort(t *testing.T) {
es := generateTest{{ .structName }}()
es.Sort(func(a, b {{ .elementName }}) bool {
return uintptr(unsafe.Pointer(a.{{ .origAccessor }})) < uintptr(unsafe.Pointer(b.{{ .origAccessor }}))
})
for i := 1; i < es.Len(); i++ {
assert.Less(t, uintptr(unsafe.Pointer(es.At(i-1).{{ .origAccessor }})), uintptr(unsafe.Pointer(es.At(i).{{ .origAccessor }})))
}
es.Sort(func(a, b {{ .elementName }}) bool {
return uintptr(unsafe.Pointer(a.{{ .origAccessor }})) > uintptr(unsafe.Pointer(b.{{ .origAccessor }}))
})
for i := 1; i < es.Len(); i++ {
assert.Greater(t, uintptr(unsafe.Pointer(es.At(i-1).{{ .origAccessor }})), uintptr(unsafe.Pointer(es.At(i).{{ .origAccessor }})))
}
}
{{- end }}
func generateTest{{ .structName }}() {{ .structName }} {
ms := New{{ .structName }}()
*ms.{{ .origAccessor }} = internal.GenTest{{ .elementOriginName }}{{ if .elementNullable }}Ptr{{ end }}Slice()
return ms
}
================================================
FILE: internal/cmd/pdatagen/internal/pdata/templates.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pdata // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/pdata"
import (
_ "embed" // Blank import required for go:embed to work.
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/tmplutil"
)
var (
//go:embed templates/message.go.tmpl
messageTemplateBytes []byte
messageTemplate = tmplutil.Parse("message.go", messageTemplateBytes)
//go:embed templates/message_internal.go.tmpl
messageInternalTemplateBytes []byte
messageInternalTemplate = tmplutil.Parse("message_internal.go", messageInternalTemplateBytes)
//go:embed templates/message_test.go.tmpl
messageTestTemplateBytes []byte
messageTestTemplate = tmplutil.Parse("message_test.go", messageTestTemplateBytes)
//go:embed templates/primitive_slice.go.tmpl
primitiveSliceTemplateBytes []byte
primitiveSliceTemplate = tmplutil.Parse("primitive_slice.go", primitiveSliceTemplateBytes)
//go:embed templates/primitive_slice_internal.go.tmpl
primitiveSliceInternalTemplateBytes []byte
primitiveSliceInternalTemplate = tmplutil.Parse("primitive_slice_internal.go", primitiveSliceInternalTemplateBytes)
//go:embed templates/primitive_slice_test.go.tmpl
primitiveSliceTestTemplateBytes []byte
primitiveSliceTestTemplate = tmplutil.Parse("primitive_slice_test.go", primitiveSliceTestTemplateBytes)
//go:embed templates/slice.go.tmpl
sliceTemplateBytes []byte
sliceTemplate = tmplutil.Parse("slice.go", sliceTemplateBytes)
//go:embed templates/slice_internal.go.tmpl
sliceInternalTemplateBytes []byte
sliceInternalTemplate = tmplutil.Parse("slice_internal.go", sliceInternalTemplateBytes)
//go:embed templates/slice_test.go.tmpl
sliceTestTemplateBytes []byte
sliceTestTemplate = tmplutil.Parse("slice_test.go", sliceTestTemplateBytes)
)
================================================
FILE: internal/cmd/pdatagen/internal/pdata/typed_field.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pdata // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/pdata"
import (
"strings"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/tmplutil"
)
const typedAccessorsTemplate = `// {{ .fieldName }} returns the {{ .lowerFieldName }} associated with this {{ .structName }}.
func (ms {{ .structName }}) {{ .fieldName }}() {{ .packageName }}{{ .returnType }} {
return {{ .packageName }}{{ .returnType }}(ms.orig.{{ .originFieldName }})
}
// Set{{ .fieldName }} replaces the {{ .lowerFieldName }} associated with this {{ .structName }}.
func (ms {{ .structName }}) Set{{ .fieldName }}(v {{ .packageName }}{{ .returnType }}) {
ms.state.AssertMutable()
ms.orig.{{ .originFieldName }} = {{ .messageType }}(v)
}`
const typedAccessorsTestTemplate = `func Test{{ .structName }}_{{ .fieldName }}(t *testing.T) {
ms := New{{ .structName }}()
assert.Equal(t, {{ .packageName }}{{ .returnType }}({{ .defaultVal }}), ms.{{ .fieldName }}())
testVal{{ .fieldName }} := {{ .packageName }}{{ .returnType }}({{ .testValue }})
ms.Set{{ .fieldName }}(testVal{{ .fieldName }})
assert.Equal(t, testVal{{ .fieldName }}, ms.{{ .fieldName }}())
}`
const typedSetTestTemplate = `orig.{{ .originFieldName }} = {{ .testValue }}`
// TypedField is a field that has defined a custom type (e.g. "type Timestamp uint64")
type TypedField struct {
fieldName string
originFieldName string
protoID uint32
returnType *TypedType
}
type TypedType struct {
structName string
packageName string
protoType proto.Type
messageName string
defaultVal string
testVal string
}
func (ptf *TypedField) GenerateAccessors(ms *messageStruct) string {
t := tmplutil.Parse("typedAccessorsTemplate", []byte(typedAccessorsTemplate))
return tmplutil.Execute(t, ptf.templateFields(ms))
}
func (ptf *TypedField) GenerateAccessorsTest(ms *messageStruct) string {
t := tmplutil.Parse("typedAccessorsTestTemplate", []byte(typedAccessorsTestTemplate))
return tmplutil.Execute(t, ptf.templateFields(ms))
}
func (ptf *TypedField) GenerateTestValue(ms *messageStruct) string {
t := tmplutil.Parse("typedSetTestTemplate", []byte(typedSetTestTemplate))
return tmplutil.Execute(t, ptf.templateFields(ms))
}
type ProtoTypedField struct {
*proto.Field
}
func (ptf ProtoTypedField) GenMarshalJSON() string {
if ptf.MessageName == "TraceID" || ptf.MessageName == "SpanID" || ptf.MessageName == "ProfileID" {
return "if !orig." + ptf.Name + ".IsEmpty() {\n" + ptf.Field.GenMarshalJSON() + "\n}"
}
return ptf.Field.GenMarshalJSON()
}
func (ptf *TypedField) toProtoField(ms *messageStruct) proto.FieldInterface {
return ProtoTypedField{&proto.Field{
Type: ptf.returnType.protoType,
ID: ptf.protoID,
Name: ptf.getOriginFieldName(),
MessageName: ptf.returnType.messageName,
ParentMessageName: ms.protoName,
}}
}
func (ptf *TypedField) getOriginFieldName() string {
if ptf.originFieldName == "" {
return ptf.fieldName
}
return ptf.originFieldName
}
func (ptf *TypedField) templateFields(ms *messageStruct) map[string]any {
pf := ptf.toProtoField(ms)
messageType := pf.GoType()
defaultVal := ptf.returnType.defaultVal
testVal := ptf.returnType.testVal
if ptf.returnType.protoType == proto.TypeMessage || ptf.returnType.protoType == proto.TypeEnum {
messageType = "internal." + messageType
defaultVal = "internal." + defaultVal
testVal = "internal." + testVal
}
return map[string]any{
"structName": ms.getName(),
"defaultVal": defaultVal,
"packageName": func() string {
if ptf.returnType.packageName != ms.packageName {
return ptf.returnType.packageName + "."
}
return ""
}(),
"hasWrapper": usedByOtherDataTypes(ptf.returnType.packageName),
"returnType": ptf.returnType.structName,
"fieldName": ptf.fieldName,
"originFieldName": ptf.getOriginFieldName(),
"lowerFieldName": strings.ToLower(ptf.fieldName),
"testValue": testVal,
"messageType": messageType,
}
}
var _ Field = (*TypedField)(nil)
================================================
FILE: internal/cmd/pdatagen/internal/pdata/xpdata_entity_package.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pdata // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/pdata"
import (
"path/filepath"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
)
var xpdataEntity = &Package{
info: &PackageInfo{
name: "entity",
path: filepath.Join("xpdata", "entity"),
imports: []string{
`"encoding/binary"`,
`"fmt"`,
`"iter"`,
`"math"`,
`"sort"`,
``,
`"go.opentelemetry.io/collector/pdata/internal"`,
`"go.opentelemetry.io/collector/pdata/internal/json"`,
`"go.opentelemetry.io/collector/pdata/internal/proto"`,
`"go.opentelemetry.io/collector/pdata/pcommon"`,
},
testImports: []string{
`"testing"`,
``,
`"github.com/stretchr/testify/assert"`,
`gootlpcommon "go.opentelemetry.io/proto/slim/otlp/common/v1"`,
``,
`"go.opentelemetry.io/collector/pdata/internal"`,
`"go.opentelemetry.io/collector/pdata/internal/json"`,
`"go.opentelemetry.io/collector/pdata/pcommon"`,
},
},
structs: []baseStruct{
entityRefSlice,
entityRef,
},
}
var entityRefSlice = &messageSlice{
structName: "EntityRefSlice",
packageName: "entity",
elementNullable: true,
element: entityRef,
}
var entityRef = &messageStruct{
structName: "EntityRef",
packageName: "entity",
protoName: "EntityRef",
upstreamProto: "gootlpcommon.EntityRef",
fields: []Field{
&PrimitiveField{
fieldName: "SchemaUrl",
protoID: 1,
protoType: proto.TypeString,
},
&PrimitiveField{
fieldName: "Type",
protoID: 2,
protoType: proto.TypeString,
},
&SliceField{
fieldName: "IdKeys",
protoID: 3,
protoType: proto.TypeString,
returnSlice: stringSlice,
},
&SliceField{
fieldName: "DescriptionKeys",
protoID: 4,
protoType: proto.TypeString,
returnSlice: stringSlice,
},
},
}
================================================
FILE: internal/cmd/pdatagen/internal/proto/copy.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package proto // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
import (
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/tmplutil"
)
const copyOther = `{{ if .repeated -}}
dest.{{ .fieldName }} = append(dest.{{ .fieldName }}[:0], src.{{ .fieldName }}...)
{{ else if ne .oneOfGroup "" -}}
var ov *{{ .oneOfMessageName }}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &{{ .oneOfMessageName }}{}
} else {
ov = ProtoPool{{ .oneOfMessageName }}.Get().(*{{ .oneOfMessageName }})
}
ov.{{ .fieldName }} = t.{{ .fieldName }}
dest.{{ .oneOfGroup }} = ov
{{ else if .nullable -}}
if src.Has{{ .fieldName }}() {
dest.Set{{ .fieldName }}(src.{{ .fieldName }})
} else {
dest.Remove{{ .fieldName }}()
}
{{ else -}}
dest.{{ .fieldName }} = src.{{ .fieldName }}
{{- end }}`
const copyMessage = `{{ if .repeated -}}
dest.{{ .fieldName }} = Copy{{ .messageName }}{{ if .nullable }}Ptr{{ end }}Slice(dest.{{ .fieldName }}, src.{{ .fieldName }})
{{- else if ne .oneOfGroup "" -}}
var ov *{{ .oneOfMessageName }}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &{{ .oneOfMessageName }}{}
} else {
ov = ProtoPool{{ .oneOfMessageName }}.Get().(*{{ .oneOfMessageName }})
}
ov.{{ .fieldName }} = New{{ .messageName }}()
Copy{{ .messageName }}(ov.{{ .fieldName }}, t.{{ .fieldName }})
dest.{{ .oneOfGroup }} = ov
{{- else if .nullable -}}
dest.{{ .fieldName }} = Copy{{ .messageName }}(dest.{{ .fieldName }}, src.{{ .fieldName }})
{{- else -}}
Copy{{ .messageName }}(&dest.{{ .fieldName }}, &src.{{ .fieldName }})
{{- end }}
`
func (pf *Field) GenCopy() string {
tf := pf.getTemplateFields()
if pf.Type == TypeMessage {
return tmplutil.Execute(tmplutil.Parse("copyMessage", []byte(copyMessage)), tf)
}
return tmplutil.Execute(tmplutil.Parse("copyOther", []byte(copyOther)), tf)
}
================================================
FILE: internal/cmd/pdatagen/internal/proto/delete.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package proto // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
import (
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/tmplutil"
)
const deleteOther = `{{ if ne .oneOfGroup "" -}}
if metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov.{{ .fieldName }} = {{ .defaultValue }}
ProtoPool{{ .oneOfMessageName }}.Put(ov)
}
{{- end -}}`
const deleteMessage = `{{ if .repeated -}}
for i := range orig.{{ .fieldName }} {
{{ if .nullable -}}
Delete{{ .messageName }}(orig.{{ .fieldName }}[i], true)
{{ else -}}
Delete{{ .messageName }}(&orig.{{ .fieldName }}[i], false)
{{- end -}}
}
{{- else if ne .oneOfGroup "" -}}
Delete{{ .messageName }}(ov.{{ .fieldName }}, true)
ov.{{ .fieldName }} = nil
ProtoPool{{ .oneOfMessageName }}.Put(ov)
{{- else if .nullable -}}
Delete{{ .messageName }}(orig.{{ .fieldName }}, true)
{{- else -}}
Delete{{ .messageName }}(&orig.{{ .fieldName }}, false)
{{- end -}}
`
func (pf *Field) GenDelete() string {
tf := pf.getTemplateFields()
if pf.Type == TypeMessage {
return tmplutil.Execute(tmplutil.Parse("deleteMessage", []byte(deleteMessage)), tf)
}
if pf.OneOfGroup != "" {
return tmplutil.Execute(tmplutil.Parse("deleteOther", []byte(deleteOther)), tf)
}
return ""
}
================================================
FILE: internal/cmd/pdatagen/internal/proto/enum.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package proto // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
import (
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/tmplutil"
)
const enumMessageTemplate = `
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
const (
{{- range .Fields }}
{{ $.Name }}_{{ .Name }} = {{ $.Name }}({{ .Value }})
{{- end }}
)
{{ .Description }}
type {{ .Name }} int32
var {{ .Name }}_name = map[int32]string {
{{- range .Fields }}
{{ .Value }}: "{{ .Name }}",
{{- end }}
}
var {{ .Name }}_value = map[string]int32 {
{{- range .Fields }}
"{{ .Name }}": {{ .Value }},
{{- end }}
}
`
type Enum struct {
Name string
Description string
Fields []*EnumField
}
type EnumField struct {
Name string
Value int
}
func (ms *Enum) GenerateEnum() []byte {
return []byte(tmplutil.Execute(tmplutil.Parse("enumMessageTemplate", []byte(enumMessageTemplate)), ms))
}
================================================
FILE: internal/cmd/pdatagen/internal/proto/field.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package proto // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
import (
"fmt"
"strings"
)
// FieldInterface temporary interface until we generate the proto fields with pdatagen.
// TODO: Remove when no more wrappers needed.
type FieldInterface interface {
GenTestFailingUnmarshalProtoValues() string
GenTestEncodingValues() string
GenPool() string
GenDelete() string
GenCopy() string
GenMarshalJSON() string
GenUnmarshalJSON() string
GenSizeProto() string
GenMarshalProto() string
GenUnmarshalProto() string
GenMessageField() string
GenOneOfMessages() string
GenTest() string
GoType() string
DefaultValue() string
TestValue() string
GetName() string
}
type Field struct {
Type Type
Name string
OneOfGroup string
OneOfMessageName string
MessageName string
ParentMessageName string
ID uint32
Repeated bool
Nullable bool
}
func (pf *Field) GetName() string { return pf.Name }
func (pf *Field) wireType() WireType {
switch pf.Type {
case TypeInt32, TypeInt64, TypeUint32, TypeUint64, TypeSInt32, TypeSInt64, TypeBool, TypeEnum:
// In proto3, repeated scalar types are packed; hence they use Length-Delimited (Wire Type 2).
if pf.Repeated {
return WireTypeLen
}
return WireTypeVarint
case TypeFixed32, TypeSFixed32, TypeFloat:
// In proto3, repeated scalar types are packed; hence they use Length-Delimited (Wire Type 2).
if pf.Repeated {
return WireTypeLen
}
return WireTypeI32
case TypeFixed64, TypeSFixed64, TypeDouble:
// In proto3, repeated scalar types are packed; hence they use Length-Delimited (Wire Type 2).
if pf.Repeated {
return WireTypeLen
}
return WireTypeI64
case TypeBytes, TypeMessage, TypeString:
return WireTypeLen
default:
panic("unsupported field type")
}
}
func (pf *Field) DefaultValue() string {
switch pf.Type {
case TypeInt32, TypeInt64, TypeUint32, TypeUint64, TypeSInt32, TypeSInt64, TypeEnum, TypeFixed32, TypeSFixed32, TypeFloat, TypeFixed64, TypeSFixed64, TypeDouble:
return pf.GoType() + `(0)`
case TypeBool:
return `false`
case TypeBytes:
return `nil`
case TypeString:
return `""`
case TypeMessage:
if pf.Nullable {
return `&` + pf.MessageName + `{}`
}
return pf.MessageName + `{}`
default:
panic("unsupported field type")
}
}
func (pf *Field) GenTest() string {
if pf.Repeated {
return "orig." + pf.GetName() + " = " + pf.TestValue()
}
if pf.Type != TypeMessage && pf.Nullable {
return "orig.Set" + pf.GetName() + "(" + pf.TestValue() + ")"
}
return "orig." + pf.GetName() + " = " + pf.TestValue()
}
func (pf *Field) TestValue() string {
if pf.Repeated {
return pf.MemberGoType() + "{" + pf.DefaultValue() + ", " + pf.rawTestValue() + "}"
}
return pf.rawTestValue()
}
func (pf *Field) rawTestValue() string {
switch pf.Type {
case TypeInt32, TypeInt64, TypeUint32, TypeUint64, TypeSInt32, TypeSInt64, TypeEnum, TypeFixed32, TypeSFixed32, TypeFixed64, TypeSFixed64:
return pf.GoType() + "(13)"
case TypeFloat, TypeDouble:
return pf.GoType() + "(3.1415926)"
case TypeBool:
return `true`
case TypeBytes:
return `[]byte{1, 2, 3}`
case TypeString:
return `"test_` + strings.ToLower(pf.Name) + `"`
case TypeMessage:
if pf.Nullable {
return `GenTest` + pf.MessageName + `()`
}
return `*GenTest` + pf.MessageName + `()`
default:
panic("unsupported field type")
}
}
func (pf *Field) GoType() string {
switch pf.Type {
case TypeDouble:
return "float64"
case TypeFloat:
return "float32"
case TypeInt32, TypeSInt32, TypeSFixed32:
return "int32"
case TypeInt64, TypeSInt64, TypeSFixed64:
return "int64"
case TypeUint32, TypeFixed32:
return "uint32"
case TypeUint64, TypeFixed64:
return "uint64"
case TypeBool:
return "bool"
case TypeString:
return "string"
case TypeBytes:
return "[]byte"
case TypeMessage, TypeEnum:
return pf.MessageName
default:
panic("unsupported field type")
}
}
func (pf *Field) MemberGoType() string {
ptrGoType := func() string {
if pf.Nullable {
return "*" + pf.GoType()
}
return pf.GoType()
}
if pf.Repeated {
return "[]" + ptrGoType()
}
if pf.Type == TypeMessage {
return ptrGoType()
}
return pf.GoType()
}
func (pf *Field) GenMessageField() string {
return pf.Name + " " + pf.MemberGoType()
}
func (pf *Field) getTemplateFields() map[string]any {
bitSize := 0
switch pf.Type {
case TypeFixed64, TypeSFixed64, TypeInt64, TypeUint64, TypeSInt64, TypeDouble:
bitSize = 64
case TypeFixed32, TypeSFixed32, TypeInt32, TypeUint32, TypeSInt32, TypeFloat, TypeEnum:
bitSize = 32
}
protoTag := genProtoTag(pf.ID, pf.wireType())
return map[string]any{
"protoTagSize": len(protoTag),
"protoTag": protoTag,
"protoFieldID": pf.ID,
"jsonTag": genJSONTag(pf.Name),
"fieldName": pf.Name,
"messageName": pf.MessageName,
"parentMessageName": pf.ParentMessageName,
"oneOfGroup": pf.OneOfGroup,
"oneOfMessageName": pf.OneOfMessageName,
"repeated": pf.Repeated,
"nullable": pf.Nullable,
"bitSize": bitSize,
"goType": pf.GoType(),
"defaultValue": pf.DefaultValue(),
"testValue": pf.TestValue(),
}
}
func genJSONTag(fieldName string) string {
// Extract last word because for Enums we use the full name.
return lowerFirst(ExtractNameFromFull(fieldName))
}
// genProtoTag encodes the field key, and returns it in the reverse order.
func genProtoTag(fieldNumber uint32, wt WireType) []string {
x := fieldNumber<<3 | uint32(wt)
i := 0
keybuf := make([]byte, 0)
for i = 0; x > 127; i++ {
keybuf = append(keybuf, 0x80|uint8(x&0x7F))
x >>= 7
}
keybuf = append(keybuf, uint8(x))
ret := make([]string, 0, len(keybuf))
for i = len(keybuf) - 1; i >= 0; i-- {
ret = append(ret, fmt.Sprintf("%#v", keybuf[i]))
}
return ret
}
func ExtractNameFromFull(fullName string) string {
// Extract last word because for Enums we use the full name.
lastSpaceIndex := strings.LastIndex(fullName, ".")
if lastSpaceIndex != -1 {
return fullName[lastSpaceIndex+1:]
}
return fullName
}
func lowerFirst(s string) string {
return strings.ToLower(s[0:1]) + s[1:]
}
================================================
FILE: internal/cmd/pdatagen/internal/proto/json_marshal.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package proto // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
import (
"fmt"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/tmplutil"
)
const marshalJSONPrimitive = `{{ if .repeated -}}
if len(orig.{{ .fieldName }}) > 0 {
dest.WriteObjectField("{{ .jsonTag }}")
dest.WriteArrayStart()
dest.Write{{ upperFirst .goType }}(orig.{{ .fieldName }}[0])
for i := 1; i < len(orig.{{ .fieldName }}); i++ {
dest.WriteMore()
dest.Write{{ upperFirst .goType }}(orig.{{ .fieldName }}[i])
}
dest.WriteArrayEnd()
}
{{ else if ne .oneOfGroup "" -}}
dest.WriteObjectField("{{ .jsonTag }}")
dest.Write{{ upperFirst .goType }}(orig.{{ .fieldName }})
{{- else }}
{{- if not .nullable -}}
if orig.{{ .fieldName }} != {{ .defaultValue }} {
{{- else -}}
if orig.Has{{ .fieldName }} () {
{{ end -}}
dest.WriteObjectField("{{ .jsonTag }}")
dest.Write{{ upperFirst .goType }}(orig.{{ .fieldName }})
}
{{- end }}`
const marshalJSONEnum = `{{ if .repeated -}}
if len(orig.{{ .fieldName }}) > 0 {
dest.WriteObjectField("{{ .jsonTag }}")
dest.WriteArrayStart()
dest.WriteInt32(int32(orig.{{ .fieldName }}[0]))
for i := 1; i < len(orig.{{ .fieldName }}); i++ {
dest.WriteMore()
dest.WriteInt32(int32(orig.{{ .fieldName }}[i]))
}
dest.WriteArrayEnd()
}
{{- else }}
if int32(orig.{{ .fieldName }}) != 0 {
dest.WriteObjectField("{{ .jsonTag }}")
dest.WriteInt32(int32(orig.{{ .fieldName }}))
}
{{- end }}`
const marshalJSONMessage = `{{ if .repeated -}}
if len(orig.{{ .fieldName }}) > 0 {
dest.WriteObjectField("{{ .jsonTag }}")
dest.WriteArrayStart()
orig.{{ .fieldName }}[0].MarshalJSON(dest)
for i := 1; i < len(orig.{{ .fieldName }}); i++ {
dest.WriteMore()
orig.{{ .fieldName }}[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
{{- else }}
{{- if .nullable -}}
if orig.{{ .fieldName }} != nil {
{{ end -}}
dest.WriteObjectField("{{ .jsonTag }}")
orig.{{ .fieldName }}.MarshalJSON(dest)
{{- if .nullable -}}
}
{{- end }}{{- end }}`
const marshalJSONBytes = `{{ if .repeated -}}
if len(orig.{{ .fieldName }}) > 0 {
dest.WriteObjectField("{{ .jsonTag }}")
dest.WriteArrayStart()
dest.WriteBytes(orig.{{ .fieldName }}[0])
for i := 1; i < len(orig.{{ .fieldName }}); i++ {
dest.WriteMore()
dest.WriteBytes(orig.{{ .fieldName }}[i])
}
dest.WriteArrayEnd()
}
{{- else }}{{ if not .nullable }}
if len(orig.{{ .fieldName }}) > 0 {
{{- end }}
dest.WriteObjectField("{{ .jsonTag }}")
dest.WriteBytes(orig.{{ .fieldName }})
{{- if not .nullable -}}
}
{{- end }}{{- end }}`
func (pf *Field) GenMarshalJSON() string {
tf := pf.getTemplateFields()
switch pf.Type {
case TypeBytes:
return tmplutil.Execute(tmplutil.Parse("marshalJSONBytes", []byte(marshalJSONBytes)), tf)
case TypeMessage:
return tmplutil.Execute(tmplutil.Parse("marshalJSONMessage", []byte(marshalJSONMessage)), tf)
case TypeEnum:
return tmplutil.Execute(tmplutil.Parse("marshalJSONEnum", []byte(marshalJSONEnum)), tf)
case TypeDouble, TypeFloat,
TypeFixed64, TypeSFixed64, TypeFixed32, TypeSFixed32,
TypeInt32, TypeInt64, TypeUint32, TypeUint64,
TypeSInt32, TypeSInt64,
TypeBool, TypeString:
return tmplutil.Execute(tmplutil.Parse("marshalJSONPrimitive", []byte(marshalJSONPrimitive)), tf)
}
panic(fmt.Sprintf("unhandled case %T", pf.Type))
}
================================================
FILE: internal/cmd/pdatagen/internal/proto/json_unmarshal.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package proto // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
import (
"fmt"
"strings"
"github.com/ettle/strcase"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/tmplutil"
)
const unmarshalJSONPrimitive = ` case {{ .allJSONTags }}:
{{ if .repeated -}}
for iter.ReadArray() {
orig.{{ .fieldName }} = append(orig.{{ .fieldName }}, iter.Read{{ upperFirst .goType }}())
}
{{ else if ne .oneOfGroup "" -}}
{
var ov *{{ .oneOfMessageName }}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &{{ .oneOfMessageName }}{}
} else {
ov = ProtoPool{{ .oneOfMessageName }}.Get().(*{{ .oneOfMessageName }})
}
ov.{{ .fieldName }} = iter.Read{{ upperFirst .goType }}()
orig.{{ .oneOfGroup }} = ov
}
{{ else if .nullable -}}
orig.Set{{ .fieldName }}(iter.Read{{ upperFirst .goType }}())
{{ else -}}
orig.{{ .fieldName }} = iter.Read{{ upperFirst .goType }}()
{{- end }}`
const unmarshalJSONEnum = ` case {{ .allJSONTags }}:
{{ if .repeated -}}
for iter.ReadArray() {
orig.{{ .fieldName }} = append(orig.{{ .fieldName }}, {{ .messageName }}(iter.ReadEnumValue({{ .messageName }}_value)))
}
{{ else -}}
orig.{{ .fieldName }} = {{ .messageName }}(iter.ReadEnumValue({{ .messageName }}_value))
{{- end }}`
const unmarshalJSONMessage = ` case {{ .allJSONTags }}:
{{ if .repeated -}}
for iter.ReadArray() {
orig.{{ .fieldName }} = append(orig.{{ .fieldName }}, {{ if .nullable }}New{{ .messageName }}(){{ else }}{{ .defaultValue }}{{ end }})
orig.{{ .fieldName }}[len(orig.{{ .fieldName }}) - 1].UnmarshalJSON(iter)
}
{{ else if ne .oneOfGroup "" -}}
{
var ov *{{ .oneOfMessageName }}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &{{ .oneOfMessageName }}{}
} else {
ov = ProtoPool{{ .oneOfMessageName }}.Get().(*{{ .oneOfMessageName }})
}
ov.{{ .fieldName }} = New{{ .messageName }}()
ov.{{ .fieldName }}.UnmarshalJSON(iter)
orig.{{ .oneOfGroup }} = ov
}
{{ else -}}
{{ if .nullable }}orig.{{ .fieldName }} = New{{ .messageName }}(){{ end }}
orig.{{ .fieldName }}.UnmarshalJSON(iter)
{{- end }}`
const unmarshalJSONBytes = ` case {{ .allJSONTags }}:
{{ if .repeated -}}
for iter.ReadArray() {
orig.{{ .fieldName }} = append(orig.{{ .fieldName }}, iter.ReadBytes())
}
{{ else if ne .oneOfGroup "" -}}
{
var ov *{{ .oneOfMessageName }}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &{{ .oneOfMessageName }}{}
} else {
ov = ProtoPool{{ .oneOfMessageName }}.Get().(*{{ .oneOfMessageName }})
}
ov.{{ .fieldName }} = iter.ReadBytes()
orig.{{ .oneOfGroup }} = ov
}
{{ else -}}
orig.{{ .fieldName }} = iter.ReadBytes()
{{- end }}`
func (pf *Field) GenUnmarshalJSON() string {
tf := pf.getTemplateFields()
tf["allJSONTags"] = allJSONTags(pf.Name)
switch pf.Type {
case TypeBytes:
return tmplutil.Execute(tmplutil.Parse("unmarshalJSONBytes", []byte(unmarshalJSONBytes)), tf)
case TypeMessage:
return tmplutil.Execute(tmplutil.Parse("unmarshalJSONMessage", []byte(unmarshalJSONMessage)), tf)
case TypeEnum:
return tmplutil.Execute(tmplutil.Parse("unmarshalJSONEnum", []byte(unmarshalJSONEnum)), tf)
case TypeDouble, TypeFloat,
TypeFixed64, TypeSFixed64, TypeFixed32, TypeSFixed32,
TypeInt32, TypeInt64, TypeUint32, TypeUint64,
TypeSInt32, TypeSInt64,
TypeBool, TypeString:
return tmplutil.Execute(tmplutil.Parse("unmarshalJSONPrimitive", []byte(unmarshalJSONPrimitive)), tf)
}
panic(fmt.Sprintf("unhandled case %T", pf.Type))
}
func allJSONTags(str string) string {
snake := strcase.ToSnake(str)
if !strings.EqualFold(str, snake) {
return `"` + lowerFirst(str) + `", "` + snake + `"`
}
return `"` + lowerFirst(str) + `"`
}
================================================
FILE: internal/cmd/pdatagen/internal/proto/message.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package proto // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
import (
_ "embed"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/tmplutil"
)
var (
//go:embed templates/message.go.tmpl
messageTemplateBytes []byte
messageTemplate = tmplutil.Parse("message_internal_test.go", messageTemplateBytes)
//go:embed templates/message_test.go.tmpl
messageTestTemplateBytes []byte
messageTestTemplate = tmplutil.Parse("message_internal_test.go", messageTestTemplateBytes)
)
type Message struct {
Name string
Description string
OriginFullName string
UpstreamMessage string
Fields []FieldInterface
metadata *Metadata
}
func (ms *Message) GenerateMessage(imports, testImports []string) []byte {
ms.metadata = newMetadata(ms)
return []byte(tmplutil.Execute(messageTemplate, ms.templateFields(imports, testImports)))
}
func (ms *Message) GenerateMessageTests(imports, testImports []string) []byte {
return []byte(tmplutil.Execute(messageTestTemplate, ms.templateFields(imports, testImports)))
}
func (ms *Message) GenerateMetadata() string {
return string(ms.metadata.Generate())
}
func (ms *Message) templateFields(imports, testImports []string) map[string]any {
return map[string]any{
"fields": ms.Fields,
"messageName": ms.Name,
"upstreamMessage": ms.UpstreamMessage,
"description": ms.Description,
"imports": imports,
"testImports": testImports,
// 0 size means no metadata is needed
"metadataSize": ms.metadataSize(),
"GenerateMetadata": ms.GenerateMetadata,
}
}
func (ms *Message) metadataSize() int {
if ms.metadata == nil {
return 0
}
if len(ms.metadata.OptionalFields) == 0 {
return 0
}
return ms.metadata.OptionalFields[len(ms.metadata.OptionalFields)-1].Value/64 + 1
}
================================================
FILE: internal/cmd/pdatagen/internal/proto/metadata.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package proto // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
import (
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/tmplutil"
)
const metadataMessageTemplate = `
{{- range .OptionalFields }}
const fieldBlock{{ $.Name }}{{ .Name }} = uint64({{ .Value }} >> 6)
const fieldBit{{ $.Name }}{{ .Name }} = uint64(1 << {{ .Value }} & 0x3F)
func (m *{{ $.Name }}) Set{{ .Name }}(value {{ .GoType }}) {
m.{{ .Name }} = value
m.metadata[fieldBlock{{ $.Name }}{{ .Name }}] |= fieldBit{{ $.Name }}{{ .Name }}
}
func (m *{{ $.Name }}) Remove{{ .Name }}() {
m.{{ .Name }} = {{ .DefaultValue }}
m.metadata[fieldBlock{{ $.Name }}{{ .Name }}] &^= fieldBit{{ $.Name }}{{ .Name }}
}
func (m *{{ $.Name }}) Has{{ .Name }}() bool {
return m.metadata[fieldBlock{{ $.Name }}{{ .Name }}] & fieldBit{{ $.Name }}{{ .Name }} != 0
}
{{- end }}
`
type Metadata struct {
Name string
OptionalFields []*MetadataOptionalField
}
type MetadataOptionalField struct {
*Field
Value int
}
func newMetadata(ms *Message) *Metadata {
meta := &Metadata{
Name: ms.Name,
}
value := 0
for _, fieldI := range ms.Fields {
field, ok := fieldI.(*Field)
if !ok {
continue
}
if field.Repeated {
continue
}
switch field.Type {
case TypeDouble, TypeFloat, TypeInt32, TypeInt64, TypeUint32, TypeUint64, TypeSInt32, TypeSInt64, TypeFixed32, TypeFixed64, TypeSFixed32, TypeSFixed64, TypeBool:
if !field.Nullable {
continue
}
default:
continue
}
meta.OptionalFields = append(meta.OptionalFields, &MetadataOptionalField{
Field: field,
Value: value,
})
value++
}
if len(meta.OptionalFields) == 0 {
return nil
}
return meta
}
func (meta *Metadata) Generate() []byte {
return []byte(tmplutil.Execute(tmplutil.Parse("metadataMessageTemplate", []byte(metadataMessageTemplate)), meta))
}
================================================
FILE: internal/cmd/pdatagen/internal/proto/oneof_message.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package proto // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
import (
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/tmplutil"
)
const oneOfMessageOrigOtherTemplate = `
type {{ .oneOfMessageName }} struct {
{{ .fieldName }} {{ .goType }}
}
func (m *{{ .parentMessageName }}) Get{{ .fieldName }}() {{ .goType }} {
if v, ok := m.Get{{ .oneOfGroup }}().(*{{ .oneOfMessageName }}); ok {
return v.{{ .fieldName }}
}
return {{ .defaultValue }}
}
`
const oneOfMessageOrigMessageTemplate = `
type {{ .oneOfMessageName }} struct {
{{ .fieldName }} *{{ .goType }}
}
func (m *{{ .parentMessageName }}) Get{{ .fieldName }}() *{{ .goType }} {
if v, ok := m.Get{{ .oneOfGroup }}().(*{{ .oneOfMessageName }}); ok {
return v.{{ .fieldName }}
}
return nil
}
`
func (pf *Field) GenOneOfMessages() string {
tf := pf.getTemplateFields()
if pf.OneOfGroup != "" {
if pf.Type == TypeMessage {
return tmplutil.Execute(tmplutil.Parse("oneOfMessageOrigMessageTemplate", []byte(oneOfMessageOrigMessageTemplate)), tf)
}
return tmplutil.Execute(tmplutil.Parse("oneOfMessageOrigOtherTemplate", []byte(oneOfMessageOrigOtherTemplate)), tf)
}
return ""
}
================================================
FILE: internal/cmd/pdatagen/internal/proto/pools.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package proto // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
import (
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/tmplutil"
)
const poolVarOrigTemplate = `
ProtoPool{{ .oneOfMessageName }} = sync.Pool{
New: func() any {
return &{{ .oneOfMessageName }}{}
},
}
`
func (pf *Field) GenPool() string {
tf := pf.getTemplateFields()
if pf.OneOfGroup != "" {
return tmplutil.Execute(tmplutil.Parse("poolVarOrigTemplate", []byte(poolVarOrigTemplate)), tf)
}
return ""
}
================================================
FILE: internal/cmd/pdatagen/internal/proto/proto.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package proto // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
type Type int32
const (
TypeDouble Type = iota
TypeFloat
TypeInt32
TypeInt64
TypeUint32
TypeUint64
TypeSInt32
TypeSInt64
TypeFixed32
TypeFixed64
TypeSFixed32
TypeSFixed64
TypeBool
TypeEnum
TypeString
TypeBytes
TypeMessage
)
================================================
FILE: internal/cmd/pdatagen/internal/proto/proto_marshal.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package proto // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
import (
"fmt"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/tmplutil"
)
const marshalProtoFloat = `{{ if .repeated -}}
l = len(orig.{{ .fieldName }})
if l > 0 {
for i := l - 1; i >= 0; i-- {
pos -= {{ div .bitSize 8 }}
binary.LittleEndian.PutUint{{ .bitSize }}(buf[pos:], math.Float{{ .bitSize }}bits(orig.{{ .fieldName }}[i]))
}
pos = proto.EncodeVarint(buf, pos, uint64(l*{{ div .bitSize 8 }}))
{{ range .protoTag -}}
pos--
buf[pos] = {{ . }}
{{ end -}}
}
{{- else if ne .oneOfGroup "" -}}
pos -= {{ div .bitSize 8 }}
binary.LittleEndian.PutUint{{ .bitSize }}(buf[pos:], math.Float{{ .bitSize }}bits(orig.{{ .fieldName }}))
{{ range .protoTag -}}
pos--
buf[pos] = {{ . }}
{{ end -}}
{{- else }}
{{- if not .nullable -}}
if orig.{{ .fieldName }} != {{ .defaultValue }} {
{{- else -}}
if orig.Has{{ .fieldName }}() {
{{- end }}
pos -= {{ div .bitSize 8 }}
binary.LittleEndian.PutUint{{ .bitSize }}(buf[pos:], math.Float{{ .bitSize }}bits(orig.{{ .fieldName }}))
{{ range .protoTag -}}
pos--
buf[pos] = {{ . }}
{{ end -}}
}
{{- end }}`
const marshalProtoFixed = `{{ if .repeated -}}
l = len(orig.{{ .fieldName }})
if l > 0 {
for i := l - 1; i >= 0; i-- {
pos -= {{ div .bitSize 8 }}
binary.LittleEndian.PutUint{{ .bitSize }}(buf[pos:], uint{{ .bitSize }}(orig.{{ .fieldName }}[i]))
}
pos = proto.EncodeVarint(buf, pos, uint64(l*{{ div .bitSize 8 }}))
{{ range .protoTag -}}
pos--
buf[pos] = {{ . }}
{{ end -}}
}
{{- else if ne .oneOfGroup "" -}}
pos -= {{ div .bitSize 8 }}
binary.LittleEndian.PutUint{{ .bitSize }}(buf[pos:], uint{{ .bitSize }}(orig.{{ .fieldName }}))
{{ range .protoTag -}}
pos--
buf[pos] = {{ . }}
{{ end -}}
{{- else }}
{{- if not .nullable -}}
if orig.{{ .fieldName }} != {{ .defaultValue }} {
{{- else -}}
if orig.Has{{ .fieldName }}() {
{{- end }}
pos -= {{ div .bitSize 8 }}
binary.LittleEndian.PutUint{{ .bitSize }}(buf[pos:], uint{{ .bitSize }}(orig.{{ .fieldName }}))
{{ range .protoTag -}}
pos--
buf[pos] = {{ . }}
{{ end -}}
}
{{- end }}`
const marshalProtoBool = `{{ if .repeated -}}
l = len(orig.{{ .fieldName }})
if l > 0 {
for i := l - 1; i >= 0; i-- {
pos--
if orig.{{ .fieldName }}[i] {
buf[pos] = 1
} else {
buf[pos] = 0
}
}
pos = proto.EncodeVarint(buf, pos, uint64(l))
{{ range .protoTag -}}
pos--
buf[pos] = {{ . }}
{{ end -}}
}
{{- else if ne .oneOfGroup "" -}}
pos--
if orig.{{ .fieldName }} {
buf[pos] = 1
} else {
buf[pos] = 0
}
{{ range .protoTag -}}
pos--
buf[pos] = {{ . }}
{{ end -}}
{{- else }}
{{- if not .nullable -}}
if orig.{{ .fieldName }} != {{ .defaultValue }} {
{{- else -}}
if orig.Has{{ .fieldName }}() {
{{- end }}
pos--
if orig.{{ .fieldName }} {
buf[pos] = 1
} else {
buf[pos] = 0
}
{{ range .protoTag -}}
pos--
buf[pos] = {{ . }}
{{ end -}}
}
{{- end }}`
const marshalProtoVarint = `{{ if .repeated -}}
l = len(orig.{{ .fieldName }})
if l > 0 {
endPos := pos
for i := l - 1; i >= 0; i-- {
pos = proto.EncodeVarint(buf, pos, uint64(orig.{{ .fieldName }}[i]))
}
pos = proto.EncodeVarint(buf, pos, uint64(endPos-pos))
{{ range .protoTag -}}
pos--
buf[pos] = {{ . }}
{{ end -}}
}
{{- else if ne .oneOfGroup "" -}}
pos = proto.EncodeVarint(buf, pos, uint64(orig.{{ .fieldName }}))
{{ range .protoTag -}}
pos--
buf[pos] = {{ . }}
{{ end -}}
{{- else }}
{{- if not .nullable -}}
if orig.{{ .fieldName }} != {{ .defaultValue }} {
{{- else -}}
if orig.Has{{ .fieldName }}() {
{{- end }}
pos = proto.EncodeVarint(buf, pos, uint64(orig.{{ .fieldName }}))
{{ range .protoTag -}}
pos--
buf[pos] = {{ . }}
{{ end -}}
}
{{- end }}`
const marshalProtoBytesString = `{{ if .repeated -}}
for i := len(orig.{{ .fieldName }}) - 1; i >= 0; i-- {
l = len(orig.{{ .fieldName }}[i])
pos -= l
copy(buf[pos:], orig.{{ .fieldName }}[i])
pos = proto.EncodeVarint(buf, pos, uint64(l))
{{ range .protoTag -}}
pos--
buf[pos] = {{ . }}
{{ end -}}
}
{{- else -}}
l = len(orig.{{ .fieldName }})
{{ if not .nullable -}}
if l > 0 {
{{ end -}}
pos -= l
copy(buf[pos:], orig.{{ .fieldName }})
pos = proto.EncodeVarint(buf, pos, uint64(l))
{{ range .protoTag -}}
pos--
buf[pos] = {{ . }}
{{ end -}}
{{- if not .nullable -}}
}
{{- end }}{{- end }}`
const marshalProtoMessage = `{{ if .repeated -}}
for i := len(orig.{{ .fieldName }}) - 1; i >= 0; i-- {
l = orig.{{ .fieldName }}[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
{{ range .protoTag -}}
pos--
buf[pos] = {{ . }}
{{ end -}}
}
{{- else if .nullable -}}
if orig.{{ .fieldName }} != nil {
l = orig.{{ .fieldName }}.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
{{ range .protoTag -}}
pos--
buf[pos] = {{ . }}
{{ end -}}
}
{{- else -}}
l = orig.{{ .fieldName }}.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
{{ range .protoTag -}}
pos--
buf[pos] = {{ . }}
{{ end -}}
{{- end }}`
const marshalProtoSignedVarint = `{{ if .repeated -}}
l = len(orig.{{ .fieldName }})
if l > 0 {
endPos := pos
for i := l - 1; i >= 0; i-- {
pos = proto.EncodeVarint(buf, pos, uint64((uint{{ .bitSize }}(orig.{{ .fieldName }}[i])<<1)^uint{{ .bitSize }}(orig.{{ .fieldName }}[i]>>{{ sub .bitSize 1}})))
}
pos = proto.EncodeVarint(buf, pos, uint64(endPos-pos))
{{ range .protoTag -}}
pos--
buf[pos] = {{ . }}
{{ end -}}
}
{{- else if ne .oneOfGroup "" -}}
pos = proto.EncodeVarint(buf, pos, uint64((uint{{ .bitSize }}(orig.{{ .fieldName }})<<1)^uint{{ .bitSize }}(orig.{{ .fieldName }}>>{{ sub .bitSize 1}})))
{{ range .protoTag -}}
pos--
buf[pos] = {{ . }}
{{ end -}}
{{- else }}
{{- if not .nullable -}}
if orig.{{ .fieldName }} != {{ .defaultValue }} {
{{- else -}}
if orig.Has{{ .fieldName }}() {
{{- end }}
pos = proto.EncodeVarint(buf, pos, uint64((uint{{ .bitSize }}(orig.{{ .fieldName }})<<1)^uint{{ .bitSize }}(orig.{{ .fieldName }}>>{{ sub .bitSize 1}})))
{{ range .protoTag -}}
pos--
buf[pos] = {{ . }}
{{ end -}}
}
{{- end }}`
func (pf *Field) GenMarshalProto() string {
tf := pf.getTemplateFields()
switch pf.Type {
case TypeDouble, TypeFloat:
return tmplutil.Execute(tmplutil.Parse("marshalProtoFloat", []byte(marshalProtoFloat)), tf)
case TypeFixed64, TypeSFixed64, TypeFixed32, TypeSFixed32:
return tmplutil.Execute(tmplutil.Parse("marshalProtoFixed", []byte(marshalProtoFixed)), tf)
case TypeInt32, TypeInt64, TypeUint32, TypeUint64, TypeEnum:
return tmplutil.Execute(tmplutil.Parse("marshalProtoVarint", []byte(marshalProtoVarint)), tf)
case TypeBool:
return tmplutil.Execute(tmplutil.Parse("marshalProtoBool", []byte(marshalProtoBool)), tf)
case TypeBytes, TypeString:
return tmplutil.Execute(tmplutil.Parse("marshalProtoBytesString", []byte(marshalProtoBytesString)), tf)
case TypeMessage:
return tmplutil.Execute(tmplutil.Parse("marshalProtoMessage", []byte(marshalProtoMessage)), tf)
case TypeSInt32, TypeSInt64:
return tmplutil.Execute(tmplutil.Parse("marshalProtoSignedVarint", []byte(marshalProtoSignedVarint)), tf)
}
panic(fmt.Sprintf("unhandled case %T", pf.Type))
}
================================================
FILE: internal/cmd/pdatagen/internal/proto/proto_size.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package proto // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
import (
"fmt"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/tmplutil"
)
const sizeProtoI8 = `{{ if .repeated -}}
l = len(orig.{{ .fieldName }})
if l > 0 {
l *= 8
n+= {{ .protoTagSize }} + proto.Sov(uint64(l)) + l
}
{{- else if ne .oneOfGroup "" }}
n+= {{ add .protoTagSize 8 }}
{{- else }}
{{- if not .nullable -}}
if orig.{{ .fieldName }} != {{ .defaultValue }} {
{{- else -}}
if orig.Has{{ .fieldName }}() {
{{- end }}
n+= {{ add .protoTagSize 8 }}
}
{{- end }}`
const sizeProtoI4 = `{{ if .repeated -}}
l = len(orig.{{ .fieldName }})
if l > 0 {
l *= 4
n+= + {{ .protoTagSize }} + proto.Sov(uint64(l)) + l
}
{{- else if ne .oneOfGroup "" }}
n+= {{ add .protoTagSize 4 }}
{{- else }}
{{- if not .nullable -}}
if orig.{{ .fieldName }} != {{ .defaultValue }} {
{{- else -}}
if orig.Has{{ .fieldName }}() {
{{- end }}
n+= {{ add .protoTagSize 4 }}
}
{{- end }}`
const sizeProtoBool = `{{ if .repeated -}}
l = len(orig.{{ .fieldName }})
if l > 0 {
n+= + {{ .protoTagSize }} + proto.Sov(uint64(l)) + l
}
{{- else if ne .oneOfGroup "" }}
n+= {{ add .protoTagSize 1 }}
{{- else -}}
{{- if not .nullable -}}
if orig.{{ .fieldName }} != {{ .defaultValue }} {
{{- else -}}
if orig.Has{{ .fieldName }}() {
{{- end }}
n+= {{ add .protoTagSize 1 }}
}
{{- end }}`
const sizeProtoVarint = `{{ if .repeated }}
if len(orig.{{ .fieldName }}) > 0 {
l = 0
for _, e := range orig.{{ .fieldName }} {
l += proto.Sov(uint64(e))
}
n+= {{ .protoTagSize }} + proto.Sov(uint64(l)) + l
}
{{- else if ne .oneOfGroup "" }}
n+= {{ .protoTagSize }} + proto.Sov(uint64(orig.{{ .fieldName }}))
{{- else }}
{{- if not .nullable -}}
if orig.{{ .fieldName }} != {{ .defaultValue }} {
{{- else -}}
if orig.Has{{ .fieldName }}() {
{{- end }}
n+= {{ .protoTagSize }} + proto.Sov(uint64(orig.{{ .fieldName }}))
}
{{- end }}`
const sizeProtoBytesString = `{{ if .repeated -}}
for _, s := range orig.{{ .fieldName }} {
l = len(s)
n+= {{ .protoTagSize }} + proto.Sov(uint64(l)) + l
}
{{- else if ne .oneOfGroup "" -}}
l = len(orig.{{ .fieldName }})
n+= {{ .protoTagSize }} + proto.Sov(uint64(l)) + l
{{- else }}
l = len(orig.{{ .fieldName }})
if l > 0 {
n+= {{ .protoTagSize }} + proto.Sov(uint64(l)) + l
}
{{- end }}`
const sizeProtoMessage = `{{ if .repeated -}}
for i := range orig.{{ .fieldName }} {
l = orig.{{ .fieldName }}[i].SizeProto()
n+= {{ .protoTagSize }} + proto.Sov(uint64(l)) + l
}
{{- else if .nullable -}}
if orig.{{ .fieldName }} != nil {
l = orig.{{ .fieldName }}.SizeProto()
n+= {{ .protoTagSize }} + proto.Sov(uint64(l)) + l
}
{{- else -}}
l = orig.{{ .fieldName }}.SizeProto()
n+= {{ .protoTagSize }} + proto.Sov(uint64(l)) + l
{{- end }}`
const sizeProtoSignedVarint = `{{ if .repeated -}}
if len(orig.{{ .fieldName }}) > 0 {
l = 0
for _, e := range orig.{{ .fieldName }} {
l += proto.Soz(uint64(e))
}
n+= {{ .protoTagSize }} + proto.Sov(uint64(l)) + l
}
{{- else if ne .oneOfGroup "" -}}
n+= {{ .protoTagSize }} + proto.Soz(uint64(orig.{{ .fieldName }}))
{{- else -}}
{{- if not .nullable -}}
if orig.{{ .fieldName }} != {{ .defaultValue }} {
{{- else -}}
if orig.Has{{ .fieldName }}() {
{{- end }}
n+= {{ .protoTagSize }} + proto.Soz(uint64(orig.{{ .fieldName }}))
}
{{- end }}`
func (pf *Field) GenSizeProto() string {
tf := pf.getTemplateFields()
switch pf.Type {
case TypeFixed64, TypeSFixed64, TypeDouble:
return tmplutil.Execute(tmplutil.Parse("sizeProtoI8", []byte(sizeProtoI8)), tf)
case TypeFixed32, TypeSFixed32, TypeFloat:
return tmplutil.Execute(tmplutil.Parse("sizeProtoI4", []byte(sizeProtoI4)), tf)
case TypeInt32, TypeInt64, TypeUint32, TypeUint64, TypeEnum:
return tmplutil.Execute(tmplutil.Parse("sizeProtoVarint", []byte(sizeProtoVarint)), tf)
case TypeBool:
return tmplutil.Execute(tmplutil.Parse("sizeProtoBool", []byte(sizeProtoBool)), tf)
case TypeBytes, TypeString:
return tmplutil.Execute(tmplutil.Parse("sizeProtoBytesString", []byte(sizeProtoBytesString)), tf)
case TypeMessage:
return tmplutil.Execute(tmplutil.Parse("sizeProtoMessage", []byte(sizeProtoMessage)), tf)
case TypeSInt32, TypeSInt64:
return tmplutil.Execute(tmplutil.Parse("sizeProtoSignedVarint", []byte(sizeProtoSignedVarint)), tf)
}
panic(fmt.Sprintf("unhandled case %T", pf.Type))
}
================================================
FILE: internal/cmd/pdatagen/internal/proto/proto_unmarshal.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package proto // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
import (
"fmt"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/tmplutil"
)
const unmarshalProtoFloat = `{{ if .repeated -}}
case {{ .protoFieldID }}:
switch wireType {
case proto.WireTypeLen:
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
size := length / {{ div .bitSize 8 }}
orig.{{ .fieldName }} = make([]{{ .goType }}, size)
var num uint{{ .bitSize }}
for i := 0; i < size; i++ {
num, startPos, err = proto.ConsumeI{{ .bitSize }}(buf[:pos], startPos)
if err != nil {
return err
}
orig.{{ .fieldName }}[i] = math.Float{{ .bitSize }}frombits(num)
}
if startPos != pos {
return fmt.Errorf("proto: invalid field len = %d for field {{ .fieldName }}", pos - startPos)
}
case proto.WireTypeI{{ .bitSize }}:
var num uint{{ .bitSize }}
num, pos, err = proto.ConsumeI{{ .bitSize }}(buf, pos)
if err != nil {
return err
}
orig.{{ .fieldName }} = append(orig.{{ .fieldName }}, math.Float{{ .bitSize }}frombits(num))
default:
return fmt.Errorf("proto: wrong wireType = %d for field {{ .fieldName }}", wireType)
}
{{- else }}
case {{ .protoFieldID }}:
if wireType != proto.WireTypeI{{ .bitSize }} {
return fmt.Errorf("proto: wrong wireType = %d for field {{ .fieldName }}", wireType)
}
var num uint{{ .bitSize }}
num, pos, err = proto.ConsumeI{{ .bitSize }}(buf, pos)
if err != nil {
return err
}
{{ if ne .oneOfGroup "" -}}
var ov *{{ .oneOfMessageName }}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &{{ .oneOfMessageName }}{}
} else {
ov = ProtoPool{{ .oneOfMessageName }}.Get().(*{{ .oneOfMessageName }})
}
ov.{{ .fieldName }} = math.Float{{ .bitSize }}frombits(num)
orig.{{ .oneOfGroup }} = ov
{{- else if .nullable -}}
orig.Set{{ .fieldName }}(math.Float{{ .bitSize }}frombits(num))
{{- else -}}
orig.{{ .fieldName }} = math.Float{{ .bitSize }}frombits(num)
{{- end }}{{- end }}`
const unmarshalProtoFixed = `{{ if .repeated -}}
case {{ .protoFieldID }}:
switch wireType {
case proto.WireTypeLen:
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
size := length / {{ div .bitSize 8 }}
orig.{{ .fieldName }} = make([]{{ .goType }}, size)
var num uint{{ .bitSize }}
for i := 0; i < size; i++ {
num, startPos, err = proto.ConsumeI{{ .bitSize }}(buf[:pos], startPos)
if err != nil {
return err
}
orig.{{ .fieldName }}[i] = {{ .goType }}(num)
}
if startPos != pos {
return fmt.Errorf("proto: invalid field len = %d for field {{ .fieldName }}", pos - startPos)
}
case proto.WireTypeI{{ .bitSize }}:
var num uint{{ .bitSize }}
num, pos, err = proto.ConsumeI{{ .bitSize }}(buf, pos)
if err != nil {
return err
}
orig.{{ .fieldName }} = append(orig.{{ .fieldName }}, {{ .goType }}(num))
default:
return fmt.Errorf("proto: wrong wireType = %d for field {{ .fieldName }}", wireType)
}
{{- else }}
case {{ .protoFieldID }}:
if wireType != proto.WireTypeI{{ .bitSize }} {
return fmt.Errorf("proto: wrong wireType = %d for field {{ .fieldName }}", wireType)
}
var num uint{{ .bitSize }}
num, pos, err = proto.ConsumeI{{ .bitSize }}(buf, pos)
if err != nil {
return err
}
{{ if ne .oneOfGroup "" -}}
var ov *{{ .oneOfMessageName }}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &{{ .oneOfMessageName }}{}
} else {
ov = ProtoPool{{ .oneOfMessageName }}.Get().(*{{ .oneOfMessageName }})
}
ov.{{ .fieldName }} = {{ .goType }}(num)
orig.{{ .oneOfGroup }} = ov
{{- else }}
orig.{{ .fieldName }} = {{ .goType }}(num)
{{- end }}{{- end }}`
const unmarshalProtoBool = `{{ if .repeated -}}
case {{ .protoFieldID }}:
switch wireType {
case proto.WireTypeLen:
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
// Optimistically assume that bools are encoded as 1 byte even in variant form.
orig.{{ .fieldName }} = make([]bool, 0, length)
var num uint64
for startPos < pos {
num, startPos, err = proto.ConsumeVarint(buf[:pos], startPos)
if err != nil {
return err
}
orig.{{ .fieldName }} = append(orig.{{ .fieldName }}, num != 0)
}
if startPos != pos {
return fmt.Errorf("proto: invalid field len = %d for field {{ .fieldName }}", pos - startPos)
}
case proto.WireTypeVarint:
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.{{ .fieldName }} = append(orig.{{ .fieldName }}, num != 0)
default:
return fmt.Errorf("proto: wrong wireType = %d for field {{ .fieldName }}", wireType)
}
{{- else }}
case {{ .protoFieldID }}:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field {{ .fieldName }}", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
{{ if ne .oneOfGroup "" -}}
var ov *{{ .oneOfMessageName }}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &{{ .oneOfMessageName }}{}
} else {
ov = ProtoPool{{ .oneOfMessageName }}.Get().(*{{ .oneOfMessageName }})
}
ov.{{ .fieldName }} = num != 0
orig.{{ .oneOfGroup }} = ov
{{- else if .nullable -}}
orig.Set{{ .fieldName }}(num != 0)
{{- else -}}
orig.{{ .fieldName }} = num != 0
{{- end }}{{- end }}`
const unmarshalProtoVarint = `{{ if .repeated -}}
case {{ .protoFieldID }}:
switch wireType {
case proto.WireTypeLen:
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
var num uint64
for startPos < pos {
num, startPos, err = proto.ConsumeVarint(buf[:pos], startPos)
if err != nil {
return err
}
orig.{{ .fieldName }} = append(orig.{{ .fieldName }}, {{ .goType }}(num))
}
if startPos != pos {
return fmt.Errorf("proto: invalid field len = %d for field {{ .fieldName }}", pos - startPos)
}
case proto.WireTypeVarint:
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.{{ .fieldName }} = append(orig.{{ .fieldName }}, {{ .goType }}(num))
default:
return fmt.Errorf("proto: wrong wireType = %d for field {{ .fieldName }}", wireType)
}
{{- else }}
case {{ .protoFieldID }}:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field {{ .fieldName }}", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
{{ if ne .oneOfGroup "" -}}
var ov *{{ .oneOfMessageName }}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &{{ .oneOfMessageName }}{}
} else {
ov = ProtoPool{{ .oneOfMessageName }}.Get().(*{{ .oneOfMessageName }})
}
ov.{{ .fieldName }} = {{ .goType }}(num)
orig.{{ .oneOfGroup }} = ov
{{- else if .nullable -}}
orig.Set{{ .fieldName }}({{ .goType }}(num))
{{- else -}}
orig.{{ .fieldName }} = {{ .goType }}(num)
{{- end }}{{- end }}`
const unmarshalProtoString = `
case {{ .protoFieldID }}:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field {{ .fieldName }}", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
{{ if ne .oneOfGroup "" -}}
var ov *{{ .oneOfMessageName }}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &{{ .oneOfMessageName }}{}
} else {
ov = ProtoPool{{ .oneOfMessageName }}.Get().(*{{ .oneOfMessageName }})
}
ov.{{ .fieldName }} = string(buf[startPos:pos])
orig.{{ .oneOfGroup }} = ov
{{- else if .repeated -}}
orig.{{ .fieldName }} = append(orig.{{ .fieldName }}, string(buf[startPos:pos]))
{{- else -}}
orig.{{ .fieldName }} = string(buf[startPos:pos])
{{- end }}`
const unmarshalProtoBytes = `
case {{ .protoFieldID }}:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field {{ .fieldName }}", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
{{ if ne .oneOfGroup "" -}}
var ov *{{ .oneOfMessageName }}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &{{ .oneOfMessageName }}{}
} else {
ov = ProtoPool{{ .oneOfMessageName }}.Get().(*{{ .oneOfMessageName }})
}
if length != 0 {
ov.{{ .fieldName }} = make([]byte, length)
copy(ov.{{ .fieldName }}, buf[startPos:pos])
}
orig.{{ .oneOfGroup }} = ov
{{- else if .repeated -}}
if length != 0 {
orig.{{ .fieldName }} = append(orig.{{ .fieldName }}, make([]byte, length))
copy(orig.{{ .fieldName }}[len(orig.{{ .fieldName }}) - 1], buf[startPos:pos])
} else {
orig.{{ .fieldName }} = append(orig.{{ .fieldName }}, nil)
}
{{- else -}}
if length != 0 {
orig.{{ .fieldName }} = make([]byte, length)
copy(orig.{{ .fieldName }}, buf[startPos:pos])
}
{{- end }}`
const unmarshalProtoMessage = `
case {{ .protoFieldID }}:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field {{ .fieldName }}", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
{{ if ne .oneOfGroup "" -}}
var ov *{{ .oneOfMessageName }}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &{{ .oneOfMessageName }}{}
} else {
ov = ProtoPool{{ .oneOfMessageName }}.Get().(*{{ .oneOfMessageName }})
}
ov.{{ .fieldName }} = New{{ .messageName }}()
err = ov.{{ .fieldName }}.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
orig.{{ .oneOfGroup }} = ov
{{- else if .repeated -}}
orig.{{ .fieldName }} = append(orig.{{ .fieldName }}, {{ if .nullable }}New{{ .messageName }}(){{ else }}{{ .defaultValue }}{{ end }})
err = orig.{{ .fieldName }}[len(orig.{{ .fieldName }})-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
{{- else }}
{{ if .nullable }}orig.{{ .fieldName }} = New{{ .messageName }}(){{ end }}
err = orig.{{ .fieldName }}.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
{{- end }}`
const unmarshalProtoSignedVarint = `{{ if .repeated -}}
case {{ .protoFieldID }}:
switch wireType {
case proto.WireTypeLen:
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
// Optimistically assume that bools are encoded as 1 byte even in variant form.
orig.{{ .fieldName }} = make([]bool, 0, pos - startPos)
var num uint64
for startPos < pos {
num, startPos, err = proto.ConsumeVarint(buf[:pos], startPos)
if err != nil {
return err
}
orig.{{ .fieldName }} = append(orig.{{ .fieldName }}, int{{ .bitSize }}(uint{{ .bitSize }}(num >> 1) ^ uint{{ .bitSize }}(int{{ .bitSize }}((num&1)<<{{ sub .bitSize 1 }})>>{{ sub .bitSize 1 }})))
}
if startPos != pos {
return fmt.Errorf("proto: invalid field len = %d for field {{ .fieldName }}", pos - startPos)
}
case proto.WireTypeVarint:
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.{{ .fieldName }} = append(orig.{{ .fieldName }}, int{{ .bitSize }}(uint{{ .bitSize }}(num >> 1) ^ uint{{ .bitSize }}(int{{ .bitSize }}((num&1)<<{{ sub .bitSize 1 }})>>{{ sub .bitSize 1 }})))
default:
return fmt.Errorf("proto: wrong wireType = %d for field {{ .fieldName }}", wireType)
}
{{- else }}
case {{ .protoFieldID }}:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field {{ .fieldName }}", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
{{ if ne .oneOfGroup "" -}}
var ov *{{ .oneOfMessageName }}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &{{ .oneOfMessageName }}{}
} else {
ov = ProtoPool{{ .oneOfMessageName }}.Get().(*{{ .oneOfMessageName }})
}
ov.{{ .fieldName }} = int{{ .bitSize }}(uint{{ .bitSize }}(num >> 1) ^ uint{{ .bitSize }}(int{{ .bitSize }}((num&1)<<{{ sub .bitSize 1 }})>>{{ sub .bitSize 1 }}))
orig.{{ .oneOfGroup }} = ov
{{- else if .nullable -}}
orig.Set{{ .fieldName }}(int{{ .bitSize }}(uint{{ .bitSize }}(num >> 1) ^ uint{{ .bitSize }}(int{{ .bitSize }}((num&1)<<{{ sub .bitSize 1 }})>>{{ sub .bitSize 1 }})))
{{- else -}}
orig.{{ .fieldName }} = int{{ .bitSize }}(uint{{ .bitSize }}(num >> 1) ^ uint{{ .bitSize }}(int{{ .bitSize }}((num&1)<<{{ sub .bitSize 1 }})>>{{ sub .bitSize 1 }}))
{{- end }}{{- end }}`
func (pf *Field) GenUnmarshalProto() string {
tf := pf.getTemplateFields()
switch pf.Type {
case TypeDouble, TypeFloat:
return tmplutil.Execute(tmplutil.Parse("unmarshalProtoFloat", []byte(unmarshalProtoFloat)), tf)
case TypeFixed64, TypeSFixed64, TypeFixed32, TypeSFixed32:
return tmplutil.Execute(tmplutil.Parse("unmarshalProtoFixed", []byte(unmarshalProtoFixed)), tf)
case TypeInt32, TypeInt64, TypeUint32, TypeUint64, TypeEnum:
return tmplutil.Execute(tmplutil.Parse("unmarshalProtoVarint", []byte(unmarshalProtoVarint)), tf)
case TypeBool:
return tmplutil.Execute(tmplutil.Parse("unmarshalProtoBool", []byte(unmarshalProtoBool)), tf)
case TypeString:
return tmplutil.Execute(tmplutil.Parse("unmarshalProtoString", []byte(unmarshalProtoString)), tf)
case TypeBytes:
return tmplutil.Execute(tmplutil.Parse("unmarshalProtoBytes", []byte(unmarshalProtoBytes)), tf)
case TypeMessage:
return tmplutil.Execute(tmplutil.Parse("unmarshalProtoMessage", []byte(unmarshalProtoMessage)), tf)
case TypeSInt32, TypeSInt64:
return tmplutil.Execute(tmplutil.Parse("unmarshalProtoSignedVarint", []byte(unmarshalProtoSignedVarint)), tf)
}
panic(fmt.Sprintf("unhandled case %T", pf.Type))
}
================================================
FILE: internal/cmd/pdatagen/internal/proto/templates/message.go.tmpl
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
{{ range $index, $element := .imports -}}
{{ $element }}
{{ end }}
)
{{- range .fields }}
{{ .GenOneOfMessages }}
{{- end }}
{{ .description }}
type {{ .messageName }} struct {
{{- range .fields }}
{{ .GenMessageField }}
{{- end }}
{{ if gt .metadataSize 0 -}}
metadata [{{ .metadataSize }}]uint64
{{ end -}}
}
var (
protoPool{{ .messageName }} = sync.Pool{
New: func() any {
return &{{ .messageName }}{}
},
}
{{- range .fields }}{{ .GenPool }}{{- end }}
)
func New{{ .messageName }}() *{{ .messageName }} {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &{{ .messageName }}{}
}
return protoPool{{ .messageName }}.Get().(*{{ .messageName }})
}
func Delete{{ .messageName }}(orig *{{ .messageName }}, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
{{- range .fields }}
{{ .GenDelete }}
{{- end }}
orig.Reset()
if nullable {
protoPool{{ .messageName }}.Put(orig)
}
}
func Copy{{ .messageName }}(dest, src *{{ .messageName }}) *{{ .messageName }}{
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil;
}
if dest == nil {
dest = New{{ .messageName }}();
}
{{- range .fields }}
{{ .GenCopy }}
{{- end }}
return dest
}
func Copy{{ .messageName }}Slice(dest, src []{{ .messageName }}) []{{ .messageName }} {
var newDest []{{ .messageName }}
if cap(dest) < len(src) {
newDest = make([]{{ .messageName }}, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
Delete{{ .messageName }}(&dest[i], false)
}
}
for i := range src {
Copy{{ .messageName }}(&newDest[i], &src[i])
}
return newDest
}
func Copy{{ .messageName }}PtrSlice(dest, src []*{{ .messageName }}) []*{{ .messageName }} {
var newDest []*{{ .messageName }}
if cap(dest) < len(src) {
newDest = make([]*{{ .messageName }}, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = New{{ .messageName }}()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
Delete{{ .messageName }}(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = New{{ .messageName }}()
}
}
for i := range src {
Copy{{ .messageName }}(newDest[i], src[i])
}
return newDest
}
func (orig *{{ .messageName }}) Reset() {
*orig = {{ .messageName }}{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *{{ .messageName }}) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
{{ range .fields -}}
{{ .GenMarshalJSON }}
{{ end -}}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *{{ .messageName }}) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
{{ range .fields -}}
{{ .GenUnmarshalJSON }}
{{ end -}}
default:
iter.Skip()
}
}
}
func (orig *{{ .messageName }}) SizeProto() int {
var n int
var l int
_ = l
{{ range .fields -}}
{{ .GenSizeProto }}
{{ end -}}
return n
}
func (orig *{{ .messageName }}) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
{{ range .fields -}}
{{ .GenMarshalProto }}
{{ end -}}
return len(buf) - pos
}
func (orig *{{ .messageName }}) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
{{ range .fields -}}
{{ .GenUnmarshalProto }}
{{ end -}}
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
{{ if gt .metadataSize 0 -}}
{{ call .GenerateMetadata }}
{{ end -}}
func GenTest{{ .messageName }}() *{{ .messageName }} {
orig := New{{ .messageName }}()
{{- range .fields }}
{{ .GenTest }}
{{- end }}
return orig
}
func GenTest{{ .messageName }}PtrSlice() []*{{ .messageName }} {
orig := make([]*{{ .messageName }}, 5)
orig[0] = New{{ .messageName }}()
orig[1] = GenTest{{ .messageName }}()
orig[2] = New{{ .messageName }}()
orig[3] = GenTest{{ .messageName }}()
orig[4] = New{{ .messageName }}()
return orig
}
func GenTest{{ .messageName }}Slice() []{{ .messageName }} {
orig := make([]{{ .messageName }}, 5)
orig[1] = *GenTest{{ .messageName }}()
orig[3] = *GenTest{{ .messageName }}()
return orig
}
================================================
FILE: internal/cmd/pdatagen/internal/proto/templates/message_test.go.tmpl
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
{{ range $index, $element := .testImports -}}
{{ $element }}
{{ end }}
)
func TestCopy{{ .messageName }}(t *testing.T) {
for name, src := range genTestEncodingValues{{ .messageName }}() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := New{{ .messageName }}()
Copy{{ .messageName }}(dest, src)
assert.Equal(t, src, dest)
Copy{{ .messageName }}(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopy{{ .messageName }}Slice(t *testing.T) {
src := []{{ .messageName }}{}
dest := []{{ .messageName }}{}
// Test CopyTo empty
dest = Copy{{ .messageName }}Slice(dest, src)
assert.Equal(t, []{{ .messageName }}{}, dest)
// Test CopyTo larger slice
src = GenTest{{ .messageName }}Slice()
dest = Copy{{ .messageName }}Slice(dest, src)
assert.Equal(t, GenTest{{ .messageName }}Slice(), dest)
// Test CopyTo same size slice
dest = Copy{{ .messageName }}Slice(dest, src)
assert.Equal(t, GenTest{{ .messageName }}Slice(), dest)
// Test CopyTo smaller size slice
dest = Copy{{ .messageName }}Slice(dest, []{{ .messageName }}{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = Copy{{ .messageName }}Slice(dest, src)
assert.Equal(t, GenTest{{ .messageName }}Slice(), dest)
}
func TestCopy{{ .messageName }}PtrSlice(t *testing.T) {
src := []*{{ .messageName }}{}
dest := []*{{ .messageName }}{}
// Test CopyTo empty
dest = Copy{{ .messageName }}PtrSlice(dest, src)
assert.Equal(t, []*{{ .messageName }}{}, dest)
// Test CopyTo larger slice
src = GenTest{{ .messageName }}PtrSlice()
dest = Copy{{ .messageName }}PtrSlice(dest, src)
assert.Equal(t, GenTest{{ .messageName }}PtrSlice(), dest)
// Test CopyTo same size slice
dest = Copy{{ .messageName }}PtrSlice(dest, src)
assert.Equal(t, GenTest{{ .messageName }}PtrSlice(), dest)
// Test CopyTo smaller size slice
dest = Copy{{ .messageName }}PtrSlice(dest, []*{{ .messageName }}{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = Copy{{ .messageName }}PtrSlice(dest, src)
assert.Equal(t, GenTest{{ .messageName }}PtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSON{{ .messageName }}Unknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := New{{ .messageName }}()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, New{{ .messageName }}(), dest)
}
func TestMarshalAndUnmarshalJSON{{ .messageName }}(t *testing.T) {
for name, src := range genTestEncodingValues{{ .messageName }}() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := New{{ .messageName }}()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
Delete{{ .messageName }}(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProto{{ .messageName }}Failing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValues{{ .messageName }}() {
t.Run(name, func(t *testing.T) {
dest := New{{ .messageName }}()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProto{{ .messageName }}Unknown(t *testing.T) {
dest := New{{ .messageName }}()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, New{{ .messageName }}(), dest)
}
func TestMarshalAndUnmarshalProto{{ .messageName }}(t *testing.T) {
for name, src := range genTestEncodingValues{{ .messageName }}(){
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := New{{ .messageName }}()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
Delete{{ .messageName }}(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobuf{{ .messageName }}(t *testing.T) {
for name, src := range genTestEncodingValues{{ .messageName }}(){
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &{{ .upstreamMessage }}{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := New{{ .messageName }}()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValues{{ .messageName }}() map[string][]byte {
return map[string][]byte{
"invalid_field": { 0x02 },
{{- range .fields }}{{ .GenTestFailingUnmarshalProtoValues }}{{- end }}
}
}
func genTestEncodingValues{{ .messageName }}() map[string]*{{ .messageName }} {
return map[string]*{{ .messageName }}{
"empty": New{{ .messageName }}(),
{{- range .fields }}{{ .GenTestEncodingValues }}{{- end }}
}
}
================================================
FILE: internal/cmd/pdatagen/internal/proto/test_encoding_values.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package proto // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
import (
"slices"
"strings"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/tmplutil"
)
const encodingTestValuesScalar = `{{ if ne .oneOfGroup "" -}}
"{{ .fieldName }}/default": { {{ .oneOfGroup }}: &{{ .oneOfMessageName }}{{ "{" }}{{ .fieldName }}: {{ .defaultValue }}} },
"{{ .fieldName }}/test": { {{ .oneOfGroup }}: &{{ .oneOfMessageName }}{{ "{" }}{{ .fieldName }}: {{ .testValue }}} },
{{- else if .nullable }}
"{{ .fieldName }}/test": func () *{{ .parentMessageName }} {
ms := New{{ .parentMessageName }}()
ms.Set{{ .fieldName }}({{ .testValue }})
return ms
}(),
{{- else }}
"{{ .fieldName }}/test": { {{ .fieldName }}: {{ .testValue }} },
{{- end }}`
const encodingTestValuesMessage = `{{ if ne .oneOfGroup "" -}}
"{{ .fieldName }}/default": { {{ .oneOfGroup }}: &{{ .oneOfMessageName }}{{ "{" }}{{ .fieldName }}: {{ .defaultValue }}} },
"{{ .fieldName }}/test": { {{ .oneOfGroup }}: &{{ .oneOfMessageName }}{{ "{" }}{{ .fieldName }}: {{ .testValue }}} },
{{- else }}
"{{ .fieldName }}/test": { {{ .fieldName }}: {{ .testValue }} },
{{- end }}`
func (pf *Field) GenTestEncodingValues() string {
tf := pf.getTemplateFields()
switch pf.Type {
case TypeMessage:
return tmplutil.Execute(tmplutil.Parse("encodingTestValuesMessage", []byte(encodingTestValuesMessage)), tf)
case
TypeDouble, TypeFloat,
TypeFixed64, TypeSFixed64, TypeFixed32, TypeSFixed32,
TypeInt32, TypeInt64, TypeUint32, TypeUint64,
TypeSInt32, TypeSInt64,
TypeBool, TypeString, TypeBytes, TypeEnum:
return tmplutil.Execute(tmplutil.Parse("encodingTestValuesScalar", []byte(encodingTestValuesScalar)), tf)
}
return ""
}
const failingUnmarshalProtoValuesScalar = `
"{{ .fieldName }}/wrong_wire_type": []byte{ {{ .wrongWireTypeArray }} },
"{{ .fieldName }}/missing_value": []byte{ {{ .missingValueArray }} },`
func (pf *Field) GenTestFailingUnmarshalProtoValues() string {
tf := pf.getTemplateFields()
tf["wrongWireTypeArray"] = protoTagAsByteArray(genProtoTag(pf.ID, WireTypeEndGroup))
tf["missingValueArray"] = protoTagAsByteArray(tf["protoTag"].([]string))
switch pf.Type {
case TypeMessage,
TypeDouble, TypeFloat,
TypeFixed64, TypeSFixed64, TypeFixed32, TypeSFixed32,
TypeInt32, TypeInt64, TypeUint32, TypeUint64,
TypeSInt32, TypeSInt64,
TypeBool, TypeString, TypeBytes, TypeEnum:
return tmplutil.Execute(tmplutil.Parse("failingUnmarshalProtoValuesScalar", []byte(failingUnmarshalProtoValuesScalar)), tf)
}
return ""
}
func protoTagAsByteArray(protoTag []string) string {
slices.Reverse(protoTag)
return strings.Join(protoTag, ", ")
}
================================================
FILE: internal/cmd/pdatagen/internal/proto/wire_type.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package proto // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/proto"
// WireType represents the proto wire type.
type WireType uint32
const (
WireTypeVarint WireType = 0
WireTypeI64 WireType = 1
WireTypeLen WireType = 2
WireTypeStartGroup WireType = 3
WireTypeEndGroup WireType = 4
WireTypeI32 WireType = 5
)
================================================
FILE: internal/cmd/pdatagen/internal/tmplutil/template.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package tmplutil // import "go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/tmplutil"
import (
"strings"
"text/template"
"github.com/ettle/strcase"
)
func Parse(name string, bytes []byte) *template.Template {
return template.Must(newTemplate(name).Parse(string(bytes)))
}
func Execute(tmpl *template.Template, data any) string {
var sb strings.Builder
if err := tmpl.Execute(&sb, data); err != nil {
panic(err)
}
return sb.String()
}
func newTemplate(name string) *template.Template {
return template.New(name).Funcs(template.FuncMap{
"upperFirst": upperFirst,
"lowerFirst": lowerFirst,
"add": add,
"sub": sub,
"div": div,
"needSnake": needSnake,
"toSnake": strcase.ToSnake,
})
}
func upperFirst(s string) string {
return strings.ToUpper(s[0:1]) + s[1:]
}
func lowerFirst(s string) string {
return strings.ToLower(s[0:1]) + s[1:]
}
func add(a, b int) int {
return a + b
}
func sub(a, b int) int {
return a - b
}
func div(a, b int) int {
return a / b
}
func needSnake(str string) bool {
return strings.ToLower(str) != strcase.ToSnake(str)
}
================================================
FILE: internal/cmd/pdatagen/main.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package main
import (
"flag"
"fmt"
"os"
"path/filepath"
"go.opentelemetry.io/collector/internal/cmd/pdatagen/internal/pdata"
)
// checkErr prints the given error and exits when e is non-nil.
func checkErr(e error) {
if e != nil {
fmt.Println(e)
os.Exit(1)
}
}
func main() {
var workdir string
flag.StringVar(&workdir, "C", ".", "set work directory")
flag.Parse()
checkErr(os.Chdir(workdir))
checkErr(pdata.DeleteGeneratedFiles(filepath.Join("pdata", "internal")))
for _, fp := range pdata.AllPackages {
checkErr(pdata.DeleteGeneratedFiles(filepath.Join("pdata", fp.Path())))
checkErr(fp.GenerateFiles())
checkErr(fp.GenerateTestFiles())
checkErr(fp.GenerateInternalFiles())
checkErr(fp.GenerateProtoMessageFiles())
checkErr(fp.GenerateProtoMessageTestsFiles())
checkErr(fp.GenerateProtoEnumFiles())
}
}
================================================
FILE: internal/componentalias/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: internal/componentalias/alias.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package componentalias // import "go.opentelemetry.io/collector/internal/componentalias"
import (
"errors"
"fmt"
"go.opentelemetry.io/collector/component"
)
type TypeAliasHolder interface {
DeprecatedAlias() component.Type
SetDeprecatedAlias(component.Type)
}
func NewTypeAliasHolder() TypeAliasHolder {
ta := typeAlias(component.Type{})
return &ta
}
type typeAlias component.Type
// DeprecatedAlias returns the deprecated type typeAlias for this component, if any.
// Returns an empty component.Type if no typeAlias is configured.
func (ta *typeAlias) DeprecatedAlias() component.Type {
return component.Type(*ta)
}
// SetDeprecatedAlias sets the deprecated type typeAlias.
func (ta *typeAlias) SetDeprecatedAlias(newAlias component.Type) {
*ta = typeAlias(newAlias)
}
// ValidateComponentType returns an error if the provided factory does not match the provided component ID.
// It checks both the current type and any deprecated alias type.
func ValidateComponentType(f component.Factory, id component.ID) error {
if id.Type() == f.Type() {
return nil
}
errMsg := fmt.Sprintf("component type mismatch: component ID %q does not have type %q", id, f.Type())
if aliasHolder, ok := f.(TypeAliasHolder); ok && aliasHolder.DeprecatedAlias().String() != "" {
if id.Type() == aliasHolder.DeprecatedAlias() {
return nil
}
errMsg += fmt.Sprintf(" or deprecated alias type %q", aliasHolder.DeprecatedAlias())
}
return errors.New(errMsg)
}
================================================
FILE: internal/componentalias/alias_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package componentalias
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
)
func TestNewTypeAliasHolder(t *testing.T) {
holder := NewTypeAliasHolder()
require.NotNil(t, holder)
alias := holder.DeprecatedAlias()
assert.Equal(t, component.Type{}, alias)
assert.Empty(t, alias.String())
testType := component.MustNewType("test_alias")
holder.SetDeprecatedAlias(testType)
retrievedAlias := holder.DeprecatedAlias()
assert.Equal(t, testType, retrievedAlias)
assert.Equal(t, "test_alias", retrievedAlias.String())
}
type mockFactory struct {
factoryType component.Type
TypeAliasHolder
}
func (f *mockFactory) Type() component.Type {
return f.factoryType
}
func (f *mockFactory) CreateDefaultConfig() component.Config {
return nil
}
func TestValidateComponentType_ExactMatch(t *testing.T) {
testType := component.MustNewType("test")
factory := &mockFactory{
factoryType: testType,
TypeAliasHolder: NewTypeAliasHolder(),
}
testID := component.MustNewID(testType.String())
err := ValidateComponentType(factory, testID)
require.NoError(t, err)
}
func TestValidateComponentType_AliasMatch(t *testing.T) {
factoryType := component.MustNewType("new_name")
aliasType := component.MustNewType("old_name")
factory := &mockFactory{
factoryType: factoryType,
TypeAliasHolder: NewTypeAliasHolder(),
}
factory.SetDeprecatedAlias(aliasType)
// Test with alias type
aliasID := component.MustNewID(aliasType.String())
err := ValidateComponentType(factory, aliasID)
require.NoError(t, err)
// Test with factory type still works
factoryID := component.MustNewID(factoryType.String())
err = ValidateComponentType(factory, factoryID)
require.NoError(t, err)
}
func TestValidateComponentType_NoMatch(t *testing.T) {
factoryType := component.MustNewType("factory_type")
wrongType := component.MustNewType("wrong_type")
factory := &mockFactory{
factoryType: factoryType,
TypeAliasHolder: NewTypeAliasHolder(),
}
wrongID := component.MustNewID(wrongType.String())
err := ValidateComponentType(factory, wrongID)
require.Error(t, err)
assert.Contains(t, err.Error(), "component type mismatch")
assert.Contains(t, err.Error(), wrongType.String())
assert.Contains(t, err.Error(), factoryType.String())
}
func TestValidateComponentType_NoMatchWithAlias(t *testing.T) {
factoryType := component.MustNewType("factory_type")
aliasType := component.MustNewType("alias_type")
wrongType := component.MustNewType("wrong_type")
factory := &mockFactory{
factoryType: factoryType,
TypeAliasHolder: NewTypeAliasHolder(),
}
factory.SetDeprecatedAlias(aliasType)
wrongID := component.MustNewID(wrongType.String())
err := ValidateComponentType(factory, wrongID)
require.Error(t, err)
assert.Contains(t, err.Error(), "component type mismatch")
assert.Contains(t, err.Error(), wrongType.String())
assert.Contains(t, err.Error(), factoryType.String())
assert.Contains(t, err.Error(), "deprecated alias type")
assert.Contains(t, err.Error(), aliasType.String())
}
func TestValidateComponentType_EmptyAlias(t *testing.T) {
factoryType := component.MustNewType("factory_type")
wrongType := component.MustNewType("wrong_type")
factory := &mockFactory{
factoryType: factoryType,
TypeAliasHolder: NewTypeAliasHolder(),
}
// Don't set any alias (empty by default)
wrongID := component.MustNewID(wrongType.String())
err := ValidateComponentType(factory, wrongID)
require.Error(t, err)
assert.Contains(t, err.Error(), "component type mismatch")
assert.NotContains(t, err.Error(), "deprecated alias type")
}
type mockFactoryWithoutAlias struct {
factoryType component.Type
}
func (f *mockFactoryWithoutAlias) Type() component.Type {
return f.factoryType
}
func (f *mockFactoryWithoutAlias) CreateDefaultConfig() component.Config {
return nil
}
func TestValidateComponentType_FactoryWithoutAliasSupport(t *testing.T) {
factoryType := component.MustNewType("factory_type")
factory := &mockFactoryWithoutAlias{factoryType: factoryType}
factoryID := component.MustNewID(factoryType.String())
err := ValidateComponentType(factory, factoryID)
require.NoError(t, err)
wrongType := component.MustNewType("wrong_type")
wrongID := component.MustNewID(wrongType.String())
err = ValidateComponentType(factory, wrongID)
require.Error(t, err)
assert.Contains(t, err.Error(), "component type mismatch")
assert.NotContains(t, err.Error(), "deprecated alias type")
}
================================================
FILE: internal/componentalias/go.mod
================================================
module go.opentelemetry.io/collector/internal/componentalias
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/pdata v1.54.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../testutil
================================================
FILE: internal/componentalias/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: internal/e2e/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: internal/e2e/configauth_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/config/configauth"
"go.opentelemetry.io/collector/confmap"
)
func TestConfmapMarshalConfigAuth(t *testing.T) {
conf := confmap.New()
require.NoError(t, conf.Marshal(configauth.Config{}))
assert.Equal(t, map[string]any{}, conf.ToStringMap())
}
================================================
FILE: internal/e2e/configgrpc_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/config/configgrpc"
"go.opentelemetry.io/collector/config/confignet"
"go.opentelemetry.io/collector/confmap"
)
func TestConfmapMarshalConfigGRPC(t *testing.T) {
keepaliveClientConfig := map[string]any{
"time": time.Second * 10,
"timeout": time.Second * 10,
}
keepaliveServerConfig := map[string]any{
"server_parameters": map[string]any{},
"enforcement_policy": map[string]any{},
}
conf := confmap.New()
require.NoError(t, conf.Marshal(configgrpc.NewDefaultClientConfig()))
assert.Equal(t, map[string]any{
"keepalive": keepaliveClientConfig,
"balancer_name": "round_robin",
}, conf.ToStringMap())
conf = confmap.New()
require.NoError(t, conf.Marshal(configgrpc.NewDefaultKeepaliveClientConfig()))
assert.Equal(t, keepaliveClientConfig, conf.ToStringMap())
conf = confmap.New()
require.NoError(t, conf.Marshal(configgrpc.NewDefaultKeepaliveEnforcementPolicy()))
assert.Equal(t, map[string]any{}, conf.ToStringMap())
conf = confmap.New()
require.NoError(t, conf.Marshal(configgrpc.NewDefaultKeepaliveServerConfig()))
assert.Equal(t, keepaliveServerConfig, conf.ToStringMap())
conf = confmap.New()
require.NoError(t, conf.Marshal(configgrpc.NewDefaultKeepaliveServerParameters()))
assert.Equal(t, map[string]any{}, conf.ToStringMap())
conf = confmap.New()
require.NoError(t, conf.Marshal(configgrpc.NewDefaultServerConfig()))
assert.Equal(t, map[string]any{
"keepalive": keepaliveServerConfig,
"transport": confignet.TransportType("tcp"),
}, conf.ToStringMap())
}
================================================
FILE: internal/e2e/confighttp_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/config/confignet"
"go.opentelemetry.io/collector/confmap"
)
func TestConfmapMarshalConfigHTTP(t *testing.T) {
conf := confmap.New()
require.NoError(t, conf.Marshal(confighttp.NewDefaultClientConfig()))
assert.Equal(t, map[string]any{
"idle_conn_timeout": 90 * time.Second,
"max_idle_conns": 100,
"force_attempt_http2": true,
}, conf.ToStringMap())
conf = confmap.New()
require.NoError(t, conf.Marshal(confighttp.NewDefaultCORSConfig()))
assert.Equal(t, map[string]any{}, conf.ToStringMap())
conf = confmap.New()
require.NoError(t, conf.Marshal(confighttp.NewDefaultServerConfig()))
assert.Equal(t, map[string]any{
"cors": nil,
"idle_timeout": 60 * time.Second,
"keep_alives_enabled": true,
"read_header_timeout": 60 * time.Second,
"tls": nil,
"transport": confignet.TransportTypeTCP,
"write_timeout": 30 * time.Second,
}, conf.ToStringMap())
conf = confmap.New()
require.NoError(t, conf.Marshal(confighttp.AuthConfig{}))
assert.Equal(t, map[string]any{}, conf.ToStringMap())
}
================================================
FILE: internal/e2e/confignet_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/config/confignet"
"go.opentelemetry.io/collector/confmap"
)
func TestConfmapMarshalConfigNet(t *testing.T) {
conf := confmap.New()
require.NoError(t, conf.Marshal(confignet.NewDefaultDialerConfig()))
assert.Equal(t, map[string]any{}, conf.ToStringMap())
conf = confmap.New()
require.NoError(t, conf.Marshal(confignet.NewDefaultAddrConfig()))
assert.Equal(t, map[string]any{}, conf.ToStringMap())
conf = confmap.New()
require.NoError(t, conf.Marshal(confignet.NewDefaultTCPAddrConfig()))
assert.Equal(t, map[string]any{}, conf.ToStringMap())
}
================================================
FILE: internal/e2e/configtls_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/config/configtls"
"go.opentelemetry.io/collector/confmap"
)
func TestConfmapMarshalConfigTLS(t *testing.T) {
conf := confmap.New()
require.NoError(t, conf.Marshal(configtls.NewDefaultConfig()))
assert.Equal(t, map[string]any{}, conf.ToStringMap())
conf = confmap.New()
require.NoError(t, conf.Marshal(configtls.NewDefaultClientConfig()))
assert.Equal(t, map[string]any{}, conf.ToStringMap())
conf = confmap.New()
require.NoError(t, conf.Marshal(configtls.NewDefaultServerConfig()))
assert.Equal(t, map[string]any{}, conf.ToStringMap())
}
================================================
FILE: internal/e2e/consume_contract_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"testing"
"time"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configgrpc"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/config/configretry"
"go.opentelemetry.io/collector/config/configtls"
"go.opentelemetry.io/collector/exporter/exporterhelper"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/exporter/otlpexporter"
"go.opentelemetry.io/collector/internal/testutil"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/receiver/otlpreceiver"
)
func testExporterConfig(endpoint string) component.Config {
retryConfig := configretry.NewDefaultBackOffConfig()
retryConfig.InitialInterval = time.Millisecond // interval is short for the test purposes
return &otlpexporter.Config{
QueueConfig: configoptional.None[exporterhelper.QueueBatchConfig](),
RetryConfig: retryConfig,
ClientConfig: configgrpc.ClientConfig{
Endpoint: endpoint,
TLS: configtls.ClientConfig{
Insecure: true,
},
},
}
}
func testReceiverConfig(endpoint string) component.Config {
cfg := otlpreceiver.NewFactory().CreateDefaultConfig()
cfg.(*otlpreceiver.Config).GRPC.GetOrInsertDefault().NetAddr.Endpoint = endpoint
return cfg
}
// TestConsumeContract is an example of testing of the exporter for the contract between the
// exporter and the receiver.
func TestConsumeContractOtlpLogs(t *testing.T) {
addr := testutil.GetAvailableLocalAddress(t)
exportertest.CheckConsumeContract(exportertest.CheckConsumeContractParams{
T: t,
NumberOfTestElements: 10,
ExporterFactory: otlpexporter.NewFactory(),
Signal: pipeline.SignalLogs,
ExporterConfig: testExporterConfig(addr),
ReceiverFactory: otlpreceiver.NewFactory(),
ReceiverConfig: testReceiverConfig(addr),
})
}
func TestConsumeContractOtlpTraces(t *testing.T) {
addr := testutil.GetAvailableLocalAddress(t)
exportertest.CheckConsumeContract(exportertest.CheckConsumeContractParams{
T: t,
NumberOfTestElements: 10,
Signal: pipeline.SignalTraces,
ExporterFactory: otlpexporter.NewFactory(),
ExporterConfig: testExporterConfig(addr),
ReceiverFactory: otlpreceiver.NewFactory(),
ReceiverConfig: testReceiverConfig(addr),
})
}
func TestConsumeContractOtlpMetrics(t *testing.T) {
addr := testutil.GetAvailableLocalAddress(t)
exportertest.CheckConsumeContract(exportertest.CheckConsumeContractParams{
T: t,
NumberOfTestElements: 10,
ExporterFactory: otlpexporter.NewFactory(),
Signal: pipeline.SignalMetrics,
ExporterConfig: testExporterConfig(addr),
ReceiverFactory: otlpreceiver.NewFactory(),
ReceiverConfig: testReceiverConfig(addr),
})
}
================================================
FILE: internal/e2e/error_propagation_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"bytes"
"context"
"io"
"net"
"net/http"
"net/http/httptest"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/status"
"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/confignet"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/config/configtls"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/exporter/exporterhelper"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/exporter/otlpexporter"
"go.opentelemetry.io/collector/exporter/otlphttpexporter"
"go.opentelemetry.io/collector/internal/testutil"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/plog/plogotlp"
"go.opentelemetry.io/collector/pdata/testdata"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/otlpreceiver"
)
var _ plogotlp.GRPCServer = &logsServer{}
type logsServer struct {
plogotlp.UnimplementedGRPCServer
exportError error
}
func (r *logsServer) Export(_ context.Context, _ plogotlp.ExportRequest) (plogotlp.ExportResponse, error) {
return plogotlp.NewExportResponse(), r.exportError
}
func TestGRPCToGRPC(t *testing.T) {
// gRPC supports 17 different status codes.
// Source: https://github.com/grpc/grpc/blob/41788c90bc66caf29f28ef808d066db806389792/doc/statuscodes.md
for i := range uint32(16) {
s := status.New(codes.Code(i), "Testing error")
t.Run("Code "+s.Code().String(), func(t *testing.T) {
e := createGRPCExporter(t, s)
assertOnGRPCCode(t, e, s)
})
}
}
func TestHTTPToGRPC(t *testing.T) {
testCases := []struct {
grpc codes.Code
http int
}{
{codes.OK, http.StatusOK},
{codes.Canceled, http.StatusServiceUnavailable},
{codes.DeadlineExceeded, http.StatusServiceUnavailable},
{codes.Aborted, http.StatusServiceUnavailable},
{codes.OutOfRange, http.StatusServiceUnavailable},
{codes.Unavailable, http.StatusServiceUnavailable},
{codes.DataLoss, http.StatusServiceUnavailable},
{codes.ResourceExhausted, http.StatusTooManyRequests},
{codes.InvalidArgument, http.StatusBadRequest},
{codes.Unauthenticated, http.StatusUnauthorized},
{codes.PermissionDenied, http.StatusForbidden},
{codes.Unimplemented, http.StatusNotFound},
}
for _, tt := range testCases {
s := status.New(tt.grpc, "Testing error")
t.Run("Code "+s.Code().String(), func(t *testing.T) {
e := createGRPCExporter(t, s)
assertOnHTTPCode(t, e, tt.http)
})
}
}
func TestGRPCToHTTP(t *testing.T) {
testCases := []struct {
http int
grpc codes.Code
}{
{http.StatusOK, codes.OK},
{http.StatusBadRequest, codes.InvalidArgument},
{http.StatusUnauthorized, codes.Unauthenticated},
{http.StatusForbidden, codes.PermissionDenied},
{http.StatusNotFound, codes.Unimplemented},
{http.StatusTooManyRequests, codes.ResourceExhausted},
{http.StatusBadGateway, codes.Unavailable},
{http.StatusServiceUnavailable, codes.Unavailable},
{http.StatusGatewayTimeout, codes.Unavailable},
}
for _, tt := range testCases {
s := status.New(tt.grpc, "Testing error")
t.Run("Code "+s.Code().String(), func(t *testing.T) {
e := createHTTPExporter(t, tt.http)
assertOnGRPCCode(t, e, s)
})
}
}
func TestHTTPToHTTP(t *testing.T) {
testCases := []struct {
code int
mapping int
}{
{code: http.StatusOK},
{code: http.StatusServiceUnavailable},
{code: http.StatusTooManyRequests},
{code: http.StatusBadRequest},
{code: http.StatusUnauthorized},
{code: http.StatusForbidden},
{code: http.StatusNotFound},
{code: http.StatusInternalServerError},
// Mappings won't be necessary once the OTLP/HTTP Exporter returns consumererror.Error types.
{code: http.StatusBadGateway, mapping: http.StatusServiceUnavailable},
{code: http.StatusGatewayTimeout, mapping: http.StatusServiceUnavailable},
{code: http.StatusTeapot, mapping: http.StatusInternalServerError},
{code: http.StatusConflict, mapping: http.StatusInternalServerError},
}
for _, tt := range testCases {
t.Run("Code "+strconv.Itoa(tt.code), func(t *testing.T) {
e := createHTTPExporter(t, tt.code)
code := tt.code
if tt.mapping != 0 {
code = tt.mapping
}
assertOnHTTPCode(t, e, code)
})
}
}
func createGRPCExporter(t *testing.T, s *status.Status) consumer.Logs {
t.Helper()
ln, err := net.Listen("tcp", "localhost:")
require.NoError(t, err, "Failed to find an available address to run the gRPC server: %v", err)
srv := grpc.NewServer()
rcv := &logsServer{
exportError: s.Err(),
}
plogotlp.RegisterGRPCServer(srv, rcv)
go func() {
assert.NoError(t, srv.Serve(ln))
}()
t.Cleanup(func() {
srv.Stop()
})
f := otlpexporter.NewFactory()
cfg := f.CreateDefaultConfig().(*otlpexporter.Config)
cfg.QueueConfig = configoptional.None[exporterhelper.QueueBatchConfig]()
cfg.RetryConfig.Enabled = false
cfg.ClientConfig = configgrpc.ClientConfig{
Endpoint: ln.Addr().String(),
TLS: configtls.ClientConfig{
Insecure: true,
},
}
e, err := f.CreateLogs(context.Background(), exportertest.NewNopSettings(component.MustNewType("otlp")), cfg)
require.NoError(t, err)
err = e.Start(context.Background(), componenttest.NewNopHost())
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, e.Shutdown(context.Background()))
})
return e
}
func createHTTPExporter(t *testing.T, code int) consumer.Logs {
t.Helper()
mux := http.NewServeMux()
mux.HandleFunc("/v1/logs", func(writer http.ResponseWriter, _ *http.Request) {
writer.WriteHeader(code)
})
srv := httptest.NewServer(mux)
t.Cleanup(func() {
srv.Close()
})
f := otlphttpexporter.NewFactory()
cfg := f.CreateDefaultConfig().(*otlphttpexporter.Config)
cfg.QueueConfig = configoptional.None[exporterhelper.QueueBatchConfig]()
cfg.RetryConfig.Enabled = false
cfg.Encoding = otlphttpexporter.EncodingProto
cfg.LogsEndpoint = srv.URL + "/v1/logs"
e, err := f.CreateLogs(context.Background(), exportertest.NewNopSettings(component.MustNewType("otlp_http")), cfg)
require.NoError(t, err)
err = e.Start(context.Background(), componenttest.NewNopHost())
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, e.Shutdown(context.Background()))
})
return e
}
func assertOnGRPCCode(t *testing.T, l consumer.Logs, s *status.Status) {
t.Helper()
rf := otlpreceiver.NewFactory()
rcfg := rf.CreateDefaultConfig().(*otlpreceiver.Config)
rcfg.GRPC = configoptional.Some(
configgrpc.ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: testutil.GetAvailableLocalAddress(t),
Transport: confignet.TransportTypeTCP,
},
},
)
r, err := rf.CreateLogs(context.Background(), receiver.Settings{
ID: component.MustNewID("otlp"),
TelemetrySettings: componenttest.NewNopTelemetrySettings(),
}, rcfg, l)
require.NoError(t, err)
err = r.Start(context.Background(), componenttest.NewNopHost())
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, r.Shutdown(context.Background()))
})
conn, err := grpc.NewClient(rcfg.GRPC.Get().NetAddr.Endpoint, grpc.WithTransportCredentials(insecure.NewCredentials()))
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, conn.Close())
})
ld := testdata.GenerateLogs(2)
acc := plogotlp.NewGRPCClient(conn)
req := plogotlp.NewExportRequestFromLogs(ld)
res, err := acc.Export(context.Background(), req)
if s.Code() == codes.OK {
require.NoError(t, err)
} else {
got := status.Convert(err).Code()
require.Equal(t, s.Code(), got, "Expected code %s but got %s", s.Code().String(), got.String())
}
require.NotNil(t, res)
}
func assertOnHTTPCode(t *testing.T, l consumer.Logs, code int) {
t.Helper()
ld := testdata.GenerateLogs(2)
protoMarshaler := &plog.ProtoMarshaler{}
logProto, err := protoMarshaler.MarshalLogs(ld)
require.NoError(t, err)
addr := testutil.GetAvailableLocalAddress(t)
rf := otlpreceiver.NewFactory()
rcfg := rf.CreateDefaultConfig().(*otlpreceiver.Config)
rcfg.HTTP = configoptional.Some(
otlpreceiver.HTTPConfig{
ServerConfig: confighttp.ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: addr,
Transport: confignet.TransportTypeTCP,
},
},
LogsURLPath: "/v1/logs",
},
)
r, err := rf.CreateLogs(context.Background(), receiver.Settings{
ID: component.MustNewID("otlp"),
TelemetrySettings: componenttest.NewNopTelemetrySettings(),
}, rcfg, l)
require.NoError(t, err)
err = r.Start(context.Background(), componenttest.NewNopHost())
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, r.Shutdown(context.Background()))
})
doHTTPRequest(t, addr+"/v1/logs", logProto, code)
}
func doHTTPRequest(
t *testing.T,
url string,
data []byte,
expectStatusCode int,
) []byte {
req := createHTTPRequest(t, url, data)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
respBytes, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
if expectStatusCode == 0 {
require.Equal(t, http.StatusOK, resp.StatusCode)
} else {
require.Equal(t, expectStatusCode, resp.StatusCode)
}
return respBytes
}
func createHTTPRequest(
t *testing.T,
url string,
data []byte,
) *http.Request {
buf := bytes.NewBuffer(data)
req, err := http.NewRequest(http.MethodPost, "http://"+url, buf)
require.NoError(t, err)
req.Header.Set("Content-Type", "application/x-protobuf")
return req
}
================================================
FILE: internal/e2e/exporter_failure_attributes_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"path/filepath"
"sync/atomic"
"testing"
"time"
dto "github.com/prometheus/client_model/go"
"github.com/prometheus/common/expfmt"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/provider/envprovider"
"go.opentelemetry.io/collector/confmap/provider/fileprovider"
"go.opentelemetry.io/collector/confmap/provider/yamlprovider"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/otlphttpexporter"
"go.opentelemetry.io/collector/otelcol"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/otlpreceiver"
"go.opentelemetry.io/collector/service/telemetry/otelconftelemetry"
)
func TestExporterFailureAttributesDetailed(t *testing.T) {
t.Run("permanent error sets error.permanent", func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/metrics" {
w.WriteHeader(http.StatusNotFound)
return
}
http.Error(w, "bad request", http.StatusBadRequest)
}))
defer server.Close()
otelPort, metricsPort := startFailureAttributeCollector(t, server.URL)
require.NoError(t, sendTestMetrics(otelPort))
require.Eventually(t, func() bool {
metric := scrapeFailureMetric(t, metricsPort, "otlp_http/test")
if metric == nil {
return false
}
failurePermanent, ok := labelValue(metric, "error_permanent")
return ok && failurePermanent == "true"
}, 5*time.Second, 200*time.Millisecond, "expected permanent failure metric")
})
t.Run("transient error that recovers has no failure metric", func(t *testing.T) {
var attempts atomic.Int32
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/metrics" {
w.WriteHeader(http.StatusNotFound)
return
}
if attempts.Add(1) == 1 {
http.Error(w, "try again", http.StatusServiceUnavailable)
return
}
w.WriteHeader(http.StatusOK)
}))
defer server.Close()
otelPort, metricsPort := startFailureAttributeCollector(t, server.URL)
require.NoError(t, sendTestMetrics(otelPort))
assertNoFailureMetric(t, metricsPort, "otlp_http/test")
})
t.Run("retryable error exhausts retries", func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/metrics" {
w.WriteHeader(http.StatusNotFound)
return
}
http.Error(w, "temporarily unavailable", http.StatusServiceUnavailable)
}))
defer server.Close()
otelPort, metricsPort := startFailureAttributeCollector(t, server.URL)
require.NoError(t, sendTestMetrics(otelPort))
require.Eventually(t, func() bool {
metric := scrapeFailureMetric(t, metricsPort, "otlp_http/test")
if metric == nil {
return false
}
failurePermanent, ok := labelValue(metric, "error_permanent")
return ok && failurePermanent == "false"
}, 5*time.Second, 200*time.Millisecond, "expected retry exhaustion metric")
})
}
func startFailureAttributeCollector(t *testing.T, exporterEndpoint string) (string, string) {
t.Helper()
otelPort := getFreePort(t)
metricsPort := getFreePort(t)
t.Setenv("METRICS_PORT", metricsPort)
t.Setenv("OTEL_PORT", otelPort)
t.Setenv("EXPORTER_ENDPOINT", exporterEndpoint)
collector, err := otelcol.NewCollector(otelcol.CollectorSettings{
BuildInfo: component.NewDefaultBuildInfo(),
Factories: func() (otelcol.Factories, error) {
return otelcol.Factories{
Receivers: map[component.Type]receiver.Factory{otlpreceiver.NewFactory().Type(): otlpreceiver.NewFactory()},
Exporters: map[component.Type]exporter.Factory{
otlphttpexporter.NewFactory().Type(): otlphttpexporter.NewFactory(),
},
Telemetry: otelconftelemetry.NewFactory(),
}, nil
},
ConfigProviderSettings: otelcol.ConfigProviderSettings{
ResolverSettings: confmap.ResolverSettings{
URIs: []string{filepath.Join("testdata", "exporter_failure_attributes_test.yaml")},
ProviderFactories: []confmap.ProviderFactory{
fileprovider.NewFactory(),
yamlprovider.NewFactory(),
envprovider.NewFactory(),
},
},
},
})
require.NoError(t, err)
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
go func() {
if err := collector.Run(ctx); err != nil {
t.Logf("Collector stopped with error: %v", err)
}
}()
require.Eventually(t, func() bool {
resp, err := http.Get(fmt.Sprintf("http://localhost:%s/metrics", metricsPort))
if err != nil {
return false
}
resp.Body.Close()
return resp.StatusCode == http.StatusOK
}, 5*time.Second, 100*time.Millisecond, "collector failed to start")
return otelPort, metricsPort
}
func scrapeFailureMetric(t *testing.T, metricsPort, exporterName string) *dto.Metric {
t.Helper()
resp, err := http.Get(fmt.Sprintf("http://localhost:%s/metrics", metricsPort))
if err != nil {
return nil
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil
}
parser := expfmt.NewTextParser(model.UTF8Validation)
parsed, err := parser.TextToMetricFamilies(resp.Body)
if err != nil {
return nil
}
metricFamily, ok := parsed["otelcol_exporter_send_failed_metric_points"]
if !ok {
metricFamily, ok = parsed["otelcol_exporter_send_failed_metric_points_total"]
}
if !ok {
return nil
}
for _, metric := range metricFamily.Metric {
if hasLabel(metric, "exporter", exporterName) {
return metric
}
}
return nil
}
func hasLabel(metric *dto.Metric, name, expected string) bool {
for _, label := range metric.Label {
if label.GetName() == name && label.GetValue() == expected {
return true
}
}
return false
}
func labelValue(metric *dto.Metric, labelName string) (string, bool) {
for _, label := range metric.Label {
if label.GetName() == labelName {
return label.GetValue(), true
}
}
return "", false
}
func assertNoFailureMetric(t *testing.T, metricsPort, exporterName string) {
t.Helper()
deadline := time.Now().Add(2 * time.Second)
for time.Now().Before(deadline) {
if metric := scrapeFailureMetric(t, metricsPort, exporterName); metric != nil {
failurePermanent, _ := labelValue(metric, "error_permanent")
t.Fatalf("unexpected failure metric recorded, error_permanent=%s", failurePermanent)
}
time.Sleep(100 * time.Millisecond)
}
}
================================================
FILE: internal/e2e/go.mod
================================================
module go.opentelemetry.io/collector/internal/e2e
go 1.25.0
require (
github.com/google/go-cmp v0.7.0
github.com/google/uuid v1.6.0
github.com/prometheus/client_model v0.6.2
github.com/prometheus/common v0.67.5
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componentstatus v0.148.0
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.opentelemetry.io/collector/config/configauth v1.54.0
go.opentelemetry.io/collector/config/configgrpc v0.148.0
go.opentelemetry.io/collector/config/confighttp v0.148.0
go.opentelemetry.io/collector/config/confignet v1.54.0
go.opentelemetry.io/collector/config/configopaque v1.54.0
go.opentelemetry.io/collector/config/configoptional v1.54.0
go.opentelemetry.io/collector/config/configretry v1.54.0
go.opentelemetry.io/collector/config/configtelemetry v0.148.0
go.opentelemetry.io/collector/config/configtls v1.54.0
go.opentelemetry.io/collector/confmap v1.54.0
go.opentelemetry.io/collector/confmap/provider/envprovider v1.54.0
go.opentelemetry.io/collector/confmap/provider/fileprovider v1.54.0
go.opentelemetry.io/collector/confmap/provider/yamlprovider v1.54.0
go.opentelemetry.io/collector/connector v0.148.0
go.opentelemetry.io/collector/connector/connectortest v0.148.0
go.opentelemetry.io/collector/consumer v1.54.0
go.opentelemetry.io/collector/consumer/consumertest v0.148.0
go.opentelemetry.io/collector/exporter v1.54.0
go.opentelemetry.io/collector/exporter/debugexporter v0.148.0
go.opentelemetry.io/collector/exporter/exporterhelper v0.148.0
go.opentelemetry.io/collector/exporter/exportertest v0.148.0
go.opentelemetry.io/collector/exporter/otlpexporter v0.148.0
go.opentelemetry.io/collector/exporter/otlphttpexporter v0.148.0
go.opentelemetry.io/collector/extension v1.54.0
go.opentelemetry.io/collector/internal/sharedcomponent v0.148.0
go.opentelemetry.io/collector/internal/testutil v0.148.0
go.opentelemetry.io/collector/otelcol v0.148.0
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/collector/pdata/testdata v0.148.0
go.opentelemetry.io/collector/pipeline v1.54.0
go.opentelemetry.io/collector/processor v1.54.0
go.opentelemetry.io/collector/processor/batchprocessor v0.148.0
go.opentelemetry.io/collector/receiver v1.54.0
go.opentelemetry.io/collector/receiver/otlpreceiver v0.148.0
go.opentelemetry.io/collector/receiver/receivertest v0.148.0
go.opentelemetry.io/collector/service v0.148.0
go.opentelemetry.io/proto/otlp v1.10.0
go.uber.org/goleak v1.3.0
go.uber.org/zap v1.27.1
google.golang.org/grpc v1.79.3
google.golang.org/protobuf v1.36.11
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/ebitengine/purego v0.10.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/go-tpm v0.9.8 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.18.4 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/mostynb/go-grpc-compression v1.2.3 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pierrec/lz4/v4 v4.1.26 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/otlptranslator v1.0.0 // indirect
github.com/prometheus/procfs v0.20.1 // indirect
github.com/rs/cors v1.11.1 // indirect
github.com/shirou/gopsutil/v4 v4.26.2 // indirect
github.com/spf13/cobra v1.10.2 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.11.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector v0.148.0 // indirect
go.opentelemetry.io/collector/client v1.54.0 // indirect
go.opentelemetry.io/collector/config/configcompression v1.54.0 // indirect
go.opentelemetry.io/collector/config/configmiddleware v1.54.0 // indirect
go.opentelemetry.io/collector/confmap/xconfmap v0.148.0 // indirect
go.opentelemetry.io/collector/connector/xconnector v0.148.0 // indirect
go.opentelemetry.io/collector/consumer/consumererror v0.148.0 // indirect
go.opentelemetry.io/collector/consumer/consumererror/xconsumererror v0.148.0 // indirect
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0 // indirect
go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper v0.148.0 // indirect
go.opentelemetry.io/collector/exporter/xexporter v0.148.0 // indirect
go.opentelemetry.io/collector/extension/extensionauth v1.54.0 // indirect
go.opentelemetry.io/collector/extension/extensioncapabilities v0.148.0 // indirect
go.opentelemetry.io/collector/extension/extensionmiddleware v0.148.0 // indirect
go.opentelemetry.io/collector/extension/extensiontest v0.148.0 // indirect
go.opentelemetry.io/collector/extension/xextension v0.148.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
go.opentelemetry.io/collector/internal/fanoutconsumer v0.148.0 // indirect
go.opentelemetry.io/collector/internal/telemetry v0.148.0 // indirect
go.opentelemetry.io/collector/pdata/pprofile v0.148.0 // indirect
go.opentelemetry.io/collector/pdata/xpdata v0.148.0 // indirect
go.opentelemetry.io/collector/pipeline/xpipeline v0.148.0 // indirect
go.opentelemetry.io/collector/processor/processortest v0.148.0 // indirect
go.opentelemetry.io/collector/processor/xprocessor v0.148.0 // indirect
go.opentelemetry.io/collector/receiver/receiverhelper v0.148.0 // indirect
go.opentelemetry.io/collector/receiver/xreceiver v0.148.0 // indirect
go.opentelemetry.io/collector/service/hostcapabilities v0.148.0 // indirect
go.opentelemetry.io/contrib/bridges/otelzap v0.17.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect
go.opentelemetry.io/contrib/otelconf v0.22.0 // indirect
go.opentelemetry.io/contrib/propagators/b3 v1.42.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/prometheus v0.64.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 // indirect
go.opentelemetry.io/otel/log v0.18.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
go.opentelemetry.io/otel/sdk/log v0.18.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.34.0 // indirect
gonum.org/v1/gonum v0.17.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector => ../..
replace go.opentelemetry.io/collector/config/configopaque => ../../config/configopaque
replace go.opentelemetry.io/collector/config/configoptional => ../../config/configoptional
replace go.opentelemetry.io/collector/config/configgrpc => ../../config/configgrpc
replace go.opentelemetry.io/collector/config/confignet => ../../config/confignet
replace go.opentelemetry.io/collector/config/confighttp => ../../config/confighttp
replace go.opentelemetry.io/collector/config/configauth => ../../config/configauth
replace go.opentelemetry.io/collector/config/configretry => ../../config/configretry
replace go.opentelemetry.io/collector/config/configtls => ../../config/configtls
replace go.opentelemetry.io/collector/extension/extensionauth => ../../extension/extensionauth
replace go.opentelemetry.io/collector/exporter/otlpexporter => ../../exporter/otlpexporter
replace go.opentelemetry.io/collector/config/configcompression => ../../config/configcompression
replace go.opentelemetry.io/collector/exporter/otlphttpexporter => ../../exporter/otlphttpexporter
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/pdata/testdata => ../../pdata/testdata
replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile
replace go.opentelemetry.io/collector/consumer => ../../consumer
replace go.opentelemetry.io/collector/receiver/otlpreceiver => ../../receiver/otlpreceiver
replace go.opentelemetry.io/collector/receiver => ../../receiver
replace go.opentelemetry.io/collector/receiver/receiverhelper => ../../receiver/receiverhelper
replace go.opentelemetry.io/collector/extension => ../../extension
replace go.opentelemetry.io/collector/confmap => ../../confmap
replace go.opentelemetry.io/collector/confmap/xconfmap => ../../confmap/xconfmap
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest
replace go.opentelemetry.io/collector/exporter => ../../exporter
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/config/configtelemetry => ../../config/configtelemetry
replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer
replace go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest
replace go.opentelemetry.io/collector/client => ../../client
replace go.opentelemetry.io/collector/component/componentstatus => ../../component/componentstatus
replace go.opentelemetry.io/collector/connector => ../../connector
replace go.opentelemetry.io/collector/connector/connectortest => ../../connector/connectortest
replace go.opentelemetry.io/collector/processor => ../../processor
replace go.opentelemetry.io/collector/extension/zpagesextension => ../../extension/zpagesextension
replace go.opentelemetry.io/collector/service => ../../service
replace go.opentelemetry.io/collector/service/telemetry/telemetrytest => ../../service/telemetry/telemetrytest
replace go.opentelemetry.io/collector/extension/extensioncapabilities => ../../extension/extensioncapabilities
replace go.opentelemetry.io/collector/receiver/xreceiver => ../../receiver/xreceiver
replace go.opentelemetry.io/collector/receiver/receivertest => ../../receiver/receivertest
replace go.opentelemetry.io/collector/processor/xprocessor => ../../processor/xprocessor
replace go.opentelemetry.io/collector/connector/xconnector => ../../connector/xconnector
replace go.opentelemetry.io/collector/exporter/xexporter => ../../exporter/xexporter
replace go.opentelemetry.io/collector/pipeline => ../../pipeline
replace go.opentelemetry.io/collector/pipeline/xpipeline => ../../pipeline/xpipeline
replace go.opentelemetry.io/collector/exporter/exportertest => ../../exporter/exportertest
replace go.opentelemetry.io/collector/processor/processortest => ../../processor/processortest
replace go.opentelemetry.io/collector/consumer/consumererror/xconsumererror => ../../consumer/consumererror/xconsumererror
replace go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper => ../../exporter/exporterhelper/xexporterhelper
replace go.opentelemetry.io/collector/consumer/consumererror => ../../consumer/consumererror
replace go.opentelemetry.io/collector/internal/fanoutconsumer => ../../internal/fanoutconsumer
replace go.opentelemetry.io/collector/internal/sharedcomponent => ../../internal/sharedcomponent
replace go.opentelemetry.io/collector/internal/telemetry => ../../internal/telemetry
replace go.opentelemetry.io/collector/extension/extensiontest => ../../extension/extensiontest
replace go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest => ../../extension/extensionauth/extensionauthtest
replace go.opentelemetry.io/collector/extension/xextension => ../../extension/xextension
replace go.opentelemetry.io/collector/otelcol => ../../otelcol
replace go.opentelemetry.io/collector/confmap/provider/yamlprovider => ../../confmap/provider/yamlprovider
replace go.opentelemetry.io/collector/confmap/provider/fileprovider => ../../confmap/provider/fileprovider
replace go.opentelemetry.io/collector/service/hostcapabilities => ../../service/hostcapabilities
replace go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest => ../../extension/extensionmiddleware/extensionmiddlewaretest
replace go.opentelemetry.io/collector/extension/extensionmiddleware => ../../extension/extensionmiddleware
replace go.opentelemetry.io/collector/config/configmiddleware => ../../config/configmiddleware
replace go.opentelemetry.io/collector/pdata/xpdata => ../../pdata/xpdata
replace go.opentelemetry.io/collector/exporter/debugexporter => ../../exporter/debugexporter
replace go.opentelemetry.io/collector/processor/batchprocessor => ../../processor/batchprocessor
replace go.opentelemetry.io/collector/confmap/provider/envprovider => ../../confmap/provider/envprovider
replace go.opentelemetry.io/collector/exporter/exporterhelper => ../../exporter/exporterhelper
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../componentalias
================================================
FILE: internal/e2e/go.sum
================================================
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/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f h1:RJ+BDPLSHQO7cSjKBqjPJSbi1qfk9WcsjQDtZiw3dZw=
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f/go.mod h1:VHbbch/X4roIY22jL1s3qRbZhCiRIgUAF/PdSUcx2io=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/go-tpm-tools v0.4.7 h1:J3ycC8umYxM9A4eF73EofRZu4BxY0jjQnUnkhIBbvws=
github.com/google/go-tpm-tools v0.4.7/go.mod h1:gSyXTZHe3fgbzb6WEGd90QucmsnT1SRdlye82gH8QjQ=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
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 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mostynb/go-grpc-compression v1.2.3 h1:42/BKWMy0KEJGSdWvzqIyOZ95YcR9mLPqKctH7Uo//I=
github.com/mostynb/go-grpc-compression v1.2.3/go.mod h1:AghIxF3P57umzqM9yz795+y1Vjs47Km/Y2FE6ouQ7Lg=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY=
github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos=
github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM=
github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shirou/gopsutil/v4 v4.26.2 h1:X8i6sicvUFih4BmYIGT1m2wwgw2VG9YgrDTi7cIRGUI=
github.com/shirou/gopsutil/v4 v4.26.2/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/bridges/otelzap v0.17.0 h1:oCltVHJcblcth2z9B9dRTeZIZTe2Sf9Ad9h8bcc+s8M=
go.opentelemetry.io/contrib/bridges/otelzap v0.17.0/go.mod h1:G/VE1A/hRn6mEWdfC8rMvSdQVGM64KUPi4XilLkwcQw=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
go.opentelemetry.io/contrib/otelconf v0.22.0 h1:+kpcfczGOFM85zDZyqQCzWefhovegfn24D0WwmQz0n4=
go.opentelemetry.io/contrib/otelconf v0.22.0/go.mod h1:ojdbOukO+JRDJQmJY2PRIZEg0UYVzcOuZR59hp7xffc=
go.opentelemetry.io/contrib/propagators/b3 v1.42.0 h1:B2Pew5ufEtgkjLF+tSkXjgYZXQr9m7aCm1wLKB0URbU=
go.opentelemetry.io/contrib/propagators/b3 v1.42.0/go.mod h1:iPgUcSEF5DORW6+yNbdw/YevUy+QqJ508ncjhrRSCjc=
go.opentelemetry.io/contrib/zpages v0.67.0 h1:cIUwWSVDovuLEbDIKreptjdxMuIhGiqwq0uL8YNaq1c=
go.opentelemetry.io/contrib/zpages v0.67.0/go.mod h1:vK8fsYHgPYg4Z/XDbFSEvItSGZDbjWTvjBOu8+AiDhc=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 h1:deI9UQMoGFgrg5iLPgzueqFPHevDl+28YKfSpPTI6rY=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0/go.mod h1:PFx9NgpNUKXdf7J4Q3agRxMs3Y07QhTCVipKmLsMKnU=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0 h1:icqq3Z34UrEFk2u+HMhTtRsvo7Ues+eiJVjaJt62njs=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0/go.mod h1:W2m8P+d5Wn5kipj4/xmbt9uMqezEKfBjzVJadfABSBE=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 h1:MdKucPl/HbzckWWEisiNqMPhRrAOQX8r4jTuGr636gk=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0/go.mod h1:RolT8tWtfHcjajEH5wFIZ4Dgh5jpPdFXYV9pTAk/qjc=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0 h1:H7O6RlGOMTizyl3R08Kn5pdM06bnH8oscSj7o11tmLA=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0/go.mod h1:mBFWu/WOVDkWWsR7Tx7h6EpQB8wsv7P0Yrh0Pb7othc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 h1:uLXP+3mghfMf7XmV4PkGfFhFKuNWoCvvx5wP/wOXo0o=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0/go.mod h1:v0Tj04armyT59mnURNUJf7RCKcKzq+lgJs6QSjHjaTc=
go.opentelemetry.io/otel/exporters/prometheus v0.64.0 h1:g0LRDXMX/G1SEZtK8zl8Chm4K6GBwRkjPKE36LxiTYs=
go.opentelemetry.io/otel/exporters/prometheus v0.64.0/go.mod h1:UrgcjnarfdlBDP3GjDIJWe6HTprwSazNjwsI+Ru6hro=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 h1:KJVjPD3rcPb98rIs3HznyJlrfx9ge5oJvxxlGR+P/7s=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0/go.mod h1:K3kRa2ckmHWQaTWQdPRHc7qGXASuVuoEQXzrvlA98Ws=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 h1:lSZHgNHfbmQTPfuTmWVkEu8J8qXaQwuV30pjCcAUvP8=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0/go.mod h1:so9ounLcuoRDu033MW/E0AD4hhUjVqswrMF5FoZlBcw=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1JF26knRneorus8aOwVIDhvYx9WoDw=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs=
go.opentelemetry.io/otel/log v0.18.0 h1:XgeQIIBjZZrliksMEbcwMZefoOSMI1hdjiLEiiB0bAg=
go.opentelemetry.io/otel/log v0.18.0/go.mod h1:KEV1kad0NofR3ycsiDH4Yjcoj0+8206I6Ox2QYFSNgI=
go.opentelemetry.io/otel/log/logtest v0.18.0 h1:2QeyoKJdIgK2LJhG1yn78o/zmpXx1EditeyRDREqVS8=
go.opentelemetry.io/otel/log/logtest v0.18.0/go.mod h1:v1vh3PYR9zIa5MK6HwkH2lMrLBg/Y9Of6Qc+krlesX0=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/log v0.18.0 h1:n8OyZr7t7otkeTnPTbDNom6rW16TBYGtvyy2Gk6buQw=
go.opentelemetry.io/otel/sdk/log v0.18.0/go.mod h1:C0+wxkTwKpOCZLrlJ3pewPiiQwpzycPI/u6W0Z9fuYk=
go.opentelemetry.io/otel/sdk/log/logtest v0.18.0 h1:l3mYuPsuBx6UKE47BVcPrZoZ0q/KER57vbj2qkgDLXA=
go.opentelemetry.io/otel/sdk/log/logtest v0.18.0/go.mod h1:7cHtiVJpZebB3wybTa4NG+FUo5NPe3PROz1FqB0+qdw=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 h1:tu/dtnW1o3wfaxCOjSLn5IRX4YDcJrtlpzYkhHhGaC4=
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: internal/e2e/internal_telemetry_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"slices"
"sync"
"testing"
"time"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/provider/fileprovider"
"go.opentelemetry.io/collector/confmap/provider/yamlprovider"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/otelcol"
"go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/otlpreceiver"
"go.opentelemetry.io/collector/service/telemetry/otelconftelemetry"
)
// TestInternalTelemetry_ServiceInstanceID verifies that the service.instance.id
// attribute is generated by default (unless overridden), and is is consistent
// across all internal telemetry providers.
func TestInternalTelemetry_ServiceInstanceID(t *testing.T) {
type testcase struct {
extraYamlConfig string
checkServiceInstanceID func(t *testing.T, serviceInstanceID string)
}
for name, tt := range map[string]testcase{
"default": {
checkServiceInstanceID: func(t *testing.T, serviceInstanceID string) {
// By default, service.instance.id should be a generated UUIDv4
_, err := uuid.Parse(serviceInstanceID)
require.NoError(t, err)
},
},
"service.instance.id set in config": {
extraYamlConfig: `
service:
telemetry:
resource:
service.instance.id: "my-custom-instance-id"`,
checkServiceInstanceID: func(t *testing.T, serviceInstanceID string) {
assert.Equal(t, "my-custom-instance-id", serviceInstanceID)
},
},
} {
t.Run(name, func(t *testing.T) {
// Set up HTTP server to capture traces from collector's internal telemetry
traceSink := new(consumertest.TracesSink)
traceServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
otlpReq := ptraceotlp.NewExportRequest()
if err := otlpReq.UnmarshalProto(body); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
_ = traceSink.ConsumeTraces(r.Context(), otlpReq.Traces())
}))
defer traceServer.Close()
logSink := registerTestLogSink(t)
// Create temporary directory for the config file
tempdir := t.TempDir()
configFile := filepath.Join(tempdir, "config.yaml")
// Create YAML config
otlphttpPort := getFreePort(t)
metricsPort := getFreePort(t)
require.NoError(t, os.WriteFile(configFile, []byte(fmt.Sprintf(`
receivers:
otlp:
protocols:
http:
endpoint: localhost:%s
exporters:
nop:
service:
telemetry:
logs:
level: info
encoding: json
output_paths: [%q]
metrics:
level: normal
readers:
- pull:
exporter:
prometheus:
host: localhost
port: %s
traces:
level: normal
processors:
- simple:
exporter:
otlp:
protocol: http/protobuf
endpoint: %s
pipelines:
traces:
receivers: [otlp]
exporters: [nop]
`, otlphttpPort, logSink.url, metricsPort, traceServer.URL)[1:]), 0o600))
// Create collector
configURIs := []string{configFile}
if tt.extraYamlConfig != "" {
configURIs = append(configURIs, "yaml:"+tt.extraYamlConfig)
}
collector, err := otelcol.NewCollector(otelcol.CollectorSettings{
BuildInfo: component.NewDefaultBuildInfo(),
Factories: func() (otelcol.Factories, error) {
otlpreceiverFactory := otlpreceiver.NewFactory()
return otelcol.Factories{
Receivers: map[component.Type]receiver.Factory{
otlpreceiverFactory.Type(): otlpreceiverFactory,
},
Exporters: map[component.Type]exporter.Factory{
nopType: exportertest.NewNopFactory(),
},
Telemetry: otelconftelemetry.NewFactory(),
}, nil
},
ConfigProviderSettings: otelcol.ConfigProviderSettings{
ResolverSettings: confmap.ResolverSettings{
URIs: configURIs,
ProviderFactories: []confmap.ProviderFactory{
fileprovider.NewFactory(),
yamlprovider.NewFactory(),
},
},
},
})
require.NoError(t, err)
// Start collector
go func() {
assert.NoError(t, collector.Run(t.Context()))
}()
waitMetricsReady(t, metricsPort)
// Send some data through the pipeline to trigger internal telemetry
err = sendTestTraces(otlphttpPort)
require.NoError(t, err)
// Capture service.instance.id from the Prometheus endpoint
var metricInstanceID string
parsed := readMetrics(t, metricsPort)
targetInfo := parsed["target_info"]
require.NotNil(t, targetInfo, "target_info metric not found")
require.Len(t, targetInfo.Metric, 1)
for _, label := range targetInfo.Metric[0].Label {
if label.GetName() == "service_instance_id" {
metricInstanceID = label.GetValue()
break
}
}
tt.checkServiceInstanceID(t, metricInstanceID)
// Wait for traces, verify service.instance.id matches the one from metrics
require.EventuallyWithT(t, func(t *assert.CollectT) {
allTraces := traceSink.AllTraces()
require.NotEmpty(t, allTraces)
// Find service.instance.id in resource attributes
for _, td := range allTraces {
for i := 0; i < td.ResourceSpans().Len(); i++ {
rs := td.ResourceSpans().At(i)
if attr, ok := rs.Resource().Attributes().Get("service.instance.id"); ok {
traceInstanceID := attr.AsString()
require.Equal(t, metricInstanceID, traceInstanceID)
}
}
}
}, 10*time.Second, 500*time.Millisecond)
// Check service.instance.id in logs matches the one from metrics
var logsCount int
logContent := logSink.Bytes()
for line := range bytes.Lines(bytes.TrimSpace(logContent)) {
var logEntry map[string]any
if err := json.Unmarshal(line, &logEntry); err != nil {
continue
}
// Check for resource field with service.instance.id
// Resource attributes are nested under "resource" key as a dictionary
resource, ok := logEntry["resource"].(map[string]any)
require.True(t, ok, "log entry should have resource field")
logInstanceID, ok := resource["service.instance.id"].(string)
require.True(t, ok, "resource should have service.instance.id")
require.Equal(t, metricInstanceID, logInstanceID)
logsCount++
}
assert.NotZero(t, logsCount)
})
}
}
// Test-specific zap sink to capture logs as close as possible to logs being written to file.
// The reason we don't actually write to files is because Zap provides no way of closing file
// sinks created by zap.Config.Build.
var (
testSinksMu sync.Mutex
testSinks = make(map[string]*testSink)
)
type testSink struct {
url string
mu sync.RWMutex
buf bytes.Buffer
}
func (s *testSink) Write(p []byte) (n int, err error) {
s.mu.Lock()
defer s.mu.Unlock()
return s.buf.Write(p)
}
func (s *testSink) Bytes() []byte {
s.mu.RLock()
defer s.mu.RUnlock()
return slices.Clone(s.buf.Bytes())
}
func (*testSink) Sync() error {
return nil
}
func (*testSink) Close() error {
return nil
}
func registerTestLogSink(tb testing.TB) *testSink {
sink := &testSink{}
sink.url = fmt.Sprintf("test://%s.%p", tb.Name(), sink)
testSinksMu.Lock()
defer testSinksMu.Unlock()
testSinks[sink.url] = sink
tb.Cleanup(func() {
testSinksMu.Lock()
defer testSinksMu.Unlock()
delete(testSinks, sink.url)
})
return sink
}
func init() {
if err := zap.RegisterSink("test", func(u *url.URL) (zap.Sink, error) {
testSinksMu.Lock()
defer testSinksMu.Unlock()
sink, ok := testSinks[u.String()]
if !ok {
return nil, fmt.Errorf("no test sink registered for URL %q", u.String())
}
return sink, nil
}); err != nil {
log.Fatal(err)
}
}
================================================
FILE: internal/e2e/metric_stability_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"bufio"
"bytes"
"context"
"fmt"
"net"
"net/http"
"path/filepath"
"strconv"
"testing"
"time"
dto "github.com/prometheus/client_model/go"
"github.com/prometheus/common/expfmt"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/provider/envprovider"
"go.opentelemetry.io/collector/confmap/provider/fileprovider"
"go.opentelemetry.io/collector/confmap/provider/yamlprovider"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/debugexporter"
"go.opentelemetry.io/collector/exporter/otlphttpexporter"
"go.opentelemetry.io/collector/otelcol"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/batchprocessor"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/otlpreceiver"
"go.opentelemetry.io/collector/service/telemetry/otelconftelemetry"
)
func assertMetrics(t *testing.T, metricsPort string, expectedMetrics map[string]bool) bool {
parsed := readMetrics(t, metricsPort)
for metricName, metricFamily := range parsed {
if _, ok := expectedMetrics[metricName]; ok {
expectedMetrics[metricName] = true
assert.GreaterOrEqual(t, len(metricFamily.Metric), 1,
"metric %s should have at least one data point", metricName)
}
}
for metricName, found := range expectedMetrics {
if !found {
t.Logf("expected metric %s was not found", metricName)
return false
}
}
return true
}
func TestMetricStability(t *testing.T) {
tests := []struct {
name string
configFile string
expectedMetrics map[string]bool
otelPort string
metricsPort string
}{
{
name: "No metric readers (default)",
configFile: "metric_stability_test_no_readers.yaml",
expectedMetrics: map[string]bool{
// Process metrics
"otelcol_process_uptime": false,
"otelcol_process_cpu_seconds": false,
"otelcol_process_memory_rss": false,
"otelcol_process_runtime_heap_alloc_bytes": false,
"otelcol_process_runtime_total_alloc_bytes": false,
"otelcol_process_runtime_total_sys_memory_bytes": false,
// Batch processor metrics
"otelcol_processor_batch_batch_send_size": false,
"otelcol_processor_batch_batch_send_size_bytes": false,
"otelcol_processor_batch_metadata_cardinality": false,
"otelcol_processor_batch_timeout_trigger_send": false,
// HTTP server metrics
"http_server_request_body_size": false,
"http_server_request_duration": false,
"http_server_response_body_size": false,
// Exporter metrics
"otelcol_exporter_sent_metric_points": false,
"otelcol_exporter_send_failed_metric_points": false,
"otelcol_exporter_sent_spans": false,
"otelcol_exporter_send_failed_spans": false,
"otelcol_exporter_sent_log_records": false,
"otelcol_exporter_send_failed_log_records": false,
// Receiver metrics
"otelcol_receiver_accepted_metric_points": false,
"otelcol_receiver_refused_metric_points": false,
"otelcol_receiver_accepted_spans": false,
"otelcol_receiver_refused_spans": false,
"otelcol_receiver_accepted_log_records": false,
"otelcol_receiver_refused_log_records": false,
// Other metrics
"promhttp_metric_handler_errors_total": false,
"target_info": false,
},
otelPort: getFreePort(t),
metricsPort: "8888", // default metrics port
},
{
name: "Metric readers",
configFile: "metric_stability_test_readers.yaml",
expectedMetrics: map[string]bool{
// Process metrics
"otelcol_process_uptime_seconds_total": false,
"otelcol_process_cpu_seconds_total": false,
"otelcol_process_memory_rss_bytes": false,
"otelcol_process_runtime_heap_alloc_bytes": false,
"otelcol_process_runtime_total_alloc_bytes_total": false,
"otelcol_process_runtime_total_sys_memory_bytes": false,
// Batch processor metrics
"otelcol_processor_batch_batch_send_size": false,
"otelcol_processor_batch_batch_send_size_bytes": false,
"otelcol_processor_batch_metadata_cardinality": false,
"otelcol_processor_batch_timeout_trigger_send_total": false,
// HTTP server metrics
"http_server_request_body_size_bytes": false,
"http_server_request_duration_seconds": false,
"http_server_response_body_size_bytes": false,
// Exporter metrics - Metrics
"otelcol_exporter_sent_metric_points_total": false,
"otelcol_exporter_send_failed_metric_points_total": false,
// Exporter metrics - Traces
"otelcol_exporter_sent_spans_total": false,
"otelcol_exporter_send_failed_spans_total": false,
// Exporter metrics - Logs
"otelcol_exporter_sent_log_records_total": false,
"otelcol_exporter_send_failed_log_records_total": false,
// Receiver metrics
"otelcol_receiver_accepted_metric_points_total": false,
"otelcol_receiver_refused_metric_points_total": false,
// Receiver metrics - Traces
"otelcol_receiver_accepted_spans_total": false,
"otelcol_receiver_refused_spans_total": false,
// Receiver metrics - Logs
"otelcol_receiver_accepted_log_records_total": false,
"otelcol_receiver_refused_log_records_total": false,
// Other metrics
"promhttp_metric_handler_errors_total": false,
"target_info": false,
},
otelPort: getFreePort(t),
metricsPort: getFreePort(t),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
testMetricStability(t, test.configFile, test.expectedMetrics, test.metricsPort, test.otelPort)
})
}
}
func testMetricStability(t *testing.T, configFile string, expectedMetrics map[string]bool, metricsPort, otelPort string) {
t.Setenv("METRICS_PORT", metricsPort)
t.Setenv("OTEL_PORT", otelPort)
collector, err := otelcol.NewCollector(otelcol.CollectorSettings{
BuildInfo: component.NewDefaultBuildInfo(),
Factories: func() (otelcol.Factories, error) {
return otelcol.Factories{
Receivers: map[component.Type]receiver.Factory{otlpreceiver.NewFactory().Type(): otlpreceiver.NewFactory()},
Processors: map[component.Type]processor.Factory{batchprocessor.NewFactory().Type(): batchprocessor.NewFactory()},
Exporters: map[component.Type]exporter.Factory{
debugexporter.NewFactory().Type(): debugexporter.NewFactory(),
// otlphttpexporter is needed because the test config files use otlphttp/fail exporter
otlphttpexporter.NewFactory().Type(): otlphttpexporter.NewFactory(),
},
Telemetry: otelconftelemetry.NewFactory(),
}, nil
},
ConfigProviderSettings: otelcol.ConfigProviderSettings{
ResolverSettings: confmap.ResolverSettings{
URIs: []string{filepath.Join("testdata", configFile)},
ProviderFactories: []confmap.ProviderFactory{
fileprovider.NewFactory(),
yamlprovider.NewFactory(),
envprovider.NewFactory(),
},
},
},
})
require.NoError(t, err)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
err := collector.Run(ctx)
if err != nil {
t.Logf("Collector stopped with error: %v", err)
}
}()
waitMetricsReady(t, metricsPort)
for range 5 {
sendTestData(t, otelPort)
}
require.Eventually(t, func() bool {
return assertMetrics(t, metricsPort, expectedMetrics)
}, 10*time.Second, 200*time.Millisecond, "failed to verify metrics")
}
func sendTestData(t *testing.T, otelPort string) {
require.NoError(t, sendTestMetrics(otelPort))
require.NoError(t, sendTestTraces(otelPort))
require.NoError(t, sendTestLogs(otelPort))
}
func sendTestMetrics(otelPort string) error {
metrics := pmetric.NewMetrics()
rm := metrics.ResourceMetrics().AppendEmpty()
sm := rm.ScopeMetrics().AppendEmpty()
metric := sm.Metrics().AppendEmpty()
metric.SetName("test_metric")
metric.SetDescription("test metric")
metric.SetUnit("1")
dp := metric.SetEmptyGauge().DataPoints().AppendEmpty()
dp.SetTimestamp(pcommon.NewTimestampFromTime(time.Now()))
dp.SetDoubleValue(42.0)
client := &http.Client{}
metricsMarshaler := pmetric.ProtoMarshaler{}
metricsBytes, err := metricsMarshaler.MarshalMetrics(metrics)
if err != nil {
return fmt.Errorf("failed to marshal metrics: %w", err)
}
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:%s/v1/metrics", otelPort), bytes.NewReader(metricsBytes))
if err != nil {
return fmt.Errorf("failed to create metrics request: %w", err)
}
req.Header.Set("Content-Type", "application/x-protobuf")
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("failed to send metrics: %w", err)
}
resp.Body.Close()
return nil
}
func sendTestTraces(otelPort string) error {
traces := ptrace.NewTraces()
rs := traces.ResourceSpans().AppendEmpty()
ss := rs.ScopeSpans().AppendEmpty()
span := ss.Spans().AppendEmpty()
span.SetName("test_span")
now := time.Now()
span.SetStartTimestamp(pcommon.NewTimestampFromTime(now))
span.SetEndTimestamp(pcommon.NewTimestampFromTime(now))
span.SetTraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})
span.SetSpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8})
client := &http.Client{}
tracesMarshaler := ptrace.ProtoMarshaler{}
tracesBytes, err := tracesMarshaler.MarshalTraces(traces)
if err != nil {
return fmt.Errorf("failed to marshal traces: %w", err)
}
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:%s/v1/traces", otelPort), bytes.NewReader(tracesBytes))
if err != nil {
return fmt.Errorf("failed to create traces request: %w", err)
}
req.Header.Set("Content-Type", "application/x-protobuf")
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("failed to send traces: %w", err)
}
resp.Body.Close()
return nil
}
func sendTestLogs(otelPort string) error {
logs := plog.NewLogs()
rl := logs.ResourceLogs().AppendEmpty()
sl := rl.ScopeLogs().AppendEmpty()
log := sl.LogRecords().AppendEmpty()
log.SetTimestamp(pcommon.NewTimestampFromTime(time.Now()))
log.SetSeverityText("INFO")
log.SetSeverityNumber(plog.SeverityNumberInfo)
log.Body().SetStr("test log message")
client := &http.Client{}
logsMarshaler := plog.ProtoMarshaler{}
logsBytes, err := logsMarshaler.MarshalLogs(logs)
if err != nil {
return fmt.Errorf("failed to marshal logs: %w", err)
}
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:%s/v1/logs", otelPort), bytes.NewReader(logsBytes))
if err != nil {
return fmt.Errorf("failed to create logs request: %w", err)
}
req.Header.Set("Content-Type", "application/x-protobuf")
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("failed to send logs: %w", err)
}
resp.Body.Close()
return nil
}
func getFreePort(t *testing.T) string {
t.Helper()
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("could not get free port: %v", err)
}
defer l.Close()
return strconv.Itoa(l.Addr().(*net.TCPAddr).Port)
}
func waitMetricsReady(t *testing.T, metricsPort string) {
require.EventuallyWithT(t, func(t *assert.CollectT) {
_ = readMetrics(t, metricsPort)
}, 10*time.Second, 100*time.Millisecond, "collector failed to start")
}
func readMetrics(t require.TestingT, metricsPort string) map[string]*dto.MetricFamily {
resp, err := http.Get(fmt.Sprintf("http://localhost:%s/metrics", metricsPort))
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
reader := bufio.NewReader(resp.Body)
parser := expfmt.NewTextParser(model.UTF8Validation)
parsed, err := parser.TextToMetricFamilies(reader)
require.NoError(t, err)
return parsed
}
================================================
FILE: internal/e2e/opaque_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/config/configopaque"
"go.opentelemetry.io/collector/confmap"
)
type TestStruct struct {
Opaque configopaque.String `json:"opaque" yaml:"opaque"`
Plain string `json:"plain" yaml:"plain"`
}
var example = TestStruct{
Opaque: "opaque",
Plain: "plain",
}
func TestConfMapMarshalConfigOpaque(t *testing.T) {
conf := confmap.New()
require.NoError(t, conf.Marshal(example))
assert.Equal(t, "[REDACTED]", conf.Get("opaque"))
assert.Equal(t, "plain", conf.Get("plain"))
}
================================================
FILE: internal/e2e/otlphttp_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"bytes"
"compress/gzip"
"context"
"encoding/hex"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpcollectortrace "go.opentelemetry.io/proto/otlp/collector/trace/v1"
gootlpcommon "go.opentelemetry.io/proto/otlp/common/v1"
gootlpresource "go.opentelemetry.io/proto/otlp/resource/v1"
gootlptrace "go.opentelemetry.io/proto/otlp/trace/v1"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/testing/protocmp"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exporterhelper"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/exporter/otlphttpexporter"
"go.opentelemetry.io/collector/internal/testutil"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp"
"go.opentelemetry.io/collector/pdata/testdata"
"go.opentelemetry.io/collector/receiver/otlpreceiver"
"go.opentelemetry.io/collector/receiver/receivertest"
)
func TestInvalidConfig(t *testing.T) {
config := &otlphttpexporter.Config{
ClientConfig: confighttp.ClientConfig{
Endpoint: "",
},
}
f := otlphttpexporter.NewFactory()
set := exportertest.NewNopSettings(f.Type())
_, err := f.CreateTraces(context.Background(), set, config)
require.Error(t, err)
_, err = f.CreateMetrics(context.Background(), set, config)
require.Error(t, err)
_, err = f.CreateLogs(context.Background(), set, config)
require.Error(t, err)
}
func TestTraceNoBackend(t *testing.T) {
addr := testutil.GetAvailableLocalAddress(t)
exp := startTraces(t, "", fmt.Sprintf("http://%s/v1/traces", addr))
td := testdata.GenerateTraces(1)
assert.Error(t, exp.ConsumeTraces(context.Background(), td))
}
func TestTraceInvalidURL(t *testing.T) {
exp := startTraces(t, "http:/\\//this_is_an/*/invalid_url", "")
td := testdata.GenerateTraces(1)
require.Error(t, exp.ConsumeTraces(context.Background(), td))
exp = startTraces(t, "", "http:/\\//this_is_an/*/invalid_url")
td = testdata.GenerateTraces(1)
assert.Error(t, exp.ConsumeTraces(context.Background(), td))
}
func TestTraceError(t *testing.T) {
addr := testutil.GetAvailableLocalAddress(t)
startTracesReceiver(t, addr, consumertest.NewErr(errors.New("my_error")))
exp := startTraces(t, "", fmt.Sprintf("http://%s/v1/traces", addr))
td := testdata.GenerateTraces(1)
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: "http://" + addr,
overrideURL: "",
},
{
name: "override",
baseURL: "",
overrideURL: fmt.Sprintf("http://%s/v1/traces", addr),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sink := new(consumertest.TracesSink)
startTracesReceiver(t, addr, sink)
exp := startTraces(t, tt.baseURL, tt.overrideURL)
td := testdata.GenerateTraces(1)
require.NoError(t, exp.ConsumeTraces(context.Background(), td))
require.Eventually(t, func() bool {
return sink.SpanCount() > 0
}, 1*time.Second, 10*time.Millisecond)
allTraces := sink.AllTraces()
require.Len(t, allTraces, 1)
assert.Equal(t, td, allTraces[0])
})
}
}
func TestMetricsError(t *testing.T) {
addr := testutil.GetAvailableLocalAddress(t)
startMetricsReceiver(t, addr, consumertest.NewErr(errors.New("my_error")))
exp := startMetrics(t, "", fmt.Sprintf("http://%s/v1/metrics", addr))
md := testdata.GenerateMetrics(1)
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: "http://" + addr,
overrideURL: "",
},
{
name: "override",
baseURL: "",
overrideURL: fmt.Sprintf("http://%s/v1/metrics", addr),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sink := new(consumertest.MetricsSink)
startMetricsReceiver(t, addr, sink)
exp := startMetrics(t, tt.baseURL, tt.overrideURL)
md := testdata.GenerateMetrics(1)
require.NoError(t, exp.ConsumeMetrics(context.Background(), md))
require.Eventually(t, func() bool {
return sink.DataPointCount() > 0
}, 1*time.Second, 10*time.Millisecond)
allMetrics := sink.AllMetrics()
require.Len(t, allMetrics, 1)
assert.Equal(t, md, allMetrics[0])
})
}
}
func TestLogsError(t *testing.T) {
addr := testutil.GetAvailableLocalAddress(t)
startLogsReceiver(t, addr, consumertest.NewErr(errors.New("my_error")))
exp := startLogs(t, "", fmt.Sprintf("http://%s/v1/logs", addr))
md := testdata.GenerateLogs(1)
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: "http://" + addr,
overrideURL: "",
},
{
name: "override",
baseURL: "",
overrideURL: fmt.Sprintf("http://%s/v1/logs", addr),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sink := new(consumertest.LogsSink)
startLogsReceiver(t, addr, sink)
exp := startLogs(t, tt.baseURL, tt.overrideURL)
md := testdata.GenerateLogs(1)
require.NoError(t, exp.ConsumeLogs(context.Background(), md))
require.Eventually(t, func() bool {
return sink.LogRecordCount() > 0
}, 1*time.Second, 10*time.Millisecond)
allLogs := sink.AllLogs()
require.Len(t, allLogs, 1)
assert.Equal(t, md, allLogs[0])
})
}
}
func TestIssue_4221(t *testing.T) {
traceIDBytesSlice, err := hex.DecodeString("4303853f086f4f8c86cf198b6551df84")
require.NoError(t, err)
spanIDBytesSlice, err := hex.DecodeString("e5513c32795c41b9")
require.NoError(t, err)
svr := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
defer func() { assert.NoError(t, r.Body.Close()) }()
compressedData, err := io.ReadAll(r.Body)
assert.NoError(t, err)
gzipReader, err := gzip.NewReader(bytes.NewReader(compressedData))
assert.NoError(t, err)
data, err := io.ReadAll(gzipReader)
assert.NoError(t, err)
// Verify same base64 encoded string is received.
req := &gootlpcollectortrace.ExportTraceServiceRequest{}
assert.NoError(t, proto.Unmarshal(data, req))
assert.Empty(t, cmp.Diff(&gootlpcollectortrace.ExportTraceServiceRequest{
ResourceSpans: []*gootlptrace.ResourceSpans{
{
Resource: &gootlpresource.Resource{
Attributes: []*gootlpcommon.KeyValue{
{
Key: "service.name",
Value: &gootlpcommon.AnyValue{
Value: &gootlpcommon.AnyValue_StringValue{
StringValue: "uop.stage-eu-1",
},
},
},
{
Key: "outsystems.module.version",
Value: &gootlpcommon.AnyValue{
Value: &gootlpcommon.AnyValue_StringValue{
StringValue: "903386",
},
},
},
},
},
ScopeSpans: []*gootlptrace.ScopeSpans{
{
Scope: &gootlpcommon.InstrumentationScope{
Name: "uop_canaries",
Version: "1",
},
Spans: []*gootlptrace.Span{
{
TraceId: traceIDBytesSlice,
SpanId: spanIDBytesSlice,
StartTimeUnixNano: 1634684637873000000,
EndTimeUnixNano: 1634684637873000000,
Attributes: []*gootlpcommon.KeyValue{
{
Key: "span_index",
Value: &gootlpcommon.AnyValue{
Value: &gootlpcommon.AnyValue_IntValue{
IntValue: 3,
},
},
},
{
Key: "code.function",
Value: &gootlpcommon.AnyValue{
Value: &gootlpcommon.AnyValue_StringValue{
StringValue: "myFunction36",
},
},
},
},
Status: &gootlptrace.Status{},
},
},
},
},
},
},
}, req, protocmp.Transform()))
assert.NoError(t, err)
tr := ptraceotlp.NewExportRequest()
assert.NoError(t, tr.UnmarshalProto(data))
span := tr.Traces().ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0)
traceID := span.TraceID()
assert.Equal(t, "4303853f086f4f8c86cf198b6551df84", hex.EncodeToString(traceID[:]))
spanID := span.SpanID()
assert.Equal(t, "e5513c32795c41b9", hex.EncodeToString(spanID[:]))
}))
defer func() { svr.Close() }()
exp := startTraces(t, "", svr.URL)
md := ptrace.NewTraces()
rms := md.ResourceSpans().AppendEmpty()
rms.Resource().Attributes().PutStr("service.name", "uop.stage-eu-1")
rms.Resource().Attributes().PutStr("outsystems.module.version", "903386")
ils := rms.ScopeSpans().AppendEmpty()
ils.Scope().SetName("uop_canaries")
ils.Scope().SetVersion("1")
span := ils.Spans().AppendEmpty()
var traceIDBytes [16]byte
copy(traceIDBytes[:], traceIDBytesSlice)
span.SetTraceID(traceIDBytes)
traceID := span.TraceID()
assert.Equal(t, "4303853f086f4f8c86cf198b6551df84", hex.EncodeToString(traceID[:]))
var spanIDBytes [8]byte
copy(spanIDBytes[:], spanIDBytesSlice)
span.SetSpanID(spanIDBytes)
spanID := span.SpanID()
assert.Equal(t, "e5513c32795c41b9", hex.EncodeToString(spanID[:]))
span.SetEndTimestamp(1634684637873000000)
span.Attributes().PutInt("span_index", 3)
span.Attributes().PutStr("code.function", "myFunction36")
span.SetStartTimestamp(1634684637873000000)
assert.NoError(t, exp.ConsumeTraces(context.Background(), md))
}
func startTraces(t *testing.T, baseURL, overrideURL string) exporter.Traces {
factory := otlphttpexporter.NewFactory()
cfg := createConfig(baseURL, factory.CreateDefaultConfig())
cfg.TracesEndpoint = overrideURL
exp, err := factory.CreateTraces(context.Background(), exportertest.NewNopSettings(factory.Type()), cfg)
require.NoError(t, err)
startAndCleanup(t, exp)
return exp
}
func startMetrics(t *testing.T, baseURL, overrideURL string) exporter.Metrics {
factory := otlphttpexporter.NewFactory()
cfg := createConfig(baseURL, factory.CreateDefaultConfig())
cfg.MetricsEndpoint = overrideURL
exp, err := factory.CreateMetrics(context.Background(), exportertest.NewNopSettings(factory.Type()), cfg)
require.NoError(t, err)
startAndCleanup(t, exp)
return exp
}
func startLogs(t *testing.T, baseURL, overrideURL string) exporter.Logs {
factory := otlphttpexporter.NewFactory()
cfg := createConfig(baseURL, factory.CreateDefaultConfig())
cfg.LogsEndpoint = overrideURL
exp, err := factory.CreateLogs(context.Background(), exportertest.NewNopSettings(factory.Type()), cfg)
require.NoError(t, err)
startAndCleanup(t, exp)
return exp
}
func createConfig(baseURL string, defaultCfg component.Config) *otlphttpexporter.Config {
cfg := defaultCfg.(*otlphttpexporter.Config)
cfg.ClientConfig.Endpoint = baseURL
cfg.QueueConfig = configoptional.None[exporterhelper.QueueBatchConfig]()
cfg.RetryConfig.Enabled = false
return cfg
}
func startTracesReceiver(t *testing.T, addr string, next consumer.Traces) {
factory := otlpreceiver.NewFactory()
cfg := createReceiverConfig(addr, factory.CreateDefaultConfig())
recv, err := factory.CreateTraces(context.Background(), receivertest.NewNopSettings(factory.Type()), cfg, next)
require.NoError(t, err)
startAndCleanup(t, recv)
}
func startMetricsReceiver(t *testing.T, addr string, next consumer.Metrics) {
factory := otlpreceiver.NewFactory()
cfg := createReceiverConfig(addr, factory.CreateDefaultConfig())
recv, err := factory.CreateMetrics(context.Background(), receivertest.NewNopSettings(factory.Type()), cfg, next)
require.NoError(t, err)
startAndCleanup(t, recv)
}
func startLogsReceiver(t *testing.T, addr string, next consumer.Logs) {
factory := otlpreceiver.NewFactory()
cfg := createReceiverConfig(addr, factory.CreateDefaultConfig())
recv, err := factory.CreateLogs(context.Background(), receivertest.NewNopSettings(factory.Type()), cfg, next)
require.NoError(t, err)
startAndCleanup(t, recv)
}
func createReceiverConfig(addr string, defaultCfg component.Config) *otlpreceiver.Config {
cfg := defaultCfg.(*otlpreceiver.Config)
cfg.HTTP.GetOrInsertDefault().ServerConfig.NetAddr.Endpoint = addr
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()))
})
}
================================================
FILE: internal/e2e/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: internal/e2e/status_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"context"
"errors"
"fmt"
"strings"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zapcore"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componentstatus"
"go.opentelemetry.io/collector/config/configtelemetry"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/connector"
"go.opentelemetry.io/collector/connector/connectortest"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/extension"
"go.opentelemetry.io/collector/internal/sharedcomponent"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/service"
"go.opentelemetry.io/collector/service/extensions"
"go.opentelemetry.io/collector/service/pipelines"
"go.opentelemetry.io/collector/service/telemetry/otelconftelemetry"
)
var nopType = component.MustNewType("nop")
var wg = sync.WaitGroup{}
func Test_ComponentStatusReporting_SharedInstance(t *testing.T) {
eventsReceived := make(map[*componentstatus.InstanceID][]*componentstatus.Event)
exporterFactory := exportertest.NewNopFactory()
connectorFactory := connectortest.NewNopFactory()
// Use a different ID than receivertest and exportertest to avoid ambiguous
// configuration scenarios. Ambiguous IDs are detected in the 'otelcol' package,
// but lower level packages such as 'service' assume that IDs are disambiguated.
connID := component.NewIDWithName(nopType, "conn")
set := service.Settings{
BuildInfo: component.NewDefaultBuildInfo(),
CollectorConf: confmap.New(),
ReceiversConfigs: map[component.ID]component.Config{
component.NewID(component.MustNewType("test")): &receiverConfig{},
},
ReceiversFactories: map[component.Type]receiver.Factory{
component.MustNewType("test"): newReceiverFactory(),
},
ExportersConfigs: map[component.ID]component.Config{
component.NewID(nopType): exporterFactory.CreateDefaultConfig(),
},
ExportersFactories: map[component.Type]exporter.Factory{
nopType: exporterFactory,
},
ConnectorsConfigs: map[component.ID]component.Config{
connID: connectorFactory.CreateDefaultConfig(),
},
ConnectorsFactories: map[component.Type]connector.Factory{
nopType: connectorFactory,
},
ExtensionsConfigs: map[component.ID]component.Config{
component.NewID(component.MustNewType("watcher")): &extensionConfig{eventsReceived},
},
ExtensionsFactories: map[component.Type]extension.Factory{
component.MustNewType("watcher"): newExtensionFactory(),
},
TelemetryFactory: otelconftelemetry.NewFactory(),
}
set.BuildInfo = component.BuildInfo{Version: "test version", Command: "otelcoltest"}
cfg := service.Config{
Telemetry: &otelconftelemetry.Config{
Logs: otelconftelemetry.LogsConfig{
Level: zapcore.InfoLevel,
Development: false,
Encoding: "console",
Sampling: &otelconftelemetry.LogsSamplingConfig{
Enabled: true,
Tick: 10 * time.Second,
Initial: 100,
Thereafter: 100,
},
OutputPaths: []string{"stderr"},
ErrorOutputPaths: []string{"stderr"},
DisableCaller: false,
DisableStacktrace: false,
InitialFields: map[string]any(nil),
},
Metrics: otelconftelemetry.MetricsConfig{
Level: configtelemetry.LevelNone,
},
},
Pipelines: pipelines.Config{
pipeline.NewID(pipeline.SignalTraces): {
Receivers: []component.ID{component.NewID(component.MustNewType("test"))},
Exporters: []component.ID{component.NewID(nopType)},
},
pipeline.NewID(pipeline.SignalMetrics): {
Receivers: []component.ID{component.NewID(component.MustNewType("test"))},
Exporters: []component.ID{component.NewID(nopType)},
},
},
Extensions: extensions.Config{component.NewID(component.MustNewType("watcher"))},
}
s, err := service.New(context.Background(), set, cfg)
require.NoError(t, err)
wg.Add(1)
err = s.Start(context.Background())
require.NoError(t, err)
wg.Wait()
err = s.Shutdown(context.Background())
require.NoError(t, err)
require.Len(t, eventsReceived, 2)
for instanceID, events := range eventsReceived {
pipelineIDs := ""
instanceID.AllPipelineIDs(func(id pipeline.ID) bool {
pipelineIDs += id.String() + ","
return true
})
t.Logf("checking errors for %v - %v - %v", pipelineIDs, instanceID.Kind().String(), instanceID.ComponentID().String())
var expectedEvents []*componentstatus.Event
// The StatusOk is not guaranteed to be in the slice, set it according to the number of captured states
assert.True(t, len(events) == 4 || len(events) == 5)
receiverTestAttrs := pcommon.NewMap()
receiverTestAttrs.PutStr("scraper", "test")
if len(events) == 4 {
expectedEvents = []*componentstatus.Event{
componentstatus.NewEvent(componentstatus.StatusStarting),
componentstatus.NewEvent(componentstatus.StatusRecoverableError, componentstatus.WithAttributes(receiverTestAttrs)),
componentstatus.NewEvent(componentstatus.StatusStopping),
componentstatus.NewEvent(componentstatus.StatusStopped),
}
} else {
expectedEvents = []*componentstatus.Event{
componentstatus.NewEvent(componentstatus.StatusStarting),
componentstatus.NewEvent(componentstatus.StatusRecoverableError, componentstatus.WithAttributes(receiverTestAttrs)),
componentstatus.NewEvent(componentstatus.StatusOK),
componentstatus.NewEvent(componentstatus.StatusStopping),
componentstatus.NewEvent(componentstatus.StatusStopped),
}
}
var eventStr strings.Builder
for i, e := range events {
fmt.Fprintf(&eventStr, "%v,", e.Status())
assert.Equal(t, expectedEvents[i].Status(), e.Status())
}
t.Logf("events received: %v", eventStr.String())
}
}
func newReceiverFactory() receiver.Factory {
return receiver.NewFactory(
component.MustNewType("test"),
createDefaultReceiverConfig,
receiver.WithTraces(createTraces, component.StabilityLevelStable),
receiver.WithMetrics(createMetrics, component.StabilityLevelStable),
)
}
type testReceiver struct{}
func (t *testReceiver) Start(_ context.Context, host component.Host) error {
scraperAttrs := pcommon.NewMap()
scraperAttrs.PutStr("scraper", "test")
componentstatus.ReportStatus(host, componentstatus.NewEvent(
componentstatus.StatusRecoverableError,
componentstatus.WithError(errors.New("test recoverable error")),
componentstatus.WithAttributes(scraperAttrs),
))
go func() {
componentstatus.ReportStatus(host, componentstatus.NewEvent(componentstatus.StatusOK))
wg.Done()
}()
return nil
}
func (t *testReceiver) Shutdown(_ context.Context) error {
return nil
}
type receiverConfig struct{}
func createDefaultReceiverConfig() component.Config {
return &receiverConfig{}
}
func createTraces(
_ context.Context,
_ receiver.Settings,
cfg component.Config,
_ consumer.Traces,
) (receiver.Traces, error) {
oCfg := cfg.(*receiverConfig)
r, err := receivers.LoadOrStore(
oCfg,
func() (*testReceiver, error) {
return &testReceiver{}, nil
},
)
if err != nil {
return nil, err
}
return r, nil
}
func createMetrics(
_ context.Context,
_ receiver.Settings,
cfg component.Config,
_ consumer.Metrics,
) (receiver.Metrics, error) {
oCfg := cfg.(*receiverConfig)
r, err := receivers.LoadOrStore(
oCfg,
func() (*testReceiver, error) {
return &testReceiver{}, nil
},
)
if err != nil {
return nil, err
}
return r, nil
}
var receivers = sharedcomponent.NewMap[*receiverConfig, *testReceiver]()
func newExtensionFactory() extension.Factory {
return extension.NewFactory(
component.MustNewType("watcher"),
createDefaultExtensionConfig,
create,
component.StabilityLevelStable,
)
}
func create(_ context.Context, _ extension.Settings, cfg component.Config) (extension.Extension, error) {
oCfg := cfg.(*extensionConfig)
return &testExtension{
eventsReceived: oCfg.eventsReceived,
}, nil
}
type testExtension struct {
eventsReceived map[*componentstatus.InstanceID][]*componentstatus.Event
}
type extensionConfig struct {
eventsReceived map[*componentstatus.InstanceID][]*componentstatus.Event
}
func createDefaultExtensionConfig() component.Config {
return &extensionConfig{}
}
// Start implements the component.Component interface.
func (t *testExtension) Start(_ context.Context, _ component.Host) error {
return nil
}
// Shutdown implements the component.Component interface.
func (t *testExtension) Shutdown(_ context.Context) error {
return nil
}
// ComponentStatusChanged implements the extension.StatusWatcher interface.
func (t *testExtension) ComponentStatusChanged(
source *componentstatus.InstanceID,
event *componentstatus.Event,
) {
if source.ComponentID() == component.NewID(component.MustNewType("test")) {
t.eventsReceived[source] = append(t.eventsReceived[source], event)
}
}
// NotifyConfig implements the extensioncapabilities.ConfigWatcher interface.
func (t *testExtension) NotifyConfig(_ context.Context, _ *confmap.Conf) error {
return nil
}
// Ready implements the extensioncapabilities.PipelineWatcher interface.
func (t *testExtension) Ready() error {
return nil
}
// NotReady implements the extensioncapabilities.PipelineWatcher interface.
func (t *testExtension) NotReady() error {
return nil
}
================================================
FILE: internal/e2e/testdata/exporter_failure_attributes_test.yaml
================================================
receivers:
otlp:
protocols:
http:
endpoint: localhost:${env:OTEL_PORT}
exporters:
otlp_http/test:
endpoint: ${env:EXPORTER_ENDPOINT}
timeout: 100ms
retry_on_failure:
enabled: true
initial_interval: 10ms
max_interval: 50ms
max_elapsed_time: 200ms
sending_queue:
enabled: false
service:
telemetry:
logs:
level: info
metrics:
level: detailed
readers:
- pull:
exporter:
prometheus:
host: localhost
port: ${env:METRICS_PORT}
pipelines:
metrics:
receivers: [otlp]
exporters: [otlp_http/test]
================================================
FILE: internal/e2e/testdata/metric_stability_test_no_readers.yaml
================================================
receivers:
otlp:
protocols:
http:
endpoint: localhost:${env:OTEL_PORT}
processors:
batch:
timeout: 100ms
send_batch_size: 100
exporters:
debug:
verbosity: detailed
otlp_http/fail:
endpoint: http://127.0.0.1:65535
timeout: 100ms
retry_on_failure:
enabled: false
sending_queue:
enabled: false
service:
telemetry:
logs:
level: info
metrics:
level: detailed
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [debug]
traces/fail:
receivers: [otlp]
exporters: [otlp_http/fail]
metrics:
receivers: [otlp]
processors: [batch]
exporters: [debug]
metrics/fail:
receivers: [otlp]
exporters: [otlp_http/fail]
logs:
receivers: [otlp]
processors: [batch]
exporters: [debug]
logs/fail:
receivers: [otlp]
exporters: [otlp_http/fail]
================================================
FILE: internal/e2e/testdata/metric_stability_test_readers.yaml
================================================
receivers:
otlp:
protocols:
http:
endpoint: localhost:${env:OTEL_PORT}
processors:
batch:
timeout: 100ms
send_batch_size: 100
exporters:
debug:
verbosity: detailed
otlp_http/fail:
endpoint: http://127.0.0.1:65535
timeout: 100ms
retry_on_failure:
enabled: false
sending_queue:
enabled: false
service:
telemetry:
logs:
level: info
metrics:
level: detailed
readers:
- pull:
exporter:
prometheus:
host: localhost
port: ${env:METRICS_PORT}
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [debug]
traces/fail:
receivers: [otlp]
exporters: [otlp_http/fail]
metrics:
receivers: [otlp]
processors: [batch]
exporters: [debug]
metrics/fail:
receivers: [otlp]
exporters: [otlp_http/fail]
logs:
receivers: [otlp]
processors: [batch]
exporters: [debug]
logs/fail:
receivers: [otlp]
exporters: [otlp_http/fail]
================================================
FILE: internal/fanoutconsumer/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: internal/fanoutconsumer/go.mod
================================================
module go.opentelemetry.io/collector/internal/fanoutconsumer
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/consumer v1.54.0
go.opentelemetry.io/collector/consumer/consumertest v0.148.0
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/collector/pdata/pprofile v0.148.0
go.opentelemetry.io/collector/pdata/testdata v0.148.0
go.uber.org/goleak v1.3.0
go.uber.org/multierr v1.11.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/consumer => ../../consumer
replace go.opentelemetry.io/collector/pdata/testdata => ../../pdata/testdata
replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest
replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../testutil
================================================
FILE: internal/fanoutconsumer/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: internal/fanoutconsumer/logs.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package fanoutconsumer contains implementations of Traces/Metrics/Logs consumers
// that fan out the data to multiple other consumers.
package fanoutconsumer // import "go.opentelemetry.io/collector/internal/fanoutconsumer"
import (
"context"
"go.uber.org/multierr"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/pdata/plog"
)
// NewLogs wraps multiple log consumers in a single one.
// It fans out the incoming data to all the consumers, and does smart routing:
// - Clones only to the consumer that needs to mutate the data.
// - If all consumers needs to mutate the data one will get the original mutable data.
func NewLogs(lcs []consumer.Logs) consumer.Logs {
// Don't wrap if there is only one non-mutating consumer.
if len(lcs) == 1 && !lcs[0].Capabilities().MutatesData {
return lcs[0]
}
lc := &logsConsumer{}
for i := range lcs {
if lcs[i].Capabilities().MutatesData {
lc.mutable = append(lc.mutable, lcs[i])
} else {
lc.readonly = append(lc.readonly, lcs[i])
}
}
return lc
}
type logsConsumer struct {
mutable []consumer.Logs
readonly []consumer.Logs
}
func (lsc *logsConsumer) Capabilities() consumer.Capabilities {
// If all consumers are mutating, then the original data will be passed to one of them.
return consumer.Capabilities{MutatesData: len(lsc.mutable) > 0 && len(lsc.readonly) == 0}
}
// ConsumeLogs exports the plog.Logs to all consumers wrapped by the current one.
func (lsc *logsConsumer) ConsumeLogs(ctx context.Context, ld plog.Logs) error {
var errs error
if len(lsc.mutable) > 0 {
// Clone the data before sending to all mutating consumers except the last one.
for i := 0; i < len(lsc.mutable)-1; i++ {
errs = multierr.Append(errs, lsc.mutable[i].ConsumeLogs(ctx, cloneLogs(ld)))
}
// Send data as is to the last mutating consumer only if there are no other non-mutating consumers and the
// data is mutable. Never share the same data between a mutating and a non-mutating consumer since the
// non-mutating consumer may process data async and the mutating consumer may change the data before that.
lastConsumer := lsc.mutable[len(lsc.mutable)-1]
if len(lsc.readonly) == 0 && !ld.IsReadOnly() {
errs = multierr.Append(errs, lastConsumer.ConsumeLogs(ctx, ld))
} else {
errs = multierr.Append(errs, lastConsumer.ConsumeLogs(ctx, cloneLogs(ld)))
}
}
// Mark the data as read-only if it will be sent to more than one read-only consumer.
if len(lsc.readonly) > 1 && !ld.IsReadOnly() {
ld.MarkReadOnly()
}
for _, lc := range lsc.readonly {
errs = multierr.Append(errs, lc.ConsumeLogs(ctx, ld))
}
return errs
}
func cloneLogs(ld plog.Logs) plog.Logs {
clonedLogs := plog.NewLogs()
ld.CopyTo(clonedLogs)
return clonedLogs
}
================================================
FILE: internal/fanoutconsumer/logs_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package fanoutconsumer
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/pdata/testdata"
)
func TestLogsNotMultiplexing(t *testing.T) {
nop := consumertest.NewNop()
lfc := NewLogs([]consumer.Logs{nop})
assert.Same(t, nop, lfc)
}
func TestLogsNotMultiplexingMutating(t *testing.T) {
p := &mutatingLogsSink{LogsSink: new(consumertest.LogsSink)}
lfc := NewLogs([]consumer.Logs{p})
assert.True(t, lfc.Capabilities().MutatesData)
}
func TestLogsMultiplexingNonMutating(t *testing.T) {
p1 := new(consumertest.LogsSink)
p2 := new(consumertest.LogsSink)
p3 := new(consumertest.LogsSink)
lfc := NewLogs([]consumer.Logs{p1, p2, p3})
assert.False(t, lfc.Capabilities().MutatesData)
ld := testdata.GenerateLogs(1)
for range 2 {
err := lfc.ConsumeLogs(context.Background(), ld)
if err != nil {
t.Errorf("Wanted nil got error")
return
}
}
assert.Equal(t, ld, p1.AllLogs()[0])
assert.Equal(t, ld, p1.AllLogs()[1])
assert.Equal(t, ld, p1.AllLogs()[0])
assert.Equal(t, ld, p1.AllLogs()[1])
assert.Equal(t, ld, p2.AllLogs()[0])
assert.Equal(t, ld, p2.AllLogs()[1])
assert.Equal(t, ld, p2.AllLogs()[0])
assert.Equal(t, ld, p2.AllLogs()[1])
assert.Equal(t, ld, p3.AllLogs()[0])
assert.Equal(t, ld, p3.AllLogs()[1])
assert.Equal(t, ld, p3.AllLogs()[0])
assert.Equal(t, ld, p3.AllLogs()[1])
// The data should be marked as read only.
assert.True(t, ld.IsReadOnly())
}
func TestLogsMultiplexingMutating(t *testing.T) {
p1 := &mutatingLogsSink{LogsSink: new(consumertest.LogsSink)}
p2 := &mutatingLogsSink{LogsSink: new(consumertest.LogsSink)}
p3 := &mutatingLogsSink{LogsSink: new(consumertest.LogsSink)}
lfc := NewLogs([]consumer.Logs{p1, p2, p3})
assert.True(t, lfc.Capabilities().MutatesData)
ld := testdata.GenerateLogs(1)
for range 2 {
err := lfc.ConsumeLogs(context.Background(), ld)
if err != nil {
t.Errorf("Wanted nil got error")
return
}
}
assert.NotSame(t, &ld, &p1.AllLogs()[0])
assert.NotSame(t, &ld, &p1.AllLogs()[1])
assert.Equal(t, ld, p1.AllLogs()[0])
assert.Equal(t, ld, p1.AllLogs()[1])
assert.NotSame(t, &ld, &p2.AllLogs()[0])
assert.NotSame(t, &ld, &p2.AllLogs()[1])
assert.Equal(t, ld, p2.AllLogs()[0])
assert.Equal(t, ld, p2.AllLogs()[1])
// For this consumer, will receive the initial data.
assert.Equal(t, ld, p3.AllLogs()[0])
assert.Equal(t, ld, p3.AllLogs()[1])
assert.Equal(t, ld, p3.AllLogs()[0])
assert.Equal(t, ld, p3.AllLogs()[1])
// The data should not be marked as read only.
assert.False(t, ld.IsReadOnly())
}
func TestReadOnlyLogsMultiplexingMutating(t *testing.T) {
p1 := &mutatingLogsSink{LogsSink: new(consumertest.LogsSink)}
p2 := &mutatingLogsSink{LogsSink: new(consumertest.LogsSink)}
p3 := &mutatingLogsSink{LogsSink: new(consumertest.LogsSink)}
lfc := NewLogs([]consumer.Logs{p1, p2, p3})
assert.True(t, lfc.Capabilities().MutatesData)
ldOrig := testdata.GenerateLogs(1)
ld := testdata.GenerateLogs(1)
ld.MarkReadOnly()
for range 2 {
err := lfc.ConsumeLogs(context.Background(), ld)
if err != nil {
t.Errorf("Wanted nil got error")
return
}
}
// All consumers should receive the cloned data.
assert.NotEqual(t, ld, p1.AllLogs()[0])
assert.NotEqual(t, ld, p1.AllLogs()[1])
assert.Equal(t, ldOrig, p1.AllLogs()[0])
assert.Equal(t, ldOrig, p1.AllLogs()[1])
assert.NotEqual(t, ld, p2.AllLogs()[0])
assert.NotEqual(t, ld, p2.AllLogs()[1])
assert.Equal(t, ldOrig, p2.AllLogs()[0])
assert.Equal(t, ldOrig, p2.AllLogs()[1])
assert.NotEqual(t, ld, p3.AllLogs()[0])
assert.NotEqual(t, ld, p3.AllLogs()[1])
assert.Equal(t, ldOrig, p3.AllLogs()[0])
assert.Equal(t, ldOrig, p3.AllLogs()[1])
}
func TestLogsMultiplexingMixLastMutating(t *testing.T) {
p1 := &mutatingLogsSink{LogsSink: new(consumertest.LogsSink)}
p2 := new(consumertest.LogsSink)
p3 := &mutatingLogsSink{LogsSink: new(consumertest.LogsSink)}
lfc := NewLogs([]consumer.Logs{p1, p2, p3})
assert.False(t, lfc.Capabilities().MutatesData)
ld := testdata.GenerateLogs(1)
for range 2 {
err := lfc.ConsumeLogs(context.Background(), ld)
if err != nil {
t.Errorf("Wanted nil got error")
return
}
}
assert.NotSame(t, &ld, &p1.AllLogs()[0])
assert.NotSame(t, &ld, &p1.AllLogs()[1])
assert.Equal(t, ld, p1.AllLogs()[0])
assert.Equal(t, ld, p1.AllLogs()[1])
// For this consumer, will receive the initial data.
assert.Equal(t, ld, p2.AllLogs()[0])
assert.Equal(t, ld, p2.AllLogs()[1])
assert.Equal(t, ld, p2.AllLogs()[0])
assert.Equal(t, ld, p2.AllLogs()[1])
// For this consumer, will clone the initial data.
assert.NotSame(t, &ld, &p3.AllLogs()[0])
assert.NotSame(t, &ld, &p3.AllLogs()[1])
assert.Equal(t, ld, p3.AllLogs()[0])
assert.Equal(t, ld, p3.AllLogs()[1])
// The data should not be marked as read only.
assert.False(t, ld.IsReadOnly())
}
func TestLogsMultiplexingMixLastNonMutating(t *testing.T) {
p1 := &mutatingLogsSink{LogsSink: new(consumertest.LogsSink)}
p2 := &mutatingLogsSink{LogsSink: new(consumertest.LogsSink)}
p3 := new(consumertest.LogsSink)
lfc := NewLogs([]consumer.Logs{p1, p2, p3})
assert.False(t, lfc.Capabilities().MutatesData)
ld := testdata.GenerateLogs(1)
for range 2 {
err := lfc.ConsumeLogs(context.Background(), ld)
if err != nil {
t.Errorf("Wanted nil got error")
return
}
}
assert.NotSame(t, &ld, &p1.AllLogs()[0])
assert.NotSame(t, &ld, &p1.AllLogs()[1])
assert.Equal(t, ld, p1.AllLogs()[0])
assert.Equal(t, ld, p1.AllLogs()[1])
assert.NotSame(t, &ld, &p2.AllLogs()[0])
assert.NotSame(t, &ld, &p2.AllLogs()[1])
assert.Equal(t, ld, p2.AllLogs()[0])
assert.Equal(t, ld, p2.AllLogs()[1])
// For this consumer, will receive the initial data.
assert.Equal(t, ld, p3.AllLogs()[0])
assert.Equal(t, ld, p3.AllLogs()[1])
assert.Equal(t, ld, p3.AllLogs()[0])
assert.Equal(t, ld, p3.AllLogs()[1])
// The data should not be marked as read only.
assert.False(t, ld.IsReadOnly())
}
func TestLogsWhenErrors(t *testing.T) {
p1 := mutatingErr{Consumer: consumertest.NewErr(errors.New("my error"))}
p2 := consumertest.NewErr(errors.New("my error"))
p3 := new(consumertest.LogsSink)
lfc := NewLogs([]consumer.Logs{p1, p2, p3})
ld := testdata.GenerateLogs(1)
for range 2 {
require.Error(t, lfc.ConsumeLogs(context.Background(), ld))
}
assert.Equal(t, ld, p3.AllLogs()[0])
assert.Equal(t, ld, p3.AllLogs()[1])
assert.Equal(t, ld, p3.AllLogs()[0])
assert.Equal(t, ld, p3.AllLogs()[1])
}
type mutatingLogsSink struct {
*consumertest.LogsSink
}
func (mts *mutatingLogsSink) Capabilities() consumer.Capabilities {
return consumer.Capabilities{MutatesData: true}
}
type mutatingErr struct {
consumertest.Consumer
}
func (mts mutatingErr) Capabilities() consumer.Capabilities {
return consumer.Capabilities{MutatesData: true}
}
================================================
FILE: internal/fanoutconsumer/metrics.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package fanoutconsumer // import "go.opentelemetry.io/collector/internal/fanoutconsumer"
import (
"context"
"go.uber.org/multierr"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/pdata/pmetric"
)
// NewMetrics wraps multiple metrics consumers in a single one.
// It fans out the incoming data to all the consumers, and does smart routing:
// - Clones only to the consumer that needs to mutate the data.
// - If all consumers needs to mutate the data one will get the original mutable data.
func NewMetrics(mcs []consumer.Metrics) consumer.Metrics {
// Don't wrap if there is only one non-mutating consumer.
if len(mcs) == 1 && !mcs[0].Capabilities().MutatesData {
return mcs[0]
}
mc := &metricsConsumer{}
for i := range mcs {
if mcs[i].Capabilities().MutatesData {
mc.mutable = append(mc.mutable, mcs[i])
} else {
mc.readonly = append(mc.readonly, mcs[i])
}
}
return mc
}
type metricsConsumer struct {
mutable []consumer.Metrics
readonly []consumer.Metrics
}
func (msc *metricsConsumer) Capabilities() consumer.Capabilities {
// If all consumers are mutating, then the original data will be passed to one of them.
return consumer.Capabilities{MutatesData: len(msc.mutable) > 0 && len(msc.readonly) == 0}
}
// ConsumeMetrics exports the pmetric.Metrics to all consumers wrapped by the current one.
func (msc *metricsConsumer) ConsumeMetrics(ctx context.Context, md pmetric.Metrics) error {
var errs error
if len(msc.mutable) > 0 {
// Clone the data before sending to all mutating consumers except the last one.
for i := 0; i < len(msc.mutable)-1; i++ {
errs = multierr.Append(errs, msc.mutable[i].ConsumeMetrics(ctx, cloneMetrics(md)))
}
// Send data as is to the last mutating consumer only if there are no other non-mutating consumers and the
// data is mutable. Never share the same data between a mutating and a non-mutating consumer since the
// non-mutating consumer may process data async and the mutating consumer may change the data before that.
lastConsumer := msc.mutable[len(msc.mutable)-1]
if len(msc.readonly) == 0 && !md.IsReadOnly() {
errs = multierr.Append(errs, lastConsumer.ConsumeMetrics(ctx, md))
} else {
errs = multierr.Append(errs, lastConsumer.ConsumeMetrics(ctx, cloneMetrics(md)))
}
}
// Mark the data as read-only if it will be sent to more than one read-only consumer.
if len(msc.readonly) > 1 && !md.IsReadOnly() {
md.MarkReadOnly()
}
for _, mc := range msc.readonly {
errs = multierr.Append(errs, mc.ConsumeMetrics(ctx, md))
}
return errs
}
func cloneMetrics(md pmetric.Metrics) pmetric.Metrics {
clonedMetrics := pmetric.NewMetrics()
md.CopyTo(clonedMetrics)
return clonedMetrics
}
================================================
FILE: internal/fanoutconsumer/metrics_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package fanoutconsumer
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/pdata/testdata"
)
func TestMetricsNotMultiplexing(t *testing.T) {
nop := consumertest.NewNop()
mfc := NewMetrics([]consumer.Metrics{nop})
assert.Same(t, nop, mfc)
}
func TestMetricssNotMultiplexingMutating(t *testing.T) {
p := &mutatingMetricsSink{MetricsSink: new(consumertest.MetricsSink)}
lfc := NewMetrics([]consumer.Metrics{p})
assert.True(t, lfc.Capabilities().MutatesData)
}
func TestMetricsMultiplexingNonMutating(t *testing.T) {
p1 := new(consumertest.MetricsSink)
p2 := new(consumertest.MetricsSink)
p3 := new(consumertest.MetricsSink)
mfc := NewMetrics([]consumer.Metrics{p1, p2, p3})
assert.False(t, mfc.Capabilities().MutatesData)
md := testdata.GenerateMetrics(1)
for range 2 {
err := mfc.ConsumeMetrics(context.Background(), md)
if err != nil {
t.Errorf("Wanted nil got error")
return
}
}
assert.Equal(t, md, p1.AllMetrics()[0])
assert.Equal(t, md, p1.AllMetrics()[1])
assert.Equal(t, md, p1.AllMetrics()[0])
assert.Equal(t, md, p1.AllMetrics()[1])
assert.Equal(t, md, p2.AllMetrics()[0])
assert.Equal(t, md, p2.AllMetrics()[1])
assert.Equal(t, md, p2.AllMetrics()[0])
assert.Equal(t, md, p2.AllMetrics()[1])
assert.Equal(t, md, p3.AllMetrics()[0])
assert.Equal(t, md, p3.AllMetrics()[1])
assert.Equal(t, md, p3.AllMetrics()[0])
assert.Equal(t, md, p3.AllMetrics()[1])
// The data should be marked as read only.
assert.True(t, md.IsReadOnly())
}
func TestMetricsMultiplexingMutating(t *testing.T) {
p1 := &mutatingMetricsSink{MetricsSink: new(consumertest.MetricsSink)}
p2 := &mutatingMetricsSink{MetricsSink: new(consumertest.MetricsSink)}
p3 := &mutatingMetricsSink{MetricsSink: new(consumertest.MetricsSink)}
mfc := NewMetrics([]consumer.Metrics{p1, p2, p3})
assert.True(t, mfc.Capabilities().MutatesData)
md := testdata.GenerateMetrics(1)
for range 2 {
err := mfc.ConsumeMetrics(context.Background(), md)
if err != nil {
t.Errorf("Wanted nil got error")
return
}
}
assert.NotSame(t, &md, &p1.AllMetrics()[0])
assert.NotSame(t, &md, &p1.AllMetrics()[1])
assert.Equal(t, md, p1.AllMetrics()[0])
assert.Equal(t, md, p1.AllMetrics()[1])
assert.NotSame(t, &md, &p2.AllMetrics()[0])
assert.NotSame(t, &md, &p2.AllMetrics()[1])
assert.Equal(t, md, p2.AllMetrics()[0])
assert.Equal(t, md, p2.AllMetrics()[1])
// For this consumer, will receive the initial data.
assert.Equal(t, md, p3.AllMetrics()[0])
assert.Equal(t, md, p3.AllMetrics()[1])
assert.Equal(t, md, p3.AllMetrics()[0])
assert.Equal(t, md, p3.AllMetrics()[1])
// The data should not be marked as read only.
assert.False(t, md.IsReadOnly())
}
func TestReadOnlyMetricsMultiplexingMixFirstMutating(t *testing.T) {
p1 := &mutatingMetricsSink{MetricsSink: new(consumertest.MetricsSink)}
p2 := &mutatingMetricsSink{MetricsSink: new(consumertest.MetricsSink)}
p3 := &mutatingMetricsSink{MetricsSink: new(consumertest.MetricsSink)}
mfc := NewMetrics([]consumer.Metrics{p1, p2, p3})
assert.True(t, mfc.Capabilities().MutatesData)
mdOrig := testdata.GenerateMetrics(1)
md := testdata.GenerateMetrics(1)
md.MarkReadOnly()
for range 2 {
err := mfc.ConsumeMetrics(context.Background(), md)
if err != nil {
t.Errorf("Wanted nil got error")
return
}
}
// All consumers should receive the cloned data.
assert.NotEqual(t, md, p1.AllMetrics()[0])
assert.NotEqual(t, md, p1.AllMetrics()[1])
assert.Equal(t, mdOrig, p1.AllMetrics()[0])
assert.Equal(t, mdOrig, p1.AllMetrics()[1])
assert.NotEqual(t, md, p2.AllMetrics()[0])
assert.NotEqual(t, md, p2.AllMetrics()[1])
assert.Equal(t, mdOrig, p2.AllMetrics()[0])
assert.Equal(t, mdOrig, p2.AllMetrics()[1])
assert.NotEqual(t, md, p3.AllMetrics()[0])
assert.NotEqual(t, md, p3.AllMetrics()[1])
assert.Equal(t, mdOrig, p3.AllMetrics()[0])
assert.Equal(t, mdOrig, p3.AllMetrics()[1])
}
func TestMetricsMultiplexingMixLastMutating(t *testing.T) {
p1 := &mutatingMetricsSink{MetricsSink: new(consumertest.MetricsSink)}
p2 := new(consumertest.MetricsSink)
p3 := &mutatingMetricsSink{MetricsSink: new(consumertest.MetricsSink)}
mfc := NewMetrics([]consumer.Metrics{p1, p2, p3})
assert.False(t, mfc.Capabilities().MutatesData)
md := testdata.GenerateMetrics(1)
for range 2 {
err := mfc.ConsumeMetrics(context.Background(), md)
if err != nil {
t.Errorf("Wanted nil got error")
return
}
}
assert.NotSame(t, &md, &p1.AllMetrics()[0])
assert.NotSame(t, &md, &p1.AllMetrics()[1])
assert.Equal(t, md, p1.AllMetrics()[0])
assert.Equal(t, md, p1.AllMetrics()[1])
// For this consumer, will receive the initial data.
assert.Equal(t, md, p2.AllMetrics()[0])
assert.Equal(t, md, p2.AllMetrics()[1])
assert.Equal(t, md, p2.AllMetrics()[0])
assert.Equal(t, md, p2.AllMetrics()[1])
// For this consumer, will clone the initial data.
assert.NotSame(t, &md, &p3.AllMetrics()[0])
assert.NotSame(t, &md, &p3.AllMetrics()[1])
assert.Equal(t, md, p3.AllMetrics()[0])
assert.Equal(t, md, p3.AllMetrics()[1])
// The data should not be marked as read only.
assert.False(t, md.IsReadOnly())
}
func TestMetricsMultiplexingMixLastNonMutating(t *testing.T) {
p1 := &mutatingMetricsSink{MetricsSink: new(consumertest.MetricsSink)}
p2 := &mutatingMetricsSink{MetricsSink: new(consumertest.MetricsSink)}
p3 := new(consumertest.MetricsSink)
mfc := NewMetrics([]consumer.Metrics{p1, p2, p3})
assert.False(t, mfc.Capabilities().MutatesData)
md := testdata.GenerateMetrics(1)
for range 2 {
err := mfc.ConsumeMetrics(context.Background(), md)
if err != nil {
t.Errorf("Wanted nil got error")
return
}
}
assert.NotSame(t, &md, &p1.AllMetrics()[0])
assert.NotSame(t, &md, &p1.AllMetrics()[1])
assert.Equal(t, md, p1.AllMetrics()[0])
assert.Equal(t, md, p1.AllMetrics()[1])
assert.NotSame(t, &md, &p2.AllMetrics()[0])
assert.NotSame(t, &md, &p2.AllMetrics()[1])
assert.Equal(t, md, p2.AllMetrics()[0])
assert.Equal(t, md, p2.AllMetrics()[1])
// For this consumer, will receive the initial data.
assert.Equal(t, md, p3.AllMetrics()[0])
assert.Equal(t, md, p3.AllMetrics()[1])
assert.Equal(t, md, p3.AllMetrics()[0])
assert.Equal(t, md, p3.AllMetrics()[1])
// The data should not be marked as read only.
assert.False(t, md.IsReadOnly())
}
func TestMetricsWhenErrors(t *testing.T) {
p1 := mutatingErr{Consumer: consumertest.NewErr(errors.New("my error"))}
p2 := consumertest.NewErr(errors.New("my error"))
p3 := new(consumertest.MetricsSink)
mfc := NewMetrics([]consumer.Metrics{p1, p2, p3})
md := testdata.GenerateMetrics(1)
for range 2 {
require.Error(t, mfc.ConsumeMetrics(context.Background(), md))
}
assert.Equal(t, md, p3.AllMetrics()[0])
assert.Equal(t, md, p3.AllMetrics()[1])
assert.Equal(t, md, p3.AllMetrics()[0])
assert.Equal(t, md, p3.AllMetrics()[1])
}
type mutatingMetricsSink struct {
*consumertest.MetricsSink
}
func (mts *mutatingMetricsSink) Capabilities() consumer.Capabilities {
return consumer.Capabilities{MutatesData: true}
}
================================================
FILE: internal/fanoutconsumer/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package fanoutconsumer
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: internal/fanoutconsumer/profiles.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package fanoutconsumer // import "go.opentelemetry.io/collector/internal/fanoutconsumer"
import (
"context"
"go.uber.org/multierr"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/pdata/pprofile"
)
// NewProfiles wraps multiple profile consumers in a single one.
// It fans out the incoming data to all the consumers, and does smart routing:
// - Clones only to the consumer that needs to mutate the data.
// - If all consumers needs to mutate the data one will get the original mutable data.
func NewProfiles(tcs []xconsumer.Profiles) xconsumer.Profiles {
// Don't wrap if there is only one non-mutating consumer.
if len(tcs) == 1 && !tcs[0].Capabilities().MutatesData {
return tcs[0]
}
tc := &profilesConsumer{}
for i := range tcs {
if tcs[i].Capabilities().MutatesData {
tc.mutable = append(tc.mutable, tcs[i])
} else {
tc.readonly = append(tc.readonly, tcs[i])
}
}
return tc
}
type profilesConsumer struct {
mutable []xconsumer.Profiles
readonly []xconsumer.Profiles
}
func (tsc *profilesConsumer) Capabilities() consumer.Capabilities {
// If all consumers are mutating, then the original data will be passed to one of them.
return consumer.Capabilities{MutatesData: len(tsc.mutable) > 0 && len(tsc.readonly) == 0}
}
// ConsumeProfiles exports the pprofile.Profiles to all consumers wrapped by the current one.
func (tsc *profilesConsumer) ConsumeProfiles(ctx context.Context, td pprofile.Profiles) error {
var errs error
if len(tsc.mutable) > 0 {
// Clone the data before sending to all mutating consumers except the last one.
for i := 0; i < len(tsc.mutable)-1; i++ {
errs = multierr.Append(errs, tsc.mutable[i].ConsumeProfiles(ctx, cloneProfiles(td)))
}
// Send data as is to the last mutating consumer only if there are no other non-mutating consumers and the
// data is mutable. Never share the same data between a mutating and a non-mutating consumer since the
// non-mutating consumer may process data async and the mutating consumer may change the data before that.
lastConsumer := tsc.mutable[len(tsc.mutable)-1]
if len(tsc.readonly) == 0 && !td.IsReadOnly() {
errs = multierr.Append(errs, lastConsumer.ConsumeProfiles(ctx, td))
} else {
errs = multierr.Append(errs, lastConsumer.ConsumeProfiles(ctx, cloneProfiles(td)))
}
}
// Mark the data as read-only if it will be sent to more than one read-only consumer.
if len(tsc.readonly) > 1 && !td.IsReadOnly() {
td.MarkReadOnly()
}
for _, tc := range tsc.readonly {
errs = multierr.Append(errs, tc.ConsumeProfiles(ctx, td))
}
return errs
}
func cloneProfiles(td pprofile.Profiles) pprofile.Profiles {
clonedProfiles := pprofile.NewProfiles()
td.CopyTo(clonedProfiles)
return clonedProfiles
}
================================================
FILE: internal/fanoutconsumer/profiles_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package fanoutconsumer
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/pdata/testdata"
)
func TestProfilesNotMultiplexing(t *testing.T) {
nop := consumertest.NewNop()
tfc := NewProfiles([]xconsumer.Profiles{nop})
assert.Same(t, nop, tfc)
}
func TestProfilesNotMultiplexingMutating(t *testing.T) {
p := &mutatingProfilesSink{ProfilesSink: new(consumertest.ProfilesSink)}
lfc := NewProfiles([]xconsumer.Profiles{p})
assert.True(t, lfc.Capabilities().MutatesData)
}
func TestProfilesMultiplexingNonMutating(t *testing.T) {
p1 := new(consumertest.ProfilesSink)
p2 := new(consumertest.ProfilesSink)
p3 := new(consumertest.ProfilesSink)
tfc := NewProfiles([]xconsumer.Profiles{p1, p2, p3})
assert.False(t, tfc.Capabilities().MutatesData)
td := testdata.GenerateProfiles(1)
for range 2 {
err := tfc.ConsumeProfiles(context.Background(), td)
if err != nil {
t.Errorf("Wanted nil got error")
return
}
}
assert.Equal(t, td, p1.AllProfiles()[0])
assert.Equal(t, td, p1.AllProfiles()[1])
assert.Equal(t, td, p1.AllProfiles()[0])
assert.Equal(t, td, p1.AllProfiles()[1])
assert.Equal(t, td, p2.AllProfiles()[0])
assert.Equal(t, td, p2.AllProfiles()[1])
assert.Equal(t, td, p2.AllProfiles()[0])
assert.Equal(t, td, p2.AllProfiles()[1])
assert.Equal(t, td, p3.AllProfiles()[0])
assert.Equal(t, td, p3.AllProfiles()[1])
assert.Equal(t, td, p3.AllProfiles()[0])
assert.Equal(t, td, p3.AllProfiles()[1])
// The data should be marked as read only.
assert.True(t, td.IsReadOnly())
}
func TestProfilesMultiplexingMutating(t *testing.T) {
p1 := &mutatingProfilesSink{ProfilesSink: new(consumertest.ProfilesSink)}
p2 := &mutatingProfilesSink{ProfilesSink: new(consumertest.ProfilesSink)}
p3 := &mutatingProfilesSink{ProfilesSink: new(consumertest.ProfilesSink)}
tfc := NewProfiles([]xconsumer.Profiles{p1, p2, p3})
assert.True(t, tfc.Capabilities().MutatesData)
td := testdata.GenerateProfiles(1)
for range 2 {
err := tfc.ConsumeProfiles(context.Background(), td)
if err != nil {
t.Errorf("Wanted nil got error")
return
}
}
assert.NotSame(t, &td, &p1.AllProfiles()[0])
assert.NotSame(t, &td, &p1.AllProfiles()[1])
assert.Equal(t, td, p1.AllProfiles()[0])
assert.Equal(t, td, p1.AllProfiles()[1])
assert.NotSame(t, &td, &p2.AllProfiles()[0])
assert.NotSame(t, &td, &p2.AllProfiles()[1])
assert.Equal(t, td, p2.AllProfiles()[0])
assert.Equal(t, td, p2.AllProfiles()[1])
// For this consumer, will receive the initial data.
assert.Equal(t, td, p3.AllProfiles()[0])
assert.Equal(t, td, p3.AllProfiles()[1])
assert.Equal(t, td, p3.AllProfiles()[0])
assert.Equal(t, td, p3.AllProfiles()[1])
// The data should not be marked as read only.
assert.False(t, td.IsReadOnly())
}
func TestReadOnlyProfilesMultiplexingMutating(t *testing.T) {
p1 := &mutatingProfilesSink{ProfilesSink: new(consumertest.ProfilesSink)}
p2 := &mutatingProfilesSink{ProfilesSink: new(consumertest.ProfilesSink)}
p3 := &mutatingProfilesSink{ProfilesSink: new(consumertest.ProfilesSink)}
tfc := NewProfiles([]xconsumer.Profiles{p1, p2, p3})
assert.True(t, tfc.Capabilities().MutatesData)
tdOrig := testdata.GenerateProfiles(1)
td := testdata.GenerateProfiles(1)
td.MarkReadOnly()
for range 2 {
err := tfc.ConsumeProfiles(context.Background(), td)
if err != nil {
t.Errorf("Wanted nil got error")
return
}
}
// All consumers should receive the cloned data.
assert.NotEqual(t, td, p1.AllProfiles()[0])
assert.NotEqual(t, td, p1.AllProfiles()[1])
assert.Equal(t, tdOrig, p1.AllProfiles()[0])
assert.Equal(t, tdOrig, p1.AllProfiles()[1])
assert.NotEqual(t, td, p2.AllProfiles()[0])
assert.NotEqual(t, td, p2.AllProfiles()[1])
assert.Equal(t, tdOrig, p2.AllProfiles()[0])
assert.Equal(t, tdOrig, p2.AllProfiles()[1])
assert.NotEqual(t, td, p3.AllProfiles()[0])
assert.NotEqual(t, td, p3.AllProfiles()[1])
assert.Equal(t, tdOrig, p3.AllProfiles()[0])
assert.Equal(t, tdOrig, p3.AllProfiles()[1])
}
func TestProfilesMultiplexingMixLastMutating(t *testing.T) {
p1 := &mutatingProfilesSink{ProfilesSink: new(consumertest.ProfilesSink)}
p2 := new(consumertest.ProfilesSink)
p3 := &mutatingProfilesSink{ProfilesSink: new(consumertest.ProfilesSink)}
tfc := NewProfiles([]xconsumer.Profiles{p1, p2, p3})
assert.False(t, tfc.Capabilities().MutatesData)
td := testdata.GenerateProfiles(1)
for range 2 {
err := tfc.ConsumeProfiles(context.Background(), td)
if err != nil {
t.Errorf("Wanted nil got error")
return
}
}
assert.NotSame(t, &td, &p1.AllProfiles()[0])
assert.NotSame(t, &td, &p1.AllProfiles()[1])
assert.Equal(t, td, p1.AllProfiles()[0])
assert.Equal(t, td, p1.AllProfiles()[1])
// For this consumer, will receive the initial data.
assert.Equal(t, td, p2.AllProfiles()[0])
assert.Equal(t, td, p2.AllProfiles()[1])
assert.Equal(t, td, p2.AllProfiles()[0])
assert.Equal(t, td, p2.AllProfiles()[1])
// For this consumer, will clone the initial data.
assert.NotSame(t, &td, &p3.AllProfiles()[0])
assert.NotSame(t, &td, &p3.AllProfiles()[1])
assert.Equal(t, td, p3.AllProfiles()[0])
assert.Equal(t, td, p3.AllProfiles()[1])
// The data should not be marked as read only.
assert.False(t, td.IsReadOnly())
}
func TestProfilesMultiplexingMixLastNonMutating(t *testing.T) {
p1 := &mutatingProfilesSink{ProfilesSink: new(consumertest.ProfilesSink)}
p2 := &mutatingProfilesSink{ProfilesSink: new(consumertest.ProfilesSink)}
p3 := new(consumertest.ProfilesSink)
tfc := NewProfiles([]xconsumer.Profiles{p1, p2, p3})
assert.False(t, tfc.Capabilities().MutatesData)
td := testdata.GenerateProfiles(1)
for range 2 {
err := tfc.ConsumeProfiles(context.Background(), td)
if err != nil {
t.Errorf("Wanted nil got error")
return
}
}
assert.NotSame(t, &td, &p1.AllProfiles()[0])
assert.NotSame(t, &td, &p1.AllProfiles()[1])
assert.Equal(t, td, p1.AllProfiles()[0])
assert.Equal(t, td, p1.AllProfiles()[1])
assert.NotSame(t, &td, &p2.AllProfiles()[0])
assert.NotSame(t, &td, &p2.AllProfiles()[1])
assert.Equal(t, td, p2.AllProfiles()[0])
assert.Equal(t, td, p2.AllProfiles()[1])
// For this consumer, will receive the initial data.
assert.Equal(t, td, p3.AllProfiles()[0])
assert.Equal(t, td, p3.AllProfiles()[1])
assert.Equal(t, td, p3.AllProfiles()[0])
assert.Equal(t, td, p3.AllProfiles()[1])
// The data should not be marked as read only.
assert.False(t, td.IsReadOnly())
}
func TestProfilesWhenErrors(t *testing.T) {
p1 := mutatingErr{Consumer: consumertest.NewErr(errors.New("my error"))}
p2 := consumertest.NewErr(errors.New("my error"))
p3 := new(consumertest.ProfilesSink)
tfc := NewProfiles([]xconsumer.Profiles{p1, p2, p3})
td := testdata.GenerateProfiles(1)
for range 2 {
require.Error(t, tfc.ConsumeProfiles(context.Background(), td))
}
assert.Equal(t, td, p3.AllProfiles()[0])
assert.Equal(t, td, p3.AllProfiles()[1])
assert.Equal(t, td, p3.AllProfiles()[0])
assert.Equal(t, td, p3.AllProfiles()[1])
}
type mutatingProfilesSink struct {
*consumertest.ProfilesSink
}
func (mts *mutatingProfilesSink) Capabilities() consumer.Capabilities {
return consumer.Capabilities{MutatesData: true}
}
================================================
FILE: internal/fanoutconsumer/traces.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package fanoutconsumer // import "go.opentelemetry.io/collector/internal/fanoutconsumer"
import (
"context"
"go.uber.org/multierr"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/pdata/ptrace"
)
// NewTraces wraps multiple trace consumers in a single one.
// It fans out the incoming data to all the consumers, and does smart routing:
// - Clones only to the consumer that needs to mutate the data.
// - If all consumers needs to mutate the data one will get the original mutable data.
func NewTraces(tcs []consumer.Traces) consumer.Traces {
// Don't wrap if there is only one non-mutating consumer.
if len(tcs) == 1 && !tcs[0].Capabilities().MutatesData {
return tcs[0]
}
tc := &tracesConsumer{}
for i := range tcs {
if tcs[i].Capabilities().MutatesData {
tc.mutable = append(tc.mutable, tcs[i])
} else {
tc.readonly = append(tc.readonly, tcs[i])
}
}
return tc
}
type tracesConsumer struct {
mutable []consumer.Traces
readonly []consumer.Traces
}
func (tsc *tracesConsumer) Capabilities() consumer.Capabilities {
// If all consumers are mutating, then the original data will be passed to one of them.
return consumer.Capabilities{MutatesData: len(tsc.mutable) > 0 && len(tsc.readonly) == 0}
}
// ConsumeTraces exports the ptrace.Traces to all consumers wrapped by the current one.
func (tsc *tracesConsumer) ConsumeTraces(ctx context.Context, td ptrace.Traces) error {
var errs error
if len(tsc.mutable) > 0 {
// Clone the data before sending to all mutating consumers except the last one.
for i := 0; i < len(tsc.mutable)-1; i++ {
errs = multierr.Append(errs, tsc.mutable[i].ConsumeTraces(ctx, cloneTraces(td)))
}
// Send data as is to the last mutating consumer only if there are no other non-mutating consumers and the
// data is mutable. Never share the same data between a mutating and a non-mutating consumer since the
// non-mutating consumer may process data async and the mutating consumer may change the data before that.
lastConsumer := tsc.mutable[len(tsc.mutable)-1]
if len(tsc.readonly) == 0 && !td.IsReadOnly() {
errs = multierr.Append(errs, lastConsumer.ConsumeTraces(ctx, td))
} else {
errs = multierr.Append(errs, lastConsumer.ConsumeTraces(ctx, cloneTraces(td)))
}
}
// Mark the data as read-only if it will be sent to more than one read-only consumer.
if len(tsc.readonly) > 1 && !td.IsReadOnly() {
td.MarkReadOnly()
}
for _, tc := range tsc.readonly {
errs = multierr.Append(errs, tc.ConsumeTraces(ctx, td))
}
return errs
}
func cloneTraces(td ptrace.Traces) ptrace.Traces {
clonedTraces := ptrace.NewTraces()
td.CopyTo(clonedTraces)
return clonedTraces
}
================================================
FILE: internal/fanoutconsumer/traces_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package fanoutconsumer
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/pdata/testdata"
)
func TestTracesNotMultiplexing(t *testing.T) {
nop := consumertest.NewNop()
tfc := NewTraces([]consumer.Traces{nop})
assert.Same(t, nop, tfc)
}
func TestTracesNotMultiplexingMutating(t *testing.T) {
p := &mutatingTracesSink{TracesSink: new(consumertest.TracesSink)}
lfc := NewTraces([]consumer.Traces{p})
assert.True(t, lfc.Capabilities().MutatesData)
}
func TestTracesMultiplexingNonMutating(t *testing.T) {
p1 := new(consumertest.TracesSink)
p2 := new(consumertest.TracesSink)
p3 := new(consumertest.TracesSink)
tfc := NewTraces([]consumer.Traces{p1, p2, p3})
assert.False(t, tfc.Capabilities().MutatesData)
td := testdata.GenerateTraces(1)
for range 2 {
err := tfc.ConsumeTraces(context.Background(), td)
if err != nil {
t.Errorf("Wanted nil got error")
return
}
}
assert.Equal(t, td, p1.AllTraces()[0])
assert.Equal(t, td, p1.AllTraces()[1])
assert.Equal(t, td, p1.AllTraces()[0])
assert.Equal(t, td, p1.AllTraces()[1])
assert.Equal(t, td, p2.AllTraces()[0])
assert.Equal(t, td, p2.AllTraces()[1])
assert.Equal(t, td, p2.AllTraces()[0])
assert.Equal(t, td, p2.AllTraces()[1])
assert.Equal(t, td, p3.AllTraces()[0])
assert.Equal(t, td, p3.AllTraces()[1])
assert.Equal(t, td, p3.AllTraces()[0])
assert.Equal(t, td, p3.AllTraces()[1])
// The data should be marked as read only.
assert.True(t, td.IsReadOnly())
}
func TestTracesMultiplexingMutating(t *testing.T) {
p1 := &mutatingTracesSink{TracesSink: new(consumertest.TracesSink)}
p2 := &mutatingTracesSink{TracesSink: new(consumertest.TracesSink)}
p3 := &mutatingTracesSink{TracesSink: new(consumertest.TracesSink)}
tfc := NewTraces([]consumer.Traces{p1, p2, p3})
assert.True(t, tfc.Capabilities().MutatesData)
td := testdata.GenerateTraces(1)
for range 2 {
err := tfc.ConsumeTraces(context.Background(), td)
if err != nil {
t.Errorf("Wanted nil got error")
return
}
}
assert.NotSame(t, &td, &p1.AllTraces()[0])
assert.NotSame(t, &td, &p1.AllTraces()[1])
assert.Equal(t, td, p1.AllTraces()[0])
assert.Equal(t, td, p1.AllTraces()[1])
assert.NotSame(t, &td, &p2.AllTraces()[0])
assert.NotSame(t, &td, &p2.AllTraces()[1])
assert.Equal(t, td, p2.AllTraces()[0])
assert.Equal(t, td, p2.AllTraces()[1])
// For this consumer, will receive the initial data.
assert.Equal(t, td, p3.AllTraces()[0])
assert.Equal(t, td, p3.AllTraces()[1])
assert.Equal(t, td, p3.AllTraces()[0])
assert.Equal(t, td, p3.AllTraces()[1])
// The data should not be marked as read only.
assert.False(t, td.IsReadOnly())
}
func TestReadOnlyTracesMultiplexingMutating(t *testing.T) {
p1 := &mutatingTracesSink{TracesSink: new(consumertest.TracesSink)}
p2 := &mutatingTracesSink{TracesSink: new(consumertest.TracesSink)}
p3 := &mutatingTracesSink{TracesSink: new(consumertest.TracesSink)}
tfc := NewTraces([]consumer.Traces{p1, p2, p3})
assert.True(t, tfc.Capabilities().MutatesData)
tdOrig := testdata.GenerateTraces(1)
td := testdata.GenerateTraces(1)
td.MarkReadOnly()
for range 2 {
err := tfc.ConsumeTraces(context.Background(), td)
if err != nil {
t.Errorf("Wanted nil got error")
return
}
}
// All consumers should receive the cloned data.
assert.NotEqual(t, td, p1.AllTraces()[0])
assert.NotEqual(t, td, p1.AllTraces()[1])
assert.Equal(t, tdOrig, p1.AllTraces()[0])
assert.Equal(t, tdOrig, p1.AllTraces()[1])
assert.NotEqual(t, td, p2.AllTraces()[0])
assert.NotEqual(t, td, p2.AllTraces()[1])
assert.Equal(t, tdOrig, p2.AllTraces()[0])
assert.Equal(t, tdOrig, p2.AllTraces()[1])
assert.NotEqual(t, td, p3.AllTraces()[0])
assert.NotEqual(t, td, p3.AllTraces()[1])
assert.Equal(t, tdOrig, p3.AllTraces()[0])
assert.Equal(t, tdOrig, p3.AllTraces()[1])
}
func TestTracesMultiplexingMixLastMutating(t *testing.T) {
p1 := &mutatingTracesSink{TracesSink: new(consumertest.TracesSink)}
p2 := new(consumertest.TracesSink)
p3 := &mutatingTracesSink{TracesSink: new(consumertest.TracesSink)}
tfc := NewTraces([]consumer.Traces{p1, p2, p3})
assert.False(t, tfc.Capabilities().MutatesData)
td := testdata.GenerateTraces(1)
for range 2 {
err := tfc.ConsumeTraces(context.Background(), td)
if err != nil {
t.Errorf("Wanted nil got error")
return
}
}
assert.NotSame(t, &td, &p1.AllTraces()[0])
assert.NotSame(t, &td, &p1.AllTraces()[1])
assert.Equal(t, td, p1.AllTraces()[0])
assert.Equal(t, td, p1.AllTraces()[1])
// For this consumer, will receive the initial data.
assert.Equal(t, td, p2.AllTraces()[0])
assert.Equal(t, td, p2.AllTraces()[1])
assert.Equal(t, td, p2.AllTraces()[0])
assert.Equal(t, td, p2.AllTraces()[1])
// For this consumer, will clone the initial data.
assert.NotSame(t, &td, &p3.AllTraces()[0])
assert.NotSame(t, &td, &p3.AllTraces()[1])
assert.Equal(t, td, p3.AllTraces()[0])
assert.Equal(t, td, p3.AllTraces()[1])
// The data should not be marked as read only.
assert.False(t, td.IsReadOnly())
}
func TestTracesMultiplexingMixLastNonMutating(t *testing.T) {
p1 := &mutatingTracesSink{TracesSink: new(consumertest.TracesSink)}
p2 := &mutatingTracesSink{TracesSink: new(consumertest.TracesSink)}
p3 := new(consumertest.TracesSink)
tfc := NewTraces([]consumer.Traces{p1, p2, p3})
assert.False(t, tfc.Capabilities().MutatesData)
td := testdata.GenerateTraces(1)
for range 2 {
err := tfc.ConsumeTraces(context.Background(), td)
if err != nil {
t.Errorf("Wanted nil got error")
return
}
}
assert.NotSame(t, &td, &p1.AllTraces()[0])
assert.NotSame(t, &td, &p1.AllTraces()[1])
assert.Equal(t, td, p1.AllTraces()[0])
assert.Equal(t, td, p1.AllTraces()[1])
assert.NotSame(t, &td, &p2.AllTraces()[0])
assert.NotSame(t, &td, &p2.AllTraces()[1])
assert.Equal(t, td, p2.AllTraces()[0])
assert.Equal(t, td, p2.AllTraces()[1])
// For this consumer, will receive the initial data.
assert.Equal(t, td, p3.AllTraces()[0])
assert.Equal(t, td, p3.AllTraces()[1])
assert.Equal(t, td, p3.AllTraces()[0])
assert.Equal(t, td, p3.AllTraces()[1])
// The data should not be marked as read only.
assert.False(t, td.IsReadOnly())
}
func TestTracesWhenErrors(t *testing.T) {
p1 := mutatingErr{Consumer: consumertest.NewErr(errors.New("my error"))}
p2 := consumertest.NewErr(errors.New("my error"))
p3 := new(consumertest.TracesSink)
tfc := NewTraces([]consumer.Traces{p1, p2, p3})
td := testdata.GenerateTraces(1)
for range 2 {
require.Error(t, tfc.ConsumeTraces(context.Background(), td))
}
assert.Equal(t, td, p3.AllTraces()[0])
assert.Equal(t, td, p3.AllTraces()[1])
assert.Equal(t, td, p3.AllTraces()[0])
assert.Equal(t, td, p3.AllTraces()[1])
}
type mutatingTracesSink struct {
*consumertest.TracesSink
}
func (mts *mutatingTracesSink) Capabilities() consumer.Capabilities {
return consumer.Capabilities{MutatesData: true}
}
================================================
FILE: internal/memorylimiter/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: internal/memorylimiter/cgroups/cgroup.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Keep 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.
//go:build linux
package cgroups // import "go.opentelemetry.io/collector/internal/memorylimiter/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) (int64, error) {
text, err := cg.readFirstLine(param)
if err != nil {
return 0, err
}
return strconv.ParseInt(text, 10, 64)
}
================================================
FILE: internal/memorylimiter/cgroups/cgroup_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Keep 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.
//go:build linux
package cgroups
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
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 int64
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)
}
}
}
================================================
FILE: internal/memorylimiter/cgroups/cgroups.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Keep 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.
//go:build linux
package cgroups // import "go.opentelemetry.io/collector/internal/memorylimiter/cgroups"
import (
"bufio"
"io"
"os"
"path/filepath"
"strconv"
"strings"
)
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"
// _cgroupv2MemoryMax is the file name for the CGroup-V2 Memory max
// parameter.
_cgroupv2MemoryMax = "memory.max"
// _cgroupFSType is the Linux CGroup-V2 file system type used in
// `/proc/$PID/mountinfo`.
_cgroupv2FSType = "cgroup2"
)
const (
_procPathCGroup = "/proc/self/cgroup"
_procPathMountInfo = "/proc/self/mountinfo"
_cgroupv2MountPoint = "/sys/fs/cgroup"
)
// 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
}
if strings.HasPrefix(cgroupPath, "/sys") {
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 limit of the process
// It is a result of `memory.limit_in_bytes`. If the value of
// `memory.limit_in_bytes` was not set (-1) or (9223372036854771712), the method returns `(-1, false, nil)`.
func (cg CGroups) MemoryQuota() (int64, bool, error) {
memCGroup, exists := cg[_cgroupSubsysMemory]
if !exists {
return -1, false, nil
}
memLimitBytes, err := memCGroup.readInt(_cgroupMemoryLimitBytes)
if defined := memLimitBytes > 0; err != nil || !defined {
return -1, defined, err
}
return memLimitBytes, true, nil
}
// IsCGroupV2 returns true if the system supports and uses cgroup2.
// It gets the required information for deciding from mountinfo file.
func IsCGroupV2() (bool, error) {
return isCGroupV2(_procPathMountInfo)
}
func isCGroupV2(procPathMountInfo string) (bool, error) {
isV2 := false
newMountPoint := func(mp *MountPoint) error {
if mp.FSType == _cgroupv2FSType && mp.MountPoint == _cgroupv2MountPoint {
isV2 = true
}
return nil
}
if err := parseMountInfo(procPathMountInfo, newMountPoint); err != nil {
return false, err
}
return isV2, nil
}
// MemoryQuotaV2 returns the total memory limit of the process
// It is a result of cgroupv2 `memory.max`. If the value of
// `memory.max` was not set (max), the method returns `(-1, false, nil)`.
func MemoryQuotaV2() (int64, bool, error) {
return memoryQuotaV2(_cgroupv2MountPoint, _cgroupv2MemoryMax)
}
func memoryQuotaV2(cgroupv2MountPoint, cgroupv2MemoryMax string) (int64, bool, error) {
memoryMaxParams, err := os.Open(filepath.Clean(filepath.Join(cgroupv2MountPoint, cgroupv2MemoryMax)))
if err != nil {
if os.IsNotExist(err) {
return -1, false, nil
}
return -1, false, err
}
scanner := bufio.NewScanner(memoryMaxParams)
if scanner.Scan() {
value := strings.TrimSpace(scanner.Text())
if value == "max" {
return -1, false, nil
}
maxVal, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return -1, false, err
}
return maxVal, true, nil
}
if err := scanner.Err(); err != nil {
return -1, false, err
}
return -1, false, io.ErrUnexpectedEOF
}
================================================
FILE: internal/memorylimiter/cgroups/cgroups_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Keep 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.
//go:build linux
package cgroups
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
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.Len(t, cgroups, len(testTable))
require.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 TestCGroupsMemoryQuota(t *testing.T) {
testTable := []struct {
name string
expectedQuota int64
expectedDefined bool
shouldHaveError bool
}{
{
name: "undefined",
expectedQuota: int64(-1.0),
expectedDefined: false,
shouldHaveError: true,
},
{
name: "memory",
expectedQuota: int64(8796093018112),
expectedDefined: true,
shouldHaveError: false,
},
}
cgroups := make(CGroups)
quota, defined, err := cgroups.MemoryQuota()
assert.Equal(t, int64(-1), quota, "nonexistent")
assert.False(t, defined, "nonexistent")
require.NoError(t, err, "nonexistent")
for _, tt := range testTable {
cgroupPath := filepath.Join(testDataCGroupsPath, tt.name)
cgroups[_cgroupSubsysMemory] = 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)
}
}
}
func TestCGroupsIsCGroupV2(t *testing.T) {
testTable := []struct {
name string
expectedIsV2 bool
shouldHaveError bool
}{
{
name: "cgroupv1",
expectedIsV2: false,
shouldHaveError: false,
},
{
name: "cgroupv1v2",
expectedIsV2: false,
shouldHaveError: false,
},
{
name: "cgroupv2",
expectedIsV2: true,
shouldHaveError: false,
},
{
name: "nonexistent",
expectedIsV2: false,
shouldHaveError: true,
},
}
for _, tt := range testTable {
mountInfoPath := filepath.Join(testDataProcPath, "v2", tt.name, "mountinfo")
isV2, err := isCGroupV2(mountInfoPath)
assert.Equal(t, tt.expectedIsV2, isV2, tt.name)
if tt.shouldHaveError {
assert.Error(t, err, tt.name)
} else {
assert.NoError(t, err, tt.name)
}
}
}
func TestCGroupsMemoryQuotaV2(t *testing.T) {
testTable := []struct {
name string
expectedQuota int64
expectedDefined bool
shouldHaveError bool
}{
{
name: "memory",
expectedQuota: int64(250000000),
expectedDefined: true,
shouldHaveError: false,
},
{
name: "undefined",
expectedQuota: int64(-1),
expectedDefined: false,
shouldHaveError: false,
},
{
name: "invalid",
expectedQuota: int64(-1),
expectedDefined: false,
shouldHaveError: true,
},
{
name: "empty",
expectedQuota: int64(-1),
expectedDefined: false,
shouldHaveError: true,
},
}
quota, defined, err := memoryQuotaV2("nonexistent", "nonexistent")
assert.Equal(t, int64(-1), quota, "nonexistent")
assert.False(t, defined, "nonexistent")
require.NoError(t, err, "nonexistent")
cgroupBasePath := filepath.Join(testDataCGroupsPath, "v2")
for _, tt := range testTable {
cgroupPath := filepath.Join(cgroupBasePath, tt.name)
quota, defined, err := memoryQuotaV2(cgroupPath, "memory.max")
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)
}
}
}
================================================
FILE: internal/memorylimiter/cgroups/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Keep 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 // import "go.opentelemetry.io/collector/internal/memorylimiter/cgroups"
================================================
FILE: internal/memorylimiter/cgroups/errors.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Keep 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.
//go:build linux
package cgroups // import "go.opentelemetry.io/collector/internal/memorylimiter/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)
}
================================================
FILE: internal/memorylimiter/cgroups/mountpoint.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Keep 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.
//go:build linux
package cgroups // import "go.opentelemetry.io/collector/internal/memorylimiter/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 {
continue
}
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(filepath.Clean(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()
}
================================================
FILE: internal/memorylimiter/cgroups/mountpoint_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Keep 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.
//go:build linux
package cgroups
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
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)
require.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, errExpected, err, "[%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)
require.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)
require.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.Empty(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.Empty(t, translated, "relPaths[%d] == %q", i, path)
assert.Error(t, err, path)
}
}
================================================
FILE: internal/memorylimiter/cgroups/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package cgroups
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: internal/memorylimiter/cgroups/subsys.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Keep 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.
//go:build linux
package cgroups // import "go.opentelemetry.io/collector/internal/memorylimiter/cgroups"
import (
"bufio"
"os"
"path/filepath"
"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.SplitN(line, _cgroupSep, _csFieldCount)
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(filepath.Clean(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
}
================================================
FILE: internal/memorylimiter/cgroups/subsys_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Keep 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.
//go: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",
},
},
{
name: "sophisticated-path",
line: "4:pids:/example.slice:extra-path-designator",
expectedSubsys: &CGroupSubsys{
ID: 4,
Subsystems: []string{"pids"},
Name: "/example.slice:extra-path-designator",
},
},
}
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",
"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: "illegal-id",
line: lines[1],
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)
}
}
================================================
FILE: internal/memorylimiter/cgroups/testdata/cgroups/cpu/cpu.cfs_period_us
================================================
100000
================================================
FILE: internal/memorylimiter/cgroups/testdata/cgroups/cpu/cpu.cfs_quota_us
================================================
600000
================================================
FILE: internal/memorylimiter/cgroups/testdata/cgroups/empty/cpu.cfs_quota_us
================================================
================================================
FILE: internal/memorylimiter/cgroups/testdata/cgroups/invalid/cpu.cfs_quota_us
================================================
non-an-integer
================================================
FILE: internal/memorylimiter/cgroups/testdata/cgroups/memory/memory.limit_in_bytes
================================================
8796093018112
================================================
FILE: internal/memorylimiter/cgroups/testdata/cgroups/undefined/cpu.cfs_period_us
================================================
100000
================================================
FILE: internal/memorylimiter/cgroups/testdata/cgroups/undefined/cpu.cfs_quota_us
================================================
-1
================================================
FILE: internal/memorylimiter/cgroups/testdata/cgroups/undefined-period/cpu.cfs_quota_us
================================================
800000
================================================
FILE: internal/memorylimiter/cgroups/testdata/cgroups/v2/empty/memory.max
================================================
================================================
FILE: internal/memorylimiter/cgroups/testdata/cgroups/v2/invalid/memory.max
================================================
ngn
================================================
FILE: internal/memorylimiter/cgroups/testdata/cgroups/v2/memory/memory.max
================================================
250000000
================================================
FILE: internal/memorylimiter/cgroups/testdata/cgroups/v2/undefined/memory.max
================================================
max
================================================
FILE: internal/memorylimiter/cgroups/testdata/proc/cgroups/cgroup
================================================
3:memory:/docker/large
2:cpu,cpuacct:/docker
1:cpuset:/
================================================
FILE: internal/memorylimiter/cgroups/testdata/proc/cgroups/mountinfo
================================================
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
9 1 0:8 / /var/lib/docker/overlay2/9054a95f2cf7296867089e1bd37931742a17eb3308a795d51adb2654ee2276df/merged/sys/fs/cgroup ro,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755
10 9 0:9 /docker /var/lib/docker/overlay2/9054a95f2cf7296867089e1bd37931742a17eb3308a795d51adb2654ee2276df/merged/sys/fs/cgroup/memory ro,nosuid,nodev,noexec,relatime master:17 - cgroup cgroup rw,memory
================================================
FILE: internal/memorylimiter/cgroups/testdata/proc/invalid-cgroup/cgroup
================================================
1:cpu:/cpu
invalid-line:
================================================
FILE: internal/memorylimiter/cgroups/testdata/proc/invalid-mountinfo/mountinfo
================================================
1 0 8:1 / / rw,noatime shared:1 - ext4 /dev/sda1
================================================
FILE: internal/memorylimiter/cgroups/testdata/proc/untranslatable/cgroup
================================================
1:cpu:/docker
2:cpuacct:/docker
================================================
FILE: internal/memorylimiter/cgroups/testdata/proc/untranslatable/mountinfo
================================================
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
================================================
FILE: internal/memorylimiter/cgroups/testdata/proc/v2/cgroupv1/mountinfo
================================================
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
================================================
FILE: internal/memorylimiter/cgroups/testdata/proc/v2/cgroupv1v2/mountinfo
================================================
33 24 0:28 / /sys/fs/cgroup ro,nosuid,nodev,noexec shared:9 - tmpfs tmpfs ro,mode=755,inode64
34 33 0:29 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime shared:10 - cgroup2 cgroup2 rw,nsdelegate
35 33 0:30 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:11 - cgroup cgroup rw,xattr,name=systemd
39 33 0:34 / /sys/fs/cgroup/misc rw,nosuid,nodev,noexec,relatime shared:16 - cgroup cgroup rw,misc
40 33 0:35 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime shared:17 - cgroup cgroup rw,net_cls,net_prio
41 33 0:36 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime shared:18 - cgroup cgroup rw,rdma
42 33 0:37 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:19 - cgroup cgroup rw,memory
43 33 0:38 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:20 - cgroup cgroup rw,blkio
44 33 0:39 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:21 - cgroup cgroup rw,cpu,cpuacct
45 33 0:40 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime shared:22 - cgroup cgroup rw,pids
46 33 0:41 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime shared:23 - cgroup cgroup rw,hugetlb
47 33 0:42 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:24 - cgroup cgroup rw,freezer
48 33 0:43 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:25 - cgroup cgroup rw,perf_event
49 33 0:44 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:26 - cgroup cgroup rw,devices
50 33 0:45 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:27 - cgroup cgroup rw,cpuset
================================================
FILE: internal/memorylimiter/cgroups/testdata/proc/v2/cgroupv2/mountinfo
================================================
34 33 0:29 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime shared:10 - cgroup2 cgroup rw,nsdelegate
================================================
FILE: internal/memorylimiter/cgroups/util_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Keep 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.
//go: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
}
================================================
FILE: internal/memorylimiter/config.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package memorylimiter // import "go.opentelemetry.io/collector/internal/memorylimiter"
import (
"errors"
"time"
"go.opentelemetry.io/collector/component"
)
var (
errCheckIntervalOutOfRange = errors.New("'check_interval' must be greater than zero")
errInconsistentGCMinInterval = errors.New("'min_gc_interval_when_soft_limited' should be larger than 'min_gc_interval_when_hard_limited'")
errLimitOutOfRange = errors.New("'limit_mib' or 'limit_percentage' must be greater than zero")
errSpikeLimitOutOfRange = errors.New("'spike_limit_mib' must be smaller than 'limit_mib'")
errSpikeLimitPercentageOutOfRange = errors.New("'spike_limit_percentage' must be smaller than 'limit_percentage'")
errLimitPercentageOutOfRange = errors.New(
"'limit_percentage' and 'spike_limit_percentage' must be greater than zero and less than or equal to hundred")
)
// Config defines configuration for memory memoryLimiter processor.
type Config struct {
// 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"`
// MinGCIntervalWhenSoftLimited minimum interval between forced GC when in soft (=limit_mib - spike_limit_mib) limited mode.
// Zero value means no minimum interval.
// GCs is a CPU-heavy operation and executing it too frequently may affect the recovery capabilities of the collector.
MinGCIntervalWhenSoftLimited time.Duration `mapstructure:"min_gc_interval_when_soft_limited"`
// MinGCIntervalWhenHardLimited minimum interval between forced GC when in hard (=limit_mib) limited mode.
// Zero value means no minimum interval.
// GCs is a CPU-heavy operation and executing it too frequently may affect the recovery capabilities of the collector.
MinGCIntervalWhenHardLimited time.Duration `mapstructure:"min_gc_interval_when_hard_limited"`
// 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"`
// 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"`
}
var _ component.Config = (*Config)(nil)
func NewDefaultConfig() *Config {
return &Config{
MinGCIntervalWhenSoftLimited: 10 * time.Second,
}
}
// Validate checks if the processor configuration is valid
func (cfg *Config) Validate() error {
if cfg.CheckInterval <= 0 {
return errCheckIntervalOutOfRange
}
if cfg.MinGCIntervalWhenSoftLimited < cfg.MinGCIntervalWhenHardLimited {
return errInconsistentGCMinInterval
}
if cfg.MemoryLimitMiB == 0 && cfg.MemoryLimitPercentage == 0 {
return errLimitOutOfRange
}
if cfg.MemoryLimitPercentage > 100 || cfg.MemorySpikePercentage > 100 {
return errLimitPercentageOutOfRange
}
if cfg.MemoryLimitMiB > 0 && cfg.MemoryLimitMiB <= cfg.MemorySpikeLimitMiB {
return errSpikeLimitOutOfRange
}
if cfg.MemoryLimitPercentage > 0 && cfg.MemoryLimitPercentage <= cfg.MemorySpikePercentage {
return errSpikeLimitPercentageOutOfRange
}
return nil
}
================================================
FILE: internal/memorylimiter/config_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package memorylimiter
import (
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/confmap/confmaptest"
)
func TestUnmarshalConfig(t *testing.T) {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
require.NoError(t, err)
cfg := &Config{}
assert.NoError(t, cm.Unmarshal(&cfg))
assert.Equal(t,
&Config{
CheckInterval: 5 * time.Second,
MemoryLimitMiB: 4000,
MemorySpikeLimitMiB: 500,
}, cfg)
}
func TestConfigValidate(t *testing.T) {
tests := []struct {
name string
cfg *Config
err error
}{
{
name: "valid",
cfg: &Config{
MemoryLimitMiB: 5722,
MemorySpikeLimitMiB: 1907,
CheckInterval: 100 * time.Millisecond,
},
err: nil,
},
{
name: "zero check interval",
cfg: &Config{
CheckInterval: 0,
},
err: errCheckIntervalOutOfRange,
},
{
name: "unset memory limit",
cfg: &Config{
CheckInterval: 1 * time.Second,
MemoryLimitMiB: 0,
MemoryLimitPercentage: 0,
},
err: errLimitOutOfRange,
},
{
name: "invalid memory spike limit",
cfg: &Config{
CheckInterval: 1 * time.Second,
MemoryLimitMiB: 10,
MemorySpikeLimitMiB: 10,
},
err: errSpikeLimitOutOfRange,
},
{
name: "invalid memory percentage limit",
cfg: &Config{
CheckInterval: 1 * time.Second,
MemoryLimitPercentage: 101,
},
err: errLimitPercentageOutOfRange,
},
{
name: "invalid memory spike percentage limit",
cfg: &Config{
CheckInterval: 1 * time.Second,
MemoryLimitPercentage: 50,
MemorySpikePercentage: 60,
},
err: errSpikeLimitPercentageOutOfRange,
},
{
name: "invalid gc intervals",
cfg: &Config{
CheckInterval: 100 * time.Millisecond,
MinGCIntervalWhenSoftLimited: 50 * time.Millisecond,
MinGCIntervalWhenHardLimited: 100 * time.Millisecond,
MemoryLimitMiB: 5722,
MemorySpikeLimitMiB: 1907,
},
err: errInconsistentGCMinInterval,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.cfg.Validate()
assert.Equal(t, tt.err, err)
})
}
}
func TestUnmarshalInvalidConfig(t *testing.T) {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "negative_unsigned_limits_config.yaml"))
require.NoError(t, err)
cfg := &Config{}
err = cm.Unmarshal(&cfg)
require.ErrorContains(t, err, "'limit_mib' cannot parse value as 'uint32': -2000 overflows uint")
require.ErrorContains(t, err, "'spike_limit_mib' cannot parse value as 'uint32': -2300 overflows uint")
}
================================================
FILE: internal/memorylimiter/go.mod
================================================
module go.opentelemetry.io/collector/internal/memorylimiter
go 1.25.0
require (
github.com/shirou/gopsutil/v4 v4.26.2
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/confmap v1.54.0
go.uber.org/goleak v1.3.0
go.uber.org/zap v1.27.1
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/ebitengine/purego v0.10.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.11.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/pdata v1.54.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/sys v0.41.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/confmap => ../../confmap
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../testutil
================================================
FILE: internal/memorylimiter/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/shirou/gopsutil/v4 v4.26.2 h1:X8i6sicvUFih4BmYIGT1m2wwgw2VG9YgrDTi7cIRGUI=
github.com/shirou/gopsutil/v4 v4.26.2/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: internal/memorylimiter/iruntime/mem_info.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package iruntime // import "go.opentelemetry.io/collector/internal/memorylimiter/iruntime"
import (
"github.com/shirou/gopsutil/v4/mem"
)
// readMemInfo returns the total memory
// supports in linux, darwin and windows
func readMemInfo() (uint64, error) {
vmStat, err := mem.VirtualMemory()
return vmStat.Total, err
}
================================================
FILE: internal/memorylimiter/iruntime/mem_info_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package iruntime
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestReadMemInfo(t *testing.T) {
vmStat, err := readMemInfo()
require.NoError(t, err)
assert.Positive(t, vmStat)
}
================================================
FILE: internal/memorylimiter/iruntime/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package iruntime
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: internal/memorylimiter/iruntime/total_memory_linux.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:build linux
package iruntime // import "go.opentelemetry.io/collector/internal/memorylimiter/iruntime"
import "go.opentelemetry.io/collector/internal/memorylimiter/cgroups"
// unlimitedMemorySize defines the bytes size when memory limit is not set
// for the container and process with cgroups
const unlimitedMemorySize = 9223372036854771712
// TotalMemory returns total available memory.
// This implementation is meant for linux and uses cgroups to determine available memory.
func TotalMemory() (uint64, error) {
var memoryQuota int64
var defined bool
var err error
isV2, err := cgroups.IsCGroupV2()
if err != nil {
return 0, err
}
if isV2 {
memoryQuota, defined, err = cgroups.MemoryQuotaV2()
if err != nil {
return 0, err
}
} else {
cgv1, err := cgroups.NewCGroupsForCurrentProcess()
if err != nil {
return 0, err
}
memoryQuota, defined, err = cgv1.MemoryQuota()
if err != nil {
return 0, err
}
}
// If memory is not defined or is set to unlimitedMemorySize (v1 unset),
// we fallback to /proc/meminfo.
if memoryQuota == unlimitedMemorySize || !defined {
totalMem, err := readMemInfo()
if err != nil {
return 0, err
}
return totalMem, nil
}
return uint64(memoryQuota), nil
}
================================================
FILE: internal/memorylimiter/iruntime/total_memory_linux_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go: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.Positive(t, totalMemory)
}
================================================
FILE: internal/memorylimiter/iruntime/total_memory_other.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:build !linux
package iruntime // import "go.opentelemetry.io/collector/internal/memorylimiter/iruntime"
// TotalMemory returns total available memory for non-linux platforms.
func TotalMemory() (uint64, error) {
return readMemInfo()
}
================================================
FILE: internal/memorylimiter/iruntime/total_memory_other_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go: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.Positive(t, totalMemory)
}
================================================
FILE: internal/memorylimiter/memorylimiter.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package memorylimiter // import "go.opentelemetry.io/collector/internal/memorylimiter"
import (
"context"
"errors"
"fmt"
"runtime"
"sync"
"sync/atomic"
"time"
"go.uber.org/zap"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/internal/memorylimiter/iruntime"
)
const (
mibBytes = 1024 * 1024
)
var (
// ErrDataRefused will be returned to callers of ConsumeTraceData to indicate
// that data is being refused due to high memory usage.
ErrDataRefused = errors.New("data refused due to high memory usage")
// GetMemoryFn and ReadMemStatsFn make it overridable by tests
GetMemoryFn = iruntime.TotalMemory
ReadMemStatsFn = runtime.ReadMemStats
)
// MemoryLimiter is used to prevent out of memory situations on the collector.
type MemoryLimiter struct {
usageChecker memUsageChecker
memCheckWait time.Duration
// mustRefuse is used to indicate when data should be refused.
mustRefuse *atomic.Bool
ticker *time.Ticker
minGCIntervalWhenSoftLimited time.Duration
minGCIntervalWhenHardLimited time.Duration
lastGCDone time.Time
// The functions to read the mem values and run GC are set as a reference to help with
// testing different values.
readMemStatsFn func(m *runtime.MemStats)
runGCFn func()
// Fields used for logging.
logger *zap.Logger
refCounterLock sync.Mutex
refCounter int
waitGroup sync.WaitGroup
closed chan struct{}
}
// NewMemoryLimiter returns a new memory limiter component
func NewMemoryLimiter(cfg *Config, logger *zap.Logger) (*MemoryLimiter, error) {
usageChecker, err := getMemUsageChecker(cfg, logger)
if err != nil {
return nil, err
}
logger.Info("Memory limiter configured",
zap.Uint64("limit_mib", usageChecker.memAllocLimit/mibBytes),
zap.Uint64("spike_limit_mib", usageChecker.memSpikeLimit/mibBytes),
zap.Duration("check_interval", cfg.CheckInterval))
return &MemoryLimiter{
usageChecker: *usageChecker,
memCheckWait: cfg.CheckInterval,
ticker: time.NewTicker(cfg.CheckInterval),
minGCIntervalWhenSoftLimited: cfg.MinGCIntervalWhenSoftLimited,
minGCIntervalWhenHardLimited: cfg.MinGCIntervalWhenHardLimited,
lastGCDone: time.Now(),
readMemStatsFn: ReadMemStatsFn,
runGCFn: runtime.GC,
logger: logger,
mustRefuse: &atomic.Bool{},
}, nil
}
func (ml *MemoryLimiter) Start(_ context.Context, _ component.Host) error {
ml.refCounterLock.Lock()
defer ml.refCounterLock.Unlock()
ml.refCounter++
if ml.refCounter == 1 {
ml.closed = make(chan struct{})
ml.waitGroup.Go(func() {
for {
select {
case <-ml.ticker.C:
case <-ml.closed:
return
}
ml.CheckMemLimits()
}
})
}
return nil
}
// Shutdown resets MemoryLimiter monitoring ticker and stop monitoring
func (ml *MemoryLimiter) Shutdown(context.Context) error {
ml.refCounterLock.Lock()
defer ml.refCounterLock.Unlock()
switch ml.refCounter {
case 0:
return nil
case 1:
ml.ticker.Stop()
close(ml.closed)
ml.waitGroup.Wait()
}
ml.refCounter--
return nil
}
// MustRefuse returns true if memory has reached its configured limits
func (ml *MemoryLimiter) MustRefuse() bool {
return ml.mustRefuse.Load()
}
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), nil
}
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.Uint64("total_memory_mib", totalMemory/mibBytes),
zap.Uint32("limit_percentage", cfg.MemoryLimitPercentage),
zap.Uint32("spike_limit_percentage", cfg.MemorySpikePercentage))
return newPercentageMemUsageChecker(totalMemory, uint64(cfg.MemoryLimitPercentage),
uint64(cfg.MemorySpikePercentage)), nil
}
func (ml *MemoryLimiter) readMemStats() *runtime.MemStats {
ms := &runtime.MemStats{}
ml.readMemStatsFn(ms)
return ms
}
func memstatToZapField(ms *runtime.MemStats) zap.Field {
return zap.Uint64("cur_mem_mib", ms.Alloc/mibBytes)
}
func (ml *MemoryLimiter) doGCandReadMemStats() *runtime.MemStats {
ml.runGCFn()
ml.lastGCDone = time.Now()
ms := ml.readMemStats()
ml.logger.Info("Memory usage after GC.", memstatToZapField(ms))
return ms
}
// CheckMemLimits inspects current memory usage against threshold and toggles mustRefuse when threshold is exceeded
func (ml *MemoryLimiter) CheckMemLimits() {
ms := ml.readMemStats()
ml.logger.Debug("Currently used memory.", memstatToZapField(ms))
// Check if we are below the soft limit.
aboveSoftLimit := ml.usageChecker.aboveSoftLimit(ms)
if !aboveSoftLimit {
if ml.mustRefuse.Load() {
// Was previously refusing but enough memory is available now, no need to limit.
ml.logger.Info("Memory usage back within limits. Resuming normal operation.", memstatToZapField(ms))
}
ml.mustRefuse.Store(aboveSoftLimit)
return
}
if ml.usageChecker.aboveHardLimit(ms) {
// We are above hard 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) > ml.minGCIntervalWhenHardLimited {
ml.logger.Warn("Memory usage is above hard limit. Forcing a GC.", memstatToZapField(ms))
ms = ml.doGCandReadMemStats()
// Check the limit again to see if GC helped.
aboveSoftLimit = ml.usageChecker.aboveSoftLimit(ms)
}
} else {
// 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) > ml.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.
aboveSoftLimit = ml.usageChecker.aboveSoftLimit(ms)
}
}
if !ml.mustRefuse.Load() && aboveSoftLimit {
ml.logger.Warn("Memory usage is above soft limit. Refusing data.", memstatToZapField(ms))
}
ml.mustRefuse.Store(aboveSoftLimit)
}
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 {
if memSpikeLimit == 0 {
// If spike limit is unspecified use 20% of mem limit.
memSpikeLimit = memAllocLimit / 5
}
return &memUsageChecker{
memAllocLimit: memAllocLimit,
memSpikeLimit: memSpikeLimit,
}
}
func newPercentageMemUsageChecker(totalMemory, percentageLimit, percentageSpike uint64) *memUsageChecker {
return newFixedMemUsageChecker(percentageLimit*totalMemory/100, percentageSpike*totalMemory/100)
}
================================================
FILE: internal/memorylimiter/memorylimiter_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package memorylimiter
import (
"runtime"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"go.opentelemetry.io/collector/internal/memorylimiter/iruntime"
)
// TestMemoryPressureResponse manipulates results from querying memory and
// check expected side effects.
func TestMemoryPressureResponse(t *testing.T) {
var currentMemAlloc uint64
cfg := &Config{
CheckInterval: 1 * time.Minute,
MemoryLimitMiB: 1024,
MemorySpikeLimitMiB: 0,
}
ml, err := NewMemoryLimiter(cfg, zap.NewNop())
require.NoError(t, err)
ml.readMemStatsFn = func(ms *runtime.MemStats) {
ms.Alloc = currentMemAlloc * mibBytes
}
// Below memAllocLimit.
currentMemAlloc = 800
ml.CheckMemLimits()
assert.False(t, ml.MustRefuse())
// Above memAllocLimit.
currentMemAlloc = 1800
ml.CheckMemLimits()
assert.True(t, ml.MustRefuse())
// Check spike limit
ml.usageChecker.memSpikeLimit = 512 * mibBytes
// Below memSpikeLimit.
currentMemAlloc = 500
ml.CheckMemLimits()
assert.False(t, ml.MustRefuse())
// Above memSpikeLimit.
currentMemAlloc = 550
ml.CheckMemLimits()
assert.True(t, ml.MustRefuse())
}
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.Cleanup(func() {
GetMemoryFn = iruntime.TotalMemory
})
GetMemoryFn = func() (uint64, 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)
})
}
func TestRefuseDecision(t *testing.T) {
decision1000Limit30Spike30 := newPercentageMemUsageChecker(1000, 60, 30)
decision1000Limit60Spike50 := newPercentageMemUsageChecker(1000, 60, 50)
decision1000Limit40Spike20 := newPercentageMemUsageChecker(1000, 40, 20)
tests := []struct {
name string
usageChecker memUsageChecker
ms *runtime.MemStats
shouldRefuse bool
}{
{
name: "should refuse over limit",
usageChecker: *decision1000Limit30Spike30,
ms: &runtime.MemStats{Alloc: 600},
shouldRefuse: true,
},
{
name: "should not refuse",
usageChecker: *decision1000Limit30Spike30,
ms: &runtime.MemStats{Alloc: 100},
shouldRefuse: false,
},
{
name: "should not refuse spike, fixed usageChecker",
usageChecker: memUsageChecker{
memAllocLimit: 600,
memSpikeLimit: 500,
},
ms: &runtime.MemStats{Alloc: 300},
shouldRefuse: true,
},
{
name: "should refuse, spike, percentage usageChecker",
usageChecker: *decision1000Limit60Spike50,
ms: &runtime.MemStats{Alloc: 300},
shouldRefuse: true,
},
{
name: "should refuse, spike, percentage usageChecker",
usageChecker: *decision1000Limit40Spike20,
ms: &runtime.MemStats{Alloc: 250},
shouldRefuse: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
shouldRefuse := test.usageChecker.aboveSoftLimit(test.ms)
assert.Equal(t, test.shouldRefuse, shouldRefuse)
})
}
}
func TestCallGCWhenSoftLimit(t *testing.T) {
tests := []struct {
name string
mlCfg *Config
memAllocMiB [2]uint64
numGCs int
}{
{
name: "GC when first soft limit and not immediately",
mlCfg: &Config{
CheckInterval: 1 * time.Minute,
MinGCIntervalWhenSoftLimited: 10 * time.Second,
MemoryLimitMiB: 50,
MemorySpikeLimitMiB: 10,
},
memAllocMiB: [2]uint64{45, 45},
numGCs: 1,
},
{
name: "GC always when soft limit min interval is 0",
mlCfg: &Config{
CheckInterval: 1 * time.Minute,
MinGCIntervalWhenSoftLimited: 0,
MemoryLimitMiB: 50,
MemorySpikeLimitMiB: 10,
},
memAllocMiB: [2]uint64{45, 45},
numGCs: 2,
},
{
name: "GC when first hard limit and not immediately",
mlCfg: &Config{
CheckInterval: 1 * time.Minute,
MinGCIntervalWhenHardLimited: 10 * time.Second,
MemoryLimitMiB: 50,
MemorySpikeLimitMiB: 10,
},
memAllocMiB: [2]uint64{55, 55},
numGCs: 1,
},
{
name: "GC always when hard limit min interval is 0",
mlCfg: &Config{
CheckInterval: 1 * time.Minute,
MinGCIntervalWhenHardLimited: 0,
MemoryLimitMiB: 50,
MemorySpikeLimitMiB: 10,
},
memAllocMiB: [2]uint64{55, 55},
numGCs: 2,
},
{
name: "GC based on soft then based on hard limit",
mlCfg: &Config{
CheckInterval: 1 * time.Minute,
MinGCIntervalWhenSoftLimited: 10 * time.Second,
MinGCIntervalWhenHardLimited: 0,
MemoryLimitMiB: 50,
MemorySpikeLimitMiB: 10,
},
memAllocMiB: [2]uint64{45, 55},
numGCs: 2,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ml, err := NewMemoryLimiter(tt.mlCfg, zap.NewNop())
require.NoError(t, err)
memAllocMiB := uint64(0)
ml.readMemStatsFn = func(ms *runtime.MemStats) {
ms.Alloc = memAllocMiB * mibBytes
}
// Mark last GC in the past so that even first call can trigger GC
// Not updating the initialization code, since at the beginning of the collector no need to GC.
ml.lastGCDone = ml.lastGCDone.Add(-time.Minute)
numGCs := 0
ml.runGCFn = func() {
numGCs++
}
memAllocMiB = tt.memAllocMiB[0]
ml.CheckMemLimits()
assert.True(t, ml.MustRefuse())
// On windows, time has larger precision, and checking here again may return same time as "lastGCDone"
// which will not trigger a new GC for 0 duration, update last GC with -1 millis.
ml.lastGCDone = ml.lastGCDone.Add(-1 * time.Millisecond)
memAllocMiB = tt.memAllocMiB[1]
ml.CheckMemLimits()
assert.True(t, ml.MustRefuse())
assert.Equal(t, tt.numGCs, numGCs)
})
}
}
================================================
FILE: internal/memorylimiter/testdata/config.yaml
================================================
# 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
================================================
FILE: internal/memorylimiter/testdata/negative_unsigned_limits_config.yaml
================================================
# 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: -2000
# The maximum, in MiB, spike expected between the measurements of memory usage.
spike_limit_mib: -2300
================================================
FILE: internal/sharedcomponent/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: internal/sharedcomponent/go.mod
================================================
module go.opentelemetry.io/collector/internal/sharedcomponent
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componentstatus v0.148.0
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.uber.org/goleak v1.3.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/pdata v1.54.0 // indirect
go.opentelemetry.io/collector/pipeline v1.54.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
golang.org/x/sys v0.41.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/component/componentstatus => ../../component/componentstatus
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest
replace go.opentelemetry.io/collector/pipeline => ../../pipeline
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../testutil
================================================
FILE: internal/sharedcomponent/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: internal/sharedcomponent/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sharedcomponent
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: internal/sharedcomponent/sharedcomponent.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package sharedcomponent exposes functionality for components
// to register against a shared key, such as a configuration object, in order to be reused across signal types.
// This is particularly useful when the component relies on a shared resource such as os.File or http.Server.
package sharedcomponent // import "go.opentelemetry.io/collector/internal/sharedcomponent"
import (
"container/ring"
"context"
"sync"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componentstatus"
)
func NewMap[K comparable, V component.Component]() *Map[K, V] {
return &Map[K, V]{
components: map[K]*Component[V]{},
}
}
// Map keeps reference of all created instances for a given shared key such as a component configuration.
type Map[K comparable, V component.Component] struct {
lock sync.Mutex
components map[K]*Component[V]
}
// LoadOrStore returns the already created instance if exists, otherwise creates a new instance
// and adds it to the map of references.
func (m *Map[K, V]) LoadOrStore(key K, create func() (V, error)) (*Component[V], error) {
m.lock.Lock()
defer m.lock.Unlock()
if c, ok := m.components[key]; ok {
return c, nil
}
comp, err := create()
if err != nil {
return nil, err
}
newComp := &Component[V]{
component: comp,
removeFunc: func() {
m.lock.Lock()
defer m.lock.Unlock()
delete(m.components, key)
},
}
m.components[key] = newComp
return newComp, nil
}
// Component ensures that the wrapped component is started and stopped only once.
// When stopped it is removed from the Map.
type Component[V component.Component] struct {
component V
startOnce sync.Once
stopOnce sync.Once
removeFunc func()
hostWrapper *hostWrapper
}
// Unwrap returns the original component.
func (c *Component[V]) Unwrap() V {
return c.component
}
// Start starts the underlying component if it never started before.
func (c *Component[V]) Start(ctx context.Context, host component.Host) error {
if c.hostWrapper == nil {
var err error
c.startOnce.Do(func() {
c.hostWrapper = &hostWrapper{
host: host,
sources: make([]componentstatus.Reporter, 0),
previousEvents: ring.New(5),
}
statusReporter, isStatusReporter := host.(componentstatus.Reporter)
if isStatusReporter {
c.hostWrapper.addSource(statusReporter)
}
// It's important that status for a shared component is reported through its
// telemetry settings to keep status in sync and avoid race conditions. This logic duplicates
// and takes priority over the automated status reporting that happens in graph, making the
// status reporting in graph a no-op.
c.hostWrapper.Report(componentstatus.NewEvent(componentstatus.StatusStarting))
if err = c.component.Start(ctx, c.hostWrapper); err != nil {
c.hostWrapper.Report(componentstatus.NewPermanentErrorEvent(err))
}
})
return err
}
statusReporter, isStatusReporter := host.(componentstatus.Reporter)
if isStatusReporter {
c.hostWrapper.addSource(statusReporter)
}
return nil
}
var (
_ component.Host = (*hostWrapper)(nil)
_ componentstatus.Reporter = (*hostWrapper)(nil)
)
type hostWrapper struct {
host component.Host
sources []componentstatus.Reporter
previousEvents *ring.Ring
lock sync.Mutex
}
func (h *hostWrapper) GetExtensions() map[component.ID]component.Component {
return h.host.GetExtensions()
}
func (h *hostWrapper) Report(e *componentstatus.Event) {
// Only remember an event if it will be emitted and it has not been sent already.
h.lock.Lock()
defer h.lock.Unlock()
if len(h.sources) > 0 {
h.previousEvents.Value = e
h.previousEvents = h.previousEvents.Next()
}
for _, s := range h.sources {
s.Report(e)
}
}
func (h *hostWrapper) addSource(s componentstatus.Reporter) {
h.lock.Lock()
defer h.lock.Unlock()
h.previousEvents.Do(func(a any) {
if e, ok := a.(*componentstatus.Event); ok {
s.Report(e)
}
})
h.sources = append(h.sources, s)
}
// Shutdown shuts down the underlying component.
func (c *Component[V]) Shutdown(ctx context.Context) error {
var err error
c.stopOnce.Do(func() {
// It's important that status for a shared component is reported through its
// telemetry settings to keep status in sync and avoid race conditions. This logic duplicates
// and takes priority over the automated status reporting that happens in graph, making the
// status reporting in graph a no-op.
if c.hostWrapper != nil {
c.hostWrapper.Report(componentstatus.NewEvent(componentstatus.StatusStopping))
}
err = c.component.Shutdown(ctx)
if c.hostWrapper != nil {
if err != nil {
c.hostWrapper.Report(componentstatus.NewPermanentErrorEvent(err))
} else {
c.hostWrapper.Report(componentstatus.NewEvent(componentstatus.StatusStopped))
}
}
c.removeFunc()
})
return err
}
================================================
FILE: internal/sharedcomponent/sharedcomponent_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sharedcomponent
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componentstatus"
"go.opentelemetry.io/collector/component/componenttest"
)
var id = component.MustNewID("test")
type baseComponent struct {
component.StartFunc
component.ShutdownFunc
}
func TestNewMap(t *testing.T) {
comps := NewMap[component.ID, *baseComponent]()
assert.Empty(t, comps.components)
}
func TestNewSharedComponentsCreateError(t *testing.T) {
comps := NewMap[component.ID, *baseComponent]()
assert.Empty(t, comps.components)
myErr := errors.New("my error")
_, err := comps.LoadOrStore(
id,
func() (*baseComponent, error) { return nil, myErr },
)
require.ErrorIs(t, err, myErr)
assert.Empty(t, comps.components)
}
func TestSharedComponentsLoadOrStore(t *testing.T) {
nop := &baseComponent{}
comps := NewMap[component.ID, *baseComponent]()
got, err := comps.LoadOrStore(
id,
func() (*baseComponent, error) { return nop, nil },
)
require.NoError(t, err)
assert.Len(t, comps.components, 1)
assert.Same(t, nop, got.Unwrap())
gotSecond, err := comps.LoadOrStore(
id,
func() (*baseComponent, error) { panic("should not be called") },
)
require.NoError(t, err)
assert.Same(t, got, gotSecond)
// Shutdown nop will remove
require.NoError(t, got.Shutdown(context.Background()))
assert.Empty(t, comps.components)
gotThird, err := comps.LoadOrStore(
id,
func() (*baseComponent, error) { return nop, nil },
)
require.NoError(t, err)
assert.NotSame(t, got, gotThird)
}
func TestSharedComponent(t *testing.T) {
wantErr := errors.New("my error")
calledStart := 0
calledStop := 0
comp := &baseComponent{
StartFunc: func(context.Context, component.Host) error {
calledStart++
return wantErr
},
ShutdownFunc: func(context.Context) error {
calledStop++
return wantErr
},
}
comps := NewMap[component.ID, *baseComponent]()
got, err := comps.LoadOrStore(
id,
func() (*baseComponent, error) { return comp, nil },
)
require.NoError(t, err)
assert.Equal(t, wantErr, got.Start(context.Background(), componenttest.NewNopHost()))
assert.Equal(t, 1, calledStart)
// Second time is not called anymore.
require.NoError(t, got.Start(context.Background(), componenttest.NewNopHost()))
assert.Equal(t, 1, calledStart)
// first time, shutdown is called.
assert.Equal(t, wantErr, got.Shutdown(context.Background()))
assert.Equal(t, 1, calledStop)
// Second time is not called anymore.
require.NoError(t, got.Shutdown(context.Background()))
assert.Equal(t, 1, calledStop)
}
func TestReportStatusOnStartShutdown(t *testing.T) {
for _, tc := range []struct {
name string
startErr error
shutdownErr error
expectedStatuses []componentstatus.Status
expectedNumReporterInstances int
}{
{
name: "successful start/stop",
startErr: nil,
shutdownErr: nil,
expectedStatuses: []componentstatus.Status{
componentstatus.StatusStarting,
componentstatus.StatusOK,
componentstatus.StatusStopping,
componentstatus.StatusStopped,
},
expectedNumReporterInstances: 3,
},
{
name: "start error",
startErr: assert.AnError,
shutdownErr: nil,
expectedStatuses: []componentstatus.Status{
componentstatus.StatusStarting,
componentstatus.StatusPermanentError,
},
expectedNumReporterInstances: 1,
},
{
name: "shutdown error",
shutdownErr: assert.AnError,
expectedStatuses: []componentstatus.Status{
componentstatus.StatusStarting,
componentstatus.StatusOK,
componentstatus.StatusStopping,
componentstatus.StatusPermanentError,
},
expectedNumReporterInstances: 3,
},
} {
t.Run(tc.name, func(t *testing.T) {
reportedStatuses := make(map[*componentstatus.InstanceID][]componentstatus.Status)
newStatusFunc := func(id *componentstatus.InstanceID, ev *componentstatus.Event) {
reportedStatuses[id] = append(reportedStatuses[id], ev.Status())
}
base := &baseComponent{}
if tc.startErr != nil {
base.StartFunc = func(context.Context, component.Host) error {
return tc.startErr
}
}
if tc.shutdownErr != nil {
base.ShutdownFunc = func(context.Context) error {
return tc.shutdownErr
}
}
comps := NewMap[component.ID, *baseComponent]()
var comp *Component[*baseComponent]
var err error
for range 3 {
comp, err = comps.LoadOrStore(
id,
func() (*baseComponent, error) { return base, nil },
)
require.NoError(t, err)
}
baseHost := componenttest.NewNopHost()
for range 3 {
err = comp.Start(context.Background(), &testHost{Host: baseHost, InstanceID: &componentstatus.InstanceID{}, newStatusFunc: newStatusFunc})
if err != nil {
break
}
}
require.Equal(t, tc.startErr, err)
if tc.startErr == nil {
comp.hostWrapper.Report(componentstatus.NewEvent(componentstatus.StatusOK))
err = comp.Shutdown(context.Background())
require.Equal(t, tc.shutdownErr, err)
}
require.Len(t, reportedStatuses, tc.expectedNumReporterInstances)
for _, actualStatuses := range reportedStatuses {
require.Equal(t, tc.expectedStatuses, actualStatuses)
}
})
}
}
var (
_ component.Host = (*testHost)(nil)
_ componentstatus.Reporter = (*testHost)(nil)
)
type testHost struct {
component.Host
*componentstatus.InstanceID
newStatusFunc func(id *componentstatus.InstanceID, ev *componentstatus.Event)
}
func (h *testHost) Report(e *componentstatus.Event) {
h.newStatusFunc(h.InstanceID, e)
}
================================================
FILE: internal/statusutil/helper.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package statusutil // import "go.opentelemetry.io/collector/internal/statusutil"
import (
"net/http"
"google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// NewStatusFromMsgAndHTTPCode returns a gRPC status based on an error message string and a http status code.
// This function is shared between the http receiver and http exporter for error propagation.
func NewStatusFromMsgAndHTTPCode(errMsg string, statusCode int) *status.Status {
var c codes.Code
// Mapping based on https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md
// 429 mapping to ResourceExhausted and 400 mapping to StatusBadRequest are exceptions.
switch statusCode {
case http.StatusBadRequest:
c = codes.InvalidArgument
case http.StatusUnauthorized:
c = codes.Unauthenticated
case http.StatusForbidden:
c = codes.PermissionDenied
case http.StatusNotFound:
c = codes.Unimplemented
case http.StatusTooManyRequests:
c = codes.ResourceExhausted
case http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout:
c = codes.Unavailable
default:
c = codes.Unknown
}
return status.New(c, errMsg)
}
func GetRetryInfo(status *status.Status) *errdetails.RetryInfo {
for _, detail := range status.Details() {
if t, ok := detail.(*errdetails.RetryInfo); ok {
return t
}
}
return nil
}
================================================
FILE: internal/statusutil/helper_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package statusutil
import (
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/durationpb"
)
func Test_ErrorMsgAndHTTPCodeToStatus(t *testing.T) {
tests := []struct {
name string
errMsg string
statusCode int
expected *status.Status
}{
{
name: "Bad Request",
errMsg: "test",
statusCode: http.StatusBadRequest,
expected: status.New(codes.InvalidArgument, "test"),
},
{
name: "Unauthorized",
errMsg: "test",
statusCode: http.StatusUnauthorized,
expected: status.New(codes.Unauthenticated, "test"),
},
{
name: "Forbidden",
errMsg: "test",
statusCode: http.StatusForbidden,
expected: status.New(codes.PermissionDenied, "test"),
},
{
name: "Not Found",
errMsg: "test",
statusCode: http.StatusNotFound,
expected: status.New(codes.Unimplemented, "test"),
},
{
name: "Too Many Requests",
errMsg: "test",
statusCode: http.StatusTooManyRequests,
expected: status.New(codes.ResourceExhausted, "test"),
},
{
name: "Bad Gateway",
errMsg: "test",
statusCode: http.StatusBadGateway,
expected: status.New(codes.Unavailable, "test"),
},
{
name: "Service Unavailable",
errMsg: "test",
statusCode: http.StatusServiceUnavailable,
expected: status.New(codes.Unavailable, "test"),
},
{
name: "Gateway Timeout",
errMsg: "test",
statusCode: http.StatusGatewayTimeout,
expected: status.New(codes.Unavailable, "test"),
},
{
name: "Unsupported Media Type",
errMsg: "test",
statusCode: http.StatusUnsupportedMediaType,
expected: status.New(codes.Unknown, "test"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := NewStatusFromMsgAndHTTPCode(tt.errMsg, tt.statusCode)
assert.Equal(t, tt.expected, result)
})
}
}
func TestGetRetryInfo(t *testing.T) {
tests := []struct {
name string
input *status.Status
expected *errdetails.RetryInfo
}{
{
name: "NoDetails",
input: status.New(codes.InvalidArgument, "test"),
expected: nil,
},
{
name: "WithRetryInfoDetails",
input: func() *status.Status {
st := status.New(codes.ResourceExhausted, "test")
dt, err := st.WithDetails(&errdetails.RetryInfo{RetryDelay: durationpb.New(1 * time.Second)})
require.NoError(t, err)
return dt
}(),
expected: &errdetails.RetryInfo{RetryDelay: durationpb.New(1 * time.Second)},
},
{
name: "WithOtherDetails",
input: func() *status.Status {
st := status.New(codes.ResourceExhausted, "test")
dt, err := st.WithDetails(&errdetails.ErrorInfo{Reason: "my reason"})
require.NoError(t, err)
return dt
}(),
expected: nil,
},
{
name: "WithMultipleDetails",
input: func() *status.Status {
st := status.New(codes.ResourceExhausted, "test")
dt, err := st.WithDetails(
&errdetails.ErrorInfo{Reason: "my reason"},
&errdetails.RetryInfo{RetryDelay: durationpb.New(1 * time.Second)})
require.NoError(t, err)
return dt
}(),
expected: &errdetails.RetryInfo{RetryDelay: durationpb.New(1 * time.Second)},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := GetRetryInfo(tt.input)
assert.True(t, proto.Equal(tt.expected, result))
})
}
}
================================================
FILE: internal/telemetry/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: internal/telemetry/attribute.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package telemetry // import "go.opentelemetry.io/collector/internal/telemetry"
import (
"go.opentelemetry.io/otel/attribute"
"go.uber.org/zap"
)
const (
ComponentKindKey = "otelcol.component.kind"
ComponentIDKey = "otelcol.component.id"
PipelineIDKey = "otelcol.pipeline.id"
SignalKey = "otelcol.signal"
SignalOutputKey = "otelcol.signal.output"
)
// ToZapFields converts an OTel Go attribute set to a slice of zap fields.
func ToZapFields(attrs []attribute.KeyValue) []zap.Field {
zapFields := make([]zap.Field, 0, len(attrs))
for _, attr := range attrs {
var zapField zap.Field
key := string(attr.Key)
switch attr.Value.Type() {
case attribute.BOOL:
zapField = zap.Bool(key, attr.Value.AsBool())
case attribute.INT64:
zapField = zap.Int64(key, attr.Value.AsInt64())
case attribute.FLOAT64:
zapField = zap.Float64(key, attr.Value.AsFloat64())
case attribute.STRING:
zapField = zap.String(key, attr.Value.AsString())
case attribute.BOOLSLICE:
zapField = zap.Bools(key, attr.Value.AsBoolSlice())
case attribute.INT64SLICE:
zapField = zap.Int64s(key, attr.Value.AsInt64Slice())
case attribute.FLOAT64SLICE:
zapField = zap.Float64s(key, attr.Value.AsFloat64Slice())
case attribute.STRINGSLICE:
zapField = zap.Strings(key, attr.Value.AsStringSlice())
default:
zapField = zap.Any(key, attr.Value.AsInterface())
}
zapFields = append(zapFields, zapField)
}
return zapFields
}
================================================
FILE: internal/telemetry/attribute_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package telemetry // import "go.opentelemetry.io/collector/internal/telemetry"
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
"go.uber.org/zap"
)
func TestToZapFields(t *testing.T) {
tests := []struct {
attrs attribute.Set
expected []zap.Field
}{
{
attrs: attribute.NewSet(
attribute.String("string_key", "string_value"),
),
expected: []zap.Field{
zap.String("string_key", "string_value"),
},
},
{
attrs: attribute.NewSet(
attribute.Bool("bool_key", true),
),
expected: []zap.Field{
zap.Bool("bool_key", true),
},
},
{
attrs: attribute.NewSet(
attribute.Int64("int64_key", 42),
),
expected: []zap.Field{
zap.Int64("int64_key", 42),
},
},
{
attrs: attribute.NewSet(
attribute.Float64("float64_key", 3.14),
),
expected: []zap.Field{
zap.Float64("float64_key", 3.14),
},
},
{
attrs: attribute.NewSet(
attribute.BoolSlice("bool_slice_key", []bool{true, false, true}),
),
expected: []zap.Field{
zap.Bools("bool_slice_key", []bool{true, false, true}),
},
},
{
attrs: attribute.NewSet(
attribute.Int64Slice("int64_slice_key", []int64{1, 2, 3}),
),
expected: []zap.Field{
zap.Int64s("int64_slice_key", []int64{1, 2, 3}),
},
},
{
attrs: attribute.NewSet(
attribute.Float64Slice("float64_slice_key", []float64{1.1, 2.2, 3.3}),
),
expected: []zap.Field{
zap.Float64s("float64_slice_key", []float64{1.1, 2.2, 3.3}),
},
},
{
attrs: attribute.NewSet(
attribute.StringSlice("string_slice_key", []string{"a", "b", "c"}),
),
expected: []zap.Field{
zap.Strings("string_slice_key", []string{"a", "b", "c"}),
},
},
}
for _, tt := range tests {
name := ""
if tt.attrs.Len() > 0 {
attr, ok := tt.attrs.Get(0)
if ok {
name = string(attr.Key)
}
}
t.Run(name, func(t *testing.T) {
result := ToZapFields(tt.attrs.ToSlice())
require.Equal(t, tt.expected, result)
})
}
}
================================================
FILE: internal/telemetry/go.mod
================================================
module go.opentelemetry.io/collector/internal/telemetry
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/otel v1.42.0
go.opentelemetry.io/otel/metric v1.42.0
go.opentelemetry.io/otel/trace v1.42.0
go.uber.org/zap v1.27.1
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/pdata v1.54.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/internal/testutil => ../testutil
================================================
FILE: internal/telemetry/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: internal/telemetry/telemetry.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package telemetry // import "go.opentelemetry.io/collector/internal/telemetry"
import (
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"go.opentelemetry.io/collector/component"
)
type injectorCore interface {
DropInjectedAttributes(droppedAttrs ...string) zapcore.Core
}
type injectorTracerProvider interface {
DropInjectedAttributes(droppedAttrs ...string) trace.TracerProvider
}
type injectorMeterProvider interface {
DropInjectedAttributes(droppedAttrs ...string) metric.MeterProvider
}
func DropInjectedAttributes(ts component.TelemetrySettings, attrs ...string) component.TelemetrySettings {
ts.Logger = ts.Logger.WithOptions(zap.WrapCore(func(c zapcore.Core) zapcore.Core {
if ic, ok := c.(injectorCore); ok {
return ic.DropInjectedAttributes(attrs...)
}
return c
}))
if itp, ok := ts.TracerProvider.(injectorTracerProvider); ok {
ts.TracerProvider = itp.DropInjectedAttributes(attrs...)
}
if imp, ok := ts.MeterProvider.(injectorMeterProvider); ok {
ts.MeterProvider = imp.DropInjectedAttributes(attrs...)
}
return ts
}
================================================
FILE: internal/telemetry/telemetrytest/mock.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package telemetrytest // import "go.opentelemetry.io/collector/internal/telemetry/telemetrytest"
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
type mockInjectorCore struct {
zapcore.Core
dropped *[]string
}
func (mic mockInjectorCore) DropInjectedAttributes(droppedAttrs ...string) zapcore.Core {
*mic.dropped = append(*mic.dropped, droppedAttrs...)
return mic
}
func MockInjectorLogger(logger *zap.Logger, dropped *[]string) *zap.Logger {
return logger.WithOptions(zap.WrapCore(func(c zapcore.Core) zapcore.Core {
return mockInjectorCore{
Core: c,
dropped: dropped,
}
}))
}
================================================
FILE: internal/testutil/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: internal/testutil/README.md
================================================
## Test Utilities
The `go.opentelemetry.io/collector/internal/testutil` module provides utility functions, etc. for use by tests in other
OpenTelemetry Collector modules.
================================================
FILE: internal/testutil/benchmarks.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package testutil // import "go.opentelemetry.io/collector/internal/testutil"
import (
"os"
"testing"
)
// SkipMemoryBench will skip memory benchmarks on CI, as we currently only
// monitor duration.
func SkipMemoryBench(b *testing.B) {
if os.Getenv("MEMBENCH") == "" {
b.Skip("Skipping since the 'MEMBENCH' environment variable was not set")
}
}
// SkipGCHeavyBench will skip GC-heavy benchmarks on CI.
// These benchmarks tend to be flaky with the current settings since garbage
// collection pauses can take ~50ms which is significant with the current benchmark times.
func SkipGCHeavyBench(b *testing.B) {
if os.Getenv("GCHEAVYBENCH") == "" {
b.Skip("Skipping since the 'GCHEAVYBENCH' environment variable was not set. See https://github.com/open-telemetry/opentelemetry-collector/issues/14257.")
}
}
================================================
FILE: internal/testutil/fips.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package testutil // import "go.opentelemetry.io/collector/internal/testutil"
import (
"os"
"strings"
"testing"
)
// SkipIfFIPSOnly will mark the passed test as skipped if GODEBUG=fips140=only is detected.
// If GODEBUG=fips140=on, go may call non-compliant algorithms and the test does not need to be skipped.
func SkipIfFIPSOnly(t *testing.T, msg string) {
// NOTE: This only checks env var; at the time of writing fips140 can only be set via env
// other GODEBUG settings can be set via embedded comments or in go.mod, we may need to account for this in the future.
s := os.Getenv("GODEBUG")
if strings.Contains(s, "fips140=only") {
t.Skip("GODEBUG=fips140=only detected, skipping test:", msg)
}
}
================================================
FILE: internal/testutil/go.mod
================================================
module go.opentelemetry.io/collector/internal/testutil
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.uber.org/goleak v1.3.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
================================================
FILE: internal/testutil/go.sum
================================================
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: internal/testutil/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package testutil
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: internal/testutil/testutil.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package testutil // import "go.opentelemetry.io/collector/internal/testutil"
import (
"net"
"os/exec"
"runtime"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type portpair struct {
first string
last string
}
// 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(tb testing.TB) string {
return findAvailable(tb, "tcp4")
}
// GetAvailableLocalIPv6Address is IPv6 version of GetAvailableLocalAddress.
func GetAvailableLocalIPv6Address(tb testing.TB) string {
return findAvailable(tb, "tcp6")
}
func findAvailable(tb testing.TB, network string) string {
// 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
if runtime.GOOS == "windows" {
exclusions = getExclusionsList(tb, network)
}
var endpoint string
for !portFound {
endpoint = findAvailableAddress(tb, network)
_, port, err := net.SplitHostPort(endpoint)
require.NoError(tb, err)
portFound = true
if runtime.GOOS == "windows" {
for _, pair := range exclusions {
if port >= pair.first && port <= pair.last {
portFound = false
break
}
}
}
}
return endpoint
}
func findAvailableAddress(tb testing.TB, network string) string {
var host string
switch network {
case "tcp", "tcp4":
host = "localhost"
case "tcp6":
host = "[::1]"
}
require.NotEmpty(tb, host, "network must be either of tcp, tcp4 or tcp6")
ln, err := net.Listen("tcp", host+":0")
require.NoError(tb, 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 func() {
assert.NoError(tb, ln.Close())
}()
return ln.Addr().String()
}
// Get excluded ports on Windows from the command: netsh interface ipv4 show excludedportrange protocol=tcp
func getExclusionsList(tb testing.TB, network string) []portpair {
var cmdTCP *exec.Cmd
switch network {
case "tcp", "tcp4":
cmdTCP = exec.Command("netsh", "interface", "ipv4", "show", "excludedportrange", "protocol=tcp")
case "tcp6":
cmdTCP = exec.Command("netsh", "interface", "ipv6", "show", "excludedportrange", "protocol=tcp")
}
require.NotZero(tb, cmdTCP, "network must be either of tcp, tcp4 or tcp6")
outputTCP, errTCP := cmdTCP.CombinedOutput()
require.NoError(tb, errTCP)
exclusions := createExclusionsList(tb, string(outputTCP))
cmdUDP := exec.Command("netsh", "interface", "ipv4", "show", "excludedportrange", "protocol=udp")
outputUDP, errUDP := cmdUDP.CombinedOutput()
require.NoError(tb, errUDP)
exclusions = append(exclusions, createExclusionsList(tb, string(outputUDP))...)
return exclusions
}
func createExclusionsList(tb testing.TB, exclusionsText string) []portpair {
var exclusions []portpair
parts := strings.Split(exclusionsText, "--------")
require.Len(tb, parts, 3)
portsText := strings.Split(parts[2], "*")
require.Greater(tb, len(portsText), 1) // original text may have a suffix like " - Administered port exclusions."
lines := strings.SplitSeq(portsText[0], "\n")
for line := range lines {
if strings.TrimSpace(line) != "" {
entries := strings.Fields(strings.TrimSpace(line))
require.Len(tb, entries, 2)
pair := portpair{entries[0], entries[1]}
exclusions = append(exclusions, pair)
}
}
return exclusions
}
================================================
FILE: internal/testutil/testutil_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package testutil
import (
"net"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGetAvailableLocalAddress(t *testing.T) {
endpoint := GetAvailableLocalAddress(t)
// Endpoint should be free.
ln0, err := net.Listen("tcp", endpoint)
require.NoError(t, err)
require.NotNil(t, ln0)
t.Cleanup(func() {
assert.NoError(t, 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 TestGetAvailableLocalIpv6Address(t *testing.T) {
endpoint := GetAvailableLocalIPv6Address(t)
// Endpoint should be free.
ln0, err := net.Listen("tcp", endpoint)
require.NoError(t, err)
require.NotNil(t, ln0)
t.Cleanup(func() {
assert.NoError(t, 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(t, exclusionsText)
require.Len(t, exclusions, 2)
emptyExclusions := createExclusionsList(t, emptyExclusionsText)
require.Empty(t, emptyExclusions)
}
================================================
FILE: internal/tools/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: internal/tools/go.mod
================================================
module go.opentelemetry.io/collector/internal/tools
go 1.25.0
tool (
github.com/a8m/envsubst/cmd/envsubst
github.com/client9/misspell/cmd/misspell
github.com/dkorunic/betteralign/cmd/betteralign
github.com/golangci/golangci-lint/v2/cmd/golangci-lint
github.com/google/addlicense
github.com/jcchavezs/porto/cmd/porto
github.com/pavius/impi/cmd/impi
github.com/rhysd/actionlint/cmd/actionlint
go.opentelemetry.io/build-tools/checkapi
go.opentelemetry.io/build-tools/checkfile
go.opentelemetry.io/build-tools/chloggen
go.opentelemetry.io/build-tools/crosslink
go.opentelemetry.io/build-tools/githubgen
go.opentelemetry.io/build-tools/multimod
golang.org/x/exp/cmd/apidiff
golang.org/x/tools/cmd/goimports
golang.org/x/tools/go/analysis/passes/modernize/cmd/modernize
golang.org/x/vuln/cmd/govulncheck
gotest.tools/gotestsum
mvdan.cc/gofumpt
)
require (
4d63.com/gocheckcompilerdirectives v1.3.0 // indirect
4d63.com/gochecknoglobals v0.2.2 // indirect
codeberg.org/chavacava/garif v0.2.0 // indirect
dario.cat/mergo v1.0.2 // indirect
dev.gaijin.team/go/exhaustruct/v4 v4.0.0 // indirect
dev.gaijin.team/go/golib v0.6.0 // indirect
github.com/4meepo/tagalign v1.4.3 // indirect
github.com/Abirdcfly/dupword v0.1.7 // indirect
github.com/AdminBenni/iota-mixing v1.0.0 // indirect
github.com/AlwxSin/noinlineerr v1.0.5 // indirect
github.com/Antonboom/errname v1.1.1 // indirect
github.com/Antonboom/nilnil v1.1.1 // indirect
github.com/Antonboom/testifylint v1.6.4 // indirect
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/Djarvur/go-err113 v0.1.1 // indirect
github.com/KimMachineGun/automemlimit v0.7.5 // indirect
github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/MirrexOne/unqueryvet v1.2.1 // indirect
github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect
github.com/ProtonMail/go-crypto v1.3.0 // indirect
github.com/a8m/envsubst v1.4.3 // indirect
github.com/alecthomas/chroma/v2 v2.20.0 // indirect
github.com/alecthomas/go-check-sumtype v0.3.1 // indirect
github.com/alexkohler/nakedret/v2 v2.0.6 // indirect
github.com/alexkohler/prealloc v1.0.0 // indirect
github.com/alfatraining/structtag v1.0.0 // indirect
github.com/alingse/asasalint v0.0.11 // indirect
github.com/alingse/nilnesserr v0.2.0 // indirect
github.com/ashanbrown/forbidigo/v2 v2.3.0 // indirect
github.com/ashanbrown/makezero/v2 v2.1.0 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bitfield/gotestdox v0.2.2 // indirect
github.com/bkielbasa/cyclop v1.2.3 // indirect
github.com/blizzy78/varnamelen v0.8.0 // indirect
github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect
github.com/bombsimon/wsl/v4 v4.7.0 // indirect
github.com/bombsimon/wsl/v5 v5.3.0 // indirect
github.com/breml/bidichk v0.3.3 // indirect
github.com/breml/errchkjson v0.4.1 // indirect
github.com/butuzov/ireturn v0.4.0 // indirect
github.com/butuzov/mirror v1.3.0 // indirect
github.com/catenacyber/perfsprint v0.10.1 // indirect
github.com/ccojocar/zxcvbn-go v1.0.4 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charithe/durationcheck v0.0.11 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/lipgloss v1.1.0 // indirect
github.com/charmbracelet/x/ansi v0.8.0 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect
github.com/ckaznocha/intrange v0.3.1 // indirect
github.com/client9/misspell v0.3.4 // indirect
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
github.com/cloudflare/circl v1.6.3 // indirect
github.com/curioswitch/go-reassign v0.3.0 // indirect
github.com/cyphar/filepath-securejoin v0.5.0 // indirect
github.com/daixiang0/gci v0.13.7 // indirect
github.com/dave/dst v0.27.3 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/denis-tingaikin/go-header v0.5.0 // indirect
github.com/dkorunic/betteralign v0.8.2 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/dnephin/pflag v1.0.7 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/ettle/strcase v0.2.0 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/fatih/structtag v1.2.0 // indirect
github.com/firefart/nonamedreturns v1.0.6 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fzipp/gocyclo v0.6.0 // indirect
github.com/ghostiam/protogetter v0.3.18 // indirect
github.com/go-critic/go-critic v0.14.2 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.6.2 // indirect
github.com/go-git/go-git/v5 v5.16.5 // indirect
github.com/go-json-experiment/json v0.0.0-20250813233538-9b1f9ea2e11b // indirect
github.com/go-toolsmith/astcast v1.1.0 // indirect
github.com/go-toolsmith/astcopy v1.1.0 // indirect
github.com/go-toolsmith/astequal v1.2.0 // indirect
github.com/go-toolsmith/astfmt v1.1.0 // indirect
github.com/go-toolsmith/astp v1.1.0 // indirect
github.com/go-toolsmith/strparse v1.1.0 // indirect
github.com/go-toolsmith/typep v1.1.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.18.0 // indirect
github.com/godoc-lint/godoc-lint v0.10.1 // indirect
github.com/gofrs/flock v0.13.0 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golangci/asciicheck v0.5.0 // indirect
github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect
github.com/golangci/go-printf-func-name v0.1.1 // indirect
github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect
github.com/golangci/golangci-lint/v2 v2.6.2 // indirect
github.com/golangci/golines v0.0.0-20250217134842-442fd0091d95 // indirect
github.com/golangci/misspell v0.7.0 // indirect
github.com/golangci/plugin-module-register v0.1.2 // indirect
github.com/golangci/revgrep v0.8.0 // indirect
github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e // indirect
github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e // indirect
github.com/google/addlicense v1.2.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/go-github/v76 v76.0.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/renameio/v2 v2.0.1 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/gordonklaus/ineffassign v0.2.0 // indirect
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
github.com/gostaticanalysis/comment v1.5.0 // indirect
github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect
github.com/gostaticanalysis/nilerr v0.1.2 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jcchavezs/porto v0.7.0 // indirect
github.com/jgautheron/goconst v1.8.2 // indirect
github.com/jingyugao/rowserrcheck v1.1.1 // indirect
github.com/jjti/go-spancheck v0.6.5 // indirect
github.com/julz/importas v0.2.0 // indirect
github.com/kaptinlin/go-i18n v0.1.6 // indirect
github.com/kaptinlin/jsonschema v0.4.12 // indirect
github.com/kaptinlin/messageformat-go v0.4.0 // indirect
github.com/karamaru-alpha/copyloopvar v1.2.2 // indirect
github.com/kevinburke/ssh_config v1.4.0 // indirect
github.com/kisielk/errcheck v1.9.0 // indirect
github.com/kisielk/gotool v1.0.0 // indirect
github.com/kkHAIKE/contextcheck v1.1.6 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/kulti/thelper v0.7.1 // indirect
github.com/kunwardeep/paralleltest v1.0.15 // indirect
github.com/lasiar/canonicalheader v1.1.2 // indirect
github.com/ldez/exptostd v0.4.5 // indirect
github.com/ldez/gomoddirectives v0.7.1 // indirect
github.com/ldez/grignotin v0.10.1 // indirect
github.com/ldez/tagliatelle v0.7.2 // indirect
github.com/ldez/usetesting v0.5.0 // indirect
github.com/leonklingele/grouper v1.1.2 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/macabu/inamedparam v0.2.0 // indirect
github.com/manuelarte/embeddedstructfieldcheck v0.4.0 // indirect
github.com/manuelarte/funcorder v0.5.0 // indirect
github.com/maratori/testableexamples v1.0.1 // indirect
github.com/maratori/testpackage v1.1.2 // indirect
github.com/matoous/godox v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/mattn/go-shellwords v1.0.12 // indirect
github.com/mgechev/revive v1.12.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/moricho/tparallel v0.3.2 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nakabonne/nestif v0.3.1 // indirect
github.com/nishanths/exhaustive v0.12.0 // indirect
github.com/nishanths/predeclared v0.2.2 // indirect
github.com/nunnatsa/ginkgolinter v0.21.2 // indirect
github.com/pavius/impi v0.0.3 // indirect
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pjbgf/sha1cd v0.5.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/polyfloyd/go-errorlint v1.8.0 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/quasilyte/go-ruleguard v0.4.5 // indirect
github.com/quasilyte/go-ruleguard/dsl v0.3.23 // indirect
github.com/quasilyte/gogrep v0.5.0 // indirect
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect
github.com/raeperd/recvcheck v0.2.0 // indirect
github.com/rhysd/actionlint v1.7.11 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/ryancurrah/gomodguard v1.4.1 // indirect
github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect
github.com/sagikazarmark/locafero v0.12.0 // indirect
github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
github.com/sashamelentyev/interfacebloat v1.1.0 // indirect
github.com/sashamelentyev/usestdlibvars v1.29.0 // indirect
github.com/securego/gosec/v2 v2.22.10 // indirect
github.com/sergi/go-diff v1.4.0 // indirect
github.com/sirkon/dst v0.26.4 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/sivchari/containedctx v1.0.3 // indirect
github.com/skeema/knownhosts v1.3.2 // indirect
github.com/sonatard/noctx v0.4.0 // indirect
github.com/sourcegraph/go-diff v0.7.0 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/spf13/cobra v1.10.2 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/spf13/viper v1.21.0 // indirect
github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect
github.com/stbenjam/no-sprintf-host-port v0.2.0 // indirect
github.com/stretchr/objx v0.5.3 // indirect
github.com/stretchr/testify v1.11.1 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/tetafro/godot v1.5.4 // indirect
github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 // indirect
github.com/timonwong/loggercheck v0.11.0 // indirect
github.com/tomarrell/wrapcheck/v2 v2.11.0 // indirect
github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect
github.com/ultraware/funlen v0.2.0 // indirect
github.com/ultraware/whitespace v0.2.0 // indirect
github.com/uudashr/gocognit v1.2.0 // indirect
github.com/uudashr/iface v1.4.1 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xen0n/gosmopolitan v1.3.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yagipy/maintidx v1.0.0 // indirect
github.com/yeya24/promlinter v0.3.0 // indirect
github.com/ykadowak/zerologlint v0.1.5 // indirect
gitlab.com/bosi/decorder v0.4.2 // indirect
go-simpler.org/musttag v0.14.0 // indirect
go-simpler.org/sloglint v0.11.1 // indirect
go.augendre.info/arangolint v0.3.1 // indirect
go.augendre.info/fatcontext v0.9.0 // indirect
go.opentelemetry.io/build-tools v0.29.0 // indirect
go.opentelemetry.io/build-tools/checkapi v0.29.0 // indirect
go.opentelemetry.io/build-tools/checkfile v0.29.0 // indirect
go.opentelemetry.io/build-tools/chloggen v0.29.0 // indirect
go.opentelemetry.io/build-tools/crosslink v0.29.0 // indirect
go.opentelemetry.io/build-tools/githubgen v0.29.0 // indirect
go.opentelemetry.io/build-tools/multimod v0.29.0 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
go.yaml.in/yaml/v4 v4.0.0-rc.3 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
golang.org/x/exp/typeparams v0.0.0-20251023183803-a4bb9ffd2546 // indirect
golang.org/x/mod v0.33.0 // indirect
golang.org/x/net v0.50.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4 // indirect
golang.org/x/term v0.40.0 // indirect
golang.org/x/text v0.34.0 // indirect
golang.org/x/tools v0.42.0 // indirect
golang.org/x/vuln v1.1.4 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gotest.tools/gotestsum v1.13.0 // indirect
honnef.co/go/tools v0.7.0-0.dev.0.20250523013057-bbc2f4dd71ea // indirect
mvdan.cc/gofumpt v0.9.2 // indirect
mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 // indirect
)
retract (
v0.57.1 // Release failed, use v0.57.2
v0.57.0 // Release failed, use v0.57.2
)
================================================
FILE: internal/tools/go.sum
================================================
4d63.com/gocheckcompilerdirectives v1.3.0 h1:Ew5y5CtcAAQeTVKUVFrE7EwHMrTO6BggtEj8BZSjZ3A=
4d63.com/gocheckcompilerdirectives v1.3.0/go.mod h1:ofsJ4zx2QAuIP/NO/NAh1ig6R1Fb18/GI7RVMwz7kAY=
4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU=
4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0=
codeberg.org/chavacava/garif v0.2.0 h1:F0tVjhYbuOCnvNcU3YSpO6b3Waw6Bimy4K0mM8y6MfY=
codeberg.org/chavacava/garif v0.2.0/go.mod h1:P2BPbVbT4QcvLZrORc2T29szK3xEOlnl0GiPTJmEqBQ=
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
dev.gaijin.team/go/exhaustruct/v4 v4.0.0 h1:873r7aNneqoBB3IaFIzhvt2RFYTuHgmMjoKfwODoI1Y=
dev.gaijin.team/go/exhaustruct/v4 v4.0.0/go.mod h1:aZ/k2o4Y05aMJtiux15x8iXaumE88YdiB0Ai4fXOzPI=
dev.gaijin.team/go/golib v0.6.0 h1:v6nnznFTs4bppib/NyU1PQxobwDHwCXXl15P7DV5Zgo=
dev.gaijin.team/go/golib v0.6.0/go.mod h1:uY1mShx8Z/aNHWDyAkZTkX+uCi5PdX7KsG1eDQa2AVE=
github.com/4meepo/tagalign v1.4.3 h1:Bnu7jGWwbfpAie2vyl63Zup5KuRv21olsPIha53BJr8=
github.com/4meepo/tagalign v1.4.3/go.mod h1:00WwRjiuSbrRJnSVeGWPLp2epS5Q/l4UEy0apLLS37c=
github.com/Abirdcfly/dupword v0.1.7 h1:2j8sInznrje4I0CMisSL6ipEBkeJUJAmK1/lfoNGWrQ=
github.com/Abirdcfly/dupword v0.1.7/go.mod h1:K0DkBeOebJ4VyOICFdppB23Q0YMOgVafM0zYW0n9lF4=
github.com/AdminBenni/iota-mixing v1.0.0 h1:Os6lpjG2dp/AE5fYBPAA1zfa2qMdCAWwPMCgpwKq7wo=
github.com/AdminBenni/iota-mixing v1.0.0/go.mod h1:i4+tpAaB+qMVIV9OK3m4/DAynOd5bQFaOu+2AhtBCNY=
github.com/AlwxSin/noinlineerr v1.0.5 h1:RUjt63wk1AYWTXtVXbSqemlbVTb23JOSRiNsshj7TbY=
github.com/AlwxSin/noinlineerr v1.0.5/go.mod h1:+QgkkoYrMH7RHvcdxdlI7vYYEdgeoFOVjU9sUhw/rQc=
github.com/Antonboom/errname v1.1.1 h1:bllB7mlIbTVzO9jmSWVWLjxTEbGBVQ1Ff/ClQgtPw9Q=
github.com/Antonboom/errname v1.1.1/go.mod h1:gjhe24xoxXp0ScLtHzjiXp0Exi1RFLKJb0bVBtWKCWQ=
github.com/Antonboom/nilnil v1.1.1 h1:9Mdr6BYd8WHCDngQnNVV0b554xyisFioEKi30sksufQ=
github.com/Antonboom/nilnil v1.1.1/go.mod h1:yCyAmSw3doopbOWhJlVci+HuyNRuHJKIv6V2oYQa8II=
github.com/Antonboom/testifylint v1.6.4 h1:gs9fUEy+egzxkEbq9P4cpcMB6/G0DYdMeiFS87UiqmQ=
github.com/Antonboom/testifylint v1.6.4/go.mod h1:YO33FROXX2OoUfwjz8g+gUxQXio5i9qpVy7nXGbxDD4=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/Djarvur/go-err113 v0.1.1 h1:eHfopDqXRwAi+YmCUas75ZE0+hoBHJ2GQNLYRSxao4g=
github.com/Djarvur/go-err113 v0.1.1/go.mod h1:IaWJdYFLg76t2ihfflPZnM1LIQszWOsFDh2hhhAVF6k=
github.com/KimMachineGun/automemlimit v0.7.5 h1:RkbaC0MwhjL1ZuBKunGDjE/ggwAX43DwZrJqVwyveTk=
github.com/KimMachineGun/automemlimit v0.7.5/go.mod h1:QZxpHaGOQoYvFhv/r4u3U0JTC2ZcOwbSr11UZF46UBM=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/MirrexOne/unqueryvet v1.2.1 h1:M+zdXMq84g+E1YOLa7g7ExN3dWfZQrdDSTCM7gC+m/A=
github.com/MirrexOne/unqueryvet v1.2.1/go.mod h1:IWwCwMQlSWjAIteW0t+28Q5vouyktfujzYznSIWiuOg=
github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsuj3piCMx4=
github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo=
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
github.com/a8m/envsubst v1.4.3 h1:kDF7paGK8QACWYaQo6KtyYBozY2jhQrTuNNuUxQkhJY=
github.com/a8m/envsubst v1.4.3/go.mod h1:4jjHWQlZoaXPoLQUb7H2qT4iLkZDdmEQiOUogdUmqVU=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw=
github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA=
github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU=
github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E=
github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg=
github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/alexkohler/nakedret/v2 v2.0.6 h1:ME3Qef1/KIKr3kWX3nti3hhgNxw6aqN5pZmQiFSsuzQ=
github.com/alexkohler/nakedret/v2 v2.0.6/go.mod h1:l3RKju/IzOMQHmsEvXwkqMDzHHvurNQfAgE1eVmT40Q=
github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw=
github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE=
github.com/alfatraining/structtag v1.0.0 h1:2qmcUqNcCoyVJ0up879K614L9PazjBSFruTB0GOFjCc=
github.com/alfatraining/structtag v1.0.0/go.mod h1:p3Xi5SwzTi+Ryj64DqjLWz7XurHxbGsq6y3ubePJPus=
github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw=
github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I=
github.com/alingse/nilnesserr v0.2.0 h1:raLem5KG7EFVb4UIDAXgrv3N2JIaffeKNtcEXkEWd/w=
github.com/alingse/nilnesserr v0.2.0/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/ashanbrown/forbidigo/v2 v2.3.0 h1:OZZDOchCgsX5gvToVtEBoV2UWbFfI6RKQTir2UZzSxo=
github.com/ashanbrown/forbidigo/v2 v2.3.0/go.mod h1:5p6VmsG5/1xx3E785W9fouMxIOkvY2rRV9nMdWadd6c=
github.com/ashanbrown/makezero/v2 v2.1.0 h1:snuKYMbqosNokUKm+R6/+vOPs8yVAi46La7Ck6QYSaE=
github.com/ashanbrown/makezero/v2 v2.1.0/go.mod h1:aEGT/9q3S8DHeE57C88z2a6xydvgx8J5hgXIGWgo0MY=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
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/bitfield/gotestdox v0.2.2 h1:x6RcPAbBbErKLnapz1QeAlf3ospg8efBsedU93CDsnE=
github.com/bitfield/gotestdox v0.2.2/go.mod h1:D+gwtS0urjBrzguAkTM2wodsTQYFHdpx8eqRJ3N+9pY=
github.com/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w=
github.com/bkielbasa/cyclop v1.2.3/go.mod h1:kHTwA9Q0uZqOADdupvcFJQtp/ksSnytRMe8ztxG8Fuo=
github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M=
github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k=
github.com/bmatcuk/doublestar/v4 v4.0.2/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs=
github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bombsimon/wsl/v4 v4.7.0 h1:1Ilm9JBPRczjyUs6hvOPKvd7VL1Q++PL8M0SXBDf+jQ=
github.com/bombsimon/wsl/v4 v4.7.0/go.mod h1:uV/+6BkffuzSAVYD+yGyld1AChO7/EuLrCF/8xTiapg=
github.com/bombsimon/wsl/v5 v5.3.0 h1:nZWREJFL6U3vgW/B1lfDOigl+tEF6qgs6dGGbFeR0UM=
github.com/bombsimon/wsl/v5 v5.3.0/go.mod h1:Gp8lD04z27wm3FANIUPZycXp+8huVsn0oxc+n4qfV9I=
github.com/breml/bidichk v0.3.3 h1:WSM67ztRusf1sMoqH6/c4OBCUlRVTKq+CbSeo0R17sE=
github.com/breml/bidichk v0.3.3/go.mod h1:ISbsut8OnjB367j5NseXEGGgO/th206dVa427kR8YTE=
github.com/breml/errchkjson v0.4.1 h1:keFSS8D7A2T0haP9kzZTi7o26r7kE3vymjZNeNDRDwg=
github.com/breml/errchkjson v0.4.1/go.mod h1:a23OvR6Qvcl7DG/Z4o0el6BRAjKnaReoPQFciAl9U3s=
github.com/butuzov/ireturn v0.4.0 h1:+s76bF/PfeKEdbG8b54aCocxXmi0wvYdOVsWxVO7n8E=
github.com/butuzov/ireturn v0.4.0/go.mod h1:ghI0FrCmap8pDWZwfPisFD1vEc56VKH4NpQUxDHta70=
github.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc=
github.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI=
github.com/catenacyber/perfsprint v0.10.1 h1:u7Riei30bk46XsG8nknMhKLXG9BcXz3+3tl/WpKm0PQ=
github.com/catenacyber/perfsprint v0.10.1/go.mod h1:DJTGsi/Zufpuus6XPGJyKOTMELe347o6akPvWG9Zcsc=
github.com/ccojocar/zxcvbn-go v1.0.4 h1:FWnCIRMXPj43ukfX000kvBZvV6raSxakYr1nzyNrUcc=
github.com/ccojocar/zxcvbn-go v1.0.4/go.mod h1:3GxGX+rHmueTUMvm5ium7irpyjmm7ikxYFOSJB21Das=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charithe/durationcheck v0.0.11 h1:g1/EX1eIiKS57NTWsYtHDZ/APfeXKhye1DidBcABctk=
github.com/charithe/durationcheck v0.0.11/go.mod h1:x5iZaixRNl8ctbM+3B2RrPG5t856TxRyVQEnbIEM2X4=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
github.com/ckaznocha/intrange v0.3.1 h1:j1onQyXvHUsPWujDH6WIjhyH26gkRt/txNlV7LspvJs=
github.com/ckaznocha/intrange v0.3.1/go.mod h1:QVepyz1AkUoFQkpEqksSYpNpUo3c5W7nWh/s6SHIJJk=
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs=
github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88=
github.com/cyphar/filepath-securejoin v0.5.0 h1:hIAhkRBMQ8nIeuVwcAoymp7MY4oherZdAxD+m0u9zaw=
github.com/cyphar/filepath-securejoin v0.5.0/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
github.com/daixiang0/gci v0.13.7 h1:+0bG5eK9vlI08J+J/NWGbWPTNiXPG4WhNLJOkSxWITQ=
github.com/daixiang0/gci v0.13.7/go.mod h1:812WVN6JLFY9S6Tv76twqmNqevN0pa3SX3nih0brVzQ=
github.com/dave/dst v0.27.3 h1:P1HPoMza3cMEquVf9kKy8yXsFirry4zEnWOdYPOoIzY=
github.com/dave/dst v0.27.3/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEyc=
github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo=
github.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8=
github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY=
github.com/dkorunic/betteralign v0.8.2 h1:f3sJ/vtuuPOFd2/U3TeGfv8p+VhEyoBLuA05q6mAosU=
github.com/dkorunic/betteralign v0.8.2/go.mod h1:TAkhmNuJ3OKPAN7YgGTauiccHIc9ETjBmtE24fpLxOk=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dnephin/pflag v1.0.7 h1:oxONGlWxhmUct0YzKTgrpQv9AUA1wtPBn7zuSjJqptk=
github.com/dnephin/pflag v1.0.7/go.mod h1:uxE91IoWURlOiTUIA8Mq5ZZkAv3dPUfZNaT80Zm7OQE=
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q=
github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
github.com/firefart/nonamedreturns v1.0.6 h1:vmiBcKV/3EqKY3ZiPxCINmpS431OcE1S47AQUwhrg8E=
github.com/firefart/nonamedreturns v1.0.6/go.mod h1:R8NisJnSIpvPWheCq0mNRXJok6D8h7fagJTF8EMEwCo=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
github.com/ghostiam/protogetter v0.3.18 h1:yEpghRGtP9PjKvVXtEzGpYfQj1Wl/ZehAfU6fr62Lfo=
github.com/ghostiam/protogetter v0.3.18/go.mod h1:FjIu5Yfs6FT391m+Fjp3fbAYJ6rkL/J6ySpZBfnODuI=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-critic/go-critic v0.14.2 h1:PMvP5f+LdR8p6B29npvChUXbD1vrNlKDf60NJtgMBOo=
github.com/go-critic/go-critic v0.14.2/go.mod h1:xwntfW6SYAd7h1OqDzmN6hBX/JxsEKl5up/Y2bsxgVQ=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.16.5 h1:mdkuqblwr57kVfXri5TTH+nMFLNUxIj9Z7F5ykFbw5s=
github.com/go-git/go-git/v5 v5.16.5/go.mod h1:QOMLpNf1qxuSY4StA/ArOdfFR2TrKEjJiye2kel2m+M=
github.com/go-json-experiment/json v0.0.0-20250813233538-9b1f9ea2e11b h1:6Q4zRHXS/YLOl9Ng1b1OOOBWMidAQZR3Gel0UKPC/KU=
github.com/go-json-experiment/json v0.0.0-20250813233538-9b1f9ea2e11b/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8=
github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU=
github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s=
github.com/go-toolsmith/astcopy v1.1.0/go.mod h1:hXM6gan18VA1T/daUEHCFcYiW8Ai1tIwIzHY6srfEAw=
github.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4=
github.com/go-toolsmith/astequal v1.1.0/go.mod h1:sedf7VIdCL22LD8qIvv7Nn9MuWJruQA/ysswh64lffQ=
github.com/go-toolsmith/astequal v1.2.0 h1:3Fs3CYZ1k9Vo4FzFhwwewC3CHISHDnVUPC4x0bI2+Cw=
github.com/go-toolsmith/astequal v1.2.0/go.mod h1:c8NZ3+kSFtFY/8lPso4v8LuJjdJiUFVnSuU3s0qrrDY=
github.com/go-toolsmith/astfmt v1.1.0 h1:iJVPDPp6/7AaeLJEruMsBUlOYCmvg0MoCfJprsOmcco=
github.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlNMV634mhwuQ4=
github.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA=
github.com/go-toolsmith/astp v1.1.0/go.mod h1:0T1xFGz9hicKs8Z5MfAqSUitoUYS30pDMsRVIDHs8CA=
github.com/go-toolsmith/pkgload v1.2.2 h1:0CtmHq/02QhxcF7E9N5LIFcYFsMR5rdovfqTtRKkgIk=
github.com/go-toolsmith/pkgload v1.2.2/go.mod h1:R2hxLNRKuAsiXCo2i5J6ZQPhnPMOVtU+f0arbFPWCus=
github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8=
github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQiyP2Bvw=
github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ=
github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus=
github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY=
github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/godoc-lint/godoc-lint v0.10.1 h1:ZPUVzlDtJfA+P688JfPJPkI/SuzcBr/753yGIk5bOPA=
github.com/godoc-lint/godoc-lint v0.10.1/go.mod h1:KleLcHu/CGSvkjUH2RvZyoK1MBC7pDQg4NxMYLcBBsw=
github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=
github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/golangci/asciicheck v0.5.0 h1:jczN/BorERZwK8oiFBOGvlGPknhvq0bjnysTj4nUfo0=
github.com/golangci/asciicheck v0.5.0/go.mod h1:5RMNAInbNFw2krqN6ibBxN/zfRFa9S6tA1nPdM0l8qQ=
github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 h1:WUvBfQL6EW/40l6OmeSBYQJNSif4O11+bmWEz+C7FYw=
github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32/go.mod h1:NUw9Zr2Sy7+HxzdjIULge71wI6yEg1lWQr7Evcu8K0E=
github.com/golangci/go-printf-func-name v0.1.1 h1:hIYTFJqAGp1iwoIfsNTpoq1xZAarogrvjO9AfiW3B4U=
github.com/golangci/go-printf-func-name v0.1.1/go.mod h1:Es64MpWEZbh0UBtTAICOZiB+miW53w/K9Or/4QogJss=
github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE=
github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY=
github.com/golangci/golangci-lint/v2 v2.6.2 h1:jkMSVv36JmyTENcEertckvimvjPcD5qxNM7W7qhECvI=
github.com/golangci/golangci-lint/v2 v2.6.2/go.mod h1:fSIMDiBt9kzdpnvvV7GO6iWzyv5uaeZ+iPor+2uRczE=
github.com/golangci/golines v0.0.0-20250217134842-442fd0091d95 h1:AkK+w9FZBXlU/xUmBtSJN1+tAI4FIvy5WtnUnY8e4p8=
github.com/golangci/golines v0.0.0-20250217134842-442fd0091d95/go.mod h1:k9mmcyWKSTMcPPvQUCfRWWQ9VHJ1U9Dc0R7kaXAgtnQ=
github.com/golangci/misspell v0.7.0 h1:4GOHr/T1lTW0hhR4tgaaV1WS/lJ+ncvYCoFKmqJsj0c=
github.com/golangci/misspell v0.7.0/go.mod h1:WZyyI2P3hxPY2UVHs3cS8YcllAeyfquQcKfdeE9AFVg=
github.com/golangci/plugin-module-register v0.1.2 h1:e5WM6PO6NIAEcij3B053CohVp3HIYbzSuP53UAYgOpg=
github.com/golangci/plugin-module-register v0.1.2/go.mod h1:1+QGTsKBvAIvPvoY/os+G5eoqxWn70HYDm2uvUyGuVw=
github.com/golangci/revgrep v0.8.0 h1:EZBctwbVd0aMeRnNUsFogoyayvKHyxlV3CdUA46FX2s=
github.com/golangci/revgrep v0.8.0/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k=
github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e h1:ai0EfmVYE2bRA5htgAG9r7s3tHsfjIhN98WshBTJ9jM=
github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e/go.mod h1:Vrn4B5oR9qRwM+f54koyeH3yzphlecwERs0el27Fr/s=
github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e h1:gD6P7NEo7Eqtt0ssnqSJNNndxe69DOQ24A5h7+i3KpM=
github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e/go.mod h1:h+wZwLjUTJnm/P2rwlbJdRPZXOzaT36/FwnPnY2inzc=
github.com/google/addlicense v1.2.0 h1:W+DP4A639JGkcwBGMDvjSurZHvaq2FN0pP7se9czsKA=
github.com/google/addlicense v1.2.0/go.mod h1:Sm/DHu7Jk+T5miFHHehdIjbi4M5+dJDRS3Cq0rncIxA=
github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786 h1:rcv+Ippz6RAtvaGgKxc+8FQIpxHgsF+HBzPyYL2cyVU=
github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786/go.mod h1:apVn/GCasLZUVpAJ6oWAuyP7Ne7CEsQbTnc0plM3m+o=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-github/v76 v76.0.0 h1:MCa9VQn+VG5GG7Y7BAkBvSRUN3o+QpaEOuZwFPJmdFA=
github.com/google/go-github/v76 v76.0.0/go.mod h1:38+d/8pYDO4fBLYfBhXF5EKO0wA3UkXBjfmQapFsNCQ=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY=
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=
github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/renameio/v2 v2.0.1 h1:HyOM6qd9gF9sf15AvhbptGHUnaLTpEI9akAFFU3VyW0=
github.com/google/renameio/v2 v2.0.1/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/gordonklaus/ineffassign v0.2.0 h1:Uths4KnmwxNJNzq87fwQQDDnbNb7De00VOk9Nu0TySs=
github.com/gordonklaus/ineffassign v0.2.0/go.mod h1:TIpymnagPSexySzs7F9FnO1XFTy8IT3a59vmZp5Y9Lw=
github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk=
github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc=
github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM=
github.com/gostaticanalysis/comment v1.5.0 h1:X82FLl+TswsUMpMh17srGRuKaaXprTaytmEpgnKIDu8=
github.com/gostaticanalysis/comment v1.5.0/go.mod h1:V6eb3gpCv9GNVqb6amXzEUX3jXLVK/AdA+IrAMSqvEc=
github.com/gostaticanalysis/forcetypeassert v0.2.0 h1:uSnWrrUEYDr86OCxWa4/Tp2jeYDlogZiZHzGkWFefTk=
github.com/gostaticanalysis/forcetypeassert v0.2.0/go.mod h1:M5iPavzE9pPqWyeiVXSFghQjljW1+l/Uke3PXHS6ILY=
github.com/gostaticanalysis/nilerr v0.1.2 h1:S6nk8a9N8g062nsx63kUkF6AzbHGw7zzyHMcpu52xQU=
github.com/gostaticanalysis/nilerr v0.1.2/go.mod h1:A19UHhoY3y8ahoL7YKz6sdjDtduwTSI4CsymaC2htPA=
github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M=
github.com/gostaticanalysis/testutil v0.5.0 h1:Dq4wT1DdTwTGCQQv3rl3IvD5Ld0E6HiY+3Zh0sUGqw8=
github.com/gostaticanalysis/testutil v0.5.0/go.mod h1:OLQSbuM6zw2EvCcXTz1lVq5unyoNft372msDY0nY5Hs=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-immutable-radix/v2 v2.1.0 h1:CUW5RYIcysz+D3B+l1mDeXrQ7fUvGGCwJfdASSzbrfo=
github.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw=
github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jcchavezs/porto v0.7.0 h1:VncK84yxV7QZD4GdvoslzjnieSuruztGxLCmFi/Eu28=
github.com/jcchavezs/porto v0.7.0/go.mod h1:tQ1cJ85cNzzZg/58VuZWOLbmrjcH1wPxkWgeBjvOq5o=
github.com/jgautheron/goconst v1.8.2 h1:y0XF7X8CikZ93fSNT6WBTb/NElBu9IjaY7CCYQrCMX4=
github.com/jgautheron/goconst v1.8.2/go.mod h1:A0oxgBCHy55NQn6sYpO7UdnA9p+h7cPtoOZUmvNIako=
github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs=
github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c=
github.com/jjti/go-spancheck v0.6.5 h1:lmi7pKxa37oKYIMScialXUK6hP3iY5F1gu+mLBPgYB8=
github.com/jjti/go-spancheck v0.6.5/go.mod h1:aEogkeatBrbYsyW6y5TgDfihCulDYciL1B7rG2vSsrU=
github.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ=
github.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY=
github.com/kaptinlin/go-i18n v0.1.6 h1:XQT3qvE/xgNrAps/zXBHAKWhPlWSrFS9t1jCZ4PVnOs=
github.com/kaptinlin/go-i18n v0.1.6/go.mod h1:MoHPYUYQug2jyLygIWeT9F3VDUZEP480cKotmtQjUCc=
github.com/kaptinlin/jsonschema v0.4.12 h1:10cy+j2b69jyOX+F+u9suLK2dw0k63UqzUNctpxiV3A=
github.com/kaptinlin/jsonschema v0.4.12/go.mod h1:zG+WOWvAxUaPUnh83QrV/a/fsfloI6L6d/YM4mtZCHw=
github.com/kaptinlin/messageformat-go v0.4.0 h1:L5wPgwQZkV1Rvs19htUT2RGx8N1GCq3uQG5nB6VHRcM=
github.com/kaptinlin/messageformat-go v0.4.0/go.mod h1:LrLCV49C5ms/BZlOpFPihou+cPvhOQSvVJHj2wOe6w8=
github.com/karamaru-alpha/copyloopvar v1.2.2 h1:yfNQvP9YaGQR7VaWLYcfZUlRP2eo2vhExWKxD/fP6q0=
github.com/karamaru-alpha/copyloopvar v1.2.2/go.mod h1:oY4rGZqZ879JkJMtX3RRkcXRkmUvH0x35ykgaKgsgJY=
github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ=
github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
github.com/kisielk/errcheck v1.9.0 h1:9xt1zI9EBfcYBvdU1nVrzMzzUPUtPKs9bVSIM3TAb3M=
github.com/kisielk/errcheck v1.9.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8=
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE=
github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
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/kulti/thelper v0.7.1 h1:fI8QITAoFVLx+y+vSyuLBP+rcVIB8jKooNSCT2EiI98=
github.com/kulti/thelper v0.7.1/go.mod h1:NsMjfQEy6sd+9Kfw8kCP61W1I0nerGSYSFnGaxQkcbs=
github.com/kunwardeep/paralleltest v1.0.15 h1:ZMk4Qt306tHIgKISHWFJAO1IDQJLc6uDyJMLyncOb6w=
github.com/kunwardeep/paralleltest v1.0.15/go.mod h1:di4moFqtfz3ToSKxhNjhOZL+696QtJGCFe132CbBLGk=
github.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4=
github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI=
github.com/ldez/exptostd v0.4.5 h1:kv2ZGUVI6VwRfp/+bcQ6Nbx0ghFWcGIKInkG/oFn1aQ=
github.com/ldez/exptostd v0.4.5/go.mod h1:QRjHRMXJrCTIm9WxVNH6VW7oN7KrGSht69bIRwvdFsM=
github.com/ldez/gomoddirectives v0.7.1 h1:FaULkvUIG36hj6chpwa+FdCNGZBsD7/fO+p7CCsM6pE=
github.com/ldez/gomoddirectives v0.7.1/go.mod h1:auDNtakWJR1rC+YX7ar+HmveqXATBAyEK1KYpsIRW/8=
github.com/ldez/grignotin v0.10.1 h1:keYi9rYsgbvqAZGI1liek5c+jv9UUjbvdj3Tbn5fn4o=
github.com/ldez/grignotin v0.10.1/go.mod h1:UlDbXFCARrXbWGNGP3S5vsysNXAPhnSuBufpTEbwOas=
github.com/ldez/tagliatelle v0.7.2 h1:KuOlL70/fu9paxuxbeqlicJnCspCRjH0x8FW+NfgYUk=
github.com/ldez/tagliatelle v0.7.2/go.mod h1:PtGgm163ZplJfZMZ2sf5nhUT170rSuPgBimoyYtdaSI=
github.com/ldez/usetesting v0.5.0 h1:3/QtzZObBKLy1F4F8jLuKJiKBjjVFi1IavpoWbmqLwc=
github.com/ldez/usetesting v0.5.0/go.mod h1:Spnb4Qppf8JTuRgblLrEWb7IE6rDmUpGvxY3iRrzvDQ=
github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY=
github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/macabu/inamedparam v0.2.0 h1:VyPYpOc10nkhI2qeNUdh3Zket4fcZjEWe35poddBCpE=
github.com/macabu/inamedparam v0.2.0/go.mod h1:+Pee9/YfGe5LJ62pYXqB89lJ+0k5bsR8Wgz/C0Zlq3U=
github.com/manuelarte/embeddedstructfieldcheck v0.4.0 h1:3mAIyaGRtjK6EO9E73JlXLtiy7ha80b2ZVGyacxgfww=
github.com/manuelarte/embeddedstructfieldcheck v0.4.0/go.mod h1:z8dFSyXqp+fC6NLDSljRJeNQJJDWnY7RoWFzV3PC6UM=
github.com/manuelarte/funcorder v0.5.0 h1:llMuHXXbg7tD0i/LNw8vGnkDTHFpTnWqKPI85Rknc+8=
github.com/manuelarte/funcorder v0.5.0/go.mod h1:Yt3CiUQthSBMBxjShjdXMexmzpP8YGvGLjrxJNkO2hA=
github.com/maratori/testableexamples v1.0.1 h1:HfOQXs+XgfeRBJ+Wz0XfH+FHnoY9TVqL6Fcevpzy4q8=
github.com/maratori/testableexamples v1.0.1/go.mod h1:XE2F/nQs7B9N08JgyRmdGjYVGqxWwClLPCGSQhXQSrQ=
github.com/maratori/testpackage v1.1.2 h1:ffDSh+AgqluCLMXhM19f/cpvQAKygKAJXFl9aUjmbqs=
github.com/maratori/testpackage v1.1.2/go.mod h1:8F24GdVDFW5Ew43Et02jamrVMNXLUNaOynhDssITGfc=
github.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4=
github.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/mgechev/revive v1.12.0 h1:Q+/kkbbwerrVYPv9d9efaPGmAO/NsxwW/nE6ahpQaCU=
github.com/mgechev/revive v1.12.0/go.mod h1:VXsY2LsTigk8XU9BpZauVLjVrhICMOV3k1lpB3CXrp8=
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/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI=
github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U=
github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE=
github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg=
github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs=
github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk=
github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c=
github.com/nunnatsa/ginkgolinter v0.21.2 h1:khzWfm2/Br8ZemX8QM1pl72LwM+rMeW6VUbQ4rzh0Po=
github.com/nunnatsa/ginkgolinter v0.21.2/go.mod h1:GItSI5fw7mCGLPmkvGYrr1kEetZe7B593jcyOpyabsY=
github.com/onsi/ginkgo/v2 v2.26.0 h1:1J4Wut1IlYZNEAWIV3ALrT9NfiaGW2cDCJQSFQMs/gE=
github.com/onsi/ginkgo/v2 v2.26.0/go.mod h1:qhEywmzWTBUY88kfO0BRvX4py7scov9yR+Az2oavUzw=
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw=
github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8=
github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I=
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs=
github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
github.com/pavius/impi v0.0.3 h1:DND6MzU+BLABhOZXbELR3FU8b+zDgcq4dOCNLhiTYuI=
github.com/pavius/impi v0.0.3/go.mod h1:x/hU0bfdWIhuOT1SKwiJg++yvkk6EuOtJk8WtDZqgr8=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/polyfloyd/go-errorlint v1.8.0 h1:DL4RestQqRLr8U4LygLw8g2DX6RN1eBJOpa2mzsrl1Q=
github.com/polyfloyd/go-errorlint v1.8.0/go.mod h1:G2W0Q5roxbLCt0ZQbdoxQxXktTjwNyDbEaj3n7jvl4s=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/quasilyte/go-ruleguard v0.4.5 h1:AGY0tiOT5hJX9BTdx/xBdoCubQUAE2grkqY2lSwvZcA=
github.com/quasilyte/go-ruleguard v0.4.5/go.mod h1:Vl05zJ538vcEEwu16V/Hdu7IYZWyKSwIy4c88Ro1kRE=
github.com/quasilyte/go-ruleguard/dsl v0.3.23 h1:lxjt5B6ZCiBeeNO8/oQsegE6fLeCzuMRoVWSkXC4uvY=
github.com/quasilyte/go-ruleguard/dsl v0.3.23/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=
github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo=
github.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng=
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU=
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0=
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs=
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ=
github.com/raeperd/recvcheck v0.2.0 h1:GnU+NsbiCqdC2XX5+vMZzP+jAJC5fht7rcVTAhX74UI=
github.com/raeperd/recvcheck v0.2.0/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtzBnWNocnYU=
github.com/rhysd/actionlint v1.7.11 h1:m+aSuCpCIClS8X02xMG4Z8s87fCHPsAtYkAoWGQZgEE=
github.com/rhysd/actionlint v1.7.11/go.mod h1:8n50YougV9+50niD7oxgDTZ1KbN/ZnKiQ2xpLFeVhsI=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryancurrah/gomodguard v1.4.1 h1:eWC8eUMNZ/wM/PWuZBv7JxxqT5fiIKSIyTvjb7Elr+g=
github.com/ryancurrah/gomodguard v1.4.1/go.mod h1:qnMJwV1hX9m+YJseXEBhd2s90+1Xn6x9dLz11ualI1I=
github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU=
github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ=
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0=
github.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw=
github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ=
github.com/sashamelentyev/usestdlibvars v1.29.0 h1:8J0MoRrw4/NAXtjQqTHrbW9NN+3iMf7Knkq057v4XOQ=
github.com/sashamelentyev/usestdlibvars v1.29.0/go.mod h1:8PpnjHMk5VdeWlVb4wCdrB8PNbLqZ3wBZTZWkrpZZL8=
github.com/securego/gosec/v2 v2.22.10 h1:ntbBqdWXnu46DUOXn+R2SvPo3PiJCDugTCgTW2g4tQg=
github.com/securego/gosec/v2 v2.22.10/go.mod h1:9UNjK3tLpv/w2b0+7r82byV43wCJDNtEDQMeS+H/g2w=
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
github.com/sirkon/dst v0.26.4 h1:ETxfjyp5JKE8OCpdybyyhzTyQqq/MwbIIcs7kxcUAcA=
github.com/sirkon/dst v0.26.4/go.mod h1:e6HRc56jU5F2XT6GB8Cyci1Jb5cjX6gLqrm5+T/P7Zo=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE=
github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4=
github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg=
github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow=
github.com/sonatard/noctx v0.4.0 h1:7MC/5Gg4SQ4lhLYR6mvOP6mQVSxCrdyiExo7atBs27o=
github.com/sonatard/noctx v0.4.0/go.mod h1:64XdbzFb18XL4LporKXp8poqZtPKbCrqQ402CV+kJas=
github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0=
github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0=
github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I=
github.com/stbenjam/no-sprintf-host-port v0.2.0 h1:i8pxvGrt1+4G0czLr/WnmyH7zbZ8Bg8etvARQ1rpyl4=
github.com/stbenjam/no-sprintf-host-port v0.2.0/go.mod h1:eL0bQ9PasS0hsyTyfTjjG+E80QIyPnBVQbYZyv20Jfk=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
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.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA=
github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0=
github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag=
github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY=
github.com/tetafro/godot v1.5.4 h1:u1ww+gqpRLiIA16yF2PV1CV1n/X3zhyezbNXC3E14Sg=
github.com/tetafro/godot v1.5.4/go.mod h1:eOkMrVQurDui411nBY2FA05EYH01r14LuWY/NrVDVcU=
github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 h1:9LPGD+jzxMlnk5r6+hJnar67cgpDIz/iyD+rfl5r2Vk=
github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460=
github.com/timonwong/loggercheck v0.11.0 h1:jdaMpYBl+Uq9mWPXv1r8jc5fC3gyXx4/WGwTnnNKn4M=
github.com/timonwong/loggercheck v0.11.0/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8=
github.com/tomarrell/wrapcheck/v2 v2.11.0 h1:BJSt36snX9+4WTIXeJ7nvHBQBcm1h2SjQMSlmQ6aFSU=
github.com/tomarrell/wrapcheck/v2 v2.11.0/go.mod h1:wFL9pDWDAbXhhPZZt+nG8Fu+h29TtnZ2MW6Lx4BRXIU=
github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw=
github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw=
github.com/ultraware/funlen v0.2.0 h1:gCHmCn+d2/1SemTdYMiKLAHFYxTYz7z9VIDRaTGyLkI=
github.com/ultraware/funlen v0.2.0/go.mod h1:ZE0q4TsJ8T1SQcjmkhN/w+MceuatI6pBFSxxyteHIJA=
github.com/ultraware/whitespace v0.2.0 h1:TYowo2m9Nfj1baEQBjuHzvMRbp19i+RCcRYrSWoFa+g=
github.com/ultraware/whitespace v0.2.0/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8=
github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYRA=
github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU=
github.com/uudashr/iface v1.4.1 h1:J16Xl1wyNX9ofhpHmQ9h9gk5rnv2A6lX/2+APLTo0zU=
github.com/uudashr/iface v1.4.1/go.mod h1:pbeBPlbuU2qkNDn0mmfrxP2X+wjPMIQAy+r1MBXSXtg=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/xen0n/gosmopolitan v1.3.0 h1:zAZI1zefvo7gcpbCOrPSHJZJYA9ZgLfJqtKzZ5pHqQM=
github.com/xen0n/gosmopolitan v1.3.0/go.mod h1:rckfr5T6o4lBtM1ga7mLGKZmLxswUoH1zxHgNXOsEt4=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM=
github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk=
github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs=
github.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+YcPQN+mW4=
github.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw=
github.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg=
github.com/yuin/goldmark v1.1.25/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=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo=
gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8=
go-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ=
go-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28=
go-simpler.org/musttag v0.14.0 h1:XGySZATqQYSEV3/YTy+iX+aofbZZllJaqwFWs+RTtSo=
go-simpler.org/musttag v0.14.0/go.mod h1:uP8EymctQjJ4Z1kUnjX0u2l60WfUdQxCwSNKzE1JEOE=
go-simpler.org/sloglint v0.11.1 h1:xRbPepLT/MHPTCA6TS/wNfZrDzkGvCCqUv4Bdwc3H7s=
go-simpler.org/sloglint v0.11.1/go.mod h1:2PowwiCOK8mjiF+0KGifVOT8ZsCNiFzvfyJeJOIt8MQ=
go.augendre.info/arangolint v0.3.1 h1:n2E6p8f+zfXSFLa2e2WqFPp4bfvcuRdd50y6cT65pSo=
go.augendre.info/arangolint v0.3.1/go.mod h1:6ZKzEzIZuBQwoSvlKT+qpUfIbBfFCE5gbAoTg0/117g=
go.augendre.info/fatcontext v0.9.0 h1:Gt5jGD4Zcj8CDMVzjOJITlSb9cEch54hjRRlN3qDojE=
go.augendre.info/fatcontext v0.9.0/go.mod h1:L94brOAT1OOUNue6ph/2HnwxoNlds9aXDF2FcUntbNw=
go.opentelemetry.io/build-tools v0.29.0 h1:dG1zmHKYTMsP0zGT34+32/U+YR+lcJ8kka7Lc4RAoT4=
go.opentelemetry.io/build-tools v0.29.0/go.mod h1:jTzBit47RqVApCwStu9qw2TfGqR2Fhu5jinLHqfhghQ=
go.opentelemetry.io/build-tools/checkapi v0.29.0 h1:witlcpwRObFHyNBr0XM1P21C//jIUW25gxnO0V5sls0=
go.opentelemetry.io/build-tools/checkapi v0.29.0/go.mod h1:BUlDXzIK1uVJC4mcYBzeNTiRk1HbKA8kf3N44zlhx9g=
go.opentelemetry.io/build-tools/checkfile v0.29.0 h1:9ue2+J4x7NL9Nugahec6A9JsV4CUd0GhDiFxN3VUufE=
go.opentelemetry.io/build-tools/checkfile v0.29.0/go.mod h1:vtBRUpSPWJdfdGV3/vodhcD3wiMp8fhQ03HOwmczeJQ=
go.opentelemetry.io/build-tools/chloggen v0.29.0 h1:0HnDE47uJNlst1XtCukHB7sQYtUlJjmvdhWVdJn+GBU=
go.opentelemetry.io/build-tools/chloggen v0.29.0/go.mod h1:eby4AVJQF5uanGCnErZdhDYBSW/EJ0iqejBFNJMN4DQ=
go.opentelemetry.io/build-tools/crosslink v0.29.0 h1:sz8if4EgUejLvfulrfLF7i2yzSUEyiY4s++aWJGVMZc=
go.opentelemetry.io/build-tools/crosslink v0.29.0/go.mod h1:jWE8JLNnuAQhnISpzGsWumC4JREBHOPaxufdSeBbSWs=
go.opentelemetry.io/build-tools/githubgen v0.29.0 h1:ETjvcslIftce8aARBGtCCdxIs4Le2tSUlXD5cbzhYGE=
go.opentelemetry.io/build-tools/githubgen v0.29.0/go.mod h1:aTnMaP7dTUOZzFxAyS1OXmc27QHpSN9NK+3rD9irWqI=
go.opentelemetry.io/build-tools/multimod v0.29.0 h1:hVXibTuNuJumtn3cpzqPOX2xmcO+KogKZcB0yygHux0=
go.opentelemetry.io/build-tools/multimod v0.29.0/go.mod h1:tx762Z6RQe5Twkd04q1zzpmGQGtSljbKRy/P61EnJpo=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go=
go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/exp/typeparams v0.0.0-20251023183803-a4bb9ffd2546 h1:HDjDiATsGqvuqvkDvgJjD1IgPrVekcSXVVE21JwvzGE=
golang.org/x/exp/typeparams v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:4Mzdyp/6jzw9auFDJ3OMF5qksa7UvPnzKqTVGcb04ms=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
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-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4 h1:bTLqdHv7xrGlFbvf5/TXNxy/iUwwdkjhqQTJDjW7aj0=
golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4/go.mod h1:g5NllXBEermZrmR51cJDQxmJUHUOfRAaNyWBM+R+548=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/text v0.3.0/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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU=
golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8=
golang.org/x/vuln v1.1.4 h1:Ju8QsuyhX3Hk8ma3CesTbO8vfJD9EvUBgHvkxHBzj0I=
golang.org/x/vuln v1.1.4/go.mod h1:F+45wmU18ym/ca5PLTPLsSzr2KppzswxPP603ldA67s=
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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/gotestsum v1.13.0 h1:+Lh454O9mu9AMG1APV4o0y7oDYKyik/3kBOiCqiEpRo=
gotest.tools/gotestsum v1.13.0/go.mod h1:7f0NS5hFb0dWr4NtcsAsF0y1kzjEFfAil0HiBQJE03Q=
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
honnef.co/go/tools v0.7.0-0.dev.0.20250523013057-bbc2f4dd71ea h1:fj8r9irJSpolAGUdZBxJIRY3lLc4jH2Dt4lwnWyWwpw=
honnef.co/go/tools v0.7.0-0.dev.0.20250523013057-bbc2f4dd71ea/go.mod h1:EPDDhEZqVHhWuPI5zPAsjU0U7v9xNIWjoOVyZ5ZcniQ=
mvdan.cc/gofumpt v0.9.2 h1:zsEMWL8SVKGHNztrx6uZrXdp7AX8r421Vvp23sz7ik4=
mvdan.cc/gofumpt v0.9.2/go.mod h1:iB7Hn+ai8lPvofHd9ZFGVg2GOr8sBUw1QUWjNbmIL/s=
mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 h1:ssMzja7PDPJV8FStj7hq9IKiuiKhgz9ErWw+m68e7DI=
mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15/go.mod h1:4M5MMXl2kW6fivUT6yRGpLLPNfuGtU2Z0cPvFquGDYU=
================================================
FILE: otelcol/Makefile
================================================
include ../Makefile.Common
================================================
FILE: otelcol/buffered_core.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// This logger implements zapcore.Core and is based on zaptest/observer.
package otelcol // import "go.opentelemetry.io/collector/otelcol"
import (
"errors"
"sync"
"go.uber.org/zap/zapcore"
)
type loggedEntry struct {
zapcore.Entry
Context []zapcore.Field
}
func newBufferedCore(enab zapcore.LevelEnabler) *bufferedCore {
return &bufferedCore{LevelEnabler: enab}
}
var _ zapcore.Core = (*bufferedCore)(nil)
type bufferedCore struct {
zapcore.LevelEnabler
mu sync.Mutex
logs []loggedEntry
context []zapcore.Field
logsTaken bool
}
func (bc *bufferedCore) Level() zapcore.Level {
return zapcore.LevelOf(bc.LevelEnabler)
}
func (bc *bufferedCore) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
if bc.Enabled(ent.Level) {
return ce.AddCore(ent, bc)
}
return ce
}
func (bc *bufferedCore) With(fields []zapcore.Field) zapcore.Core {
return &bufferedCore{
LevelEnabler: bc.LevelEnabler,
logs: bc.logs,
logsTaken: bc.logsTaken,
context: append(bc.context, fields...),
}
}
func (bc *bufferedCore) Write(ent zapcore.Entry, fields []zapcore.Field) error {
bc.mu.Lock()
defer bc.mu.Unlock()
if bc.logsTaken {
return errors.New("the buffered logs have already been taken so writing is no longer supported")
}
all := make([]zapcore.Field, 0, len(fields)+len(bc.context))
all = append(all, bc.context...)
all = append(all, fields...)
bc.logs = append(bc.logs, loggedEntry{ent, all})
return nil
}
func (bc *bufferedCore) Sync() error {
return nil
}
func (bc *bufferedCore) TakeLogs() []loggedEntry {
bc.mu.Lock()
defer bc.mu.Unlock()
if bc.logsTaken {
return nil
}
logs := bc.logs
bc.logs = nil
bc.logsTaken = true
return logs
}
================================================
FILE: otelcol/buffered_core_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelcol
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zapcore"
)
func Test_bufferedCore_Level(t *testing.T) {
bc := newBufferedCore(zapcore.InfoLevel)
assert.Equal(t, zapcore.InfoLevel, bc.Level())
}
func Test_bufferedCore_Check(t *testing.T) {
t.Run("check passed", func(t *testing.T) {
bc := newBufferedCore(zapcore.InfoLevel)
e := zapcore.Entry{
Level: zapcore.InfoLevel,
}
expected := &zapcore.CheckedEntry{}
expected = expected.AddCore(e, bc)
ce := bc.Check(e, nil)
assert.Equal(t, expected, ce)
})
t.Run("check did not pass", func(t *testing.T) {
bc := newBufferedCore(zapcore.InfoLevel)
e := zapcore.Entry{
Level: zapcore.DebugLevel,
}
ce := bc.Check(e, nil)
assert.Nil(t, ce)
})
}
func Test_bufferedCore_With(t *testing.T) {
bc := newBufferedCore(zapcore.InfoLevel)
bc.logsTaken = true
bc.context = []zapcore.Field{
{Key: "original", String: "context"},
}
inputs := []zapcore.Field{
{Key: "test", String: "passed"},
}
expected := []zapcore.Field{
{Key: "original", String: "context"},
{Key: "test", String: "passed"},
}
newBC := bc.With(inputs)
assert.Equal(t, expected, newBC.(*bufferedCore).context)
assert.True(t, newBC.(*bufferedCore).logsTaken)
}
func Test_bufferedCore_Write(t *testing.T) {
bc := newBufferedCore(zapcore.InfoLevel)
e := zapcore.Entry{
Level: zapcore.DebugLevel,
Message: "test",
}
fields := []zapcore.Field{
{Key: "field1", String: "value1"},
}
err := bc.Write(e, fields)
require.NoError(t, err)
expected := loggedEntry{
e,
fields,
}
require.Len(t, bc.logs, 1)
require.Equal(t, expected, bc.logs[0])
}
func Test_bufferedCore_Sync(t *testing.T) {
bc := newBufferedCore(zapcore.InfoLevel)
assert.NoError(t, bc.Sync())
}
func Test_bufferedCore_TakeLogs(t *testing.T) {
bc := newBufferedCore(zapcore.InfoLevel)
e := zapcore.Entry{
Level: zapcore.DebugLevel,
Message: "test",
}
fields := []zapcore.Field{
{Key: "field1", String: "value1"},
}
err := bc.Write(e, fields)
require.NoError(t, err)
expected := []loggedEntry{
{
e,
fields,
},
}
assert.Equal(t, expected, bc.TakeLogs())
assert.Nil(t, bc.logs)
require.Error(t, bc.Write(e, fields))
assert.Nil(t, bc.TakeLogs())
}
================================================
FILE: otelcol/collector.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package otelcol handles the command-line, configuration, and runs the OpenTelemetry Collector.
// It contains the main [Collector] struct and its constructor [NewCollector].
// [Collector.Run] starts the Collector and then blocks until it shuts down.
package otelcol // import "go.opentelemetry.io/collector/otelcol"
import (
"context"
"errors"
"fmt"
"os"
"os/signal"
"sync"
"sync/atomic"
"go.uber.org/multierr"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/xconfmap"
"go.opentelemetry.io/collector/otelcol/internal/grpclog"
"go.opentelemetry.io/collector/service"
)
// State defines Collector's state.
type State int
const (
StateStarting State = iota
StateRunning
StateClosing
StateClosed
)
func (s State) String() string {
switch s {
case StateStarting:
return "Starting"
case StateRunning:
return "Running"
case StateClosing:
return "Closing"
case StateClosed:
return "Closed"
}
return "UNKNOWN"
}
// CollectorSettings holds configuration for creating a new Collector.
type CollectorSettings struct {
// Factories returns component factories for the collector.
//
// TODO(13263) This is a dangerous "bare" function value, should define an interface
// following style guidelines.
Factories func() (Factories, error)
// BuildInfo provides collector start information.
BuildInfo component.BuildInfo
// DisableGracefulShutdown disables the automatic graceful shutdown
// of the collector on SIGINT or SIGTERM.
// Users who want to handle signals themselves can disable this behavior
// and manually handle the signals to shutdown the collector.
DisableGracefulShutdown bool
// ConfigProviderSettings allows configuring the way the Collector retrieves its configuration
// The Collector will reload based on configuration changes from the ConfigProvider if any
// confmap.Providers watch for configuration changes.
ConfigProviderSettings ConfigProviderSettings
// ProviderModules maps provider schemes to their respective go modules.
ProviderModules map[string]string
// ConverterModules maps converter names to their respective go modules.
ConverterModules []string
// LoggingOptions provides a way to change behavior of zap logging.
LoggingOptions []zap.Option
// SkipSettingGRPCLogger avoids setting the grpc logger
SkipSettingGRPCLogger bool
}
// (Internal note) Collector Lifecycle:
// - New constructs a new Collector.
// - Run starts the collector.
// - Run calls setupConfigurationComponents to handle configuration.
// If configuration parser fails, collector's config can be reloaded.
// Collector can be shutdown if parser gets a shutdown error.
// - Run runs runAndWaitForShutdownEvent and waits for a shutdown event.
// SIGINT and SIGTERM, errors, and (*Collector).Shutdown can trigger the shutdown events.
// - Upon shutdown, pipelines are notified, then pipelines and extensions are shut down.
// - Users can call (*Collector).Shutdown anytime to shut down the collector.
// Collector represents a server providing the OpenTelemetry Collector service.
type Collector struct {
set CollectorSettings
buildZapLogger func(zap.Config, ...zap.Option) (*zap.Logger, error)
configProvider *ConfigProvider
serviceConfig *service.Config
service *service.Service
state *atomic.Int64
// shutdownChan is used to terminate the collector.
shutdownChan chan struct{}
shutdownOnce sync.Once
// 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
bc *bufferedCore
updateConfigProviderLogger func(core zapcore.Core)
}
// NewCollector creates and returns a new instance of Collector.
func NewCollector(set CollectorSettings) (*Collector, error) {
bc := newBufferedCore(zapcore.DebugLevel)
cc := newCollectorCore(bc)
options := append([]zap.Option{zap.WithCaller(true)}, set.LoggingOptions...)
logger := zap.New(cc, options...)
set.ConfigProviderSettings.ResolverSettings.ProviderSettings = confmap.ProviderSettings{Logger: logger}
set.ConfigProviderSettings.ResolverSettings.ConverterSettings = confmap.ConverterSettings{Logger: logger}
configProvider, err := NewConfigProvider(set.ConfigProviderSettings)
if err != nil {
return nil, err
}
state := new(atomic.Int64)
state.Store(int64(StateStarting))
return &Collector{
set: set,
buildZapLogger: zap.Config.Build,
state: state,
shutdownChan: make(chan struct{}),
// Per signal.Notify documentation, a size of the channel equaled with
// the number of signals getting notified on is recommended.
signalsChannel: make(chan os.Signal, 3),
asyncErrorChannel: make(chan error),
configProvider: configProvider,
bc: bc,
updateConfigProviderLogger: cc.SetCore,
}, nil
}
// GetState returns current state of the collector server.
func (col *Collector) GetState() State {
return State(col.state.Load())
}
// Shutdown shuts down the collector server.
func (col *Collector) Shutdown() {
col.shutdownOnce.Do(func() {
close(col.shutdownChan)
})
}
func buildModuleInfo(m map[component.Type]string) map[component.Type]service.ModuleInfo {
moduleInfo := make(map[component.Type]service.ModuleInfo)
for k, v := range m {
moduleInfo[k] = service.ModuleInfo{BuilderRef: v}
}
return moduleInfo
}
// setupConfigurationComponents loads the config, creates the graph, and starts the components. If all the steps succeeds it
// sets the col.service with the service currently running.
func (col *Collector) setupConfigurationComponents(ctx context.Context) error {
col.setCollectorState(StateStarting)
factories, err := col.set.Factories()
if err != nil {
return fmt.Errorf("failed to initialize factories: %w", err)
}
cfg, err := col.configProvider.Get(ctx, factories)
if err != nil {
return fmt.Errorf("failed to get config: %w", err)
}
if err = xconfmap.Validate(cfg); err != nil {
return fmt.Errorf("invalid configuration: %w", err)
}
col.serviceConfig = &cfg.Service
conf := confmap.New()
if err = conf.Marshal(cfg); err != nil {
return fmt.Errorf("could not marshal configuration: %w", err)
}
// Wrap the buildZapLogger to append LoggingOptions from collector settings,
// since service.Settings.LoggingOptions is deprecated.
buildZapLogger := col.buildZapLogger
if len(col.set.LoggingOptions) > 0 {
origBuildZapLogger := buildZapLogger
buildZapLogger = func(zapCfg zap.Config, opts ...zap.Option) (*zap.Logger, error) {
opts = append(opts, col.set.LoggingOptions...)
return origBuildZapLogger(zapCfg, opts...)
}
}
col.service, err = service.New(ctx, service.Settings{
BuildInfo: col.set.BuildInfo,
CollectorConf: conf,
ReceiversConfigs: cfg.Receivers,
ReceiversFactories: factories.Receivers,
ProcessorsConfigs: cfg.Processors,
ProcessorsFactories: factories.Processors,
ExportersConfigs: cfg.Exporters,
ExportersFactories: factories.Exporters,
ConnectorsConfigs: cfg.Connectors,
ConnectorsFactories: factories.Connectors,
ExtensionsConfigs: cfg.Extensions,
ExtensionsFactories: factories.Extensions,
ModuleInfos: service.ModuleInfos{
Receiver: buildModuleInfo(factories.ReceiverModules),
Processor: buildModuleInfo(factories.ProcessorModules),
Exporter: buildModuleInfo(factories.ExporterModules),
Extension: buildModuleInfo(factories.ExtensionModules),
Connector: buildModuleInfo(factories.ConnectorModules),
},
AsyncErrorChannel: col.asyncErrorChannel,
BuildZapLogger: buildZapLogger,
TelemetryFactory: factories.Telemetry,
}, cfg.Service)
if err != nil {
return err
}
if col.updateConfigProviderLogger != nil {
col.updateConfigProviderLogger(col.service.Logger().Core())
}
if col.bc != nil {
x := col.bc.TakeLogs()
for _, log := range x {
ce := col.service.Logger().Core().Check(log.Entry, nil)
if ce != nil {
ce.Write(log.Context...)
}
}
}
if !col.set.SkipSettingGRPCLogger {
grpclog.SetLogger(col.service.Logger())
}
if err = col.service.Start(ctx); err != nil {
return multierr.Combine(err, col.service.Shutdown(ctx))
}
col.setCollectorState(StateRunning)
return nil
}
func (col *Collector) reloadConfiguration(ctx context.Context) error {
col.service.Logger().Warn("Config updated, restart service")
col.setCollectorState(StateClosing)
if err := col.service.Shutdown(ctx); err != nil {
return fmt.Errorf("failed to shutdown the retiring config: %w", err)
}
if err := col.setupConfigurationComponents(ctx); err != nil {
return fmt.Errorf("failed to setup configuration components: %w", err)
}
return nil
}
func (col *Collector) DryRun(ctx context.Context) error {
factories, err := col.set.Factories()
if err != nil {
return fmt.Errorf("failed to initialize factories: %w", err)
}
cfg, err := col.configProvider.Get(ctx, factories)
if err != nil {
return fmt.Errorf("failed to get config: %w", err)
}
if err := xconfmap.Validate(cfg); err != nil {
return err
}
return service.Validate(ctx, service.Settings{
BuildInfo: col.set.BuildInfo,
ReceiversConfigs: cfg.Receivers,
ReceiversFactories: factories.Receivers,
ProcessorsConfigs: cfg.Processors,
ProcessorsFactories: factories.Processors,
ExportersConfigs: cfg.Exporters,
ExportersFactories: factories.Exporters,
ConnectorsConfigs: cfg.Connectors,
ConnectorsFactories: factories.Connectors,
TelemetryFactory: factories.Telemetry,
}, service.Config{
Pipelines: cfg.Service.Pipelines,
})
}
func newFallbackLogger(options []zap.Option) (*zap.Logger, error) {
ec := zap.NewProductionEncoderConfig()
ec.EncodeTime = zapcore.ISO8601TimeEncoder
zapCfg := &zap.Config{
Level: zap.NewAtomicLevelAt(zapcore.DebugLevel),
Encoding: "console",
EncoderConfig: ec,
OutputPaths: []string{"stderr"},
ErrorOutputPaths: []string{"stderr"},
}
return zapCfg.Build(options...)
}
// Run starts the collector according to the given configuration, and waits for it to complete.
// Consecutive calls to Run are not allowed, Run shouldn't be called once a collector is shut down.
// Sets up the control logic for config reloading and shutdown.
func (col *Collector) Run(ctx context.Context) error {
// setupConfigurationComponents is the "main" function responsible for startup
if err := col.setupConfigurationComponents(ctx); err != nil {
col.setCollectorState(StateClosed)
logger, loggerErr := newFallbackLogger(col.set.LoggingOptions)
if loggerErr != nil {
return errors.Join(err, fmt.Errorf("unable to create fallback logger: %w", loggerErr))
}
if col.bc != nil {
x := col.bc.TakeLogs()
for _, log := range x {
ce := logger.Core().Check(log.Entry, nil)
if ce != nil {
ce.Write(log.Context...)
}
}
}
return err
}
// Always notify with SIGHUP for configuration reloading.
signal.Notify(col.signalsChannel, SIGHUP)
defer signal.Stop(col.signalsChannel)
// Only notify with SIGTERM and SIGINT if graceful shutdown is enabled.
if !col.set.DisableGracefulShutdown {
signal.Notify(col.signalsChannel, os.Interrupt, SIGTERM)
}
// Control loop: selects between channels for various interrupts - when this loop is broken, the collector exits.
// If a configuration reload fails, we return without waiting for graceful shutdown.
LOOP:
for {
select {
case err := <-col.configProvider.Watch():
if err != nil {
col.service.Logger().Error("Config watch failed", zap.Error(err))
break LOOP
}
if err := col.reloadConfiguration(ctx); err != nil {
return err
}
case err := <-col.asyncErrorChannel:
col.service.Logger().Error("Asynchronous error received, terminating process", zap.Error(err))
break LOOP
case s := <-col.signalsChannel:
col.service.Logger().Info("Received signal from OS", zap.String("signal", s.String()))
if s != SIGHUP {
break LOOP
}
if err := col.reloadConfiguration(ctx); err != nil {
return err
}
case <-col.shutdownChan:
col.service.Logger().Info("Received shutdown request")
break LOOP
case <-ctx.Done():
col.service.Logger().Info("Context done, terminating process", zap.Error(ctx.Err()))
// Call shutdown with background context as the passed in context has been canceled
return col.shutdown(context.Background()) //nolint:contextcheck
}
}
return col.shutdown(ctx)
}
func (col *Collector) shutdown(ctx context.Context) error {
col.setCollectorState(StateClosing)
// Accumulate errors and proceed with shutting down remaining components.
var errs error
if err := col.configProvider.Shutdown(ctx); err != nil {
errs = multierr.Append(errs, fmt.Errorf("failed to shutdown config provider: %w", err))
}
// shutdown service
if err := col.service.Shutdown(ctx); err != nil {
errs = multierr.Append(errs, fmt.Errorf("failed to shutdown service after error: %w", err))
}
col.setCollectorState(StateClosed)
return errs
}
// setCollectorState provides current state of the collector
func (col *Collector) setCollectorState(state State) {
col.state.Store(int64(state))
}
================================================
FILE: otelcol/collector_core.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelcol // import "go.opentelemetry.io/collector/otelcol"
import (
"sync/atomic"
"go.uber.org/zap/zapcore"
)
var _ zapcore.Core = (*collectorCore)(nil)
type collectorCore struct {
delegate atomic.Pointer[zapcore.Core]
}
func newCollectorCore(core zapcore.Core) *collectorCore {
cc := &collectorCore{}
cc.SetCore(core)
return cc
}
func (c *collectorCore) Enabled(l zapcore.Level) bool {
return c.loadDelegate().Enabled(l)
}
func (c *collectorCore) With(f []zapcore.Field) zapcore.Core {
return newCollectorCore(c.loadDelegate().With(f))
}
func (c *collectorCore) Check(e zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
core := c.loadDelegate()
if core.Enabled(e.Level) {
return ce.AddCore(e, core)
}
return ce
}
func (c *collectorCore) Write(e zapcore.Entry, f []zapcore.Field) error {
return c.loadDelegate().Write(e, f)
}
func (c *collectorCore) Sync() error {
return c.loadDelegate().Sync()
}
func (c *collectorCore) SetCore(core zapcore.Core) {
c.delegate.Store(&core)
}
// loadDelegate returns the delegate.
func (c *collectorCore) loadDelegate() zapcore.Core {
return *c.delegate.Load()
}
================================================
FILE: otelcol/collector_core_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelcol
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zapcore"
)
func Test_collectorCore_Enabled(t *testing.T) {
cc := newCollectorCore(newBufferedCore(zapcore.InfoLevel))
assert.True(t, cc.Enabled(zapcore.ErrorLevel))
assert.False(t, cc.Enabled(zapcore.DebugLevel))
}
func Test_collectorCore_Check(t *testing.T) {
t.Run("check passed", func(t *testing.T) {
bc := newBufferedCore(zapcore.InfoLevel)
cc := newCollectorCore(bc)
e := zapcore.Entry{
Level: zapcore.InfoLevel,
}
expected := &zapcore.CheckedEntry{}
expected = expected.AddCore(e, bc)
assert.Equal(t, expected, cc.Check(e, nil))
})
t.Run("check did not pass", func(t *testing.T) {
cc := newCollectorCore(newBufferedCore(zapcore.InfoLevel))
e := zapcore.Entry{
Level: zapcore.DebugLevel,
}
assert.Nil(t, cc.Check(e, nil))
})
}
func Test_collectorCore_With(t *testing.T) {
cc := newCollectorCore(newBufferedCore(zapcore.InfoLevel))
cc.loadDelegate().(*bufferedCore).context = []zapcore.Field{
{Key: "original", String: "context"},
}
inputs := []zapcore.Field{
{Key: "test", String: "passed"},
}
expected := []zapcore.Field{
{Key: "original", String: "context"},
{Key: "test", String: "passed"},
}
newCC := cc.With(inputs)
assert.Equal(t, expected, newCC.(*collectorCore).loadDelegate().(*bufferedCore).context)
}
func Test_collectorCore_Write(t *testing.T) {
cc := newCollectorCore(newBufferedCore(zapcore.InfoLevel))
e := zapcore.Entry{
Level: zapcore.DebugLevel,
Message: "test",
}
fields := []zapcore.Field{
{Key: "field1", String: "value1"},
}
err := cc.Write(e, fields)
require.NoError(t, err)
expected := loggedEntry{
e,
fields,
}
require.Len(t, cc.loadDelegate().(*bufferedCore).logs, 1)
require.Equal(t, expected, cc.loadDelegate().(*bufferedCore).logs[0])
}
func Test_collectorCore_Sync(t *testing.T) {
cc := newCollectorCore(newBufferedCore(zapcore.InfoLevel))
assert.NoError(t, cc.Sync())
}
func Test_collectorCore_SetCore(t *testing.T) {
cc := newCollectorCore(newBufferedCore(zapcore.InfoLevel))
newCore := newBufferedCore(zapcore.DebugLevel)
cc.SetCore(newCore)
assert.Equal(t, zapcore.DebugLevel, cc.loadDelegate().(*bufferedCore).LevelEnabler)
}
================================================
FILE: otelcol/collector_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package collector handles the command-line, configuration, and runs the OC collector.
package otelcol
import (
"context"
"errors"
"os"
"path/filepath"
"sync"
"testing"
"time"
"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.yaml.in/yaml/v3"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componentstatus"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/extension"
"go.opentelemetry.io/collector/processor/processortest"
"go.opentelemetry.io/collector/service/telemetry"
"go.opentelemetry.io/collector/service/telemetry/telemetrytest"
)
func TestStateString(t *testing.T) {
assert.Equal(t, "Starting", StateStarting.String())
assert.Equal(t, "Running", StateRunning.String())
assert.Equal(t, "Closing", StateClosing.String())
assert.Equal(t, "Closed", StateClosed.String())
assert.Equal(t, "UNKNOWN", State(13).String())
}
func TestCollectorStartAsGoRoutine(t *testing.T) {
set := CollectorSettings{
BuildInfo: component.NewDefaultBuildInfo(),
Factories: nopFactories,
ConfigProviderSettings: newDefaultConfigProviderSettings(t, []string{filepath.Join("testdata", "otelcol-nop.yaml")}),
}
col, err := NewCollector(set)
require.NoError(t, err)
wg := startCollector(context.Background(), t, col)
assert.Eventually(t, func() bool {
return StateRunning == col.GetState()
}, 2*time.Second, 200*time.Millisecond)
col.Shutdown()
col.Shutdown()
wg.Wait()
assert.Equal(t, StateClosed, col.GetState())
}
func TestCollectorCancelContext(t *testing.T) {
set := CollectorSettings{
BuildInfo: component.NewDefaultBuildInfo(),
Factories: nopFactories,
ConfigProviderSettings: newDefaultConfigProviderSettings(t, []string{filepath.Join("testdata", "otelcol-nop.yaml")}),
}
col, err := NewCollector(set)
require.NoError(t, err)
ctx, cancel := context.WithCancel(context.Background())
wg := startCollector(ctx, t, col)
assert.Eventually(t, func() bool {
return StateRunning == col.GetState()
}, 2*time.Second, 200*time.Millisecond)
cancel()
wg.Wait()
assert.Equal(t, StateClosed, col.GetState())
}
func TestCollectorStateAfterConfigChange(t *testing.T) {
var watcher confmap.WatcherFunc
fileProvider := newFakeProvider("file", func(_ context.Context, uri string, w confmap.WatcherFunc) (*confmap.Retrieved, error) {
watcher = w
conf := newConfFromFile(t, uri[5:])
return confmap.NewRetrieved(conf)
})
shutdownRequests := make(chan chan struct{})
shutdown := func(ctx context.Context) error {
unblock := make(chan struct{})
select {
case <-ctx.Done():
case shutdownRequests <- unblock:
select {
case <-unblock:
case <-ctx.Done():
}
}
return nil
}
factories, err := nopFactories()
require.NoError(t, err)
factories.Telemetry = telemetry.NewFactory(
func() component.Config { return fakeTelemetryConfig{} },
telemetrytest.WithLogger(zap.NewNop(), shutdown),
)
set := ConfigProviderSettings{
ResolverSettings: confmap.ResolverSettings{
URIs: []string{filepath.Join("testdata", "otelcol-nop.yaml")},
ProviderFactories: []confmap.ProviderFactory{
fileProvider,
},
},
}
col, err := NewCollector(CollectorSettings{
BuildInfo: component.NewDefaultBuildInfo(),
Factories: func() (Factories, error) { return factories, nil },
ConfigProviderSettings: set,
})
require.NoError(t, err)
wg := startCollector(context.Background(), t, col)
assert.Eventually(t, func() bool {
return StateRunning == col.GetState()
}, 10*time.Second, 10*time.Millisecond)
// On config change, the collector will internally close
// and recreate the service. The metrics reader will try to
// push to the OTLP endpoint. We block the request to check
// the state of the collector during the config change event.
watcher(&confmap.ChangeEvent{})
unblock := <-shutdownRequests
assert.Equal(t, StateClosing, col.GetState())
close(unblock)
assert.Eventually(t, func() bool {
return StateRunning == col.GetState()
}, 10*time.Second, 10*time.Millisecond)
// Do it again, but this time call Shutdown during the
// config change to make sure the internal service shutdown
// does not influence collector shutdown.
watcher(&confmap.ChangeEvent{})
unblock = <-shutdownRequests
assert.Equal(t, StateClosing, col.GetState())
col.Shutdown()
close(unblock)
// After the config reload, the final shutdown should occur.
close(<-shutdownRequests)
wg.Wait()
assert.Equal(t, StateClosed, col.GetState())
}
func TestCollectorReportError(t *testing.T) {
col, err := NewCollector(CollectorSettings{
BuildInfo: component.NewDefaultBuildInfo(),
Factories: nopFactories,
ConfigProviderSettings: newDefaultConfigProviderSettings(t, []string{filepath.Join("testdata", "otelcol-nop.yaml")}),
})
require.NoError(t, err)
wg := startCollector(context.Background(), t, col)
assert.Eventually(t, func() bool {
return StateRunning == col.GetState()
}, 2*time.Second, 200*time.Millisecond)
col.asyncErrorChannel <- errors.New("err2")
wg.Wait()
assert.Equal(t, StateClosed, col.GetState())
}
// NewStatusWatcherExtensionFactory returns a component.ExtensionFactory to construct a status watcher extension.
func NewStatusWatcherExtensionFactory(
onStatusChanged func(source *componentstatus.InstanceID, event *componentstatus.Event),
) extension.Factory {
return extension.NewFactory(
component.MustNewType("statuswatcher"),
func() component.Config {
return &struct{}{}
},
func(context.Context, extension.Settings, component.Config) (extension.Extension, error) {
return &statusWatcherExtension{onStatusChanged: onStatusChanged}, nil
},
component.StabilityLevelStable)
}
// statusWatcherExtension receives status events reported via component status reporting for testing
// purposes.
type statusWatcherExtension struct {
component.StartFunc
component.ShutdownFunc
onStatusChanged func(source *componentstatus.InstanceID, event *componentstatus.Event)
}
func (e statusWatcherExtension) ComponentStatusChanged(source *componentstatus.InstanceID, event *componentstatus.Event) {
e.onStatusChanged(source, event)
}
func TestComponentStatusWatcher(t *testing.T) {
factories, err := nopFactories()
require.NoError(t, err)
// Use a processor factory that creates "unhealthy" processor: one that
// always reports StatusRecoverableError after successful Start.
unhealthyProcessorFactory := processortest.NewUnhealthyProcessorFactory()
factories.Processors[unhealthyProcessorFactory.Type()] = unhealthyProcessorFactory
// Keep track of all status changes in a map.
changedComponents := map[*componentstatus.InstanceID][]componentstatus.Status{}
var mux sync.Mutex
onStatusChanged := func(source *componentstatus.InstanceID, event *componentstatus.Event) {
if source.ComponentID().Type() != unhealthyProcessorFactory.Type() {
return
}
mux.Lock()
defer mux.Unlock()
changedComponents[source] = append(changedComponents[source], event.Status())
}
// Add a "statuswatcher" extension that will receive notifications when processor
// status changes.
factory := NewStatusWatcherExtensionFactory(onStatusChanged)
factories.Extensions[factory.Type()] = factory
// Create a collector
col, err := NewCollector(CollectorSettings{
BuildInfo: component.NewDefaultBuildInfo(),
Factories: func() (Factories, error) { return factories, nil },
ConfigProviderSettings: newDefaultConfigProviderSettings(t, []string{filepath.Join("testdata", "otelcol-statuswatcher.yaml")}),
})
require.NoError(t, err)
// Start the newly created collector.
wg := startCollector(context.Background(), t, col)
// An unhealthy processor asynchronously reports a recoverable error. Depending on the Go
// Scheduler the statuses reported at startup will be one of the two valid sequences below.
startupStatuses1 := []componentstatus.Status{
componentstatus.StatusStarting,
componentstatus.StatusOK,
componentstatus.StatusRecoverableError,
}
startupStatuses2 := []componentstatus.Status{
componentstatus.StatusStarting,
componentstatus.StatusRecoverableError,
}
// the modulus of the actual statuses will match the modulus of the startup statuses
startupStatuses := func(actualStatuses []componentstatus.Status) []componentstatus.Status {
if len(actualStatuses)%2 == 1 {
return startupStatuses1
}
return startupStatuses2
}
// The "unhealthy" processors will now begin to asynchronously report StatusRecoverableError.
// We expect to see these reports.
assert.Eventually(t, func() bool {
mux.Lock()
defer mux.Unlock()
for k, v := range changedComponents {
// All processors must report a status change with the same ID
assert.Equal(t, component.NewID(unhealthyProcessorFactory.Type()), k.ComponentID())
// And all must have a valid startup sequence
assert.Equal(t, startupStatuses(v), v)
}
// We have 3 processors with exactly the same ID in otelcol-statuswatcher.yaml
// We must have exactly 3 items in our map. This ensures that the "source" argument
// passed to status change func is unique per instance of source component despite
// components having the same IDs (having same ID for different component instances
// is a normal situation for processors).
return len(changedComponents) == 3
}, 2*time.Second, time.Millisecond*100)
col.Shutdown()
wg.Wait()
// Check for additional statuses after Shutdown.
for _, v := range changedComponents {
expectedStatuses := append([]componentstatus.Status{}, startupStatuses(v)...)
expectedStatuses = append(expectedStatuses, componentstatus.StatusStopping, componentstatus.StatusStopped)
assert.Equal(t, expectedStatuses, v)
}
assert.Equal(t, StateClosed, col.GetState())
}
func TestCollectorSendSignal(t *testing.T) {
col, err := NewCollector(CollectorSettings{
BuildInfo: component.NewDefaultBuildInfo(),
Factories: nopFactories,
ConfigProviderSettings: newDefaultConfigProviderSettings(t, []string{filepath.Join("testdata", "otelcol-nop.yaml")}),
})
require.NoError(t, err)
wg := startCollector(context.Background(), t, col)
assert.Eventually(t, func() bool {
return StateRunning == col.GetState()
}, 2*time.Second, 200*time.Millisecond)
col.signalsChannel <- SIGHUP
assert.Eventually(t, func() bool {
return StateRunning == col.GetState()
}, 2*time.Second, 200*time.Millisecond)
col.signalsChannel <- SIGTERM
wg.Wait()
assert.Equal(t, StateClosed, col.GetState())
}
func TestCollectorFailedShutdown(t *testing.T) {
t.Skip("This test was using telemetry shutdown failure, switch to use a component that errors on shutdown.")
col, err := NewCollector(CollectorSettings{
BuildInfo: component.NewDefaultBuildInfo(),
Factories: nopFactories,
ConfigProviderSettings: newDefaultConfigProviderSettings(t, []string{filepath.Join("testdata", "otelcol-nop.yaml")}),
})
require.NoError(t, err)
wg := sync.WaitGroup{}
wg.Go(func() {
assert.EqualError(t, col.Run(context.Background()), "failed to shutdown collector telemetry: err1")
})
assert.Eventually(t, func() bool {
return StateRunning == col.GetState()
}, 2*time.Second, 200*time.Millisecond)
col.Shutdown()
wg.Wait()
assert.Equal(t, StateClosed, col.GetState())
}
func TestCollectorStartInvalidConfig(t *testing.T) {
col, err := NewCollector(CollectorSettings{
BuildInfo: component.NewDefaultBuildInfo(),
Factories: nopFactories,
ConfigProviderSettings: newDefaultConfigProviderSettings(t, []string{filepath.Join("testdata", "otelcol-invalid.yaml")}),
})
require.NoError(t, err)
assert.EqualError(t, col.Run(context.Background()), "invalid configuration: service::pipelines::traces: references processor \"invalid\" which is not configured")
}
func TestNewCollectorInvalidConfigProviderSettings(t *testing.T) {
_, err := NewCollector(CollectorSettings{
BuildInfo: component.NewDefaultBuildInfo(),
Factories: nopFactories,
ConfigProviderSettings: ConfigProviderSettings{},
})
require.Error(t, err)
}
func TestNewCollectorUseConfig(t *testing.T) {
set := newDefaultConfigProviderSettings(t, []string{filepath.Join("testdata", "otelcol-nop.yaml")})
col, err := NewCollector(CollectorSettings{
BuildInfo: component.NewDefaultBuildInfo(),
Factories: nopFactories,
ConfigProviderSettings: set,
})
require.NoError(t, err)
require.NotNil(t, col.configProvider)
}
func TestNewCollectorValidatesResolverSettings(t *testing.T) {
set := ConfigProviderSettings{
ResolverSettings: confmap.ResolverSettings{
URIs: []string{filepath.Join("testdata", "otelcol-nop.yaml")},
},
}
_, err := NewCollector(CollectorSettings{
BuildInfo: component.NewDefaultBuildInfo(),
Factories: nopFactories,
ConfigProviderSettings: set,
})
require.Error(t, err)
}
func TestCollectorRun(t *testing.T) {
tests := map[string]struct {
factories func() (Factories, error)
configFile string
}{
"nop": {
factories: nopFactories,
configFile: "otelcol-nop.yaml",
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
set := CollectorSettings{
BuildInfo: component.NewDefaultBuildInfo(),
Factories: test.factories,
ConfigProviderSettings: newDefaultConfigProviderSettings(t, []string{filepath.Join("testdata", test.configFile)}),
}
col, err := NewCollector(set)
require.NoError(t, err)
wg := startCollector(context.Background(), t, col)
col.Shutdown()
wg.Wait()
assert.Equal(t, StateClosed, col.GetState())
})
}
}
func TestCollectorRun_AfterShutdown(t *testing.T) {
set := CollectorSettings{
BuildInfo: component.NewDefaultBuildInfo(),
Factories: nopFactories,
ConfigProviderSettings: newDefaultConfigProviderSettings(t, []string{filepath.Join("testdata", "otelcol-nop.yaml")}),
}
col, err := NewCollector(set)
require.NoError(t, err)
// Calling shutdown before collector is running should cause it to return quickly
require.NotPanics(t, func() { col.Shutdown() })
wg := startCollector(context.Background(), t, col)
col.Shutdown()
wg.Wait()
assert.Equal(t, StateClosed, col.GetState())
}
func TestCollectorRun_Errors(t *testing.T) {
tests := map[string]struct {
settings CollectorSettings
expectedErr string
}{
"factories_error": {
settings: CollectorSettings{
Factories: func() (Factories, error) {
return Factories{}, errors.New("no factories for you")
},
ConfigProviderSettings: newDefaultConfigProviderSettings(t, []string{filepath.Join("testdata", "otelcol-nop.yaml")}),
},
expectedErr: "failed to initialize factories: no factories for you",
},
"invalid_processor": {
settings: CollectorSettings{
Factories: nopFactories,
ConfigProviderSettings: newDefaultConfigProviderSettings(t, []string{filepath.Join("testdata", "otelcol-invalid.yaml")}),
},
expectedErr: `invalid configuration: service::pipelines::traces: references processor "invalid" which is not configured`,
},
"invalid_telemetry_config": {
settings: CollectorSettings{
BuildInfo: component.NewDefaultBuildInfo(),
Factories: nopFactories,
ConfigProviderSettings: newDefaultConfigProviderSettings(t, []string{filepath.Join("testdata", "otelcol-invalid-telemetry.yaml")}),
},
expectedErr: "failed to get config: cannot unmarshal the configuration: decoding failed due to the following error(s):\n\n'service.telemetry' has invalid keys: unknown",
},
"missing_telemetry_factory": {
settings: CollectorSettings{
BuildInfo: component.NewDefaultBuildInfo(),
Factories: func() (Factories, error) {
factories, _ := nopFactories()
factories.Telemetry = nil
return factories, nil
},
ConfigProviderSettings: newDefaultConfigProviderSettings(t, []string{filepath.Join("testdata", "otelcol-otelconftelemetry.yaml")}),
},
expectedErr: "failed to get config: cannot unmarshal the configuration: otelcol.Factories.Telemetry must not be nil. For example, you can use otelconftelemetry.NewFactory to build a telemetry factory",
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
col, err := NewCollector(test.settings)
require.NoError(t, err)
// Expect run to error
err = col.Run(context.Background())
require.EqualError(t, err, test.expectedErr)
// Expect state to be closed
assert.Equal(t, StateClosed, col.GetState())
})
}
}
func TestCollectorDryRun(t *testing.T) {
tests := map[string]struct {
settings CollectorSettings
expectedErr string
}{
"factories_error": {
settings: CollectorSettings{
Factories: func() (Factories, error) {
return Factories{}, errors.New("no factories for you")
},
ConfigProviderSettings: newDefaultConfigProviderSettings(t, []string{filepath.Join("testdata", "otelcol-nop.yaml")}),
},
expectedErr: "failed to initialize factories: no factories for you",
},
"invalid_processor": {
settings: CollectorSettings{
BuildInfo: component.NewDefaultBuildInfo(),
Factories: nopFactories,
ConfigProviderSettings: newDefaultConfigProviderSettings(t, []string{filepath.Join("testdata", "otelcol-invalid.yaml")}),
},
expectedErr: `service::pipelines::traces: references processor "invalid" which is not configured`,
},
"invalid_connector_use_unused_exp": {
settings: CollectorSettings{
BuildInfo: component.NewDefaultBuildInfo(),
Factories: nopFactories,
ConfigProviderSettings: newDefaultConfigProviderSettings(t, []string{filepath.Join("testdata", "otelcol-invalid-connector-unused-exp.yaml")}),
},
expectedErr: `failed to build pipelines: connector "nop/connector1" used as receiver in [logs/in2] pipeline but not used in any supported exporter pipeline`,
},
"invalid_connector_use_unused_rec": {
settings: CollectorSettings{
BuildInfo: component.NewDefaultBuildInfo(),
Factories: nopFactories,
ConfigProviderSettings: newDefaultConfigProviderSettings(t, []string{filepath.Join("testdata", "otelcol-invalid-connector-unused-rec.yaml")}),
},
expectedErr: `failed to build pipelines: connector "nop/connector1" used as exporter in [logs/in2] pipeline but not used in any supported receiver pipeline`,
},
"cyclic_connector": {
settings: CollectorSettings{
BuildInfo: component.NewDefaultBuildInfo(),
Factories: nopFactories,
ConfigProviderSettings: newDefaultConfigProviderSettings(t, []string{filepath.Join("testdata", "otelcol-cyclic-connector.yaml")}),
},
expectedErr: `failed to build pipelines: cycle detected: connector "nop/forward" (traces to traces) -> connector "nop/forward" (traces to traces)`,
},
"invalid_telemetry_config": {
settings: CollectorSettings{
BuildInfo: component.NewDefaultBuildInfo(),
Factories: nopFactories,
ConfigProviderSettings: newDefaultConfigProviderSettings(t, []string{filepath.Join("testdata", "otelcol-invalid-telemetry.yaml")}),
},
expectedErr: "failed to get config: cannot unmarshal the configuration: decoding failed due to the following error(s):\n\n'service.telemetry' has invalid keys: unknown",
},
"missing_telemetry_factory": {
settings: CollectorSettings{
BuildInfo: component.NewDefaultBuildInfo(),
Factories: func() (Factories, error) {
factories, _ := nopFactories()
factories.Telemetry = nil
return factories, nil
},
ConfigProviderSettings: newDefaultConfigProviderSettings(t, []string{filepath.Join("testdata", "otelcol-otelconftelemetry.yaml")}),
},
expectedErr: "failed to get config: cannot unmarshal the configuration: otelcol.Factories.Telemetry must not be nil. For example, you can use otelconftelemetry.NewFactory to build a telemetry factory",
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
col, err := NewCollector(test.settings)
require.NoError(t, err)
err = col.DryRun(context.Background())
if test.expectedErr == "" {
require.NoError(t, err)
} else {
require.EqualError(t, err, test.expectedErr)
}
})
}
}
func startCollector(ctx context.Context, t *testing.T, col *Collector) *sync.WaitGroup {
wg := &sync.WaitGroup{}
wg.Go(func() {
assert.NoError(t, col.Run(ctx))
})
return wg
}
type failureProvider struct{}
func newFailureProvider(_ confmap.ProviderSettings) confmap.Provider {
return &failureProvider{}
}
func (fmp *failureProvider) Retrieve(context.Context, string, confmap.WatcherFunc) (*confmap.Retrieved, error) {
return nil, errors.New("a failure occurred during configuration retrieval")
}
func (*failureProvider) Scheme() string {
return "file"
}
func (*failureProvider) Shutdown(context.Context) error {
return nil
}
type fakeProvider struct {
scheme string
ret func(ctx context.Context, uri string, watcher confmap.WatcherFunc) (*confmap.Retrieved, error)
logger *zap.Logger
}
func (f *fakeProvider) Retrieve(ctx context.Context, uri string, watcher confmap.WatcherFunc) (*confmap.Retrieved, error) {
return f.ret(ctx, uri, watcher)
}
func (f *fakeProvider) Scheme() string {
return f.scheme
}
func (f *fakeProvider) Shutdown(context.Context) error {
return nil
}
func newFakeProvider(scheme string, ret func(ctx context.Context, uri string, watcher confmap.WatcherFunc) (*confmap.Retrieved, error)) confmap.ProviderFactory {
return confmap.NewProviderFactory(func(ps confmap.ProviderSettings) confmap.Provider {
return &fakeProvider{
scheme: scheme,
ret: ret,
logger: ps.Logger,
}
})
}
func newEnvProvider() confmap.ProviderFactory {
return newFakeProvider("env", func(_ context.Context, uri string, _ confmap.WatcherFunc) (*confmap.Retrieved, error) {
// When using `env` as the default scheme for tests, the uri will not include `env:`.
// Instead of duplicating the switch cases, the scheme is added instead.
if uri[0:4] != "env:" {
uri = "env:" + uri
}
switch uri {
case "env:COMPLEX_VALUE":
return confmap.NewRetrieved([]any{"localhost:3042"})
case "env:HOST":
return confmap.NewRetrieved("localhost")
case "env:OS":
return confmap.NewRetrieved("ubuntu")
case "env:PR":
return confmap.NewRetrieved("amd")
case "env:PORT":
return confmap.NewRetrieved(3044)
case "env:INT":
return confmap.NewRetrieved(1)
case "env:INT32":
return confmap.NewRetrieved(32)
case "env:INT64":
return confmap.NewRetrieved(64)
case "env:FLOAT32":
return confmap.NewRetrieved(float32(3.25))
case "env:FLOAT64":
return confmap.NewRetrieved(float64(6.4))
case "env:BOOL":
return confmap.NewRetrieved(true)
}
return nil, errors.New("impossible")
})
}
func newDefaultConfigProviderSettings(tb testing.TB, uris []string) ConfigProviderSettings {
fileProvider := newFakeProvider("file", func(_ context.Context, uri string, _ confmap.WatcherFunc) (*confmap.Retrieved, error) {
return confmap.NewRetrieved(newConfFromFile(tb, uri[5:]))
})
return ConfigProviderSettings{
ResolverSettings: confmap.ResolverSettings{
URIs: uris,
ProviderFactories: []confmap.ProviderFactory{
fileProvider,
newEnvProvider(),
},
},
}
}
// newConfFromFile creates a new Conf by reading the given file.
func newConfFromFile(tb testing.TB, fileName string) map[string]any {
content, err := os.ReadFile(filepath.Clean(fileName))
require.NoErrorf(tb, err, "unable to read the file %v", fileName)
var data map[string]any
require.NoError(tb, yaml.Unmarshal(content, &data), "unable to parse yaml")
return confmap.NewFromStringMap(data).ToStringMap()
}
func TestProviderAndConverterModules(t *testing.T) {
set := CollectorSettings{
BuildInfo: component.NewDefaultBuildInfo(),
Factories: nopFactories,
ConfigProviderSettings: newDefaultConfigProviderSettings(t, []string{filepath.Join("testdata", "otelcol-nop.yaml")}),
ProviderModules: map[string]string{
"nop": "go.opentelemetry.io/collector/confmap/provider/testprovider v1.2.3",
},
ConverterModules: []string{
"go.opentelemetry.io/collector/converter/testconverter v1.2.3",
},
}
col, err := NewCollector(set)
require.NoError(t, err)
wg := startCollector(context.Background(), t, col)
require.NoError(t, err)
providerModules := map[string]string{
"nop": "go.opentelemetry.io/collector/confmap/provider/testprovider v1.2.3",
}
converterModules := []string{
"go.opentelemetry.io/collector/converter/testconverter v1.2.3",
}
assert.Equal(t, providerModules, col.set.ProviderModules)
assert.Equal(t, converterModules, col.set.ConverterModules)
col.Shutdown()
wg.Wait()
}
func TestCollectorLoggingOptions(t *testing.T) {
// Use zap observer to verify that LoggingOptions are applied
observerCore, observedLogs := observer.New(zapcore.InfoLevel)
factories, err := nopFactories()
require.NoError(t, err)
// Create a custom telemetry factory that uses BuildZapLogger
// This ensures BuildZapLogger (which includes LoggingOptions) is used
factories.Telemetry = telemetry.NewFactory(
func() component.Config { return fakeTelemetryConfig{} },
telemetry.WithCreateLogger(
func(_ context.Context, set telemetry.LoggerSettings, _ component.Config) (
*zap.Logger, component.ShutdownFunc, error,
) {
require.Empty(t, set.ZapOptions) // injected through BuidlZapLogger
logger, buildErr := set.BuildZapLogger(zap.NewDevelopmentConfig())
return logger, nil, buildErr
},
),
)
set := CollectorSettings{
BuildInfo: component.NewDefaultBuildInfo(),
Factories: func() (Factories, error) { return factories, nil },
ConfigProviderSettings: newDefaultConfigProviderSettings(t,
[]string{filepath.Join("testdata", "otelcol-nop.yaml")},
),
LoggingOptions: []zap.Option{
zap.WrapCore(func(zapcore.Core) zapcore.Core {
return observerCore
}),
},
}
col, err := NewCollector(set)
require.NoError(t, err)
// Start and stop the collector.
wg := startCollector(context.Background(), t, col)
assert.Eventually(t, func() bool {
return StateRunning == col.GetState() && col.service != nil
}, 2*time.Second, 200*time.Millisecond)
col.Shutdown()
wg.Wait()
// Check that logs have been redirected to our observer core,
// which proves that LoggingOptions were applied.
entries := observedLogs.All()
require.NotEmpty(t, entries, "Logger should have logged messages")
}
================================================
FILE: otelcol/collector_windows.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:build windows
package otelcol // import "go.opentelemetry.io/collector/otelcol"
import (
"context"
"flag"
"fmt"
"os"
"slices"
"time"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/eventlog"
"go.opentelemetry.io/collector/featuregate"
)
type windowsService struct {
settings CollectorSettings
col *Collector
flags *flag.FlagSet
}
// NewSvcHandler constructs a new svc.Handler using the given CollectorSettings.
func NewSvcHandler(set CollectorSettings) svc.Handler {
return &windowsService{settings: set, flags: flags(featuregate.GlobalRegistry())}
}
// 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
}
colErrorChannel := make(chan error, 1)
changes <- svc.Status{State: svc.StartPending}
if err = s.start(elog, colErrorChannel); 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(colErrorChannel); 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, colErrorChannel chan error) error {
// Parse all the flags manually.
if err := s.flags.Parse(os.Args[1:]); err != nil {
return err
}
var err error
err = updateSettingsUsingFlags(&s.settings, s.flags)
if err != nil {
return err
}
s.col, err = NewCollector(s.settings)
if err != nil {
return err
}
// Override the Zap logger to write to the Windows Event Log
// if no file output is specified.
s.col.buildZapLogger = func(cfg zap.Config, opts ...zap.Option) (*zap.Logger, error) {
for _, output := range cfg.OutputPaths {
if output != "stdout" && output != "stderr" {
// A file has been specified in the configuration,
// so do not use the Windows Event Log.
return cfg.Build(opts...)
}
}
opts = slices.Insert(opts, 0, zap.WrapCore(withWindowsCore(elog)))
return cfg.Build(opts...)
}
// col.Run 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() {
colErrorChannel <- s.col.Run(context.Background())
}()
// wait until the collector server is in the Running state
go func() {
for {
state := s.col.GetState()
if state == StateRunning {
colErrorChannel <- nil
break
}
time.Sleep(time.Millisecond * 200)
}
}()
// wait until the collector server is in the Running state, or an error was returned
return <-colErrorChannel
}
func (s *windowsService) stop(colErrorChannel chan error) error {
s.col.Shutdown()
// return the response of col.Start
return <-colErrorChannel
}
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
}
var _ zapcore.Core = (*windowsEventLogCore)(nil)
type windowsEventLogCore struct {
core zapcore.Core
elog *eventlog.Log
encoder zapcore.Encoder
}
func (w windowsEventLogCore) Enabled(level zapcore.Level) bool {
return w.core.Enabled(level)
}
func (w windowsEventLogCore) With(fields []zapcore.Field) zapcore.Core {
enc := w.encoder.Clone()
for _, field := range fields {
field.AddTo(enc)
}
return windowsEventLogCore{
core: w.core,
elog: w.elog,
encoder: enc,
}
}
func (w windowsEventLogCore) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
if w.Enabled(ent.Level) {
return ce.AddCore(ent, w)
}
return ce
}
func (w windowsEventLogCore) Write(ent zapcore.Entry, fields []zapcore.Field) error {
buf, err := w.encoder.EncodeEntry(ent, fields)
if err != nil {
_ = w.elog.Warning(2, fmt.Sprintf("failed encoding log entry %v\r\n", err))
return err
}
msg := buf.String()
buf.Free()
switch ent.Level {
case zapcore.FatalLevel, zapcore.PanicLevel, zapcore.DPanicLevel:
// golang.org/x/sys/windows/svc/eventlog does not support Critical level event logs
return w.elog.Error(3, msg)
case zapcore.ErrorLevel:
return w.elog.Error(3, msg)
case zapcore.WarnLevel:
return w.elog.Warning(2, msg)
case zapcore.InfoLevel:
return w.elog.Info(1, msg)
}
// We would not be here if debug were disabled so log as info to not drop.
return w.elog.Info(1, msg)
}
func (w windowsEventLogCore) Sync() error {
return w.core.Sync()
}
func withWindowsCore(elog *eventlog.Log) func(zapcore.Core) zapcore.Core {
return func(core zapcore.Core) zapcore.Core {
// Use the Windows Event Log
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.LineEnding = "\r\n"
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
return windowsEventLogCore{core, elog, zapcore.NewConsoleEncoder(encoderConfig)}
}
}
================================================
FILE: otelcol/collector_windows_service_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:build windows && win32service
package otelcol
import (
"encoding/xml"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"
"time"
"github.com/stretchr/testify/require"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/mgr"
)
const (
collectorServiceName = "otelcorecol"
)
// Test the collector as a Windows service.
// The test assumes that the service and respective event source are already created.
//
// To test locally:
// * Build the binary:
// - make otelcorecol
//
// * Install the Windows service
// - New-Service -Name "otelcorecol" -StartupType "Manual" -BinaryPathName "${PWD}\bin\otelcorecol_windows_$(go env GOARCH) --config ${PWD}\examples\local\otel-config.yaml"
//
// * Create event log source
// - eventcreate.exe /t information /id 1 /l application /d "Creating event provider for 'otelcorecol'" /so otelcorecol
//
// The test also must be executed with administrative privileges.
func TestCollectorAsService(t *testing.T) {
collector_executable, err := filepath.Abs(filepath.Join("..", "bin", fmt.Sprintf("otelcorecol_windows_%s", runtime.GOARCH)))
require.NoError(t, err)
_, err = os.Stat(collector_executable)
require.NoError(t, err)
scm, err := mgr.Connect()
require.NoError(t, err)
defer scm.Disconnect()
service, err := scm.OpenService(collectorServiceName)
require.NoError(t, err)
defer service.Close()
tests := []struct {
name string
configFile string
expectStartFailure bool
customSetup func(*testing.T)
customValidation func(*testing.T)
}{
{
name: "Default",
configFile: filepath.Join("..", "examples", "local", "otel-config.yaml"),
},
{
name: "ConfigFileNotFound",
configFile: filepath.Join(".", "non", "existent", "otel-config.yaml"),
expectStartFailure: true,
},
{
name: "LogToFile",
configFile: filepath.Join(".", "testdata", "otelcol-log-to-file.yaml"),
customSetup: func(t *testing.T) {
// Create the folder and clean the log file if it exists
programDataPath := os.Getenv("ProgramData")
logsPath := filepath.Join(programDataPath, "OpenTelemetry", "Collector", "Logs")
err := os.MkdirAll(logsPath, os.ModePerm)
require.NoError(t, err)
logFilePath := filepath.Join(logsPath, "otelcol.log")
err = os.Remove(logFilePath)
if err != nil && !os.IsNotExist(err) {
require.NoError(t, err)
}
},
customValidation: func(t *testing.T) {
// Check that the log file was created
programDataPath := os.Getenv("ProgramData")
logsPath := filepath.Join(programDataPath, "OpenTelemetry", "Collector", "Logs")
logFilePath := filepath.Join(logsPath, "otelcol.log")
fileinfo, err := os.Stat(logFilePath)
require.NoError(t, err)
require.NotEmpty(t, fileinfo.Size())
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
serviceConfig, err := service.Config()
require.NoError(t, err)
// Setup the command line to launch the collector as a service
fullConfigPath, err := filepath.Abs(tt.configFile)
require.NoError(t, err)
serviceConfig.BinaryPathName = fmt.Sprintf("\"%s\" --config \"%s\"", collector_executable, fullConfigPath)
err = service.UpdateConfig(serviceConfig)
require.NoError(t, err)
if tt.customSetup != nil {
tt.customSetup(t)
}
startTime := time.Now()
err = service.Start()
require.NoError(t, err)
expectedState := svc.Running
if tt.expectStartFailure {
expectedState = svc.Stopped
} else {
defer func() {
_, err = service.Control(svc.Stop)
require.NoError(t, err)
require.Eventually(t, func() bool {
status, _ := service.Query()
return status.State == svc.Stopped
}, 10*time.Second, 500*time.Millisecond)
}()
}
// Wait for the service to reach the expected state
require.Eventually(t, func() bool {
status, _ := service.Query()
return status.State == expectedState
}, 10*time.Second, 500*time.Millisecond)
if tt.customValidation != nil {
tt.customValidation(t)
} else {
// Read the events from the otelcorecol source and check that they were emitted after the service
// command started. This is a simple validation that the messages are being logged on the
// Windows event log.
cmd := exec.Command("wevtutil.exe", "qe", "Application", "/c:1", "/rd:true", "/f:RenderedXml", "/q:*[System[Provider[@Name='otelcorecol']]]")
out, err := cmd.CombinedOutput()
require.NoError(t, err)
var e Event
require.NoError(t, xml.Unmarshal([]byte(out), &e))
eventTime, err := time.Parse("2006-01-02T15:04:05.9999999Z07:00", e.System.TimeCreated.SystemTime)
require.NoError(t, err)
require.True(t, eventTime.After(startTime.In(time.UTC)))
}
})
}
}
// Helper types to read the XML events from the event log using wevtutil
type Event struct {
XMLName xml.Name `xml:"Event"`
System System `xml:"System"`
Data string `xml:"EventData>Data"`
}
type System struct {
Provider Provider `xml:"Provider"`
EventID int `xml:"EventID"`
Version int `xml:"Version"`
Level int `xml:"Level"`
Task int `xml:"Task"`
Opcode int `xml:"Opcode"`
Keywords string `xml:"Keywords"`
TimeCreated TimeCreated `xml:"TimeCreated"`
EventRecordID int `xml:"EventRecordID"`
Execution Execution `xml:"Execution"`
Channel string `xml:"Channel"`
Computer string `xml:"Computer"`
}
type Provider struct {
Name string `xml:"Name,attr"`
}
type TimeCreated struct {
SystemTime string `xml:"SystemTime,attr"`
}
type Execution struct {
ProcessID string `xml:"ProcessID,attr"`
ThreadID string `xml:"ThreadID,attr"`
}
================================================
FILE: otelcol/collector_windows_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:build windows
package otelcol
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"golang.org/x/sys/windows/svc"
"go.opentelemetry.io/collector/component"
)
func TestNewSvcHandler(t *testing.T) {
oldArgs := os.Args
defer func() { os.Args = oldArgs }()
filePath := filepath.Join("testdata", "otelcol-nop.yaml")
os.Args = []string{"otelcol", "--config", filePath}
s := NewSvcHandler(CollectorSettings{BuildInfo: component.NewDefaultBuildInfo(), Factories: nopFactories, ConfigProviderSettings: newDefaultConfigProviderSettings(t, []string{filePath})})
colDone := make(chan struct{})
requests := make(chan svc.ChangeRequest)
changes := make(chan svc.Status)
go func() {
defer close(colDone)
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)
<-colDone
}
================================================
FILE: otelcol/command.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelcol // import "go.opentelemetry.io/collector/otelcol"
//go:generate mdatagen metadata.yaml
import (
"errors"
"flag"
"fmt"
"os"
"text/tabwriter"
"github.com/spf13/cobra"
"go.opentelemetry.io/collector/featuregate"
)
// NewCommand constructs a new cobra.Command using the given CollectorSettings.
// Any URIs specified in CollectorSettings.ConfigProviderSettings.ResolverSettings.URIs
// are considered defaults and will be overwritten by config flags passed as
// command-line arguments to the executable.
// At least one Provider must be set.
func NewCommand(set CollectorSettings) *cobra.Command {
flagSet := flags(featuregate.GlobalRegistry())
rootCmd := &cobra.Command{
Use: set.BuildInfo.Command,
Version: set.BuildInfo.Version,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, _ []string) error {
err := updateSettingsUsingFlags(&set, flagSet)
if err != nil {
return err
}
col, err := NewCollector(set)
if err != nil {
return err
}
return col.Run(cmd.Context())
},
}
rootCmd.AddCommand(newFeatureGateCommand())
rootCmd.AddCommand(newComponentsCommand(set))
rootCmd.AddCommand(newValidateSubCommand(set, flagSet))
rootCmd.AddCommand(newConfigPrintSubCommand(set, flagSet))
rootCmd.Flags().AddGoFlagSet(flagSet)
return rootCmd
}
// Puts command line flags from flags into the CollectorSettings, to be used during config resolution.
func updateSettingsUsingFlags(set *CollectorSettings, flags *flag.FlagSet) error {
resolverSet := &set.ConfigProviderSettings.ResolverSettings
configFlags := getConfigFlag(flags)
if len(configFlags) > 0 {
resolverSet.URIs = configFlags
}
if len(resolverSet.URIs) == 0 {
return errors.New("at least one config flag must be provided")
}
if set.ConfigProviderSettings.ResolverSettings.DefaultScheme == "" {
set.ConfigProviderSettings.ResolverSettings.DefaultScheme = "env"
}
if len(resolverSet.ProviderFactories) == 0 {
return errors.New("at least one Provider must be supplied")
}
return nil
}
func newFeatureGateCommand() *cobra.Command {
return &cobra.Command{
Use: "featuregate [feature-id]",
Short: "Display feature gates information",
Long: "Display information about available feature gates and their status",
Args: cobra.MaximumNArgs(1),
RunE: func(_ *cobra.Command, args []string) error {
if len(args) > 0 {
found := false
featuregate.GlobalRegistry().VisitAll(func(g *featuregate.Gate) {
if g.ID() == args[0] {
found = true
fmt.Printf("Feature: %s\n", g.ID())
fmt.Printf("Enabled: %v\n", g.IsEnabled())
fmt.Printf("Stage: %s\n", g.Stage())
fmt.Printf("Description: %s\n", g.Description())
fmt.Printf("From Version: %s\n", g.FromVersion())
if g.ToVersion() != "" {
fmt.Printf("To Version: %s\n", g.ToVersion())
}
}
})
if !found {
return fmt.Errorf("feature %q not found", args[0])
}
return nil
}
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintf(w, "ID\tEnabled\tStage\tDescription\n")
featuregate.GlobalRegistry().VisitAll(func(g *featuregate.Gate) {
fmt.Fprintf(w, "%s\t%v\t%s\t%s\n",
g.ID(),
g.IsEnabled(),
g.Stage(),
g.Description())
})
return w.Flush()
},
}
}
================================================
FILE: otelcol/command_components.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelcol // import "go.opentelemetry.io/collector/otelcol"
import (
"fmt"
"sort"
"github.com/spf13/cobra"
"go.yaml.in/yaml/v3"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/connector"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/extension"
"go.opentelemetry.io/collector/internal/componentalias"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/receiver"
)
type componentWithStability struct {
Name component.Type
Module string
Stability map[string]string
}
type componentWithoutStability struct {
Scheme string `yaml:",omitempty"`
Module string
}
type componentsOutput struct {
BuildInfo component.BuildInfo
Receivers []componentWithStability
Processors []componentWithStability
Exporters []componentWithStability
Connectors []componentWithStability
Extensions []componentWithStability
Providers []componentWithoutStability
Converters []componentWithoutStability `yaml:",omitempty"`
}
// newComponentsCommand constructs a new components command using the given CollectorSettings.
func newComponentsCommand(set CollectorSettings) *cobra.Command {
return &cobra.Command{
Use: "components",
Short: "Outputs available components in this collector distribution",
Long: "Outputs available components in this collector distribution including their stability levels. The output format is not stable and can change between releases.",
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, _ []string) error {
factories, err := set.Factories()
if err != nil {
return fmt.Errorf("failed to initialize factories: %w", err)
}
components := componentsOutput{}
for _, con := range sortFactoriesByType[connector.Factory](factories.Connectors) {
components.Connectors = append(components.Connectors, componentWithStability{
Name: con.Type(),
Module: factories.ConnectorModules[con.Type()],
Stability: map[string]string{
"logs-to-logs": con.LogsToLogsStability().String(),
"logs-to-metrics": con.LogsToMetricsStability().String(),
"logs-to-traces": con.LogsToTracesStability().String(),
"metrics-to-logs": con.MetricsToLogsStability().String(),
"metrics-to-metrics": con.MetricsToMetricsStability().String(),
"metrics-to-traces": con.MetricsToTracesStability().String(),
"traces-to-logs": con.TracesToLogsStability().String(),
"traces-to-metrics": con.TracesToMetricsStability().String(),
"traces-to-traces": con.TracesToTracesStability().String(),
},
})
}
for _, ext := range sortFactoriesByType[extension.Factory](factories.Extensions) {
components.Extensions = append(components.Extensions, componentWithStability{
Name: ext.Type(),
Module: factories.ExtensionModules[ext.Type()],
Stability: map[string]string{
"extension": ext.Stability().String(),
},
})
}
for _, prs := range sortFactoriesByType[processor.Factory](factories.Processors) {
components.Processors = append(components.Processors, componentWithStability{
Name: prs.Type(),
Module: factories.ProcessorModules[prs.Type()],
Stability: map[string]string{
"logs": prs.LogsStability().String(),
"metrics": prs.MetricsStability().String(),
"traces": prs.TracesStability().String(),
},
})
}
for _, rcv := range sortFactoriesByType[receiver.Factory](factories.Receivers) {
components.Receivers = append(components.Receivers, componentWithStability{
Name: rcv.Type(),
Module: factories.ReceiverModules[rcv.Type()],
Stability: map[string]string{
"logs": rcv.LogsStability().String(),
"metrics": rcv.MetricsStability().String(),
"traces": rcv.TracesStability().String(),
},
})
}
for _, exp := range sortFactoriesByType[exporter.Factory](factories.Exporters) {
components.Exporters = append(components.Exporters, componentWithStability{
Name: exp.Type(),
Module: factories.ExporterModules[exp.Type()],
Stability: map[string]string{
"logs": exp.LogsStability().String(),
"metrics": exp.MetricsStability().String(),
"traces": exp.TracesStability().String(),
},
})
}
components.BuildInfo = set.BuildInfo
components.Providers = append(components.Providers, sortProvidersByScheme(
set.ProviderModules,
set.ConfigProviderSettings.ResolverSettings.ProviderFactories,
set.ConfigProviderSettings.ResolverSettings.ProviderSettings,
)...)
components.Converters = append(components.Converters, sortConverterModules(set.ConverterModules)...)
yamlData, err := yaml.Marshal(components)
if err != nil {
return err
}
fmt.Fprint(cmd.OutOrStdout(), string(yamlData))
return nil
},
}
}
func sortFactoriesByType[T component.Factory](factories map[component.Type]T) []T {
// Gather component types (factories map keys)
componentTypes := make([]component.Type, 0, len(factories))
for componentType := range factories {
componentTypes = append(componentTypes, componentType)
}
// Sort component types as strings
sort.Slice(componentTypes, func(i, j int) bool {
return componentTypes[i].String() < componentTypes[j].String()
})
// Build and return list of factories, sorted by component types
sortedFactories := make([]T, 0, len(factories))
for _, componentType := range componentTypes {
if !isComponentAlias(factories[componentType]) {
sortedFactories = append(sortedFactories, factories[componentType])
}
}
return sortedFactories
}
func sortProvidersByScheme(providerModules map[string]string, provFactories []confmap.ProviderFactory, set confmap.ProviderSettings) []componentWithoutStability {
schemes := make([]string, 0, len(provFactories))
for _, f := range provFactories {
provF := f.Create(set)
scheme := provF.Scheme()
if !isComponentAlias(f) {
schemes = append(schemes, scheme)
}
}
sort.Strings(schemes)
providerComponents := make([]componentWithoutStability, 0, len(providerModules))
for _, scheme := range schemes {
providerComponents = append(providerComponents, componentWithoutStability{
Scheme: scheme,
Module: providerModules[scheme],
})
}
return providerComponents
}
func sortConverterModules(modules []string) []componentWithoutStability {
sortedModulesCopy := make([]string, len(modules))
copy(sortedModulesCopy, modules)
sort.Strings(sortedModulesCopy)
sortedModules := make([]componentWithoutStability, 0, len(modules))
for _, mod := range sortedModulesCopy {
sortedModules = append(sortedModules, componentWithoutStability{
Module: mod,
})
}
return sortedModules
}
func isComponentAlias(component any) bool {
if al, ok := component.(componentalias.TypeAliasHolder); ok {
return al.DeprecatedAlias().String() != ""
}
return false
}
================================================
FILE: otelcol/command_components_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelcol
import (
"bytes"
"context"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/connector"
"go.opentelemetry.io/collector/connector/xconnector"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/xexporter"
"go.opentelemetry.io/collector/extension"
"go.opentelemetry.io/collector/internal/componentalias"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/xprocessor"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/xreceiver"
)
func TestNewBuildSubCommand(t *testing.T) {
set := CollectorSettings{
BuildInfo: component.NewDefaultBuildInfo(),
Factories: nopFactories,
ConfigProviderSettings: newDefaultConfigProviderSettings(t, []string{filepath.Join("testdata", "otelcol-nop.yaml")}),
// ensure default providers are referenced by scheme to a module
ProviderModules: map[string]string{
"file": "go.opentelemetry.io/collector/confmap/provider/testprovider v1.2.3",
"env": "go.opentelemetry.io/collector/confmap/provider/testprovider v1.2.3",
},
ConverterModules: []string{
"go.opentelemetry.io/collector/converter/testconverter v1.2.3",
},
}
cmd := NewCommand(set)
cmd.SetArgs([]string{"components"})
expectedOutput, err := os.ReadFile(filepath.Join("testdata", "components-output.yaml"))
require.NoError(t, err)
b := bytes.NewBufferString("")
cmd.SetOut(b)
err = cmd.Execute()
require.NoError(t, err)
// Trim new line at the end of the two strings to make a better comparison as string() adds an extra new
// line that makes the test fail.
assert.Equal(t, strings.TrimSpace(string(expectedOutput)), strings.TrimSpace(b.String()))
}
func TestComponentsStableOutput(t *testing.T) {
set := CollectorSettings{
BuildInfo: component.NewDefaultBuildInfo(),
Factories: newNamedNopFactories([]string{"bar", "foo", "baz"}),
ConfigProviderSettings: newDefaultConfigProviderSettings(t, []string{filepath.Join("testdata", "otelcol-nop.yaml")}),
// assumes default config provider contains `file`` and `env` schemes`.
ProviderModules: map[string]string{
"file": "go.opentelemetry.io/collector/confmap/provider/testprovider v1.2.3",
"env": "go.opentelemetry.io/collector/confmap/provider/testprovider v1.2.3",
},
ConverterModules: []string{
"go.opentelemetry.io/collector/converter/baz v1.2.3",
"go.opentelemetry.io/collector/converter/foo v1.2.3",
"go.opentelemetry.io/collector/converter/bar v1.2.3",
},
}
cmd := NewCommand(set)
cmd.SetArgs([]string{"components"})
expectedOutput, err := os.ReadFile(filepath.Join("testdata", "components-output-sorted.yaml"))
require.NoError(t, err)
// ensure output is reasonably consistent
for range 5 {
b := bytes.NewBufferString("")
cmd.SetOut(b)
err = cmd.Execute()
require.NoError(t, err)
// Trim new line at the end of the two strings to make a better comparison as string() adds an extra new
// line that makes the test fail.
assert.Equal(t, strings.TrimSpace(string(expectedOutput)), strings.TrimSpace(b.String()))
}
}
func newNamedNopFactories(
placeholderTypes []string,
) func() (Factories, error) {
return func() (Factories, error) {
var factories Factories
var err error
if factories.Connectors, err = MakeFactoryMap(newListNamedConnectorNopFactory(
placeholderTypes,
)...); err != nil {
return Factories{}, err
}
factories.ConnectorModules = make(map[component.Type]string, len(factories.Connectors))
for _, con := range factories.Connectors {
factories.ConnectorModules[con.Type()] = "go.opentelemetry.io/collector/connector/connectortest v1.2.3"
}
if factories.Extensions, err = MakeFactoryMap(newListNamedExtensionNopFactory(
placeholderTypes,
)...); err != nil {
return Factories{}, err
}
factories.ExtensionModules = make(map[component.Type]string, len(factories.Extensions))
for _, ext := range factories.Extensions {
factories.ExtensionModules[ext.Type()] = "go.opentelemetry.io/collector/extension/extensiontest v1.2.3"
}
if factories.Receivers, err = MakeFactoryMap(newListNamedReceiverNopFactory(
placeholderTypes,
)...); err != nil {
return Factories{}, err
}
factories.ReceiverModules = make(map[component.Type]string, len(factories.Receivers))
for _, rec := range factories.Receivers {
factories.ReceiverModules[rec.Type()] = "go.opentelemetry.io/collector/receiver/receivertest v1.2.3"
}
if factories.Exporters, err = MakeFactoryMap(newListNamedExporterNopFactory(
placeholderTypes,
)...); err != nil {
return Factories{}, err
}
factories.ExporterModules = make(map[component.Type]string, len(factories.Exporters))
for _, exp := range factories.Exporters {
factories.ExporterModules[exp.Type()] = "go.opentelemetry.io/collector/exporter/exportertest v1.2.3"
}
if factories.Processors, err = MakeFactoryMap(newListNamedProcessorNopFactory(
placeholderTypes,
)...); err != nil {
return Factories{}, err
}
factories.ProcessorModules = make(map[component.Type]string, len(factories.Processors))
for _, proc := range factories.Processors {
factories.ProcessorModules[proc.Type()] = "go.opentelemetry.io/collector/processor/processortest v1.2.3"
}
return factories, nil
}
}
type nopComponent struct {
component.StartFunc
component.ShutdownFunc
}
func newNamedConnecterNopFactory(typeName string) connector.Factory {
return xconnector.NewFactory(
component.MustNewType(typeName),
func() component.Config { return struct{}{} },
)
}
func newListNamedConnectorNopFactory(typeNames []string) []connector.Factory {
facts := make([]connector.Factory, 0, len(typeNames))
for _, typ := range typeNames {
facts = append(facts, newNamedConnecterNopFactory(typ))
}
return facts
}
func newNamedExtensionNopFactory(typeName string) extension.Factory {
return extension.NewFactory(
component.MustNewType(typeName),
func() component.Config { return struct{}{} },
func(context.Context, extension.Settings, component.Config) (extension.Extension, error) {
return nopComponent{}, nil
},
component.StabilityLevelStable,
)
}
func newListNamedExtensionNopFactory(typeNames []string) []extension.Factory {
facts := make([]extension.Factory, 0, len(typeNames))
for _, typ := range typeNames {
facts = append(facts, newNamedExtensionNopFactory(typ))
}
return facts
}
func newNamedReceiverNopFactory(typeName string) receiver.Factory {
return xreceiver.NewFactory(
component.MustNewType(typeName),
func() component.Config { return struct{}{} },
)
}
func newListNamedReceiverNopFactory(typeNames []string) []receiver.Factory {
facts := make([]receiver.Factory, 0, len(typeNames))
for _, typ := range typeNames {
facts = append(facts, newNamedReceiverNopFactory(typ))
}
return facts
}
func newNamedProcessorNopFactory(typeName string) processor.Factory {
return xprocessor.NewFactory(
component.MustNewType(typeName),
func() component.Config { return struct{}{} },
)
}
func newListNamedProcessorNopFactory(typeNames []string) []processor.Factory {
facts := make([]processor.Factory, 0, len(typeNames))
for _, typ := range typeNames {
facts = append(facts, newNamedProcessorNopFactory(typ))
}
return facts
}
func newNamedExportersNopFactory(typeName string) exporter.Factory {
return xexporter.NewFactory(
component.MustNewType(typeName),
func() component.Config { return struct{}{} },
)
}
func newListNamedExporterNopFactory(typeNames []string) []exporter.Factory {
facts := make([]exporter.Factory, 0, len(typeNames))
for _, typ := range typeNames {
facts = append(facts, newNamedExportersNopFactory(typ))
}
return facts
}
type mockFactory struct {
componentalias.TypeAliasHolder
name string
}
func (mockFactory) CreateDefaultConfig() component.Config {
return nil
}
func (m mockFactory) Type() component.Type {
return component.MustNewType(m.name)
}
func newMockFactory(name string) mockFactory {
return mockFactory{
TypeAliasHolder: componentalias.NewTypeAliasHolder(),
name: name,
}
}
func TestSortFactoriesByType(t *testing.T) {
for _, tt := range []struct {
name string
factories map[component.Type]mockFactory
want []mockFactory
}{
{
name: "with an empty map",
factories: map[component.Type]mockFactory{},
want: []mockFactory{},
},
{
name: "with a single factory",
factories: map[component.Type]mockFactory{
component.MustNewType("receiver"): newMockFactory("receiver_factory"),
},
want: []mockFactory{
newMockFactory("receiver_factory"),
},
},
{
name: "with multiple factories",
factories: map[component.Type]mockFactory{
component.MustNewType("processor"): newMockFactory("processor_factory"),
component.MustNewType("exporter"): newMockFactory("exporter_factory"),
component.MustNewType("receiver"): newMockFactory("receiver_factory"),
},
want: []mockFactory{
newMockFactory("exporter_factory"),
newMockFactory("processor_factory"),
newMockFactory("receiver_factory"),
},
},
{
name: "with aliases factories",
factories: func() map[component.Type]mockFactory {
alias := newMockFactory("alias_processor_factory")
alias.SetDeprecatedAlias(alias.Type())
return map[component.Type]mockFactory{
component.MustNewType("processor"): newMockFactory("processor_factory"),
component.MustNewType("alias_processor"): alias,
}
}(),
want: []mockFactory{
newMockFactory("processor_factory"),
},
},
} {
t.Run(tt.name, func(t *testing.T) {
got := sortFactoriesByType(tt.factories)
assert.Equal(t, tt.want, got)
})
}
}
================================================
FILE: otelcol/command_print.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelcol // import "go.opentelemetry.io/collector/otelcol"
import (
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"strings"
"github.com/spf13/cobra"
"go.yaml.in/yaml/v3"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/xconfmap"
"go.opentelemetry.io/collector/otelcol/internal/metadata"
)
// newConfigPrintSubCommand constructs a new print-config command using the given CollectorSettings.
func newConfigPrintSubCommand(set CollectorSettings, flagSet *flag.FlagSet) *cobra.Command {
var outputFormat string
var mode string
var validate bool
cmd := &cobra.Command{
Use: "print-config",
Aliases: []string{"print-initial-config"},
Short: "Prints the Collector's configuration in the specified mode",
Long: `Prints the Collector's configuration with different levels of processing:
- redacted: Shows the resolved configuration with sensitive data redacted (default)
- unredacted: Shows the resolved configuration with all sensitive data visible
The output prints in YAML by default. To print JSON use --format=json,
however this is considered unstable.
Validation is enabled by default, as a safety measure.
All modes are experimental, requiring the otelcol.printInitialConfig feature gate.`,
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, _ []string) error {
pc := printContext{
cmd: cmd,
stdout: cmd.OutOrStdout(),
set: set,
outputFormat: outputFormat,
validate: validate,
}
return pc.configPrintSubCommand(flagSet, mode)
},
}
formatHelp := "Output format: yaml (default), json (unstable))"
cmd.Flags().StringVar(&outputFormat, "format", "yaml", formatHelp)
modeHelp := "Operating mode: redacted (default), unredacted"
cmd.Flags().StringVar(&mode, "mode", "redacted", modeHelp)
validateHelp := "Validation mode: true (default), false"
cmd.Flags().BoolVar(&validate, "validate", true, validateHelp)
cmd.Flags().AddGoFlagSet(flagSet)
return cmd
}
type printContext struct {
cmd *cobra.Command
stdout io.Writer
set CollectorSettings
outputFormat string
validate bool
}
func (pctx *printContext) configPrintSubCommand(flagSet *flag.FlagSet, mode string) error {
if !metadata.OtelcolPrintInitialConfigFeatureGate.IsEnabled() {
return errors.New("print-config is currently experimental, use the otelcol.printInitialConfig feature gate to enable this command")
}
err := updateSettingsUsingFlags(&pctx.set, flagSet)
if err != nil {
return err
}
switch strings.ToLower(mode) {
case "redacted":
return pctx.printRedactedConfig()
case "unredacted":
return pctx.printUnredactedConfig()
default:
return fmt.Errorf("invalid mode %q: modes are: redacted, unredacted", mode)
}
}
// printConfigData formats and prints configuration data in yaml or json format.
func (pctx *printContext) printConfigData(data map[string]any) error {
format := pctx.outputFormat
if format == "" {
format = "yaml"
}
switch {
case strings.EqualFold(format, "yaml"):
b, err := yaml.Marshal(data)
if err != nil {
return err
}
fmt.Fprintf(pctx.stdout, "%s\n", b)
return nil
case strings.EqualFold(format, "json"):
encoder := json.NewEncoder(pctx.stdout)
encoder.SetIndent("", " ")
return encoder.Encode(data)
}
return fmt.Errorf("unrecognized print format: %s", format)
}
func (pctx *printContext) getPrintableConfig() (any, error) {
var factories Factories
if pctx.set.Factories != nil {
var err error
factories, err = pctx.set.Factories()
if err != nil {
return nil, fmt.Errorf("failed to get factories: %w", err)
}
}
configProvider, err := NewConfigProvider(pctx.set.ConfigProviderSettings)
if err != nil {
return nil, fmt.Errorf("failed to create config provider: %w", err)
}
cfg, err := configProvider.Get(pctx.cmd.Context(), factories)
if err != nil {
return nil, fmt.Errorf("failed to get config: %w", err)
}
return cfg, nil
}
// printUnredactedConfig prints resolved configuration before interpreting
// with the intended types for each component, thus it shows the full
// configuration without considering configuopaque. Use with caution.
func (pctx *printContext) printUnredactedConfig() error {
if pctx.validate {
// Validation serves prevent revealing invalid data.
cfg, err := pctx.getPrintableConfig()
if err != nil {
return err
}
if err = xconfmap.Validate(cfg); err != nil {
return fmt.Errorf("invalid configuration: %w", err)
}
// Note: we discard the validated configuration.
}
resolver, err := confmap.NewResolver(pctx.set.ConfigProviderSettings.ResolverSettings)
if err != nil {
return fmt.Errorf("failed to create new resolver: %w", err)
}
conf, err := resolver.Resolve(pctx.cmd.Context())
if err != nil {
return fmt.Errorf("error while resolving config: %w", err)
}
return pctx.printConfigData(conf.ToStringMap())
}
// printRedactedConfig prints resolved configuration with its assigned
// types, but without validation. Notably, configopaque strings are printed
// as "[redacted]". This is the default.
func (pctx *printContext) printRedactedConfig() error {
cfg, err := pctx.getPrintableConfig()
if err != nil {
return err
}
if pctx.validate {
if err = xconfmap.Validate(cfg); err != nil {
return fmt.Errorf("invalid configuration: %w", err)
}
}
confMap := confmap.New()
if err := confMap.Marshal(cfg); err != nil {
return fmt.Errorf("failed to marshal config: %w", err)
}
return pctx.printConfigData(confMap.ToStringMap())
}
================================================
FILE: otelcol/command_print_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelcol // import "go.opentelemetry.io/collector/otelcol"
import (
"bytes"
"context"
"errors"
"fmt"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configopaque"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/provider/fileprovider"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/xexporter"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/otelcol/internal/metadata"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/xreceiver"
"go.opentelemetry.io/collector/service/telemetry"
)
type printExporterConfig struct {
Timeout time.Duration `mapstructure:"timeout"`
}
type printReceiverConfig struct {
Opaque configopaque.String `mapstructure:"opaque"`
Other string `mapstructure:"other,omitempty"`
}
func (c *printExporterConfig) Validate() error {
if c.Timeout < 0 {
return errors.New("timeout cannot be negative")
}
return nil
}
func TestPrintCommand(t *testing.T) {
const nonexistentConfig = "file:nope.yaml"
validConfig := fmt.Sprint("file:", filepath.Join("testdata", "print.yaml"))
invalidConfig1 := fmt.Sprint("file:", filepath.Join("testdata", "print_invalid.yaml"))
invalidConfig2 := fmt.Sprint("file:", filepath.Join("testdata", "print_negative.yaml"))
defaultConfig := fmt.Sprint("file:", filepath.Join("testdata", "print_default.yaml"))
tests := []struct {
name string
ofmt string
path string
errString string
outString map[string]string
disableFF bool // disable the feature flag
validate bool // add validation (even redacted)
errOnlyRedacted bool // error applies only in redacted mode
}{
{
name: "file not found",
path: nonexistentConfig,
errString: "cannot retrieve the configuration: unable to read the file",
},
{
name: "valid yaml",
path: validConfig,
},
{
name: "invalid syntax without validate",
path: invalidConfig1,
errString: "'timeout' time: invalid duration",
errOnlyRedacted: true,
},
{
name: "validation fail",
path: invalidConfig2,
validate: true,
errString: "timeout cannot be negative",
},
{
name: "no feature flag",
path: validConfig,
disableFF: true,
errString: "use the otelcol.printInitialConfig feature gate",
},
{
name: "field is set yaml",
path: validConfig,
outString: map[string]string{
"redacted": `timeout: 5s`,
"unredacted": `timeout: 5s`,
},
},
{
name: "default field value",
path: defaultConfig,
outString: map[string]string{
"redacted": `timeout: 1s`,
// Since the structure is empty before
// interpretation, no default is expanded.
"unredacted": `e: null`,
},
},
{
name: "field is set json",
ofmt: "json",
path: validConfig,
outString: map[string]string{
// Note: JSON does not format as a time.Duration
"redacted": `"timeout": 5000000000`,
// Note: the original input is "5s"
"unredacted": `"timeout": "5s"`,
},
},
{
name: "opaque field",
path: validConfig,
outString: map[string]string{
"redacted": `opaque: '[REDACTED]'`,
"unredacted": `opaque: OOO`,
},
},
{
name: "opaque default",
path: defaultConfig,
outString: map[string]string{
"redacted": `opaque: '[REDACTED]'`,
// Note: the default opaque value does not print,
// the other value is set in defaultConfig so that
// the whole component config is not defaulted.
"unredacted": `other: lala`,
},
},
}
for _, test := range tests {
testModes := []string{"redacted", "unredacted", "unrecognized"}
for _, mode := range testModes {
t.Run(fmt.Sprint(test.name, "_", mode), func(t *testing.T) {
// Save current feature flag state and restore after test
fg := featuregate.GlobalRegistry()
fg.VisitAll(func(g *featuregate.Gate) {
if g.ID() == metadata.OtelcolPrintInitialConfigFeatureGate.ID() {
defer func() {
_ = fg.Set(metadata.OtelcolPrintInitialConfigFeatureGate.ID(), g.IsEnabled())
}()
}
})
if test.disableFF {
require.NoError(t, fg.Set(metadata.OtelcolPrintInitialConfigFeatureGate.ID(), false))
} else {
require.NoError(t, fg.Set(metadata.OtelcolPrintInitialConfigFeatureGate.ID(), true))
}
testR := component.MustNewType("r")
testE := component.MustNewType("e")
testReceiver := xreceiver.NewFactory(
testR,
func() component.Config {
return printReceiverConfig{
Opaque: "1234",
Other: "",
}
},
xreceiver.WithLogs(func(context.Context, receiver.Settings, component.Config, consumer.Logs) (receiver.Logs, error) {
return nil, nil
}, component.StabilityLevelStable),
)
testExporter := xexporter.NewFactory(
testE,
func() component.Config {
return printExporterConfig{
Timeout: time.Second,
}
},
xexporter.WithLogs(func(context.Context, exporter.Settings, component.Config) (exporter.Logs, error) {
return nil, nil
}, component.StabilityLevelStable),
)
var stdout bytes.Buffer
set := confmap.ResolverSettings{}
set.ProviderFactories = []confmap.ProviderFactory{
fileprovider.NewFactory(),
}
set.DefaultScheme = "file"
set.URIs = []string{test.path}
cmd := newConfigPrintSubCommand(CollectorSettings{
Factories: func() (Factories, error) {
return Factories{
Receivers: map[component.Type]receiver.Factory{
testR: testReceiver,
},
Exporters: map[component.Type]exporter.Factory{
testE: testExporter,
},
Telemetry: telemetry.NewFactory(func() component.Config {
return fakeTelemetryConfig{}
}),
}, nil
},
ConfigProviderSettings: ConfigProviderSettings{
ResolverSettings: set,
},
}, flags(featuregate.GlobalRegistry()))
cmd.SetOut(&stdout)
args := []string{
"--mode", mode,
"--format", test.ofmt,
}
if test.validate {
args = append(args, "--validate=true")
} else {
args = append(args, "--validate=false")
}
cmd.SetArgs(args)
err := cmd.Execute()
expectErr := test.errString != ""
expectErrMsg := test.errString
switch mode {
case "redacted":
case "unredacted":
if test.errOnlyRedacted {
expectErr = false
expectErrMsg = ""
}
default:
expectErr = true
if test.disableFF {
expectErrMsg = "feature gate"
} else {
expectErrMsg = "unrecognized"
}
}
if expectErr {
require.Error(t, err)
require.ErrorContains(t, err, expectErrMsg)
} else {
require.NoError(t, err)
}
if test.outString[mode] != "" {
require.Contains(t, stdout.String(), test.outString[mode])
}
})
}
}
}
================================================
FILE: otelcol/command_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelcol
import (
"context"
"io"
"os"
"path/filepath"
"testing"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/featuregate"
)
func TestNewCommandVersion(t *testing.T) {
cmd := NewCommand(CollectorSettings{BuildInfo: component.BuildInfo{Version: "test_version"}})
assert.Equal(t, "test_version", cmd.Version)
}
func TestNewCommandNoConfigURI(t *testing.T) {
cmd := NewCommand(CollectorSettings{Factories: nopFactories})
require.Error(t, cmd.Execute())
}
// This test emulates usage of Collector in Jaeger all-in-one, which
// allows running the binary with no explicit configuration.
func TestNewCommandProgrammaticallyPassedConfig(t *testing.T) {
cmd := NewCommand(CollectorSettings{Factories: nopFactories, ConfigProviderSettings: ConfigProviderSettings{
ResolverSettings: confmap.ResolverSettings{
ProviderFactories: []confmap.ProviderFactory{confmap.NewProviderFactory(newFailureProvider)},
DefaultScheme: "file",
},
}})
otelRunE := cmd.RunE
cmd.RunE = func(c *cobra.Command, args []string) error {
configFlag := c.Flag("config")
cfg := `
service:
extensions: [invalid_component_name]
receivers:
invalid_component_name:
`
require.NoError(t, configFlag.Value.Set("yaml:"+cfg))
return otelRunE(cmd, args)
}
// verify that cmd.Execute was run with the implicitly provided config.
require.ErrorContains(t, cmd.Execute(), "invalid_component_name")
}
func TestAddFlagToSettings(t *testing.T) {
filePath := filepath.Join("testdata", "otelcol-invalid.yaml")
fileProvider := newFakeProvider("file", func(_ context.Context, _ string, _ confmap.WatcherFunc) (*confmap.Retrieved, error) {
return confmap.NewRetrieved(newConfFromFile(t, filePath))
})
set := CollectorSettings{
ConfigProviderSettings: ConfigProviderSettings{
ResolverSettings: confmap.ResolverSettings{
URIs: []string{filePath},
ProviderFactories: []confmap.ProviderFactory{fileProvider},
},
},
}
flgs := flags(featuregate.NewRegistry())
err := flgs.Parse([]string{"--config=otelcol-nop.yaml"})
require.NoError(t, err)
err = updateSettingsUsingFlags(&set, flgs)
require.NoError(t, err)
require.Len(t, set.ConfigProviderSettings.ResolverSettings.URIs, 1)
}
func TestInvalidCollectorSettings(t *testing.T) {
set := CollectorSettings{
ConfigProviderSettings: ConfigProviderSettings{
ResolverSettings: confmap.ResolverSettings{
URIs: []string{"--config=otelcol-nop.yaml"},
},
},
}
cmd := NewCommand(set)
require.Error(t, cmd.Execute())
}
func TestNewCommandInvalidComponent(t *testing.T) {
filePath := filepath.Join("testdata", "otelcol-invalid.yaml")
fileProvider := newFakeProvider("file", func(_ context.Context, _ string, _ confmap.WatcherFunc) (*confmap.Retrieved, error) {
return confmap.NewRetrieved(newConfFromFile(t, filePath))
})
set := ConfigProviderSettings{
ResolverSettings: confmap.ResolverSettings{
URIs: []string{filePath},
ProviderFactories: []confmap.ProviderFactory{fileProvider},
},
}
cmd := NewCommand(CollectorSettings{Factories: nopFactories, ConfigProviderSettings: set})
require.Error(t, cmd.Execute())
}
func TestNoProvidersReturnsError(t *testing.T) {
set := CollectorSettings{
ConfigProviderSettings: ConfigProviderSettings{
ResolverSettings: confmap.ResolverSettings{
URIs: []string{filepath.Join("testdata", "otelcol-invalid.yaml")},
},
},
}
flgs := flags(featuregate.NewRegistry())
err := flgs.Parse([]string{"--config=otelcol-nop.yaml"})
require.NoError(t, err)
err = updateSettingsUsingFlags(&set, flgs)
require.ErrorContains(t, err, "at least one Provider must be supplied")
}
func Test_UseUnifiedEnvVarExpansionRules(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "default scheme set",
input: "file",
expected: "file",
},
{
name: "default scheme not set",
input: "",
expected: "env",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fileProvider := newFakeProvider("file", func(_ context.Context, _ string, _ confmap.WatcherFunc) (*confmap.Retrieved, error) {
return &confmap.Retrieved{}, nil
})
set := CollectorSettings{
ConfigProviderSettings: ConfigProviderSettings{
ResolverSettings: confmap.ResolverSettings{
ProviderFactories: []confmap.ProviderFactory{fileProvider},
DefaultScheme: tt.input,
},
},
}
flgs := flags(featuregate.NewRegistry())
err := flgs.Parse([]string{"--config=otelcol-nop.yaml"})
require.NoError(t, err)
err = updateSettingsUsingFlags(&set, flgs)
require.NoError(t, err)
require.Equal(t, tt.expected, set.ConfigProviderSettings.ResolverSettings.DefaultScheme)
})
}
}
func TestNewFeatureGateCommand(t *testing.T) {
t.Run("list all featuregates", func(t *testing.T) {
cmd := newFeatureGateCommand()
require.NotNil(t, cmd)
// Capture stdout
oldStdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
err := cmd.RunE(cmd, []string{})
require.NoError(t, err)
w.Close()
out, _ := io.ReadAll(r)
os.Stdout = oldStdout
output := string(out)
assert.Contains(t, output, "ID")
assert.Contains(t, output, "Enabled")
assert.Contains(t, output, "Stage")
assert.Contains(t, output, "Description")
})
t.Run("specific featuregate details", func(t *testing.T) {
cmd := newFeatureGateCommand()
// Register a test feature gate in the global registry
featuregate.GlobalRegistry().MustRegister("test.feature", featuregate.StageBeta,
featuregate.WithRegisterDescription("Test feature description"))
// Capture stdout
oldStdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
err := cmd.RunE(cmd, []string{"test.feature"})
require.NoError(t, err)
w.Close()
out, _ := io.ReadAll(r)
os.Stdout = oldStdout
output := string(out)
assert.Contains(t, output, "Feature: test.feature")
assert.Contains(t, output, "Description: Test feature description")
assert.Contains(t, output, "Stage: Beta")
})
t.Run("non-existent featuregate", func(t *testing.T) {
cmd := newFeatureGateCommand()
err := cmd.RunE(cmd, []string{"non.existent.feature"})
require.Error(t, err)
assert.Contains(t, err.Error(), "feature \"non.existent.feature\" not found")
})
t.Run("rejects multiple arguments", func(t *testing.T) {
cmd := newFeatureGateCommand()
cmd.SetArgs([]string{"gate1", "gate2"})
err := cmd.Execute()
require.Error(t, err)
})
}
================================================
FILE: otelcol/command_validate.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelcol // import "go.opentelemetry.io/collector/otelcol"
import (
"flag"
"github.com/spf13/cobra"
)
// newValidateSubCommand constructs a new validate sub command using the given CollectorSettings.
func newValidateSubCommand(set CollectorSettings, flagSet *flag.FlagSet) *cobra.Command {
validateCmd := &cobra.Command{
Use: "validate",
Short: "Validates the config without running the collector",
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, _ []string) error {
if err := updateSettingsUsingFlags(&set, flagSet); err != nil {
return err
}
col, err := NewCollector(set)
if err != nil {
return err
}
return col.DryRun(cmd.Context())
},
}
validateCmd.Flags().AddGoFlagSet(flagSet)
return validateCmd
}
================================================
FILE: otelcol/command_validate_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelcol
import (
"context"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/featuregate"
)
func TestValidateSubCommandNoConfig(t *testing.T) {
cmd := newValidateSubCommand(CollectorSettings{Factories: nopFactories}, flags(featuregate.GlobalRegistry()))
err := cmd.Execute()
require.ErrorContains(t, err, "at least one config flag must be provided")
}
func TestValidateSubCommandInvalidComponents(t *testing.T) {
filePath := filepath.Join("testdata", "otelcol-invalid-components.yaml")
fileProvider := newFakeProvider("file", func(_ context.Context, _ string, _ confmap.WatcherFunc) (*confmap.Retrieved, error) {
return confmap.NewRetrieved(newConfFromFile(t, filePath))
})
cmd := newValidateSubCommand(CollectorSettings{Factories: nopFactories, ConfigProviderSettings: ConfigProviderSettings{
ResolverSettings: confmap.ResolverSettings{
URIs: []string{filePath},
ProviderFactories: []confmap.ProviderFactory{fileProvider},
DefaultScheme: "file",
},
}}, flags(featuregate.GlobalRegistry()))
err := cmd.Execute()
require.ErrorContains(t, err, "unknown type: \"nosuchprocessor\"")
}
================================================
FILE: otelcol/config.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelcol // import "go.opentelemetry.io/collector/otelcol"
import (
"errors"
"fmt"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/service"
"go.opentelemetry.io/collector/service/pipelines"
)
var (
errMissingExporters = errors.New("no exporter configuration specified in config")
errMissingReceivers = errors.New("no receiver configuration specified in config")
errEmptyConfigurationFile = errors.New("empty configuration file")
)
// Config defines the configuration for the various elements of collector or agent.
type Config struct {
// Receivers is a map of ComponentID to Receivers.
Receivers map[component.ID]component.Config `mapstructure:"receivers"`
// Exporters is a map of ComponentID to Exporters.
Exporters map[component.ID]component.Config `mapstructure:"exporters"`
// Processors is a map of ComponentID to Processors.
Processors map[component.ID]component.Config `mapstructure:"processors"`
// Connectors is a map of ComponentID to connectors.
Connectors map[component.ID]component.Config `mapstructure:"connectors"`
// Extensions is a map of ComponentID to extensions.
Extensions map[component.ID]component.Config `mapstructure:"extensions"`
Service service.Config `mapstructure:"service"`
// prevent unkeyed literal initialization
_ struct{}
}
// Validate returns an error if the config is invalid.
//
// 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).
func (cfg *Config) Validate() error {
// There must be at least one property set in the configuration file.
if len(cfg.Receivers) == 0 && len(cfg.Exporters) == 0 && len(cfg.Processors) == 0 && len(cfg.Connectors) == 0 && len(cfg.Extensions) == 0 {
return errEmptyConfigurationFile
}
// Currently, there is no default receiver enabled.
// The configuration must specify at least one receiver to be valid.
if !pipelines.AllowNoPipelines.IsEnabled() && len(cfg.Receivers) == 0 {
return errMissingReceivers
}
// Currently, there is no default exporter enabled.
// The configuration must specify at least one exporter to be valid.
if !pipelines.AllowNoPipelines.IsEnabled() && len(cfg.Exporters) == 0 {
return errMissingExporters
}
// Validate the connector configuration.
for connID := range cfg.Connectors {
if _, ok := cfg.Exporters[connID]; ok {
return fmt.Errorf("connectors::%s: ambiguous ID: Found both %q exporter and %q connector. "+
"Change one of the components' IDs to eliminate ambiguity (e.g. rename %q connector to %q)",
connID, connID, connID, connID, connID.String()+"/connector")
}
if _, ok := cfg.Receivers[connID]; ok {
return fmt.Errorf("connectors::%s: ambiguous ID: Found both %q receiver and %q connector. "+
"Change one of the components' IDs to eliminate ambiguity (e.g. rename %q connector to %q)",
connID, connID, connID, connID, connID.String()+"/connector")
}
}
// Check that all enabled extensions in the service are configured.
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 fmt.Errorf("service::extensions: references extension %q which is not configured", ref)
}
}
// Check that all pipelines reference only configured components.
for pipelineID, pipeline := range cfg.Service.Pipelines {
// 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 _, ok := cfg.Receivers[ref]; ok {
continue
}
if _, ok := cfg.Connectors[ref]; ok {
continue
}
return fmt.Errorf("service::pipelines::%s: references receiver %q which is not configured", pipelineID.String(), ref)
}
// 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 fmt.Errorf("service::pipelines::%s: references processor %q which is not configured", pipelineID.String(), ref)
}
}
// 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 _, ok := cfg.Exporters[ref]; ok {
continue
}
if _, ok := cfg.Connectors[ref]; ok {
continue
}
return fmt.Errorf("service::pipelines::%s: references exporter %q which is not configured", pipelineID.String(), ref)
}
}
return nil
}
================================================
FILE: otelcol/config_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelcol
import (
"errors"
"fmt"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/confmap/xconfmap"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/service"
"go.opentelemetry.io/collector/service/pipelines"
)
var (
errInvalidRecvConfig = errors.New("invalid receiver config")
errInvalidExpConfig = errors.New("invalid exporter config")
errInvalidProcConfig = errors.New("invalid processor config")
errInvalidConnConfig = errors.New("invalid connector config")
errInvalidExtConfig = errors.New("invalid extension config")
)
type errConfig struct {
validateErr error
}
func (c *errConfig) Validate() error {
return c.validateErr
}
func TestConfigValidate(t *testing.T) {
testCases := []struct {
name string // test case name (also file name containing config yaml)
cfgFn func() *Config
expected error
}{
{
name: "valid",
cfgFn: generateConfig,
expected: nil,
},
{
name: "valid-telemetry-config",
cfgFn: func() *Config {
cfg := generateConfig()
cfg.Service.Telemetry = fakeTelemetryConfig{}
return cfg
},
expected: nil,
},
{
name: "empty configuration file",
cfgFn: func() *Config {
cfg := generateConfig()
cfg.Receivers = nil
cfg.Connectors = nil
cfg.Processors = nil
cfg.Exporters = nil
cfg.Extensions = nil
return cfg
},
expected: errEmptyConfigurationFile,
},
{
name: "missing-exporters",
cfgFn: func() *Config {
cfg := generateConfig()
cfg.Exporters = nil
return cfg
},
expected: errMissingExporters,
},
{
name: "missing-receivers",
cfgFn: func() *Config {
cfg := generateConfig()
cfg.Receivers = nil
return cfg
},
expected: errMissingReceivers,
},
{
name: "invalid-telemetry-config",
cfgFn: func() *Config {
cfg := generateConfig()
cfg.Service.Telemetry = fakeTelemetryConfig{Invalid: true}
return cfg
},
expected: errors.New("service::telemetry: invalid config"),
},
{
name: "invalid-extension-reference",
cfgFn: func() *Config {
cfg := generateConfig()
cfg.Service.Extensions = append(cfg.Service.Extensions, component.MustNewIDWithName("nop", "2"))
return cfg
},
expected: errors.New(`service::extensions: references extension "nop/2" which is not configured`),
},
{
name: "invalid-receiver-reference",
cfgFn: func() *Config {
cfg := generateConfig()
pipe := cfg.Service.Pipelines[pipeline.NewID(pipeline.SignalTraces)]
pipe.Receivers = append(pipe.Receivers, component.MustNewIDWithName("nop", "2"))
return cfg
},
expected: errors.New(`service::pipelines::traces: references receiver "nop/2" which is not configured`),
},
{
name: "invalid-processor-reference",
cfgFn: func() *Config {
cfg := generateConfig()
pipe := cfg.Service.Pipelines[pipeline.NewID(pipeline.SignalTraces)]
pipe.Processors = append(pipe.Processors, component.MustNewIDWithName("nop", "2"))
return cfg
},
expected: errors.New(`service::pipelines::traces: references processor "nop/2" which is not configured`),
},
{
name: "invalid-exporter-reference",
cfgFn: func() *Config {
cfg := generateConfig()
pipe := cfg.Service.Pipelines[pipeline.NewID(pipeline.SignalTraces)]
pipe.Exporters = append(pipe.Exporters, component.MustNewIDWithName("nop", "2"))
return cfg
},
expected: errors.New(`service::pipelines::traces: references exporter "nop/2" which is not configured`),
},
{
name: "invalid-receiver-config",
cfgFn: func() *Config {
cfg := generateConfig()
cfg.Receivers[component.MustNewID("nop")] = &errConfig{
validateErr: errInvalidRecvConfig,
}
return cfg
},
expected: fmt.Errorf(`receivers::nop: %w`, errInvalidRecvConfig),
},
{
name: "invalid-exporter-config",
cfgFn: func() *Config {
cfg := generateConfig()
cfg.Exporters[component.MustNewID("nop")] = &errConfig{
validateErr: errInvalidExpConfig,
}
return cfg
},
expected: fmt.Errorf(`exporters::nop: %w`, errInvalidExpConfig),
},
{
name: "invalid-processor-config",
cfgFn: func() *Config {
cfg := generateConfig()
cfg.Processors[component.MustNewID("nop")] = &errConfig{
validateErr: errInvalidProcConfig,
}
return cfg
},
expected: fmt.Errorf(`processors::nop: %w`, errInvalidProcConfig),
},
{
name: "invalid-extension-config",
cfgFn: func() *Config {
cfg := generateConfig()
cfg.Extensions[component.MustNewID("nop")] = &errConfig{
validateErr: errInvalidExtConfig,
}
return cfg
},
expected: fmt.Errorf(`extensions::nop: %w`, errInvalidExtConfig),
},
{
name: "invalid-connector-config",
cfgFn: func() *Config {
cfg := generateConfig()
cfg.Connectors[component.MustNewIDWithName("nop", "conn")] = &errConfig{
validateErr: errInvalidConnConfig,
}
return cfg
},
expected: fmt.Errorf(`connectors::nop/conn: %w`, errInvalidConnConfig),
},
{
name: "ambiguous-connector-name-as-receiver",
cfgFn: func() *Config {
cfg := generateConfig()
cfg.Receivers[component.MustNewID("nop2")] = &errConfig{}
cfg.Connectors[component.MustNewID("nop2")] = &errConfig{}
pipe := cfg.Service.Pipelines[pipeline.NewID(pipeline.SignalTraces)]
pipe.Receivers = append(pipe.Receivers, component.MustNewIDWithName("nop", "2"))
pipe.Exporters = append(pipe.Exporters, component.MustNewIDWithName("nop", "2"))
return cfg
},
expected: errors.New(`connectors::nop2: ambiguous ID: Found both "nop2" receiver and "nop2" connector. Change one of the components' IDs to eliminate ambiguity (e.g. rename "nop2" connector to "nop2/connector")`),
},
{
name: "ambiguous-connector-name-as-exporter",
cfgFn: func() *Config {
cfg := generateConfig()
cfg.Exporters[component.MustNewID("nop2")] = &errConfig{}
cfg.Connectors[component.MustNewID("nop2")] = &errConfig{}
pipe := cfg.Service.Pipelines[pipeline.NewID(pipeline.SignalTraces)]
pipe.Receivers = append(pipe.Receivers, component.MustNewIDWithName("nop", "2"))
pipe.Exporters = append(pipe.Exporters, component.MustNewIDWithName("nop", "2"))
return cfg
},
expected: errors.New(`connectors::nop2: ambiguous ID: Found both "nop2" exporter and "nop2" connector. Change one of the components' IDs to eliminate ambiguity (e.g. rename "nop2" connector to "nop2/connector")`),
},
{
name: "invalid-connector-reference-as-receiver",
cfgFn: func() *Config {
cfg := generateConfig()
pipe := cfg.Service.Pipelines[pipeline.NewID(pipeline.SignalTraces)]
pipe.Receivers = append(pipe.Receivers, component.MustNewIDWithName("nop", "conn2"))
return cfg
},
expected: errors.New(`service::pipelines::traces: references receiver "nop/conn2" which is not configured`),
},
{
name: "invalid-connector-reference-as-receiver",
cfgFn: func() *Config {
cfg := generateConfig()
pipe := cfg.Service.Pipelines[pipeline.NewID(pipeline.SignalTraces)]
pipe.Exporters = append(pipe.Exporters, component.MustNewIDWithName("nop", "conn2"))
return cfg
},
expected: errors.New(`service::pipelines::traces: references exporter "nop/conn2" which is not configured`),
},
{
name: "invalid-service-config",
cfgFn: func() *Config {
cfg := generateConfig()
cfg.Service.Pipelines = nil
return cfg
},
expected: fmt.Errorf(`service::pipelines: %w`, errors.New(`service must have at least one pipeline`)),
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
cfg := tt.cfgFn()
err := xconfmap.Validate(cfg)
if tt.expected != nil {
require.EqualError(t, err, tt.expected.Error())
} else {
require.NoError(t, err)
}
})
}
}
func TestNoPipelinesFeatureGate(t *testing.T) {
cfg := generateConfig()
cfg.Receivers = nil
cfg.Exporters = nil
cfg.Service.Pipelines = pipelines.Config{}
require.Error(t, xconfmap.Validate(cfg))
gate := pipelines.AllowNoPipelines
require.NoError(t, featuregate.GlobalRegistry().Set(gate.ID(), true))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(gate.ID(), false))
}()
require.NoError(t, xconfmap.Validate(cfg))
}
func generateConfig() *Config {
return &Config{
Receivers: map[component.ID]component.Config{
component.MustNewID("nop"): &errConfig{},
},
Exporters: map[component.ID]component.Config{
component.MustNewID("nop"): &errConfig{},
},
Processors: map[component.ID]component.Config{
component.MustNewID("nop"): &errConfig{},
},
Connectors: map[component.ID]component.Config{
component.MustNewIDWithName("nop", "conn"): &errConfig{},
},
Extensions: map[component.ID]component.Config{
component.MustNewID("nop"): &errConfig{},
},
Service: service.Config{
Telemetry: fakeTelemetryConfig{},
Extensions: []component.ID{component.MustNewID("nop")},
Pipelines: pipelines.Config{
pipeline.NewID(pipeline.SignalTraces): {
Receivers: []component.ID{component.MustNewID("nop")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
},
}
}
type fakeTelemetryConfig struct {
Invalid bool `mapstructure:"invalid"`
}
func (cfg fakeTelemetryConfig) Validate() error {
if cfg.Invalid {
return errors.New("invalid config")
}
return nil
}
================================================
FILE: otelcol/configprovider.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelcol // import "go.opentelemetry.io/collector/otelcol"
import (
"context"
"fmt"
"go.opentelemetry.io/collector/confmap"
)
// ConfigProvider provides the service configuration.
//
// The typical usage is the following:
//
// cfgProvider.Get(...)
// cfgProvider.Watch() // wait for an event.
// cfgProvider.Get(...)
// cfgProvider.Watch() // wait for an event.
// // repeat Get/Watch cycle until it is time to shut down the Collector process.
// cfgProvider.Shutdown()
type ConfigProvider struct {
mapResolver *confmap.Resolver
}
// ConfigProviderSettings are the settings to configure the behavior of the ConfigProvider.
type ConfigProviderSettings struct {
// ResolverSettings are the settings to configure the behavior of the confmap.Resolver.
ResolverSettings confmap.ResolverSettings
// prevent unkeyed literal initialization
_ struct{}
}
// NewConfigProvider returns a new ConfigProvider that provides the service configuration:
// * Initially it resolves the "configuration map":
// - Retrieve the confmap.Conf by merging all retrieved maps from the given `locations` in order.
// - Then applies all the confmap.Converter in the given order.
//
// * Then unmarshalls the confmap.Conf into the service Config.
func NewConfigProvider(set ConfigProviderSettings) (*ConfigProvider, error) {
mr, err := confmap.NewResolver(set.ResolverSettings)
if err != nil {
return nil, err
}
return &ConfigProvider{
mapResolver: mr,
}, nil
}
// Get returns the service configuration, or error otherwise.
//
// Should never be called concurrently with itself, Watch or Shutdown.
func (cm *ConfigProvider) Get(ctx context.Context, factories Factories) (*Config, error) {
conf, err := cm.mapResolver.Resolve(ctx)
if err != nil {
return nil, fmt.Errorf("cannot resolve the configuration: %w", err)
}
var cfg *configSettings
if cfg, err = unmarshal(conf, factories); err != nil {
return nil, fmt.Errorf("cannot unmarshal the configuration: %w", err)
}
return &Config{
Receivers: cfg.Receivers.Configs(),
Processors: cfg.Processors.Configs(),
Exporters: cfg.Exporters.Configs(),
Connectors: cfg.Connectors.Configs(),
Extensions: cfg.Extensions.Configs(),
Service: cfg.Service,
}, nil
}
// Watch blocks until any configuration change was detected or an unrecoverable error
// happened during monitoring the configuration changes.
//
// Error is nil if the configuration is changed and needs to be re-fetched. Any non-nil
// error indicates that there was a problem with watching the config changes.
//
// Should never be called concurrently with itself or Get.
func (cm *ConfigProvider) Watch() <-chan error {
return cm.mapResolver.Watch()
}
// Shutdown signals that the provider is no longer in use and the that should close
// and release any resources that it may have created.
//
// This function must terminate the Watch channel.
//
// Should never be called concurrently with itself or Get.
func (cm *ConfigProvider) Shutdown(ctx context.Context) error {
return cm.mapResolver.Shutdown(ctx)
}
================================================
FILE: otelcol/configprovider_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelcol
import (
"context"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.yaml.in/yaml/v3"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/connector/connectortest"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/extension/extensiontest"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/processor/processortest"
"go.opentelemetry.io/collector/receiver/receivertest"
"go.opentelemetry.io/collector/service"
"go.opentelemetry.io/collector/service/pipelines"
)
func TestConfigProvider(t *testing.T) {
nopComponentID := component.MustNewID("nop")
nopConComponentID := component.MustNewIDWithName("nop", "con")
tests := map[string]struct {
filename string
factories func() (Factories, error)
expectedConfig *Config
}{
"nop": {
filename: "otelcol-nop.yaml",
factories: nopFactories,
expectedConfig: &Config{
Connectors: map[component.ID]component.Config{
nopConComponentID: connectortest.NewNopFactory().CreateDefaultConfig(),
},
Exporters: map[component.ID]component.Config{
nopComponentID: exportertest.NewNopFactory().CreateDefaultConfig(),
},
Extensions: map[component.ID]component.Config{
nopComponentID: extensiontest.NewNopFactory().CreateDefaultConfig(),
},
Processors: map[component.ID]component.Config{
nopComponentID: processortest.NewNopFactory().CreateDefaultConfig(),
},
Receivers: map[component.ID]component.Config{
nopComponentID: receivertest.NewNopFactory().CreateDefaultConfig(),
},
Service: service.Config{
Telemetry: fakeTelemetryConfig{},
Extensions: []component.ID{nopComponentID},
Pipelines: map[pipeline.ID]*pipelines.PipelineConfig{
pipeline.NewID(pipeline.SignalTraces): {
Receivers: []component.ID{nopComponentID},
Processors: []component.ID{nopComponentID},
Exporters: []component.ID{nopComponentID, nopConComponentID},
},
pipeline.NewID(pipeline.SignalMetrics): {
Receivers: []component.ID{nopComponentID},
Processors: []component.ID{nopComponentID},
Exporters: []component.ID{nopComponentID},
},
pipeline.NewID(pipeline.SignalLogs): {
Receivers: []component.ID{nopComponentID, nopConComponentID},
Processors: []component.ID{nopComponentID},
Exporters: []component.ID{nopComponentID},
},
},
},
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
yamlBytes, err := os.ReadFile(filepath.Join("testdata", test.filename))
require.NoError(t, err)
yamlProvider := newFakeProvider("yaml", func(context.Context, string, confmap.WatcherFunc) (*confmap.Retrieved, error) {
var rawConf any
if yamlErr := yaml.Unmarshal(yamlBytes, &rawConf); yamlErr != nil {
return nil, yamlErr
}
return confmap.NewRetrieved(rawConf)
})
set := ConfigProviderSettings{
ResolverSettings: confmap.ResolverSettings{
URIs: []string{"yaml:" + string(yamlBytes)},
ProviderFactories: []confmap.ProviderFactory{yamlProvider},
},
}
cp, err := NewConfigProvider(set)
require.NoError(t, err)
factories, err := test.factories()
require.NoError(t, err)
cfg, err := cp.Get(context.Background(), factories)
require.NoError(t, err)
assert.Equal(t, test.expectedConfig, cfg)
})
}
}
================================================
FILE: otelcol/documentation.md
================================================
[comment]: <> (Code generated by mdatagen. DO NOT EDIT.)
# otelcol
## Feature Gates
This component has the following feature gates:
| Feature Gate | Stage | Description | From Version | To Version | Reference |
| ------------ | ----- | ----------- | ------------ | ---------- | --------- |
| `otelcol.printInitialConfig` | beta | if set to true, enable the print-config command | v0.120.0 | N/A | [Link](https://github.com/open-telemetry/opentelemetry-collector/pull/11775) |
For more information about feature gates, see the [Feature Gates](https://github.com/open-telemetry/opentelemetry-collector/blob/main/featuregate/README.md) documentation.
================================================
FILE: otelcol/factories.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelcol // import "go.opentelemetry.io/collector/otelcol"
import (
"fmt"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/connector"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/extension"
"go.opentelemetry.io/collector/internal/componentalias"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/service/telemetry"
)
// 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[component.Type]receiver.Factory
// Processors maps processor type names in the config to the respective factory.
Processors map[component.Type]processor.Factory
// Exporters maps exporter type names in the config to the respective factory.
Exporters map[component.Type]exporter.Factory
// Extensions maps extension type names in the config to the respective factory.
Extensions map[component.Type]extension.Factory
// Connectors maps connector type names in the config to the respective factory.
Connectors map[component.Type]connector.Factory
// Telemetry is the factory to create the telemetry providers for the service.
Telemetry telemetry.Factory
// ReceiverModules maps receiver types to their respective go modules.
ReceiverModules map[component.Type]string
// ProcessorModules maps processor types to their respective go modules.
ProcessorModules map[component.Type]string
// ExporterModules maps exporter types to their respective go modules.
ExporterModules map[component.Type]string
// ExtensionModules maps extension types to their respective go modules.
ExtensionModules map[component.Type]string
// ConnectorModules maps connector types to their respective go modules.
ConnectorModules map[component.Type]string
}
// MakeFactoryMap takes a list of factories and returns a map with Factory type as keys.
// It returns a non-nil error when there are factories with duplicate type.
// If a factory has a deprecated alias, the map will also contain an entry for the alias.
func MakeFactoryMap[T component.Factory](factories ...T) (map[component.Type]T, error) {
fMap := make(map[component.Type]T, len(factories))
for _, f := range factories {
if _, ok := fMap[f.Type()]; ok {
return fMap, fmt.Errorf("duplicate component factory %q", f.Type())
}
fMap[f.Type()] = f
// If factory has a deprecated alias, add it to the map as well
if aliasHolder, ok := any(f).(componentalias.TypeAliasHolder); ok {
alias := aliasHolder.DeprecatedAlias()
if alias.String() != "" {
if _, exists := fMap[alias]; exists {
return fMap, fmt.Errorf("duplicate component factory %q (alias of %q)", alias, f.Type())
}
fMap[alias] = f
}
}
}
return fMap, nil
}
================================================
FILE: otelcol/factories_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelcol
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/connector"
"go.opentelemetry.io/collector/connector/connectortest"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/extension"
"go.opentelemetry.io/collector/extension/extensiontest"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/processortest"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/receivertest"
"go.opentelemetry.io/collector/receiver/xreceiver"
"go.opentelemetry.io/collector/service/telemetry"
)
func nopFactories() (Factories, error) {
var factories Factories
var err error
if factories.Connectors, err = MakeFactoryMap(connectortest.NewNopFactory()); err != nil {
return Factories{}, err
}
factories.ConnectorModules = make(map[component.Type]string, len(factories.Connectors))
for _, con := range factories.Connectors {
factories.ConnectorModules[con.Type()] = "go.opentelemetry.io/collector/connector/connectortest v1.2.3"
}
if factories.Extensions, err = MakeFactoryMap(extensiontest.NewNopFactory()); err != nil {
return Factories{}, err
}
factories.ExtensionModules = make(map[component.Type]string, len(factories.Extensions))
for _, ext := range factories.Extensions {
factories.ExtensionModules[ext.Type()] = "go.opentelemetry.io/collector/extension/extensiontest v1.2.3"
}
if factories.Receivers, err = MakeFactoryMap(receivertest.NewNopFactory()); err != nil {
return Factories{}, err
}
factories.ReceiverModules = make(map[component.Type]string, len(factories.Receivers))
for _, rec := range factories.Receivers {
factories.ReceiverModules[rec.Type()] = "go.opentelemetry.io/collector/receiver/receivertest v1.2.3"
}
if factories.Exporters, err = MakeFactoryMap(exportertest.NewNopFactory()); err != nil {
return Factories{}, err
}
factories.ExporterModules = make(map[component.Type]string, len(factories.Exporters))
for _, exp := range factories.Exporters {
factories.ExporterModules[exp.Type()] = "go.opentelemetry.io/collector/exporter/exportertest v1.2.3"
}
if factories.Processors, err = MakeFactoryMap(processortest.NewNopFactory()); err != nil {
return Factories{}, err
}
factories.ProcessorModules = make(map[component.Type]string, len(factories.Processors))
for _, proc := range factories.Processors {
factories.ProcessorModules[proc.Type()] = "go.opentelemetry.io/collector/processor/processortest v1.2.3"
}
factories.Telemetry = telemetry.NewFactory(func() component.Config {
return fakeTelemetryConfig{}
})
return factories, err
}
func TestMakeFactoryMap(t *testing.T) {
type testCase struct {
name string
in []component.Factory
out map[component.Type]component.Factory
}
fRec := receiver.NewFactory(component.MustNewType("rec"), nil)
fRec2 := receiver.NewFactory(component.MustNewType("rec"), nil)
fRec3 := xreceiver.NewFactory(component.MustNewType("new_rec"), nil, xreceiver.WithDeprecatedTypeAlias(component.MustNewType("rec")))
fPro := processor.NewFactory(component.MustNewType("pro"), nil)
fCon := connector.NewFactory(component.MustNewType("con"), nil)
fExp := exporter.NewFactory(component.MustNewType("exp"), nil)
fExt := extension.NewFactory(component.MustNewType("ext"), nil, nil, component.StabilityLevelUndefined)
testCases := []testCase{
{
name: "different names",
in: []component.Factory{fRec, fPro, fCon, fExp, fExt},
out: map[component.Type]component.Factory{
fRec.Type(): fRec,
fPro.Type(): fPro,
fCon.Type(): fCon,
fExp.Type(): fExp,
fExt.Type(): fExt,
},
},
{
name: "same name",
in: []component.Factory{fRec, fPro, fCon, fExp, fExt, fRec2},
},
{
name: "with deprecated alias",
in: []component.Factory{fRec3},
out: map[component.Type]component.Factory{
fRec3.Type(): fRec3,
component.MustNewType("rec"): fRec3,
},
},
{
name: "conflicting alias name",
in: []component.Factory{fRec, fRec3},
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
out, err := MakeFactoryMap(tt.in...)
if tt.out == nil {
assert.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tt.out, out)
})
}
}
================================================
FILE: otelcol/flags.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelcol // import "go.opentelemetry.io/collector/otelcol"
import (
"errors"
"flag"
"strings"
"go.opentelemetry.io/collector/featuregate"
)
const (
configFlag = "config"
)
type configFlagValue struct {
values []string
sets []string
}
func (s *configFlagValue) Set(val string) error {
s.values = append(s.values, val)
return nil
}
func (s *configFlagValue) String() string {
return "[" + strings.Join(s.values, ", ") + "]"
}
func flags(reg *featuregate.Registry) *flag.FlagSet {
flagSet := new(flag.FlagSet)
cfgs := new(configFlagValue)
flagSet.Var(cfgs, configFlag, "Locations to the config file(s), note that only a"+
" single location can be set per flag entry e.g. `--config=file:/path/to/first --config=file:path/to/second`.")
flagSet.Func("set",
"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. Example --set=processors.batch.timeout=2s",
func(s string) error {
before, after, ok := strings.Cut(s, "=")
if !ok {
// No need for more context, see TestSetFlag/invalid_set.
return errors.New("missing equal sign")
}
cfgs.sets = append(cfgs.sets, "yaml:"+strings.TrimSpace(strings.ReplaceAll(before, ".", "::"))+": "+strings.TrimSpace(after))
return nil
})
reg.RegisterFlags(flagSet)
return flagSet
}
func getConfigFlag(flagSet *flag.FlagSet) []string {
cfv := flagSet.Lookup(configFlag).Value.(*configFlagValue)
return append(cfv.values, cfv.sets...)
}
================================================
FILE: otelcol/flags_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelcol
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/featuregate"
)
func TestSetFlag(t *testing.T) {
tests := []struct {
name string
args []string
expectedConfigs []string
expectedErr string
}{
{
name: "simple set",
args: []string{"--set=key=value"},
expectedConfigs: []string{"yaml:key: value"},
},
{
name: "complex nested key",
args: []string{"--set=outer.inner=value"},
expectedConfigs: []string{"yaml:outer::inner: value"},
},
{
name: "set array",
args: []string{"--set=key=[a, b, c]"},
expectedConfigs: []string{"yaml:key: [a, b, c]"},
},
{
name: "set map",
args: []string{"--set=key={a: c}"},
expectedConfigs: []string{"yaml:key: {a: c}"},
},
{
name: "set and config",
args: []string{"--set=key=value", "--config=file:testdata/otelcol-nop.yaml"},
expectedConfigs: []string{"file:testdata/otelcol-nop.yaml", "yaml:key: value"},
},
{
name: "config and set",
args: []string{"--config=file:testdata/otelcol-nop.yaml", "--set=key=value"},
expectedConfigs: []string{"file:testdata/otelcol-nop.yaml", "yaml:key: value"},
},
{
name: "invalid set",
args: []string{"--set=key:name"},
expectedErr: `invalid value "key:name" for flag -set: missing equal sign`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
flgs := flags(featuregate.NewRegistry())
err := flgs.Parse(tt.args)
if tt.expectedErr != "" {
assert.EqualError(t, err, tt.expectedErr)
return
}
require.NoError(t, err)
assert.Equal(t, tt.expectedConfigs, getConfigFlag(flgs))
})
}
}
================================================
FILE: otelcol/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package otelcol
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: otelcol/go.mod
================================================
module go.opentelemetry.io/collector/otelcol
go 1.25.0
require (
github.com/spf13/cobra v1.10.2
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componentstatus v0.148.0
go.opentelemetry.io/collector/config/configopaque v1.54.0
go.opentelemetry.io/collector/confmap v1.54.0
go.opentelemetry.io/collector/confmap/provider/fileprovider v1.54.0
go.opentelemetry.io/collector/confmap/xconfmap v0.148.0
go.opentelemetry.io/collector/connector v0.148.0
go.opentelemetry.io/collector/connector/connectortest v0.148.0
go.opentelemetry.io/collector/connector/xconnector v0.148.0
go.opentelemetry.io/collector/consumer v1.54.0
go.opentelemetry.io/collector/exporter v1.54.0
go.opentelemetry.io/collector/exporter/exportertest v0.148.0
go.opentelemetry.io/collector/exporter/xexporter v0.148.0
go.opentelemetry.io/collector/extension v1.54.0
go.opentelemetry.io/collector/extension/extensiontest v0.148.0
go.opentelemetry.io/collector/featuregate v1.54.0
go.opentelemetry.io/collector/internal/componentalias v0.148.0
go.opentelemetry.io/collector/pipeline v1.54.0
go.opentelemetry.io/collector/processor v1.54.0
go.opentelemetry.io/collector/processor/processortest v0.148.0
go.opentelemetry.io/collector/processor/xprocessor v0.148.0
go.opentelemetry.io/collector/receiver v1.54.0
go.opentelemetry.io/collector/receiver/receivertest v0.148.0
go.opentelemetry.io/collector/receiver/xreceiver v0.148.0
go.opentelemetry.io/collector/service v0.148.0
go.opentelemetry.io/collector/service/telemetry/telemetrytest v0.148.0
go.uber.org/goleak v1.3.0
go.uber.org/multierr v1.11.0
go.uber.org/zap v1.27.1
go.yaml.in/yaml/v3 v3.0.4
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa
golang.org/x/sys v0.42.0
google.golang.org/grpc v1.79.3
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/ebitengine/purego v0.10.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.5 // indirect
github.com/prometheus/otlptranslator v1.0.0 // indirect
github.com/prometheus/procfs v0.20.1 // indirect
github.com/shirou/gopsutil/v4 v4.26.2 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.11.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/component/componenttest v0.148.0 // indirect
go.opentelemetry.io/collector/config/configtelemetry v0.148.0 // indirect
go.opentelemetry.io/collector/consumer/consumererror v0.148.0 // indirect
go.opentelemetry.io/collector/consumer/consumertest v0.148.0 // indirect
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0 // indirect
go.opentelemetry.io/collector/extension/extensioncapabilities v0.148.0 // indirect
go.opentelemetry.io/collector/internal/fanoutconsumer v0.148.0 // indirect
go.opentelemetry.io/collector/internal/telemetry v0.148.0 // indirect
go.opentelemetry.io/collector/pdata v1.54.0 // indirect
go.opentelemetry.io/collector/pdata/pprofile v0.148.0 // indirect
go.opentelemetry.io/collector/pdata/testdata v0.148.0 // indirect
go.opentelemetry.io/collector/pdata/xpdata v0.148.0 // indirect
go.opentelemetry.io/collector/pipeline/xpipeline v0.148.0 // indirect
go.opentelemetry.io/collector/service/hostcapabilities v0.148.0 // indirect
go.opentelemetry.io/contrib/otelconf v0.22.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/prometheus v0.64.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 // indirect
go.opentelemetry.io/otel/log v0.18.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
go.opentelemetry.io/otel/sdk/log v0.18.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/text v0.34.0 // indirect
gonum.org/v1/gonum v0.17.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/service => ../service
replace go.opentelemetry.io/collector/service/telemetry/telemetrytest => ../service/telemetry/telemetrytest
replace go.opentelemetry.io/collector/service/hostcapabilities => ../service/hostcapabilities
replace go.opentelemetry.io/collector/connector => ../connector
replace go.opentelemetry.io/collector/connector/connectortest => ../connector/connectortest
replace go.opentelemetry.io/collector/component => ../component
replace go.opentelemetry.io/collector/component/componenttest => ../component/componenttest
replace go.opentelemetry.io/collector/pdata => ../pdata
replace go.opentelemetry.io/collector/pdata/testdata => ../pdata/testdata
replace go.opentelemetry.io/collector/pdata/pprofile => ../pdata/pprofile
replace go.opentelemetry.io/collector/extension/zpagesextension => ../extension/zpagesextension
replace go.opentelemetry.io/collector/extension => ../extension
replace go.opentelemetry.io/collector/exporter => ../exporter
replace go.opentelemetry.io/collector/confmap => ../confmap
replace go.opentelemetry.io/collector/confmap/xconfmap => ../confmap/xconfmap
replace go.opentelemetry.io/collector/config/configtelemetry => ../config/configtelemetry
replace go.opentelemetry.io/collector/processor => ../processor
replace go.opentelemetry.io/collector/consumer => ../consumer
replace go.opentelemetry.io/collector/receiver => ../receiver
replace go.opentelemetry.io/collector/featuregate => ../featuregate
replace go.opentelemetry.io/collector/config/configretry => ../config/configretry
replace go.opentelemetry.io/collector/config/confighttp => ../config/confighttp
replace go.opentelemetry.io/collector/config/configauth => ../config/configauth
replace go.opentelemetry.io/collector/extension/extensionauth => ../extension/extensionauth
replace go.opentelemetry.io/collector/config/configcompression => ../config/configcompression
replace go.opentelemetry.io/collector/config/configtls => ../config/configtls
replace go.opentelemetry.io/collector/config/configopaque => ../config/configopaque
replace go.opentelemetry.io/collector/consumer/xconsumer => ../consumer/xconsumer
replace go.opentelemetry.io/collector/consumer/consumertest => ../consumer/consumertest
replace go.opentelemetry.io/collector/client => ../client
replace go.opentelemetry.io/collector/component/componentstatus => ../component/componentstatus
replace go.opentelemetry.io/collector/extension/extensioncapabilities => ../extension/extensioncapabilities
replace go.opentelemetry.io/collector/receiver/xreceiver => ../receiver/xreceiver
replace go.opentelemetry.io/collector/receiver/receivertest => ../receiver/receivertest
replace go.opentelemetry.io/collector/processor/xprocessor => ../processor/xprocessor
replace go.opentelemetry.io/collector/connector/xconnector => ../connector/xconnector
replace go.opentelemetry.io/collector/exporter/xexporter => ../exporter/xexporter
replace go.opentelemetry.io/collector/pipeline => ../pipeline
replace go.opentelemetry.io/collector/pipeline/xpipeline => ../pipeline/xpipeline
replace go.opentelemetry.io/collector/exporter/exportertest => ../exporter/exportertest
replace go.opentelemetry.io/collector/processor/processortest => ../processor/processortest
replace go.opentelemetry.io/collector/consumer/consumererror => ../consumer/consumererror
replace go.opentelemetry.io/collector/internal/fanoutconsumer => ../internal/fanoutconsumer
replace go.opentelemetry.io/collector/extension/extensiontest => ../extension/extensiontest
replace go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest => ../extension/extensionauth/extensionauthtest
replace go.opentelemetry.io/collector/extension/xextension => ../extension/xextension
replace go.opentelemetry.io/collector/confmap/provider/fileprovider => ../confmap/provider/fileprovider
replace go.opentelemetry.io/collector/internal/telemetry => ../internal/telemetry
replace go.opentelemetry.io/collector/config/configmiddleware => ../config/configmiddleware
replace go.opentelemetry.io/collector/extension/extensionmiddleware => ../extension/extensionmiddleware
replace go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest => ../extension/extensionmiddleware/extensionmiddlewaretest
replace go.opentelemetry.io/collector/config/configoptional => ../config/configoptional
replace go.opentelemetry.io/collector/pdata/xpdata => ../pdata/xpdata
replace go.opentelemetry.io/collector/exporter/exporterhelper => ../exporter/exporterhelper
replace go.opentelemetry.io/collector/internal/testutil => ../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../internal/componentalias
replace go.opentelemetry.io/collector/config/confignet => ../config/confignet
================================================
FILE: otelcol/go.sum
================================================
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/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f h1:RJ+BDPLSHQO7cSjKBqjPJSbi1qfk9WcsjQDtZiw3dZw=
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f/go.mod h1:VHbbch/X4roIY22jL1s3qRbZhCiRIgUAF/PdSUcx2io=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
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 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY=
github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos=
github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM=
github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shirou/gopsutil/v4 v4.26.2 h1:X8i6sicvUFih4BmYIGT1m2wwgw2VG9YgrDTi7cIRGUI=
github.com/shirou/gopsutil/v4 v4.26.2/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
go.opentelemetry.io/contrib/otelconf v0.22.0 h1:+kpcfczGOFM85zDZyqQCzWefhovegfn24D0WwmQz0n4=
go.opentelemetry.io/contrib/otelconf v0.22.0/go.mod h1:ojdbOukO+JRDJQmJY2PRIZEg0UYVzcOuZR59hp7xffc=
go.opentelemetry.io/contrib/zpages v0.67.0 h1:cIUwWSVDovuLEbDIKreptjdxMuIhGiqwq0uL8YNaq1c=
go.opentelemetry.io/contrib/zpages v0.67.0/go.mod h1:vK8fsYHgPYg4Z/XDbFSEvItSGZDbjWTvjBOu8+AiDhc=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 h1:deI9UQMoGFgrg5iLPgzueqFPHevDl+28YKfSpPTI6rY=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0/go.mod h1:PFx9NgpNUKXdf7J4Q3agRxMs3Y07QhTCVipKmLsMKnU=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0 h1:icqq3Z34UrEFk2u+HMhTtRsvo7Ues+eiJVjaJt62njs=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0/go.mod h1:W2m8P+d5Wn5kipj4/xmbt9uMqezEKfBjzVJadfABSBE=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 h1:MdKucPl/HbzckWWEisiNqMPhRrAOQX8r4jTuGr636gk=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0/go.mod h1:RolT8tWtfHcjajEH5wFIZ4Dgh5jpPdFXYV9pTAk/qjc=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0 h1:H7O6RlGOMTizyl3R08Kn5pdM06bnH8oscSj7o11tmLA=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0/go.mod h1:mBFWu/WOVDkWWsR7Tx7h6EpQB8wsv7P0Yrh0Pb7othc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 h1:uLXP+3mghfMf7XmV4PkGfFhFKuNWoCvvx5wP/wOXo0o=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0/go.mod h1:v0Tj04armyT59mnURNUJf7RCKcKzq+lgJs6QSjHjaTc=
go.opentelemetry.io/otel/exporters/prometheus v0.64.0 h1:g0LRDXMX/G1SEZtK8zl8Chm4K6GBwRkjPKE36LxiTYs=
go.opentelemetry.io/otel/exporters/prometheus v0.64.0/go.mod h1:UrgcjnarfdlBDP3GjDIJWe6HTprwSazNjwsI+Ru6hro=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 h1:KJVjPD3rcPb98rIs3HznyJlrfx9ge5oJvxxlGR+P/7s=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0/go.mod h1:K3kRa2ckmHWQaTWQdPRHc7qGXASuVuoEQXzrvlA98Ws=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 h1:lSZHgNHfbmQTPfuTmWVkEu8J8qXaQwuV30pjCcAUvP8=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0/go.mod h1:so9ounLcuoRDu033MW/E0AD4hhUjVqswrMF5FoZlBcw=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1JF26knRneorus8aOwVIDhvYx9WoDw=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs=
go.opentelemetry.io/otel/log v0.18.0 h1:XgeQIIBjZZrliksMEbcwMZefoOSMI1hdjiLEiiB0bAg=
go.opentelemetry.io/otel/log v0.18.0/go.mod h1:KEV1kad0NofR3ycsiDH4Yjcoj0+8206I6Ox2QYFSNgI=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/log v0.18.0 h1:n8OyZr7t7otkeTnPTbDNom6rW16TBYGtvyy2Gk6buQw=
go.opentelemetry.io/otel/sdk/log v0.18.0/go.mod h1:C0+wxkTwKpOCZLrlJ3pewPiiQwpzycPI/u6W0Z9fuYk=
go.opentelemetry.io/otel/sdk/log/logtest v0.18.0 h1:l3mYuPsuBx6UKE47BVcPrZoZ0q/KER57vbj2qkgDLXA=
go.opentelemetry.io/otel/sdk/log/logtest v0.18.0/go.mod h1:7cHtiVJpZebB3wybTa4NG+FUo5NPe3PROz1FqB0+qdw=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 h1:tu/dtnW1o3wfaxCOjSLn5IRX4YDcJrtlpzYkhHhGaC4=
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: otelcol/internal/configunmarshaler/configs.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configunmarshaler // import "go.opentelemetry.io/collector/otelcol/internal/configunmarshaler"
import (
"errors"
"fmt"
"golang.org/x/exp/maps"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/confmap"
)
type Configs[F component.Factory] struct {
cfgs map[component.ID]component.Config
factories map[component.Type]F
}
func NewConfigs[F component.Factory](factories map[component.Type]F) *Configs[F] {
return &Configs[F]{factories: factories}
}
func (c *Configs[F]) Unmarshal(conf *confmap.Conf) error {
rawCfgs := make(map[component.ID]map[string]any)
if err := conf.Unmarshal(&rawCfgs); err != nil {
return err
}
// Prepare resulting map.
c.cfgs = make(map[component.ID]component.Config)
// Iterate over raw configs and create a config for each.
for id := range rawCfgs {
// Find factory based on component kind and type that we read from config source.
factory, ok := c.factories[id.Type()]
if !ok {
return errorUnknownType(id, maps.Keys(c.factories))
}
// Get the configuration from the confmap.Conf to preserve internal representation.
sub, err := conf.Sub(id.String())
if err != nil {
return errorUnmarshalError(id, err)
}
// Create the default config for this component.
cfg := factory.CreateDefaultConfig()
// 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.
if err := sub.Unmarshal(&cfg); err != nil {
return errorUnmarshalError(id, err)
}
c.cfgs[id] = cfg
}
return nil
}
func (c *Configs[F]) Configs() map[component.ID]component.Config {
return c.cfgs
}
func errorUnknownType(id component.ID, factories []component.Type) error {
if id.Type().String() == "logging" {
return errors.New("the logging exporter has been deprecated, use the debug exporter instead")
}
return fmt.Errorf("unknown type: %q for id: %q (valid values: %v)", id.Type(), id, factories)
}
func errorUnmarshalError(id component.ID, err error) error {
return fmt.Errorf("error reading configuration for %q: %w", id, err)
}
================================================
FILE: otelcol/internal/configunmarshaler/configs_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configunmarshaler
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/connector/connectortest"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/extension/extensiontest"
"go.opentelemetry.io/collector/processor/processortest"
"go.opentelemetry.io/collector/receiver/receivertest"
)
var nopType = component.MustNewType("nop")
var testKinds = []struct {
kind string
factories map[component.Type]component.Factory
}{
{
kind: "receiver",
factories: map[component.Type]component.Factory{
nopType: receivertest.NewNopFactory(),
},
},
{
kind: "processor",
factories: map[component.Type]component.Factory{
nopType: processortest.NewNopFactory(),
},
},
{
kind: "exporter",
factories: map[component.Type]component.Factory{
nopType: exportertest.NewNopFactory(),
},
},
{
kind: "connector",
factories: map[component.Type]component.Factory{
nopType: connectortest.NewNopFactory(),
},
},
{
kind: "extension",
factories: map[component.Type]component.Factory{
nopType: extensiontest.NewNopFactory(),
},
},
}
func TestUnmarshal(t *testing.T) {
for _, tk := range testKinds {
t.Run(tk.kind, func(t *testing.T) {
cfgs := NewConfigs(tk.factories)
conf := confmap.NewFromStringMap(map[string]any{
"nop": nil,
"nop/my" + tk.kind: nil,
})
require.NoError(t, cfgs.Unmarshal(conf))
assert.Equal(t, map[component.ID]component.Config{
component.NewID(nopType): tk.factories[nopType].CreateDefaultConfig(),
component.NewIDWithName(nopType, "my"+tk.kind): tk.factories[nopType].CreateDefaultConfig(),
}, cfgs.Configs())
})
}
}
func TestUnmarshalError(t *testing.T) {
for _, tk := range testKinds {
t.Run(tk.kind, func(t *testing.T) {
testCases := []struct {
name string
conf *confmap.Conf
// string that the error must contain
expectedError string
}{
{
name: "invalid-type",
conf: confmap.NewFromStringMap(map[string]any{
"nop": nil,
"/custom": nil,
}),
expectedError: "the part before / should not be empty",
},
{
name: "invalid-name-after-slash",
conf: confmap.NewFromStringMap(map[string]any{
"nop": nil,
"nop/": nil,
}),
expectedError: "the part after / should not be empty",
},
{
name: "unknown-type",
conf: confmap.NewFromStringMap(map[string]any{
"nosuch" + tk.kind: nil,
}),
expectedError: "unknown type: \"nosuch" + tk.kind + "\" for id: \"nosuch" + tk.kind + "\" (valid values: [nop])",
},
{
name: "duplicate",
conf: confmap.NewFromStringMap(map[string]any{
"nop /my" + tk.kind + " ": nil,
" nop/ my" + tk.kind: nil,
}),
expectedError: "duplicate name",
},
{
name: "invalid-section",
conf: confmap.NewFromStringMap(map[string]any{
"nop": map[string]any{
"unknown_section": tk.kind,
},
}),
expectedError: "error reading configuration for \"nop\"",
},
{
name: "invalid-sub-config",
conf: confmap.NewFromStringMap(map[string]any{
"nop": "tests",
}),
expectedError: "'[nop]' expected type 'map[string]interface {}', got unconvertible type 'string'",
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
cfgs := NewConfigs(tk.factories)
err := cfgs.Unmarshal(tt.conf)
assert.ErrorContains(t, err, tt.expectedError)
})
}
})
}
}
func TestUnmarshal_LoggingExporter(t *testing.T) {
conf := confmap.NewFromStringMap(map[string]any{
"logging": nil,
})
factories := map[component.Type]component.Factory{
nopType: exportertest.NewNopFactory(),
}
cfgs := NewConfigs(factories)
err := cfgs.Unmarshal(conf)
assert.ErrorContains(t, err, "the logging exporter has been deprecated, use the debug exporter instead")
}
================================================
FILE: otelcol/internal/configunmarshaler/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configunmarshaler
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: otelcol/internal/grpclog/logger.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package grpclog // import "go.opentelemetry.io/collector/otelcol/internal/grpclog"
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"go.uber.org/zap/zapgrpc"
"google.golang.org/grpc/grpclog"
)
// SetLogger constructs a zapgrpc.Logger instance, and installs it as grpc logger, cloned from baseLogger with
// exact configuration. The minimum level of gRPC logs is set to WARN should the loglevel of the collector is set to
// INFO to avoid copious logging from grpc framework.
func SetLogger(baseLogger *zap.Logger) *zapgrpc.Logger {
logger := zapgrpc.NewLogger(baseLogger.WithOptions(zap.WrapCore(func(core zapcore.Core) zapcore.Core {
var c zapcore.Core
var err error
loglevel := baseLogger.Level()
if loglevel == zapcore.InfoLevel {
loglevel = zapcore.WarnLevel
}
// NewIncreaseLevelCore errors only if the new log level is less than the initial core level.
c, err = zapcore.NewIncreaseLevelCore(core, loglevel)
// In case of an error changing the level, move on, this happens when using the NopCore
if err != nil {
c = core
}
return c.With([]zapcore.Field{zap.Bool("grpc_log", true)})
}), zap.AddCallerSkip(5)))
grpclog.SetLoggerV2(logger)
return logger
}
================================================
FILE: otelcol/internal/grpclog/logger_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package grpclog
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"google.golang.org/grpc/grpclog"
)
func TestGRPCLogger(t *testing.T) {
tests := []struct {
name string
cfg zap.Config
infoLogged bool
warnLogged bool
}{
{
"collector_info_level_grpc_log_warn",
zap.Config{
Level: zap.NewAtomicLevelAt(zapcore.InfoLevel),
Encoding: "console",
},
false,
true,
},
{
"collector_debug_level_grpc_log_debug",
zap.Config{
Level: zap.NewAtomicLevelAt(zapcore.DebugLevel),
Encoding: "console",
},
true,
true,
},
{
"collector_warn_level_grpc_log_warn",
zap.Config{
Development: false, // this must set the grpc loggerV2 to loggerV2
Level: zap.NewAtomicLevelAt(zapcore.WarnLevel),
Encoding: "console",
},
false,
true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
obsInfo, obsWarn := false, false
callerInfo := ""
hook := zap.Hooks(func(entry zapcore.Entry) error {
switch entry.Level {
case zapcore.InfoLevel:
obsInfo = true
case zapcore.WarnLevel:
obsWarn = true
}
callerInfo = entry.Caller.String()
return nil
})
// create new collector zap logger
logger, err := test.cfg.Build(hook)
require.NoError(t, err)
// create GRPCLogger
glogger := SetLogger(logger)
assert.NotNil(t, glogger)
// grpc does not usually call the logger directly, but through various wrappers that add extra depth
component := &mockComponent{logger: grpclog.Component("channelz")}
component.Info(test.name)
component.Warning(test.name)
assert.Equal(t, obsInfo, test.infoLogged)
assert.Equal(t, obsWarn, test.warnLogged)
// match the file name and line number of Warning() call above
assert.Contains(t, callerInfo, "internal/grpclog/logger_test.go:77")
})
}
}
type mockComponent struct {
logger grpclog.DepthLoggerV2
}
func (c *mockComponent) Info(args ...any) {
c.logger.Info(args...)
}
func (c *mockComponent) Warning(args ...any) {
c.logger.Warning(args...)
}
================================================
FILE: otelcol/internal/grpclog/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package grpclog
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: otelcol/internal/metadata/generated_feature_gates.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/featuregate"
)
var OtelcolPrintInitialConfigFeatureGate = featuregate.GlobalRegistry().MustRegister(
"otelcol.printInitialConfig",
featuregate.StageBeta,
featuregate.WithRegisterDescription("if set to true, enable the print-config command"),
featuregate.WithRegisterReferenceURL("https://github.com/open-telemetry/opentelemetry-collector/pull/11775"),
featuregate.WithRegisterFromVersion("v0.120.0"),
)
================================================
FILE: otelcol/metadata.yaml
================================================
type: otelcol
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
distributions: [core, contrib]
stability:
beta: [metrics, traces, logs]
alpha: [profiles]
feature_gates:
- id: otelcol.printInitialConfig
description: 'if set to true, enable the print-config command'
stage: beta
from_version: 'v0.120.0'
reference_url: 'https://github.com/open-telemetry/opentelemetry-collector/pull/11775'
================================================
FILE: otelcol/otelcoltest/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: otelcol/otelcoltest/config.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelcoltest // import "go.opentelemetry.io/collector/otelcol/otelcoltest"
import (
"context"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/provider/envprovider"
"go.opentelemetry.io/collector/confmap/provider/fileprovider"
"go.opentelemetry.io/collector/confmap/provider/httpprovider"
"go.opentelemetry.io/collector/confmap/provider/yamlprovider"
"go.opentelemetry.io/collector/confmap/xconfmap"
"go.opentelemetry.io/collector/otelcol"
)
// LoadConfig loads a config.Config from file, and does NOT validate the configuration.
//
// If factories.Telemetry is nil, a no-op telemetry factory will be used. This
// factory does not support any telemetry configuration.
func LoadConfig(fileName string, factories otelcol.Factories) (*otelcol.Config, error) {
if factories.Telemetry == nil {
factories.Telemetry = nopTelemetryFactory()
}
provider, err := otelcol.NewConfigProvider(otelcol.ConfigProviderSettings{
ResolverSettings: confmap.ResolverSettings{
URIs: []string{fileName},
ProviderFactories: []confmap.ProviderFactory{
fileprovider.NewFactory(),
envprovider.NewFactory(),
yamlprovider.NewFactory(),
httpprovider.NewFactory(),
},
DefaultScheme: "env",
},
})
if err != nil {
return nil, err
}
return provider.Get(context.Background(), factories)
}
// LoadConfigAndValidate loads a config from the file, and validates the configuration.
func LoadConfigAndValidate(fileName string, factories otelcol.Factories) (*otelcol.Config, error) {
cfg, err := LoadConfig(fileName, factories)
if err != nil {
return nil, err
}
return cfg, xconfmap.Validate(cfg)
}
================================================
FILE: otelcol/otelcoltest/config_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelcoltest
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/service/pipelines"
)
func TestLoadConfig(t *testing.T) {
factories, err := NopFactories()
require.NoError(t, err)
cfg, err := LoadConfig(filepath.Join("testdata", "config.yaml"), factories)
require.NoError(t, err)
// Verify extensions.
require.Len(t, cfg.Extensions, 2)
assert.Contains(t, cfg.Extensions, component.MustNewID("nop"))
assert.Contains(t, cfg.Extensions, component.MustNewIDWithName("nop", "myextension"))
// Verify receivers
require.Len(t, cfg.Receivers, 2)
assert.Contains(t, cfg.Receivers, component.MustNewID("nop"))
assert.Contains(t, cfg.Receivers, component.MustNewIDWithName("nop", "myreceiver"))
// Verify exporters
assert.Len(t, cfg.Exporters, 2)
assert.Contains(t, cfg.Exporters, component.MustNewID("nop"))
assert.Contains(t, cfg.Exporters, component.MustNewIDWithName("nop", "myexporter"))
// Verify procs
assert.Len(t, cfg.Processors, 2)
assert.Contains(t, cfg.Processors, component.MustNewID("nop"))
assert.Contains(t, cfg.Processors, component.MustNewIDWithName("nop", "myprocessor"))
// Verify connectors
assert.Len(t, cfg.Connectors, 1)
assert.Contains(t, cfg.Connectors, component.MustNewIDWithName("nop", "myconnector"))
// Verify service.
require.Len(t, cfg.Service.Extensions, 1)
assert.Contains(t, cfg.Service.Extensions, component.MustNewID("nop"))
require.Len(t, cfg.Service.Pipelines, 1)
assert.Equal(t,
&pipelines.PipelineConfig{
Receivers: []component.ID{component.MustNewID("nop")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("nop")},
},
cfg.Service.Pipelines[pipeline.NewID(pipeline.SignalTraces)],
"Did not load pipeline config correctly")
// Verify telemetry
assert.Equal(t, struct{}{}, cfg.Service.Telemetry)
}
func TestLoadConfig_DefaultNopTelemetry(t *testing.T) {
factories, err := NopFactories()
require.NoError(t, err)
factories.Telemetry = nil
cfg, err := LoadConfig(filepath.Join("testdata", "config.yaml"), factories)
require.NoError(t, err)
assert.Equal(t, struct{}{}, cfg.Service.Telemetry)
}
func TestLoadConfigAndValidate(t *testing.T) {
factories, err := NopFactories()
require.NoError(t, err)
cfgValidate, errValidate := LoadConfigAndValidate(filepath.Join("testdata", "config.yaml"), factories)
require.NoError(t, errValidate)
cfg, errLoad := LoadConfig(filepath.Join("testdata", "config.yaml"), factories)
require.NoError(t, errLoad)
assert.Equal(t, cfg, cfgValidate)
}
func TestLoadConfigEnv(t *testing.T) {
factories, err := NopFactories()
require.NoError(t, err)
for _, tt := range []struct {
file string
}{
{file: filepath.Join("testdata", "config_env.yaml")},
{file: filepath.Join("testdata", "config_default_scheme.yaml")},
} {
t.Run(tt.file, func(t *testing.T) {
t.Setenv("RECEIVERS", "[nop]")
cfg, err := LoadConfigAndValidate(tt.file, factories)
require.NoError(t, err)
assert.Equal(t, []component.ID{component.MustNewID("nop")}, cfg.Service.Pipelines[pipeline.NewID(pipeline.SignalTraces)].Receivers)
})
}
}
================================================
FILE: otelcol/otelcoltest/go.mod
================================================
module go.opentelemetry.io/collector/otelcol/otelcoltest
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/confmap v1.54.0
go.opentelemetry.io/collector/confmap/provider/envprovider v1.54.0
go.opentelemetry.io/collector/confmap/provider/fileprovider v1.54.0
go.opentelemetry.io/collector/confmap/provider/httpprovider v1.54.0
go.opentelemetry.io/collector/confmap/provider/yamlprovider v1.54.0
go.opentelemetry.io/collector/confmap/xconfmap v0.148.0
go.opentelemetry.io/collector/connector/connectortest v0.148.0
go.opentelemetry.io/collector/exporter/exportertest v0.148.0
go.opentelemetry.io/collector/extension/extensiontest v0.148.0
go.opentelemetry.io/collector/otelcol v0.148.0
go.opentelemetry.io/collector/pipeline v1.54.0
go.opentelemetry.io/collector/processor/processortest v0.148.0
go.opentelemetry.io/collector/receiver/receivertest v0.148.0
go.opentelemetry.io/collector/service v0.148.0
go.uber.org/goleak v1.3.0
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/ebitengine/purego v0.10.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.5 // indirect
github.com/prometheus/otlptranslator v1.0.0 // indirect
github.com/prometheus/procfs v0.20.1 // indirect
github.com/shirou/gopsutil/v4 v4.26.2 // indirect
github.com/spf13/cobra v1.10.2 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.11.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/component/componentstatus v0.148.0 // indirect
go.opentelemetry.io/collector/component/componenttest v0.148.0 // indirect
go.opentelemetry.io/collector/config/configtelemetry v0.148.0 // indirect
go.opentelemetry.io/collector/connector v0.148.0 // indirect
go.opentelemetry.io/collector/connector/xconnector v0.148.0 // indirect
go.opentelemetry.io/collector/consumer v1.54.0 // indirect
go.opentelemetry.io/collector/consumer/consumererror v0.148.0 // indirect
go.opentelemetry.io/collector/consumer/consumertest v0.148.0 // indirect
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0 // indirect
go.opentelemetry.io/collector/exporter v1.54.0 // indirect
go.opentelemetry.io/collector/exporter/xexporter v0.148.0 // indirect
go.opentelemetry.io/collector/extension v1.54.0 // indirect
go.opentelemetry.io/collector/extension/extensioncapabilities v0.148.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
go.opentelemetry.io/collector/internal/fanoutconsumer v0.148.0 // indirect
go.opentelemetry.io/collector/internal/telemetry v0.148.0 // indirect
go.opentelemetry.io/collector/pdata v1.54.0 // indirect
go.opentelemetry.io/collector/pdata/pprofile v0.148.0 // indirect
go.opentelemetry.io/collector/pdata/testdata v0.148.0 // indirect
go.opentelemetry.io/collector/pdata/xpdata v0.148.0 // indirect
go.opentelemetry.io/collector/pipeline/xpipeline v0.148.0 // indirect
go.opentelemetry.io/collector/processor v1.54.0 // indirect
go.opentelemetry.io/collector/processor/xprocessor v0.148.0 // indirect
go.opentelemetry.io/collector/receiver v1.54.0 // indirect
go.opentelemetry.io/collector/receiver/xreceiver v0.148.0 // indirect
go.opentelemetry.io/collector/service/hostcapabilities v0.148.0 // indirect
go.opentelemetry.io/contrib/otelconf v0.22.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/prometheus v0.64.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 // indirect
go.opentelemetry.io/otel/log v0.18.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
go.opentelemetry.io/otel/sdk/log v0.18.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.34.0 // indirect
gonum.org/v1/gonum v0.17.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/receiver => ../../receiver
replace go.opentelemetry.io/collector/consumer => ../../consumer
replace go.opentelemetry.io/collector/confmap/provider/httpprovider => ../../confmap/provider/httpprovider
replace go.opentelemetry.io/collector/confmap/provider/yamlprovider => ../../confmap/provider/yamlprovider
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/config/configtelemetry => ../../config/configtelemetry
replace go.opentelemetry.io/collector/processor => ../../processor
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile
replace go.opentelemetry.io/collector/connector => ../../connector
replace go.opentelemetry.io/collector/connector/connectortest => ../../connector/connectortest
replace go.opentelemetry.io/collector/config/configretry => ../../config/configretry
replace go.opentelemetry.io/collector/confmap/provider/fileprovider => ../../confmap/provider/fileprovider
replace go.opentelemetry.io/collector/confmap/provider/envprovider => ../../confmap/provider/envprovider
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest
replace go.opentelemetry.io/collector/extension => ../../extension
replace go.opentelemetry.io/collector/exporter => ../../exporter
replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer
replace go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest
replace go.opentelemetry.io/collector/component/componentstatus => ../../component/componentstatus
replace go.opentelemetry.io/collector/extension/extensioncapabilities => ../../extension/extensioncapabilities
replace go.opentelemetry.io/collector/receiver/xreceiver => ../../receiver/xreceiver
replace go.opentelemetry.io/collector/receiver/receivertest => ../../receiver/receivertest
replace go.opentelemetry.io/collector/processor/xprocessor => ../../processor/xprocessor
replace go.opentelemetry.io/collector/connector/xconnector => ../../connector/xconnector
replace go.opentelemetry.io/collector/exporter/xexporter => ../../exporter/xexporter
replace go.opentelemetry.io/collector/pipeline => ../../pipeline
replace go.opentelemetry.io/collector/pipeline/xpipeline => ../../pipeline/xpipeline
replace go.opentelemetry.io/collector/exporter/exportertest => ../../exporter/exportertest
replace go.opentelemetry.io/collector/consumer/consumererror => ../../consumer/consumererror
replace go.opentelemetry.io/collector/config/configopaque => ../../config/configopaque
replace go.opentelemetry.io/collector/config/configauth => ../../config/configauth
replace go.opentelemetry.io/collector/config/configtls => ../../config/configtls
replace go.opentelemetry.io/collector/config/configcompression => ../../config/configcompression
replace go.opentelemetry.io/collector/config/confighttp => ../../config/confighttp
replace go.opentelemetry.io/collector/client => ../../client
replace go.opentelemetry.io/collector/extension/extensionauth => ../../extension/extensionauth
replace go.opentelemetry.io/collector/otelcol => ../
replace go.opentelemetry.io/collector/extension/zpagesextension => ../../extension/zpagesextension
replace go.opentelemetry.io/collector/service => ../../service
replace go.opentelemetry.io/collector/service/telemetry/telemetrytest => ../../service/telemetry/telemetrytest
replace go.opentelemetry.io/collector/service/hostcapabilities => ../../service/hostcapabilities
replace go.opentelemetry.io/collector/internal/fanoutconsumer => ../../internal/fanoutconsumer
replace go.opentelemetry.io/collector/extension/extensiontest => ../../extension/extensiontest
replace go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest => ../../extension/extensionauth/extensionauthtest
replace go.opentelemetry.io/collector/extension/xextension => ../../extension/xextension
replace go.opentelemetry.io/collector/confmap => ../../confmap
replace go.opentelemetry.io/collector/confmap/xconfmap => ../../confmap/xconfmap
replace go.opentelemetry.io/collector/processor/processortest => ../../processor/processortest
replace go.opentelemetry.io/collector/pdata/testdata => ../../pdata/testdata
replace go.opentelemetry.io/collector/internal/telemetry => ../../internal/telemetry
replace go.opentelemetry.io/collector/config/configmiddleware => ../../config/configmiddleware
replace go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest => ../../extension/extensionmiddleware/extensionmiddlewaretest
replace go.opentelemetry.io/collector/extension/extensionmiddleware => ../../extension/extensionmiddleware
replace go.opentelemetry.io/collector/pdata/xpdata => ../../pdata/xpdata
replace go.opentelemetry.io/collector/exporter/exporterhelper => ../../exporter/exporterhelper
replace go.opentelemetry.io/collector/config/configoptional => ../../config/configoptional
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
replace go.opentelemetry.io/collector/config/confignet => ../../config/confignet
================================================
FILE: otelcol/otelcoltest/go.sum
================================================
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/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f h1:RJ+BDPLSHQO7cSjKBqjPJSbi1qfk9WcsjQDtZiw3dZw=
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f/go.mod h1:VHbbch/X4roIY22jL1s3qRbZhCiRIgUAF/PdSUcx2io=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
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 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY=
github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos=
github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM=
github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shirou/gopsutil/v4 v4.26.2 h1:X8i6sicvUFih4BmYIGT1m2wwgw2VG9YgrDTi7cIRGUI=
github.com/shirou/gopsutil/v4 v4.26.2/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
go.opentelemetry.io/contrib/otelconf v0.22.0 h1:+kpcfczGOFM85zDZyqQCzWefhovegfn24D0WwmQz0n4=
go.opentelemetry.io/contrib/otelconf v0.22.0/go.mod h1:ojdbOukO+JRDJQmJY2PRIZEg0UYVzcOuZR59hp7xffc=
go.opentelemetry.io/contrib/zpages v0.67.0 h1:cIUwWSVDovuLEbDIKreptjdxMuIhGiqwq0uL8YNaq1c=
go.opentelemetry.io/contrib/zpages v0.67.0/go.mod h1:vK8fsYHgPYg4Z/XDbFSEvItSGZDbjWTvjBOu8+AiDhc=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 h1:deI9UQMoGFgrg5iLPgzueqFPHevDl+28YKfSpPTI6rY=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0/go.mod h1:PFx9NgpNUKXdf7J4Q3agRxMs3Y07QhTCVipKmLsMKnU=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0 h1:icqq3Z34UrEFk2u+HMhTtRsvo7Ues+eiJVjaJt62njs=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0/go.mod h1:W2m8P+d5Wn5kipj4/xmbt9uMqezEKfBjzVJadfABSBE=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 h1:MdKucPl/HbzckWWEisiNqMPhRrAOQX8r4jTuGr636gk=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0/go.mod h1:RolT8tWtfHcjajEH5wFIZ4Dgh5jpPdFXYV9pTAk/qjc=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0 h1:H7O6RlGOMTizyl3R08Kn5pdM06bnH8oscSj7o11tmLA=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0/go.mod h1:mBFWu/WOVDkWWsR7Tx7h6EpQB8wsv7P0Yrh0Pb7othc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 h1:uLXP+3mghfMf7XmV4PkGfFhFKuNWoCvvx5wP/wOXo0o=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0/go.mod h1:v0Tj04armyT59mnURNUJf7RCKcKzq+lgJs6QSjHjaTc=
go.opentelemetry.io/otel/exporters/prometheus v0.64.0 h1:g0LRDXMX/G1SEZtK8zl8Chm4K6GBwRkjPKE36LxiTYs=
go.opentelemetry.io/otel/exporters/prometheus v0.64.0/go.mod h1:UrgcjnarfdlBDP3GjDIJWe6HTprwSazNjwsI+Ru6hro=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 h1:KJVjPD3rcPb98rIs3HznyJlrfx9ge5oJvxxlGR+P/7s=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0/go.mod h1:K3kRa2ckmHWQaTWQdPRHc7qGXASuVuoEQXzrvlA98Ws=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 h1:lSZHgNHfbmQTPfuTmWVkEu8J8qXaQwuV30pjCcAUvP8=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0/go.mod h1:so9ounLcuoRDu033MW/E0AD4hhUjVqswrMF5FoZlBcw=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1JF26knRneorus8aOwVIDhvYx9WoDw=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs=
go.opentelemetry.io/otel/log v0.18.0 h1:XgeQIIBjZZrliksMEbcwMZefoOSMI1hdjiLEiiB0bAg=
go.opentelemetry.io/otel/log v0.18.0/go.mod h1:KEV1kad0NofR3ycsiDH4Yjcoj0+8206I6Ox2QYFSNgI=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/log v0.18.0 h1:n8OyZr7t7otkeTnPTbDNom6rW16TBYGtvyy2Gk6buQw=
go.opentelemetry.io/otel/sdk/log v0.18.0/go.mod h1:C0+wxkTwKpOCZLrlJ3pewPiiQwpzycPI/u6W0Z9fuYk=
go.opentelemetry.io/otel/sdk/log/logtest v0.18.0 h1:l3mYuPsuBx6UKE47BVcPrZoZ0q/KER57vbj2qkgDLXA=
go.opentelemetry.io/otel/sdk/log/logtest v0.18.0/go.mod h1:7cHtiVJpZebB3wybTa4NG+FUo5NPe3PROz1FqB0+qdw=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 h1:tu/dtnW1o3wfaxCOjSLn5IRX4YDcJrtlpzYkhHhGaC4=
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: otelcol/otelcoltest/metadata.yaml
================================================
type: otelcol/otelcoltest
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: otelcol/otelcoltest/nop_factories.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelcoltest // import "go.opentelemetry.io/collector/otelcol/otelcoltest"
import (
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/connector/connectortest"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/extension/extensiontest"
"go.opentelemetry.io/collector/otelcol"
"go.opentelemetry.io/collector/processor/processortest"
"go.opentelemetry.io/collector/receiver/receivertest"
"go.opentelemetry.io/collector/service/telemetry"
)
// NopFactories returns a otelcol.Factories with all nop factories.
func NopFactories() (otelcol.Factories, error) {
var factories otelcol.Factories
// MakeFactoryMap can never return an error with a single Factory
factories.Extensions, _ = otelcol.MakeFactoryMap(extensiontest.NewNopFactory())
factories.Receivers, _ = otelcol.MakeFactoryMap(receivertest.NewNopFactory())
factories.Exporters, _ = otelcol.MakeFactoryMap(exportertest.NewNopFactory())
factories.Processors, _ = otelcol.MakeFactoryMap(processortest.NewNopFactory())
factories.Connectors, _ = otelcol.MakeFactoryMap(connectortest.NewNopFactory())
factories.Telemetry = nopTelemetryFactory()
return factories, nil
}
func nopTelemetryFactory() telemetry.Factory {
return telemetry.NewFactory(
func() component.Config { return struct{}{} },
)
}
================================================
FILE: otelcol/otelcoltest/nop_factories_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelcoltest
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
)
var nopType = component.MustNewType("nop")
func TestNopFactories(t *testing.T) {
nopFactories, err := NopFactories()
require.NoError(t, err)
require.Len(t, nopFactories.Receivers, 1)
nopReceiverFactory, ok := nopFactories.Receivers[nopType]
require.True(t, ok)
require.Equal(t, nopType, nopReceiverFactory.Type())
require.Len(t, nopFactories.Processors, 1)
nopProcessorFactory, ok := nopFactories.Processors[nopType]
require.True(t, ok)
require.Equal(t, nopType, nopProcessorFactory.Type())
require.Len(t, nopFactories.Exporters, 1)
nopExporterFactory, ok := nopFactories.Exporters[nopType]
require.True(t, ok)
require.Equal(t, nopType, nopExporterFactory.Type())
require.Len(t, nopFactories.Extensions, 1)
nopExtensionFactory, ok := nopFactories.Extensions[nopType]
require.True(t, ok)
require.Equal(t, nopType, nopExtensionFactory.Type())
require.Len(t, nopFactories.Connectors, 1)
nopConnectorFactory, ok := nopFactories.Connectors[nopType]
require.True(t, ok)
require.Equal(t, nopType, nopConnectorFactory.Type())
}
================================================
FILE: otelcol/otelcoltest/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelcoltest
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: otelcol/otelcoltest/testdata/config.yaml
================================================
receivers:
nop:
nop/myreceiver:
processors:
nop:
nop/myprocessor:
exporters:
nop:
nop/myexporter:
connectors:
nop/myconnector:
extensions:
nop:
nop/myextension:
service:
extensions: [nop]
pipelines:
traces:
receivers: [nop]
processors: [nop]
exporters: [nop]
================================================
FILE: otelcol/otelcoltest/testdata/config_default_scheme.yaml
================================================
receivers:
nop:
exporters:
nop:
service:
pipelines:
traces:
receivers: ${RECEIVERS}
exporters: [nop]
================================================
FILE: otelcol/otelcoltest/testdata/config_env.yaml
================================================
receivers:
nop:
exporters:
nop:
service:
pipelines:
traces:
receivers: ${env:RECEIVERS}
exporters: [nop]
================================================
FILE: otelcol/signals_others.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:build !(js && wasm)
package otelcol // import "go.opentelemetry.io/collector/otelcol"
import (
"syscall"
)
const (
SIGHUP = syscall.SIGHUP
SIGTERM = syscall.SIGTERM
)
================================================
FILE: otelcol/signals_wasm.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:build js && wasm
package otelcol // import "go.opentelemetry.io/collector/otelcol"
import (
"syscall"
)
const (
SIGHUP = syscall.Signal(-1)
SIGTERM = syscall.Signal(-1)
)
================================================
FILE: otelcol/testdata/components-output-sorted.yaml
================================================
buildinfo:
command: otelcol
description: OpenTelemetry Collector
version: latest
receivers:
- name: bar
module: go.opentelemetry.io/collector/receiver/receivertest v1.2.3
stability:
logs: Undefined
metrics: Undefined
traces: Undefined
- name: baz
module: go.opentelemetry.io/collector/receiver/receivertest v1.2.3
stability:
logs: Undefined
metrics: Undefined
traces: Undefined
- name: foo
module: go.opentelemetry.io/collector/receiver/receivertest v1.2.3
stability:
logs: Undefined
metrics: Undefined
traces: Undefined
processors:
- name: bar
module: go.opentelemetry.io/collector/processor/processortest v1.2.3
stability:
logs: Undefined
metrics: Undefined
traces: Undefined
- name: baz
module: go.opentelemetry.io/collector/processor/processortest v1.2.3
stability:
logs: Undefined
metrics: Undefined
traces: Undefined
- name: foo
module: go.opentelemetry.io/collector/processor/processortest v1.2.3
stability:
logs: Undefined
metrics: Undefined
traces: Undefined
exporters:
- name: bar
module: go.opentelemetry.io/collector/exporter/exportertest v1.2.3
stability:
logs: Undefined
metrics: Undefined
traces: Undefined
- name: baz
module: go.opentelemetry.io/collector/exporter/exportertest v1.2.3
stability:
logs: Undefined
metrics: Undefined
traces: Undefined
- name: foo
module: go.opentelemetry.io/collector/exporter/exportertest v1.2.3
stability:
logs: Undefined
metrics: Undefined
traces: Undefined
connectors:
- name: bar
module: go.opentelemetry.io/collector/connector/connectortest v1.2.3
stability:
logs-to-logs: Undefined
logs-to-metrics: Undefined
logs-to-traces: Undefined
metrics-to-logs: Undefined
metrics-to-metrics: Undefined
metrics-to-traces: Undefined
traces-to-logs: Undefined
traces-to-metrics: Undefined
traces-to-traces: Undefined
- name: baz
module: go.opentelemetry.io/collector/connector/connectortest v1.2.3
stability:
logs-to-logs: Undefined
logs-to-metrics: Undefined
logs-to-traces: Undefined
metrics-to-logs: Undefined
metrics-to-metrics: Undefined
metrics-to-traces: Undefined
traces-to-logs: Undefined
traces-to-metrics: Undefined
traces-to-traces: Undefined
- name: foo
module: go.opentelemetry.io/collector/connector/connectortest v1.2.3
stability:
logs-to-logs: Undefined
logs-to-metrics: Undefined
logs-to-traces: Undefined
metrics-to-logs: Undefined
metrics-to-metrics: Undefined
metrics-to-traces: Undefined
traces-to-logs: Undefined
traces-to-metrics: Undefined
traces-to-traces: Undefined
extensions:
- name: bar
module: go.opentelemetry.io/collector/extension/extensiontest v1.2.3
stability:
extension: Stable
- name: baz
module: go.opentelemetry.io/collector/extension/extensiontest v1.2.3
stability:
extension: Stable
- name: foo
module: go.opentelemetry.io/collector/extension/extensiontest v1.2.3
stability:
extension: Stable
providers:
- scheme: env
module: go.opentelemetry.io/collector/confmap/provider/testprovider v1.2.3
- scheme: file
module: go.opentelemetry.io/collector/confmap/provider/testprovider v1.2.3
converters:
- module: go.opentelemetry.io/collector/converter/bar v1.2.3
- module: go.opentelemetry.io/collector/converter/baz v1.2.3
- module: go.opentelemetry.io/collector/converter/foo v1.2.3
================================================
FILE: otelcol/testdata/components-output.yaml
================================================
buildinfo:
command: otelcol
description: OpenTelemetry Collector
version: latest
receivers:
- name: nop
module: go.opentelemetry.io/collector/receiver/receivertest v1.2.3
stability:
logs: Stable
metrics: Stable
traces: Stable
processors:
- name: nop
module: go.opentelemetry.io/collector/processor/processortest v1.2.3
stability:
logs: Stable
metrics: Stable
traces: Stable
exporters:
- name: nop
module: go.opentelemetry.io/collector/exporter/exportertest v1.2.3
stability:
logs: Stable
metrics: Stable
traces: Stable
connectors:
- name: nop
module: go.opentelemetry.io/collector/connector/connectortest v1.2.3
stability:
logs-to-logs: Development
logs-to-metrics: Development
logs-to-traces: Development
metrics-to-logs: Development
metrics-to-metrics: Development
metrics-to-traces: Development
traces-to-logs: Development
traces-to-metrics: Development
traces-to-traces: Development
extensions:
- name: nop
module: go.opentelemetry.io/collector/extension/extensiontest v1.2.3
stability:
extension: Stable
providers:
- scheme: env
module: go.opentelemetry.io/collector/confmap/provider/testprovider v1.2.3
- scheme: file
module: go.opentelemetry.io/collector/confmap/provider/testprovider v1.2.3
converters:
- module: go.opentelemetry.io/collector/converter/testconverter v1.2.3
================================================
FILE: otelcol/testdata/configs/1-config-first.yaml
================================================
receivers:
bar:
key: val
exporters:
bar:
key: val
================================================
FILE: otelcol/testdata/configs/1-config-output.yaml
================================================
exporters:
bar:
key: val
foo:
key: val
receivers:
bar:
key: val
foo:
key: val
================================================
FILE: otelcol/testdata/configs/1-config-second.yaml
================================================
receivers:
foo:
key: val
exporters:
foo:
key: val
================================================
FILE: otelcol/testdata/configs/2-config-output.yaml
================================================
exporters:
bar:
key: val
foo:
key: val
receivers:
bar:
key: val
foo:
key: val
service:
pipelines:
logs:
exporters:
- foo
- bar
receivers:
- foo
- bar
================================================
FILE: otelcol/testdata/otelcol-cyclic-connector.yaml
================================================
receivers:
nop:
exporters:
nop:
connectors:
nop/forward:
service:
pipelines:
traces/in:
receivers: [nop/forward]
processors: [ ]
exporters: [nop/forward]
traces/out:
receivers: [nop/forward]
processors: [ ]
exporters: [nop/forward]
================================================
FILE: otelcol/testdata/otelcol-invalid-components.yaml
================================================
receivers:
nop:
exporters:
nop:
processors:
nosuchprocessor:
service:
pipelines:
traces:
receivers: [nop]
exporters: [nop]
processors: [nop]
================================================
FILE: otelcol/testdata/otelcol-invalid-connector-unused-exp.yaml
================================================
receivers:
nop:
exporters:
nop:
connectors:
nop/connector1:
service:
pipelines:
logs/in1:
receivers: [nop]
processors: [ ]
exporters: [nop]
logs/in2:
receivers: [nop/connector1]
processors: [ ]
exporters: [nop]
logs/out:
receivers: [nop]
processors: [ ]
exporters: [nop]
================================================
FILE: otelcol/testdata/otelcol-invalid-connector-unused-rec.yaml
================================================
receivers:
nop:
exporters:
nop:
connectors:
nop/connector1:
service:
pipelines:
logs/in1:
receivers: [nop]
processors: [ ]
exporters: [nop]
logs/in2:
receivers: [nop]
processors: [ ]
exporters: [nop/connector1]
logs/out:
receivers: [nop]
processors: [ ]
exporters: [nop]
================================================
FILE: otelcol/testdata/otelcol-invalid-receiver-type.yaml
================================================
receivers:
nop_logs:
processors:
nop:
exporters:
nop:
service:
pipelines:
traces:
receivers: [nop_logs]
processors: [nop]
exporters: [nop]
================================================
FILE: otelcol/testdata/otelcol-invalid-telemetry.yaml
================================================
receivers:
nop:
exporters:
nop:
service:
telemetry:
unknown: key
pipelines:
metrics:
receivers: [nop]
exporters: [nop]
================================================
FILE: otelcol/testdata/otelcol-invalid.yaml
================================================
receivers:
nop:
processors:
nop:
exporters:
nop:
service:
pipelines:
traces:
receivers: [nop]
processors: [invalid]
exporters: [nop]
================================================
FILE: otelcol/testdata/otelcol-log-to-file.yaml
================================================
extensions:
zpages:
receivers:
otlp:
protocols:
grpc:
http:
exporters:
debug:
verbosity: detailed
service:
telemetry:
logs:
level: info
output_paths:
# The folder need to be created prior to starting the collector
- ${ProgramData}\OpenTelemetry\Collector\Logs\otelcol.log
pipelines:
traces:
receivers: [otlp]
exporters: [debug]
metrics:
receivers: [otlp]
exporters: [debug]
extensions: [zpages]
================================================
FILE: otelcol/testdata/otelcol-nop.yaml
================================================
receivers:
nop:
processors:
nop:
exporters:
nop:
extensions:
nop:
connectors:
nop/con:
service:
extensions: [nop]
pipelines:
traces:
receivers: [nop]
processors: [nop]
exporters: [nop, nop/con]
metrics:
receivers: [nop]
processors: [nop]
exporters: [nop]
logs:
receivers: [nop, nop/con]
processors: [nop]
exporters: [nop]
================================================
FILE: otelcol/testdata/otelcol-otelconftelemetry.yaml
================================================
receivers:
nop:
exporters:
nop:
service:
telemetry:
# Set configuration understood by otelconftelemetry
metrics:
level: none
pipelines:
metrics:
receivers: [nop]
exporters: [nop]
================================================
FILE: otelcol/testdata/otelcol-statuswatcher.yaml
================================================
receivers:
nop:
processors:
nop:
unhealthy:
exporters:
nop:
extensions:
statuswatcher:
service:
extensions: [statuswatcher]
pipelines:
traces:
receivers: [nop]
processors: [nop,unhealthy]
exporters: [nop]
metrics:
receivers: [nop]
processors: [nop,unhealthy]
exporters: [nop]
logs:
receivers: [nop]
processors: [nop,unhealthy]
exporters: [nop]
================================================
FILE: otelcol/testdata/otelcol-valid-connector-use.yaml
================================================
receivers:
nop:
exporters:
nop:
connectors:
nop/connector1:
service:
pipelines:
logs/in1:
receivers: [nop]
processors: [ ]
exporters: [nop]
logs/in2:
receivers: [nop]
processors: [ ]
exporters: [nop/connector1]
logs/out:
receivers: [nop/connector1]
processors: [ ]
exporters: [nop]
================================================
FILE: otelcol/testdata/print.yaml
================================================
receivers:
r:
opaque: "OOO"
exporters:
e:
timeout: 5s
service:
pipelines:
logs:
receivers: [r]
exporters: [e]
================================================
FILE: otelcol/testdata/print_default.yaml
================================================
receivers:
r:
other: lala
exporters:
e:
service:
pipelines:
logs:
receivers: [r]
exporters: [e]
================================================
FILE: otelcol/testdata/print_invalid.yaml
================================================
receivers:
r:
exporters:
e:
timeout: invalid
service:
pipelines:
logs:
receivers: [r]
exporters: [e]
================================================
FILE: otelcol/testdata/print_negative.yaml
================================================
receivers:
r:
exporters:
e:
timeout: -1
service:
pipelines:
logs:
receivers: [r]
exporters: [e]
================================================
FILE: otelcol/unmarshal_dry_run_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelcol
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/receiver"
)
var _ component.Config = (*Config)(nil)
type ValidateTestConfig struct {
Number int `mapstructure:"number"`
String string `mapstructure:"string"`
}
var genericType component.Type = component.MustNewType("generic")
func NewFactories(_ *testing.T) func() (Factories, error) {
return func() (Factories, error) {
factories, err := nopFactories()
if err != nil {
return Factories{}, err
}
factories.Receivers[genericType] = receiver.NewFactory(
genericType,
func() component.Config {
return &ValidateTestConfig{
Number: 1,
String: "default",
}
})
return factories, nil
}
}
var sampleYAMLConfig = `
receivers:
generic:
number: ${mock:number}
string: ${mock:number}
exporters:
nop:
service:
pipelines:
traces:
receivers: [generic]
exporters: [nop]
`
func TestDryRunWithExpandedValues(t *testing.T) {
tests := []struct {
name string
yamlConfig string
mockMap map[string]string
expectErr bool
}{
{
name: "string that looks like an integer",
yamlConfig: sampleYAMLConfig,
mockMap: map[string]string{
"number": "123",
},
expectErr: true,
},
{
name: "string that looks like a bool",
yamlConfig: sampleYAMLConfig,
mockMap: map[string]string{
"number": "true",
},
expectErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
collector, err := NewCollector(CollectorSettings{
Factories: NewFactories(t),
ConfigProviderSettings: ConfigProviderSettings{
ResolverSettings: confmap.ResolverSettings{
URIs: []string{"file:file"},
DefaultScheme: "mock",
ProviderFactories: []confmap.ProviderFactory{
newFakeProvider("mock", func(_ context.Context, uri string, _ confmap.WatcherFunc) (*confmap.Retrieved, error) {
return confmap.NewRetrievedFromYAML([]byte(tt.mockMap[uri[len("mock:"):]]))
}),
newFakeProvider("file", func(_ context.Context, _ string, _ confmap.WatcherFunc) (*confmap.Retrieved, error) {
return confmap.NewRetrievedFromYAML([]byte(tt.yamlConfig))
}),
},
},
},
SkipSettingGRPCLogger: true,
})
require.NoError(t, err)
err = collector.DryRun(context.Background())
if tt.expectErr {
require.Error(t, err)
return
}
require.NoError(t, err)
})
}
}
================================================
FILE: otelcol/unmarshaler.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelcol // import "go.opentelemetry.io/collector/otelcol"
import (
"errors"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/connector"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/extension"
"go.opentelemetry.io/collector/otelcol/internal/configunmarshaler"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/service"
)
var errNilTelemetryFactory = errors.New("otelcol.Factories.Telemetry must not be nil. For example, you can use otelconftelemetry.NewFactory to build a telemetry factory")
type configSettings struct {
Receivers *configunmarshaler.Configs[receiver.Factory] `mapstructure:"receivers"`
Processors *configunmarshaler.Configs[processor.Factory] `mapstructure:"processors"`
Exporters *configunmarshaler.Configs[exporter.Factory] `mapstructure:"exporters"`
Connectors *configunmarshaler.Configs[connector.Factory] `mapstructure:"connectors"`
Extensions *configunmarshaler.Configs[extension.Factory] `mapstructure:"extensions"`
Service service.Config `mapstructure:"service"`
}
// unmarshal the configSettings from a confmap.Conf.
// After the config is unmarshalled, `Validate()` must be called to validate.
func unmarshal(v *confmap.Conf, factories Factories) (*configSettings, error) {
if factories.Telemetry == nil {
return nil, errNilTelemetryFactory
}
// Unmarshal top level sections and validate.
cfg := &configSettings{
Receivers: configunmarshaler.NewConfigs(factories.Receivers),
Processors: configunmarshaler.NewConfigs(factories.Processors),
Exporters: configunmarshaler.NewConfigs(factories.Exporters),
Connectors: configunmarshaler.NewConfigs(factories.Connectors),
Extensions: configunmarshaler.NewConfigs(factories.Extensions),
// TODO: Add a component.ServiceFactory to allow this to be defined by the Service.
Service: service.Config{
Telemetry: factories.Telemetry.CreateDefaultConfig(),
},
}
err := v.Unmarshal(&cfg)
return cfg, err
}
================================================
FILE: otelcol/unmarshaler_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelcol
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/service"
"go.opentelemetry.io/collector/service/pipelines"
)
func TestUnmarshalEmpty(t *testing.T) {
factories, err := nopFactories()
require.NoError(t, err)
_, err = unmarshal(confmap.New(), factories)
assert.NoError(t, err)
}
func TestUnmarshalEmptyAllSections(t *testing.T) {
factories, err := nopFactories()
require.NoError(t, err)
conf := confmap.NewFromStringMap(map[string]any{
"receivers": nil,
"processors": nil,
"exporters": nil,
"connectors": nil,
"extensions": nil,
"service": nil,
})
cfg, err := unmarshal(conf, factories)
require.NoError(t, err)
assert.Equal(t, fakeTelemetryConfig{}, cfg.Service.Telemetry)
}
func TestUnmarshalUnknownTopLevel(t *testing.T) {
factories, err := nopFactories()
require.NoError(t, err)
conf := confmap.NewFromStringMap(map[string]any{
"unknown_section": nil,
})
_, err = unmarshal(conf, factories)
assert.ErrorContains(t, err, "has invalid keys: unknown_section")
}
func TestPipelineConfigUnmarshalError(t *testing.T) {
testCases := []struct {
// test case name (also file name containing config yaml)
name string
conf *confmap.Conf
// string that the error must contain
expectError string
}{
{
name: "duplicate-pipeline",
conf: confmap.NewFromStringMap(map[string]any{
"traces/ pipe": nil,
"traces /pipe": nil,
}),
expectError: "duplicate name",
},
{
name: "invalid-pipeline-name-after-slash",
conf: confmap.NewFromStringMap(map[string]any{
"metrics/": nil,
}),
expectError: "in \"metrics/\" id: the part after / should not be empty",
},
{
name: "invalid-pipeline-section",
conf: confmap.NewFromStringMap(map[string]any{
"traces": map[string]any{
"unknown_section": nil,
},
}),
expectError: "'[traces]' has invalid keys: unknown_section",
},
{
name: "invalid-pipeline-sub-config",
conf: confmap.NewFromStringMap(map[string]any{
"traces": "string",
}),
expectError: "'[traces]' expected a map or struct, got \"string\"",
},
{
name: "invalid-pipeline-type",
conf: confmap.NewFromStringMap(map[string]any{
"/metrics": nil,
}),
expectError: "in \"/metrics\" id: the part before / should not be empty",
},
{
name: "invalid-sequence-value",
conf: confmap.NewFromStringMap(map[string]any{
"traces": map[string]any{
"receivers": map[string]any{
"nop": map[string]any{
"some": "config",
},
},
},
}),
expectError: "'[traces].receivers' source data must be an array or slice, got map",
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
pips := new(pipelines.Config)
err := tt.conf.Unmarshal(&pips)
assert.ErrorContains(t, err, tt.expectError)
})
}
}
func TestServiceUnmarshalError(t *testing.T) {
testCases := []struct {
// test case name (also file name containing config yaml)
name string
conf *confmap.Conf
// string that the error must contain
expectError string
}{
{
name: "invalid-telemetry-unknown-key",
conf: confmap.NewFromStringMap(map[string]any{
"telemetry": map[string]any{
"unknown": "key",
},
}),
expectError: "decoding failed due to the following error(s):\n\n'telemetry' has invalid keys: unknown",
},
{
name: "invalid-service-extensions-section",
conf: confmap.NewFromStringMap(map[string]any{
"extensions": []any{
map[string]any{
"nop": map[string]any{
"some": "config",
},
},
},
}),
expectError: "'extensions[0]' has invalid keys: nop",
},
{
name: "invalid-service-section",
conf: confmap.NewFromStringMap(map[string]any{
"unknown_section": "string",
}),
expectError: "has invalid keys: unknown_section",
},
{
name: "invalid-pipelines-config",
conf: confmap.NewFromStringMap(map[string]any{
"pipelines": "string",
}),
expectError: "'pipelines' expected type 'pipelines.Config', got unconvertible type 'string'",
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
err := tt.conf.Unmarshal(&service.Config{
Telemetry: fakeTelemetryConfig{},
})
require.ErrorContains(t, err, tt.expectError)
})
}
}
================================================
FILE: pdata/Makefile
================================================
include ../Makefile.Common
================================================
FILE: pdata/README.md
================================================
# Pipeline data (pdata)
| Status | |
| ------------- |-----------|
| Stability | [stable]: traces, metrics, logs |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Apkg%2Fpdata) [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aclosed+is%3Aissue+label%3Apkg%2Fpdata) |
| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@bogdandrutu](https://www.github.com/bogdandrutu), [@dmitryax](https://www.github.com/dmitryax) |
[stable]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#stable
Pipeline data (pdata) implements data structures that represent telemetry data in-memory. All data received
is converted into this format, travels through the pipeline in this format, and is converted from this format by
exporters when sending.
Current implementation primarily uses OTLP protobuf structs as the underlying data structures for many 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. The underlying data structure is kept private so that we are free to make changes to it
in the future.
The pdata API is designed to avoid mutable data sharing and bugs that stem from that. Each pdata instance cannot
contain a reference to an object that is used in another pdata instance.
## API naming convention
### Package names
Names of pdata packages don't follow names of the protobuf packages. The pdata has a package per telemetry type
starting with `p`, e.g. `ptrace`, and `pcommon` package which includes pdata API for protobuf definitions from
`common` and `resource` protobuf packages.
### Protobuf message representation in pdata
Pipeline data structs SHOULD be based on the names of the underlying OTLP protobuf messages. Data types for
protobuf messages defined as part of another message SHOULD include the owner's name as a prefix. The following
examples are two pdata structs based on protobuf messages defined at the package level and as part of another
message:
- `pmetric.NumberDataPoint` based on a `NumberDataPoint` protobuf message defined in
[metrics.proto](https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/metrics/v1/metrics.proto)
package.
- `pmetric.ExponentialHistogramDataPointBuckets` based on a `Buckets` protobuf message defined in
`ExponentialHistogramDataPoint` message of
[metrics.proto](https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/metrics/v1/metrics.proto)
package.
Exceptions to the naming rules are possible, but not encouraged. Another name can be chosen for brevity or if the
struct provides a different interface to manipulate the underlying data. The following exceptions are currently
accepted:
- `plog.Logs` based on `LogsData` protobuf message.
- `pmetric.Metrics` based on `MetricsData` protobuf message.
- `ptrace.Traces` based on `TracesData` protobuf message.
- `pcommon.Slice` based on `ArrayValue` protobuf message.
- `pcommon.Map` based on `KeyValueList` protobuf message.
- `pcommon.Value` based on `AnyValue` protobuf message.
Each pdata struct MUST have an initialization function starting with `New` prefix. Usage of zero-initialized values is
prohibited and can cause a panic. Each pdata struct MUST provide the following methods:
- `MoveTo()`: moves all the data from one struct instance to another. The destination instance is overwritten and the
source instance is re-initialized as a new empty instance.
- `CopyTo()`: deep copies all the data from one struct instance to another. The destination instance is
overwritten and the source instance is not modified.
Each pdata struct based on a protobuf message SHOULD have getter methods for every protobuf field. Exceptions to
this rule are allowed if exposing a field is not desirable in pdata. For example, a deprecated protobuf field MAY
not be exposed in pdata. Also, pdata MAY provide a different interface to manipulate protobuf fields without
exposing them explicitly as is done with `pcommon.Map`.
### Protobuf fields representation in pdata
#### Singular fields
Name of a pdata getter methods representing singular protobuf fields SHOULD match the name the protobuf field but
written in CamelCase aligned with Go naming conventions. If a protobuf field is of scalar or enum type, the
corresponding pdata struct MUST provide a setter method with `Set` prefix.
Some fields of scalar type in a protobuf message MAY be represented by another pdata type providing an additional
interface, for example `pcommon.Timestamp` is another type that wraps `uint64` value and provides an additional
interface to work with timestamps. pdata fields returning `pcommon.Timestamp` don't follow the recommended naming
schema, `Timestamp` word is used instead of `TimeUnixNano` to represent protobuf fields that contain
`time_unix_nano`, for example `StartTimestamp` pdata field is used for `start_time_unix_nano` protobuf fields.
#### Optional fields
Each optional field in a protobuf message exposed in pdata MUST have the following additional methods:
- A method starting with `Has` to determine if the field is set. For example,
`func (ms HistogramDataPoint) HasMin() bool`.
- A method starting with `Remove` prefix to remove a value associated with the optional field. For example,
`func (ms HistogramDataPoint) RemoveMin()`.
#### OneOf fields
A pdata struct representing a protobuf message with an exposed oneof field MUST provide getter and setter methods for
each option of the oneof field. Name of each setter and getter SHOULD be called the same as the protobuf oneof field
options. The following exception is adopted in pdata for numeric oneof protobuf fields for brevity:
```protobuf
oneof value {
double as_double = 4;
sfixed64 as_int = 6;
}
```
is represented in pdata in a shorter form of the following methods:
```golang
DoubleValue() float64
SetDoubleValue(float64)
IntValue() int64
SetIntValue(v int64)
```
If a oneof field option is another protobuf message, the setter name MUST include `Empty` in its name. Such setter
MUST set the oneof field to an empty initialized struct and return it. The following conditions define whether the
name of the oneof field must be included in the setter and getter method names:
1. If a oneof field represents the whole protobuf message, name of the oneof field itself MAY be omitted in setter
and getter methods. For example:
```go
func (ms Metric) Sum() Sum
func (ms Metric) SetEmptySum() Sum
```
2. If a oneof field is relevant only to a particular field of the message, the pdata getter and setter methods MUST
include name of the oneof field along with name of the option. For example:
```go
func (ms NumberDataPoint) IntValue() int64
func (ms NumberDataPoint) SetIntValue(v int64)
```
Additionally, the pdata struct MUST provide a type getter for every exposed oneof protobuf field. Name of the getter
MUST be `Type` for oneof fields representing the whole protobuf message (1), or `Type` for oneof fields
relevant only to a particular field. The function MUST return an `int32` enum type called `Type` for (1)
and `Type` for (2). For example:
- `func (ms Metric) Type() MetricType` (1)
- `func (ms NumberDataPoint) ValueType() NumberDataPointValueType` (2)
The enum constants MUST be called the same as the type with a suffix named after the oneof option, e.g.:
- `MetricTypeSum` (1)
- `NumberDataPointValueTypeString` (2)
An unset oneof protobuf value MUST be represented by a pdata constant with `Empty` suffix, e.g.:
- `MetricTypeEmpty` (1)
- `NumberDataPointValueTypeEmpty` (2)
The pdata enum type SHOULD have `String()` method returning a string value of the corresponding oneof field option.
#### Repeated protobuf fields
Each repeated protobuf message field exposed in pdata MUST be represented as another pdata struct that SHOULD be
called the same as the underlying protobuf field with `Slice` suffix. An exception example is `pdata.Map` that
provides a different interface to manipulate the underlying protobuf data.
#### Repeated scalar fields
Each repeated scalar protobuf field exposed in pdata MUST be represented as another pdata type wrapping a native go
slice. Name of the type SHOULD include name of the primitive data type ending with `Slice` suffix, e.g. `UInt64Slice`.
#### Enum fields
Each protobuf enum field exposed in pdata MUST have a type declared with underlying `int64` type. Name of the type
SHOULD follow the same rules as for struct type names representing protobuf messages:
- If a protobuf enum is defined on the package level, pdata type SHOULD have the same name.
- If a protobuf enum is defined as part of another message, pdata type SHOULD include the protobuf message name as a
prefix.
Constants defined for the pdata int64 type MUST have the same names and numeric values defined in protobuf. Names
of the constants must be translated from ALL_CAPS_SNAKE_CASE to CamelCase.
The pdata enum type SHOULD have `String()` method returning a string value for each constant.
Example of a protobuf enum definition in pdata:
```golang
type AggregationTemporality int32
const (
AggregationTemporalityUnspecified = AggregationTemporality(otlpmetrics.AggregationTemporality_AGGREGATION_TEMPORALITY_UNSPECIFIED)
AggregationTemporalityDelta = AggregationTemporality(otlpmetrics.AggregationTemporality_AGGREGATION_TEMPORALITY_DELTA)
AggregationTemporalityCumulative = AggregationTemporality(otlpmetrics.AggregationTemporality_AGGREGATION_TEMPORALITY_CUMULATIVE)
)
```
### Flags
Flags fields are typically defined in protobuf as `uint32` fields representing 32 distinct boolean flags. The
exposed protobuf flags fields MUST be defined as new types with `uint32` underlying type and called according to
the same rules as struct names representing protobuf messages and other enum types. Each pdata flags type MUST have
an empty variable of the fields type with `Default` prefix. Each flag MUST have a getter method returning a
particular flag and a setter method with `With` prefix returning a new instance of the pdata flags type. Any instance
of the pdata type is immutable since it's just a wrapper over `uint32`.
The following pdata type is used for log record flags field:
```golang
type LogRecordFlags uint32
var DefaultLogRecordFlags = LogRecordFlags(0)
func (ms LogRecordFlags) IsSampled() bool
func (ms LogRecordFlags) WithIsSampled(b bool) LogRecordFlags
```
### Examples
The following protobuf message:
```protobuf
message NumberDataPoint {
reserved 1;
repeated opentelemetry.proto.common.v1.KeyValue attributes = 7;
fixed64 start_time_unix_nano = 2;
fixed64 time_unix_nano = 3;
oneof value {
double as_double = 4;
sfixed64 as_int = 6;
}
repeated Exemplar exemplars = 5;
uint32 flags = 8;
}
```
is represented by the following pdata API
```go
type NumberDataPoint
func NewNumberDataPoint() NumberDataPoint
func (ms NumberDataPoint) MoveTo(dest NumberDataPoint)
func (ms NumberDataPoint) CopyTo(dest NumberDataPoint)
func (ms NumberDataPoint) Attributes() pcommon.Map
func (ms NumberDataPoint) StartTimestamp() pcommon.Timestamp
func (ms NumberDataPoint) SetStartTimestamp(v pcommon.Timestamp)
func (ms NumberDataPoint) Timestamp() pcommon.Timestamp
func (ms NumberDataPoint) SetTimestamp(v pcommon.Timestamp)
func (ms NumberDataPoint) ValueType() NumberDataPointValueType
func (ms NumberDataPoint) DoubleValue() float64
func (ms NumberDataPoint) SetDoubleValue(v float64)
func (ms NumberDataPoint) IntValue() int64
func (ms NumberDataPoint) SetIntValue(v int64)
func (ms NumberDataPoint) Exemplars() ExemplarSlice
func (ms NumberDataPoint) Flags() DataPointFlags
func (ms NumberDataPoint) SetFlags(v DataPointFlags)
```
================================================
FILE: pdata/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:generate mdatagen metadata.yaml
// Package pdata provides the data model definitions for all supported pipeline data.
package pdata // import "go.opentelemetry.io/collector/pdata"
================================================
FILE: pdata/documentation.md
================================================
[comment]: <> (Code generated by mdatagen. DO NOT EDIT.)
# pdata
## Feature Gates
This component has the following feature gates:
| Feature Gate | Stage | Description | From Version | To Version | Reference |
| ------------ | ----- | ----------- | ------------ | ---------- | --------- |
| `pdata.useCustomProtoEncoding` | stable | When enabled, enable custom proto encoding. This is a required step to enable featuregate pdata.useProtoPooling. | v0.133.0 | v0.137.0 | [Link](https://github.com/open-telemetry/opentelemetry-collector/issues/13631) |
| `pdata.useProtoPooling` | alpha | When enabled, enable using local memory pools for underlying data that the pdata messages are pushed to. | v0.133.0 | N/A | [Link](https://github.com/open-telemetry/opentelemetry-collector/issues/13631) |
For more information about feature gates, see the [Feature Gates](https://github.com/open-telemetry/opentelemetry-collector/blob/main/featuregate/README.md) documentation.
================================================
FILE: pdata/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package pdata
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: pdata/go.mod
================================================
module go.opentelemetry.io/collector/pdata
go 1.25.0
require (
github.com/json-iterator/go v1.1.12
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/featuregate v1.54.0
go.opentelemetry.io/collector/internal/testutil v0.148.0
go.opentelemetry.io/proto/slim/otlp v1.10.0
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0
go.uber.org/goleak v1.3.0
go.uber.org/multierr v1.11.0
google.golang.org/grpc v1.79.3
google.golang.org/protobuf v1.36.11
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
retract (
v1.0.0-rc10 // RC version scheme discovered to be alphabetical, use v1.0.0-rcv0011 instead
v0.57.1 // Release failed, use v0.57.2
v0.57.0 // Release failed, use v0.57.2
)
replace go.opentelemetry.io/collector/featuregate => ../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../internal/testutil
================================================
FILE: pdata/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: pdata/internal/.gitignore
================================================
.patched-otlp-proto
opentelemetry-proto
================================================
FILE: pdata/internal/bytesid.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/pdata/internal"
import (
"encoding/hex"
"go.opentelemetry.io/collector/pdata/internal/json"
)
// unmarshalJSON inflates trace id from hex string, possibly enclosed in quotes.
// Called by Protobuf JSON deserialization.
func unmarshalJSON(dst []byte, iter *json.Iterator) {
src := iter.ReadStringAsSlice()
if len(src) == 0 {
return
}
if len(dst) != hex.DecodedLen(len(src)) {
iter.ReportError("ID.UnmarshalJSONIter", "length mismatch")
return
}
_, err := hex.Decode(dst, src)
if err != nil {
iter.ReportError("ID.UnmarshalJSONIter", err.Error())
return
}
}
================================================
FILE: pdata/internal/bytesid_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/internal/json"
)
func TestUnmarshalJSON(t *testing.T) {
iter := json.BorrowIterator(nil)
defer json.ReturnIterator(iter)
id := [16]byte{}
unmarshalJSON(id[:], iter.ResetBytes([]byte(`""`)))
require.NoError(t, iter.Error())
assert.Equal(t, [16]byte{}, id)
idBytes := [16]byte{0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78}
unmarshalJSON(id[:], iter.ResetBytes([]byte(`"12345678123456781234567812345678"`)))
require.NoError(t, iter.Error())
assert.Equal(t, idBytes, id)
unmarshalJSON(id[:], iter.ResetBytes([]byte(`"nothex"`)))
require.Error(t, iter.Error())
unmarshalJSON(id[:], iter.ResetBytes([]byte(`"1"`)))
require.Error(t, iter.Error())
unmarshalJSON(id[:], iter.ResetBytes([]byte(`"123"`)))
require.Error(t, iter.Error())
unmarshalJSON(id[:], iter.ResetBytes([]byte(`"`)))
require.Error(t, iter.Error())
}
================================================
FILE: pdata/internal/config.schema.yaml
================================================
$defs:
aggregation_temporality:
description: 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: integer
x-customType: int32
profile_id:
description: ProfileID is a custom data type that is used for all profile_id fields in OTLP Protobuf messages.
type: array
items:
type: string
x-customType: byte
severity_number:
description: SeverityNumber represent possible values for LogRecord.SeverityNumber
type: integer
x-customType: int32
span_id:
description: SpanID is a custom data type that is used for all span_id fields in OTLP Protobuf messages.
type: array
items:
type: string
x-customType: byte
span_kind:
description: SpanKind is the type of span. Can be used to specify additional relationships between spans in addition to a parent/child relationship.
type: integer
x-customType: int32
status_code:
description: StatusCode is the status of the span, for the semantics of codes see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#set-status
type: integer
x-customType: int32
trace_id:
description: TraceID is a custom data type that is used for all trace_id fields in OTLP Protobuf messages.
type: array
items:
type: string
x-customType: byte
================================================
FILE: pdata/internal/generated_enum_aggregationtemporality.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
const (
AggregationTemporality_AGGREGATION_TEMPORALITY_UNSPECIFIED = AggregationTemporality(0)
AggregationTemporality_AGGREGATION_TEMPORALITY_DELTA = AggregationTemporality(1)
AggregationTemporality_AGGREGATION_TEMPORALITY_CUMULATIVE = AggregationTemporality(2)
)
// 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
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,
}
================================================
FILE: pdata/internal/generated_enum_severitynumber.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
const (
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)
)
// SeverityNumber represent possible values for LogRecord.SeverityNumber
type SeverityNumber int32
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,
}
================================================
FILE: pdata/internal/generated_enum_spankind.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
const (
SpanKind_SPAN_KIND_UNSPECIFIED = SpanKind(0)
SpanKind_SPAN_KIND_INTERNAL = SpanKind(1)
SpanKind_SPAN_KIND_SERVER = SpanKind(2)
SpanKind_SPAN_KIND_CLIENT = SpanKind(3)
SpanKind_SPAN_KIND_PRODUCER = SpanKind(4)
SpanKind_SPAN_KIND_CONSUMER = SpanKind(5)
)
// SpanKind is the type of span.
// Can be used to specify additional relationships between spans in addition to a parent/child relationship.
type SpanKind int32
var 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 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,
}
================================================
FILE: pdata/internal/generated_enum_statuscode.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
const (
StatusCode_STATUS_CODE_UNSET = StatusCode(0)
StatusCode_STATUS_CODE_OK = StatusCode(1)
StatusCode_STATUS_CODE_ERROR = StatusCode(2)
)
// StatusCode is the status of the span, for the semantics of codes see
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#set-status
type StatusCode int32
var StatusCode_name = map[int32]string{
0: "STATUS_CODE_UNSET",
1: "STATUS_CODE_OK",
2: "STATUS_CODE_ERROR",
}
var StatusCode_value = map[string]int32{
"STATUS_CODE_UNSET": 0,
"STATUS_CODE_OK": 1,
"STATUS_CODE_ERROR": 2,
}
================================================
FILE: pdata/internal/generated_proto_anyvalue.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"encoding/binary"
"fmt"
"math"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
func (m *AnyValue) GetValue() any {
if m != nil {
return m.Value
}
return nil
}
type AnyValue_StringValue struct {
StringValue string
}
func (m *AnyValue) GetStringValue() string {
if v, ok := m.GetValue().(*AnyValue_StringValue); ok {
return v.StringValue
}
return ""
}
type AnyValue_BoolValue struct {
BoolValue bool
}
func (m *AnyValue) GetBoolValue() bool {
if v, ok := m.GetValue().(*AnyValue_BoolValue); ok {
return v.BoolValue
}
return false
}
type AnyValue_IntValue struct {
IntValue int64
}
func (m *AnyValue) GetIntValue() int64 {
if v, ok := m.GetValue().(*AnyValue_IntValue); ok {
return v.IntValue
}
return int64(0)
}
type AnyValue_DoubleValue struct {
DoubleValue float64
}
func (m *AnyValue) GetDoubleValue() float64 {
if v, ok := m.GetValue().(*AnyValue_DoubleValue); ok {
return v.DoubleValue
}
return float64(0)
}
type AnyValue_ArrayValue struct {
ArrayValue *ArrayValue
}
func (m *AnyValue) GetArrayValue() *ArrayValue {
if v, ok := m.GetValue().(*AnyValue_ArrayValue); ok {
return v.ArrayValue
}
return nil
}
type AnyValue_KvlistValue struct {
KvlistValue *KeyValueList
}
func (m *AnyValue) GetKvlistValue() *KeyValueList {
if v, ok := m.GetValue().(*AnyValue_KvlistValue); ok {
return v.KvlistValue
}
return nil
}
type AnyValue_BytesValue struct {
BytesValue []byte
}
func (m *AnyValue) GetBytesValue() []byte {
if v, ok := m.GetValue().(*AnyValue_BytesValue); ok {
return v.BytesValue
}
return nil
}
type AnyValue_StringValueStrindex struct {
StringValueStrindex int32
}
func (m *AnyValue) GetStringValueStrindex() int32 {
if v, ok := m.GetValue().(*AnyValue_StringValueStrindex); ok {
return v.StringValueStrindex
}
return int32(0)
}
type AnyValue struct {
Value any
}
var (
protoPoolAnyValue = sync.Pool{
New: func() any {
return &AnyValue{}
},
}
ProtoPoolAnyValue_StringValue = sync.Pool{
New: func() any {
return &AnyValue_StringValue{}
},
}
ProtoPoolAnyValue_BoolValue = sync.Pool{
New: func() any {
return &AnyValue_BoolValue{}
},
}
ProtoPoolAnyValue_IntValue = sync.Pool{
New: func() any {
return &AnyValue_IntValue{}
},
}
ProtoPoolAnyValue_DoubleValue = sync.Pool{
New: func() any {
return &AnyValue_DoubleValue{}
},
}
ProtoPoolAnyValue_ArrayValue = sync.Pool{
New: func() any {
return &AnyValue_ArrayValue{}
},
}
ProtoPoolAnyValue_KvlistValue = sync.Pool{
New: func() any {
return &AnyValue_KvlistValue{}
},
}
ProtoPoolAnyValue_BytesValue = sync.Pool{
New: func() any {
return &AnyValue_BytesValue{}
},
}
ProtoPoolAnyValue_StringValueStrindex = sync.Pool{
New: func() any {
return &AnyValue_StringValueStrindex{}
},
}
)
func NewAnyValue() *AnyValue {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &AnyValue{}
}
return protoPoolAnyValue.Get().(*AnyValue)
}
func DeleteAnyValue(orig *AnyValue, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
switch ov := orig.Value.(type) {
case *AnyValue_StringValue:
if metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov.StringValue = ""
ProtoPoolAnyValue_StringValue.Put(ov)
}
case *AnyValue_BoolValue:
if metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov.BoolValue = false
ProtoPoolAnyValue_BoolValue.Put(ov)
}
case *AnyValue_IntValue:
if metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov.IntValue = int64(0)
ProtoPoolAnyValue_IntValue.Put(ov)
}
case *AnyValue_DoubleValue:
if metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov.DoubleValue = float64(0)
ProtoPoolAnyValue_DoubleValue.Put(ov)
}
case *AnyValue_ArrayValue:
DeleteArrayValue(ov.ArrayValue, true)
ov.ArrayValue = nil
ProtoPoolAnyValue_ArrayValue.Put(ov)
case *AnyValue_KvlistValue:
DeleteKeyValueList(ov.KvlistValue, true)
ov.KvlistValue = nil
ProtoPoolAnyValue_KvlistValue.Put(ov)
case *AnyValue_BytesValue:
if metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov.BytesValue = nil
ProtoPoolAnyValue_BytesValue.Put(ov)
}
case *AnyValue_StringValueStrindex:
if metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov.StringValueStrindex = int32(0)
ProtoPoolAnyValue_StringValueStrindex.Put(ov)
}
}
orig.Reset()
if nullable {
protoPoolAnyValue.Put(orig)
}
}
func CopyAnyValue(dest, src *AnyValue) *AnyValue {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewAnyValue()
}
switch t := src.Value.(type) {
case *AnyValue_StringValue:
var ov *AnyValue_StringValue
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &AnyValue_StringValue{}
} else {
ov = ProtoPoolAnyValue_StringValue.Get().(*AnyValue_StringValue)
}
ov.StringValue = t.StringValue
dest.Value = ov
case *AnyValue_BoolValue:
var ov *AnyValue_BoolValue
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &AnyValue_BoolValue{}
} else {
ov = ProtoPoolAnyValue_BoolValue.Get().(*AnyValue_BoolValue)
}
ov.BoolValue = t.BoolValue
dest.Value = ov
case *AnyValue_IntValue:
var ov *AnyValue_IntValue
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &AnyValue_IntValue{}
} else {
ov = ProtoPoolAnyValue_IntValue.Get().(*AnyValue_IntValue)
}
ov.IntValue = t.IntValue
dest.Value = ov
case *AnyValue_DoubleValue:
var ov *AnyValue_DoubleValue
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &AnyValue_DoubleValue{}
} else {
ov = ProtoPoolAnyValue_DoubleValue.Get().(*AnyValue_DoubleValue)
}
ov.DoubleValue = t.DoubleValue
dest.Value = ov
case *AnyValue_ArrayValue:
var ov *AnyValue_ArrayValue
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &AnyValue_ArrayValue{}
} else {
ov = ProtoPoolAnyValue_ArrayValue.Get().(*AnyValue_ArrayValue)
}
ov.ArrayValue = NewArrayValue()
CopyArrayValue(ov.ArrayValue, t.ArrayValue)
dest.Value = ov
case *AnyValue_KvlistValue:
var ov *AnyValue_KvlistValue
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &AnyValue_KvlistValue{}
} else {
ov = ProtoPoolAnyValue_KvlistValue.Get().(*AnyValue_KvlistValue)
}
ov.KvlistValue = NewKeyValueList()
CopyKeyValueList(ov.KvlistValue, t.KvlistValue)
dest.Value = ov
case *AnyValue_BytesValue:
var ov *AnyValue_BytesValue
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &AnyValue_BytesValue{}
} else {
ov = ProtoPoolAnyValue_BytesValue.Get().(*AnyValue_BytesValue)
}
ov.BytesValue = t.BytesValue
dest.Value = ov
case *AnyValue_StringValueStrindex:
var ov *AnyValue_StringValueStrindex
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &AnyValue_StringValueStrindex{}
} else {
ov = ProtoPoolAnyValue_StringValueStrindex.Get().(*AnyValue_StringValueStrindex)
}
ov.StringValueStrindex = t.StringValueStrindex
dest.Value = ov
default:
dest.Value = nil
}
return dest
}
func CopyAnyValueSlice(dest, src []AnyValue) []AnyValue {
var newDest []AnyValue
if cap(dest) < len(src) {
newDest = make([]AnyValue, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteAnyValue(&dest[i], false)
}
}
for i := range src {
CopyAnyValue(&newDest[i], &src[i])
}
return newDest
}
func CopyAnyValuePtrSlice(dest, src []*AnyValue) []*AnyValue {
var newDest []*AnyValue
if cap(dest) < len(src) {
newDest = make([]*AnyValue, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewAnyValue()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteAnyValue(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewAnyValue()
}
}
for i := range src {
CopyAnyValue(newDest[i], src[i])
}
return newDest
}
func (orig *AnyValue) Reset() {
*orig = AnyValue{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *AnyValue) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
switch orig := orig.Value.(type) {
case *AnyValue_StringValue:
dest.WriteObjectField("stringValue")
dest.WriteString(orig.StringValue)
case *AnyValue_BoolValue:
dest.WriteObjectField("boolValue")
dest.WriteBool(orig.BoolValue)
case *AnyValue_IntValue:
dest.WriteObjectField("intValue")
dest.WriteInt64(orig.IntValue)
case *AnyValue_DoubleValue:
dest.WriteObjectField("doubleValue")
dest.WriteFloat64(orig.DoubleValue)
case *AnyValue_ArrayValue:
if orig.ArrayValue != nil {
dest.WriteObjectField("arrayValue")
orig.ArrayValue.MarshalJSON(dest)
}
case *AnyValue_KvlistValue:
if orig.KvlistValue != nil {
dest.WriteObjectField("kvlistValue")
orig.KvlistValue.MarshalJSON(dest)
}
case *AnyValue_BytesValue:
dest.WriteObjectField("bytesValue")
dest.WriteBytes(orig.BytesValue)
case *AnyValue_StringValueStrindex:
dest.WriteObjectField("stringValueStrindex")
dest.WriteInt32(orig.StringValueStrindex)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *AnyValue) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "stringValue", "string_value":
{
var ov *AnyValue_StringValue
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &AnyValue_StringValue{}
} else {
ov = ProtoPoolAnyValue_StringValue.Get().(*AnyValue_StringValue)
}
ov.StringValue = iter.ReadString()
orig.Value = ov
}
case "boolValue", "bool_value":
{
var ov *AnyValue_BoolValue
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &AnyValue_BoolValue{}
} else {
ov = ProtoPoolAnyValue_BoolValue.Get().(*AnyValue_BoolValue)
}
ov.BoolValue = iter.ReadBool()
orig.Value = ov
}
case "intValue", "int_value":
{
var ov *AnyValue_IntValue
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &AnyValue_IntValue{}
} else {
ov = ProtoPoolAnyValue_IntValue.Get().(*AnyValue_IntValue)
}
ov.IntValue = iter.ReadInt64()
orig.Value = ov
}
case "doubleValue", "double_value":
{
var ov *AnyValue_DoubleValue
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &AnyValue_DoubleValue{}
} else {
ov = ProtoPoolAnyValue_DoubleValue.Get().(*AnyValue_DoubleValue)
}
ov.DoubleValue = iter.ReadFloat64()
orig.Value = ov
}
case "arrayValue", "array_value":
{
var ov *AnyValue_ArrayValue
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &AnyValue_ArrayValue{}
} else {
ov = ProtoPoolAnyValue_ArrayValue.Get().(*AnyValue_ArrayValue)
}
ov.ArrayValue = NewArrayValue()
ov.ArrayValue.UnmarshalJSON(iter)
orig.Value = ov
}
case "kvlistValue", "kvlist_value":
{
var ov *AnyValue_KvlistValue
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &AnyValue_KvlistValue{}
} else {
ov = ProtoPoolAnyValue_KvlistValue.Get().(*AnyValue_KvlistValue)
}
ov.KvlistValue = NewKeyValueList()
ov.KvlistValue.UnmarshalJSON(iter)
orig.Value = ov
}
case "bytesValue", "bytes_value":
{
var ov *AnyValue_BytesValue
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &AnyValue_BytesValue{}
} else {
ov = ProtoPoolAnyValue_BytesValue.Get().(*AnyValue_BytesValue)
}
ov.BytesValue = iter.ReadBytes()
orig.Value = ov
}
case "stringValueStrindex", "string_value_strindex":
{
var ov *AnyValue_StringValueStrindex
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &AnyValue_StringValueStrindex{}
} else {
ov = ProtoPoolAnyValue_StringValueStrindex.Get().(*AnyValue_StringValueStrindex)
}
ov.StringValueStrindex = iter.ReadInt32()
orig.Value = ov
}
default:
iter.Skip()
}
}
}
func (orig *AnyValue) SizeProto() int {
var n int
var l int
_ = l
switch orig := orig.Value.(type) {
case nil:
_ = orig
break
case *AnyValue_StringValue:
l = len(orig.StringValue)
n += 1 + proto.Sov(uint64(l)) + l
case *AnyValue_BoolValue:
n += 2
case *AnyValue_IntValue:
n += 1 + proto.Sov(uint64(orig.IntValue))
case *AnyValue_DoubleValue:
n += 9
case *AnyValue_ArrayValue:
if orig.ArrayValue != nil {
l = orig.ArrayValue.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
case *AnyValue_KvlistValue:
if orig.KvlistValue != nil {
l = orig.KvlistValue.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
case *AnyValue_BytesValue:
l = len(orig.BytesValue)
n += 1 + proto.Sov(uint64(l)) + l
case *AnyValue_StringValueStrindex:
n += 1 + proto.Sov(uint64(orig.StringValueStrindex))
}
return n
}
func (orig *AnyValue) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
switch orig := orig.Value.(type) {
case *AnyValue_StringValue:
l = len(orig.StringValue)
pos -= l
copy(buf[pos:], orig.StringValue)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
case *AnyValue_BoolValue:
pos--
if orig.BoolValue {
buf[pos] = 1
} else {
buf[pos] = 0
}
pos--
buf[pos] = 0x10
case *AnyValue_IntValue:
pos = proto.EncodeVarint(buf, pos, uint64(orig.IntValue))
pos--
buf[pos] = 0x18
case *AnyValue_DoubleValue:
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], math.Float64bits(orig.DoubleValue))
pos--
buf[pos] = 0x21
case *AnyValue_ArrayValue:
if orig.ArrayValue != nil {
l = orig.ArrayValue.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x2a
}
case *AnyValue_KvlistValue:
if orig.KvlistValue != nil {
l = orig.KvlistValue.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x32
}
case *AnyValue_BytesValue:
l = len(orig.BytesValue)
pos -= l
copy(buf[pos:], orig.BytesValue)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x3a
case *AnyValue_StringValueStrindex:
pos = proto.EncodeVarint(buf, pos, uint64(orig.StringValueStrindex))
pos--
buf[pos] = 0x40
}
return len(buf) - pos
}
func (orig *AnyValue) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field StringValue", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
var ov *AnyValue_StringValue
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &AnyValue_StringValue{}
} else {
ov = ProtoPoolAnyValue_StringValue.Get().(*AnyValue_StringValue)
}
ov.StringValue = string(buf[startPos:pos])
orig.Value = ov
case 2:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field BoolValue", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
var ov *AnyValue_BoolValue
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &AnyValue_BoolValue{}
} else {
ov = ProtoPoolAnyValue_BoolValue.Get().(*AnyValue_BoolValue)
}
ov.BoolValue = num != 0
orig.Value = ov
case 3:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field IntValue", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
var ov *AnyValue_IntValue
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &AnyValue_IntValue{}
} else {
ov = ProtoPoolAnyValue_IntValue.Get().(*AnyValue_IntValue)
}
ov.IntValue = int64(num)
orig.Value = ov
case 4:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field DoubleValue", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
var ov *AnyValue_DoubleValue
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &AnyValue_DoubleValue{}
} else {
ov = ProtoPoolAnyValue_DoubleValue.Get().(*AnyValue_DoubleValue)
}
ov.DoubleValue = math.Float64frombits(num)
orig.Value = ov
case 5:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field ArrayValue", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
var ov *AnyValue_ArrayValue
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &AnyValue_ArrayValue{}
} else {
ov = ProtoPoolAnyValue_ArrayValue.Get().(*AnyValue_ArrayValue)
}
ov.ArrayValue = NewArrayValue()
err = ov.ArrayValue.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
orig.Value = ov
case 6:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field KvlistValue", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
var ov *AnyValue_KvlistValue
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &AnyValue_KvlistValue{}
} else {
ov = ProtoPoolAnyValue_KvlistValue.Get().(*AnyValue_KvlistValue)
}
ov.KvlistValue = NewKeyValueList()
err = ov.KvlistValue.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
orig.Value = ov
case 7:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field BytesValue", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
var ov *AnyValue_BytesValue
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &AnyValue_BytesValue{}
} else {
ov = ProtoPoolAnyValue_BytesValue.Get().(*AnyValue_BytesValue)
}
if length != 0 {
ov.BytesValue = make([]byte, length)
copy(ov.BytesValue, buf[startPos:pos])
}
orig.Value = ov
case 8:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field StringValueStrindex", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
var ov *AnyValue_StringValueStrindex
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &AnyValue_StringValueStrindex{}
} else {
ov = ProtoPoolAnyValue_StringValueStrindex.Get().(*AnyValue_StringValueStrindex)
}
ov.StringValueStrindex = int32(num)
orig.Value = ov
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestAnyValue() *AnyValue {
orig := NewAnyValue()
orig.Value = &AnyValue_StringValue{StringValue: "test_stringvalue"}
return orig
}
func GenTestAnyValuePtrSlice() []*AnyValue {
orig := make([]*AnyValue, 5)
orig[0] = NewAnyValue()
orig[1] = GenTestAnyValue()
orig[2] = NewAnyValue()
orig[3] = GenTestAnyValue()
orig[4] = NewAnyValue()
return orig
}
func GenTestAnyValueSlice() []AnyValue {
orig := make([]AnyValue, 5)
orig[1] = *GenTestAnyValue()
orig[3] = *GenTestAnyValue()
return orig
}
================================================
FILE: pdata/internal/generated_proto_anyvalue_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpcommon "go.opentelemetry.io/proto/slim/otlp/common/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyAnyValue(t *testing.T) {
for name, src := range genTestEncodingValuesAnyValue() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewAnyValue()
CopyAnyValue(dest, src)
assert.Equal(t, src, dest)
CopyAnyValue(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyAnyValueSlice(t *testing.T) {
src := []AnyValue{}
dest := []AnyValue{}
// Test CopyTo empty
dest = CopyAnyValueSlice(dest, src)
assert.Equal(t, []AnyValue{}, dest)
// Test CopyTo larger slice
src = GenTestAnyValueSlice()
dest = CopyAnyValueSlice(dest, src)
assert.Equal(t, GenTestAnyValueSlice(), dest)
// Test CopyTo same size slice
dest = CopyAnyValueSlice(dest, src)
assert.Equal(t, GenTestAnyValueSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyAnyValueSlice(dest, []AnyValue{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyAnyValueSlice(dest, src)
assert.Equal(t, GenTestAnyValueSlice(), dest)
}
func TestCopyAnyValuePtrSlice(t *testing.T) {
src := []*AnyValue{}
dest := []*AnyValue{}
// Test CopyTo empty
dest = CopyAnyValuePtrSlice(dest, src)
assert.Equal(t, []*AnyValue{}, dest)
// Test CopyTo larger slice
src = GenTestAnyValuePtrSlice()
dest = CopyAnyValuePtrSlice(dest, src)
assert.Equal(t, GenTestAnyValuePtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyAnyValuePtrSlice(dest, src)
assert.Equal(t, GenTestAnyValuePtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyAnyValuePtrSlice(dest, []*AnyValue{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyAnyValuePtrSlice(dest, src)
assert.Equal(t, GenTestAnyValuePtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONAnyValueUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewAnyValue()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewAnyValue(), dest)
}
func TestMarshalAndUnmarshalJSONAnyValue(t *testing.T) {
for name, src := range genTestEncodingValuesAnyValue() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewAnyValue()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteAnyValue(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoAnyValueFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesAnyValue() {
t.Run(name, func(t *testing.T) {
dest := NewAnyValue()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoAnyValueUnknown(t *testing.T) {
dest := NewAnyValue()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewAnyValue(), dest)
}
func TestMarshalAndUnmarshalProtoAnyValue(t *testing.T) {
for name, src := range genTestEncodingValuesAnyValue() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewAnyValue()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteAnyValue(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufAnyValue(t *testing.T) {
for name, src := range genTestEncodingValuesAnyValue() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpcommon.AnyValue{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewAnyValue()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesAnyValue() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"StringValue/wrong_wire_type": {0xc},
"StringValue/missing_value": {0xa},
"BoolValue/wrong_wire_type": {0x14},
"BoolValue/missing_value": {0x10},
"IntValue/wrong_wire_type": {0x1c},
"IntValue/missing_value": {0x18},
"DoubleValue/wrong_wire_type": {0x24},
"DoubleValue/missing_value": {0x21},
"ArrayValue/wrong_wire_type": {0x2c},
"ArrayValue/missing_value": {0x2a},
"KvlistValue/wrong_wire_type": {0x34},
"KvlistValue/missing_value": {0x32},
"BytesValue/wrong_wire_type": {0x3c},
"BytesValue/missing_value": {0x3a},
"StringValueStrindex/wrong_wire_type": {0x44},
"StringValueStrindex/missing_value": {0x40},
}
}
func genTestEncodingValuesAnyValue() map[string]*AnyValue {
return map[string]*AnyValue{
"empty": NewAnyValue(),
"StringValue/default": {Value: &AnyValue_StringValue{StringValue: ""}},
"StringValue/test": {Value: &AnyValue_StringValue{StringValue: "test_stringvalue"}}, "BoolValue/default": {Value: &AnyValue_BoolValue{BoolValue: false}},
"BoolValue/test": {Value: &AnyValue_BoolValue{BoolValue: true}}, "IntValue/default": {Value: &AnyValue_IntValue{IntValue: int64(0)}},
"IntValue/test": {Value: &AnyValue_IntValue{IntValue: int64(13)}}, "DoubleValue/default": {Value: &AnyValue_DoubleValue{DoubleValue: float64(0)}},
"DoubleValue/test": {Value: &AnyValue_DoubleValue{DoubleValue: float64(3.1415926)}}, "ArrayValue/default": {Value: &AnyValue_ArrayValue{ArrayValue: &ArrayValue{}}},
"ArrayValue/test": {Value: &AnyValue_ArrayValue{ArrayValue: GenTestArrayValue()}}, "KvlistValue/default": {Value: &AnyValue_KvlistValue{KvlistValue: &KeyValueList{}}},
"KvlistValue/test": {Value: &AnyValue_KvlistValue{KvlistValue: GenTestKeyValueList()}}, "BytesValue/default": {Value: &AnyValue_BytesValue{BytesValue: nil}},
"BytesValue/test": {Value: &AnyValue_BytesValue{BytesValue: []byte{1, 2, 3}}}, "StringValueStrindex/default": {Value: &AnyValue_StringValueStrindex{StringValueStrindex: int32(0)}},
"StringValueStrindex/test": {Value: &AnyValue_StringValueStrindex{StringValueStrindex: int32(13)}},
}
}
================================================
FILE: pdata/internal/generated_proto_arrayvalue.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// 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 {
Values []AnyValue
}
var (
protoPoolArrayValue = sync.Pool{
New: func() any {
return &ArrayValue{}
},
}
)
func NewArrayValue() *ArrayValue {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &ArrayValue{}
}
return protoPoolArrayValue.Get().(*ArrayValue)
}
func DeleteArrayValue(orig *ArrayValue, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
for i := range orig.Values {
DeleteAnyValue(&orig.Values[i], false)
}
orig.Reset()
if nullable {
protoPoolArrayValue.Put(orig)
}
}
func CopyArrayValue(dest, src *ArrayValue) *ArrayValue {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewArrayValue()
}
dest.Values = CopyAnyValueSlice(dest.Values, src.Values)
return dest
}
func CopyArrayValueSlice(dest, src []ArrayValue) []ArrayValue {
var newDest []ArrayValue
if cap(dest) < len(src) {
newDest = make([]ArrayValue, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteArrayValue(&dest[i], false)
}
}
for i := range src {
CopyArrayValue(&newDest[i], &src[i])
}
return newDest
}
func CopyArrayValuePtrSlice(dest, src []*ArrayValue) []*ArrayValue {
var newDest []*ArrayValue
if cap(dest) < len(src) {
newDest = make([]*ArrayValue, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewArrayValue()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteArrayValue(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewArrayValue()
}
}
for i := range src {
CopyArrayValue(newDest[i], src[i])
}
return newDest
}
func (orig *ArrayValue) Reset() {
*orig = ArrayValue{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *ArrayValue) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if len(orig.Values) > 0 {
dest.WriteObjectField("values")
dest.WriteArrayStart()
orig.Values[0].MarshalJSON(dest)
for i := 1; i < len(orig.Values); i++ {
dest.WriteMore()
orig.Values[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *ArrayValue) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "values":
for iter.ReadArray() {
orig.Values = append(orig.Values, AnyValue{})
orig.Values[len(orig.Values)-1].UnmarshalJSON(iter)
}
default:
iter.Skip()
}
}
}
func (orig *ArrayValue) SizeProto() int {
var n int
var l int
_ = l
for i := range orig.Values {
l = orig.Values[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *ArrayValue) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
for i := len(orig.Values) - 1; i >= 0; i-- {
l = orig.Values[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
}
return len(buf) - pos
}
func (orig *ArrayValue) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Values", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Values = append(orig.Values, AnyValue{})
err = orig.Values[len(orig.Values)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestArrayValue() *ArrayValue {
orig := NewArrayValue()
orig.Values = []AnyValue{{}, *GenTestAnyValue()}
return orig
}
func GenTestArrayValuePtrSlice() []*ArrayValue {
orig := make([]*ArrayValue, 5)
orig[0] = NewArrayValue()
orig[1] = GenTestArrayValue()
orig[2] = NewArrayValue()
orig[3] = GenTestArrayValue()
orig[4] = NewArrayValue()
return orig
}
func GenTestArrayValueSlice() []ArrayValue {
orig := make([]ArrayValue, 5)
orig[1] = *GenTestArrayValue()
orig[3] = *GenTestArrayValue()
return orig
}
================================================
FILE: pdata/internal/generated_proto_arrayvalue_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpcommon "go.opentelemetry.io/proto/slim/otlp/common/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyArrayValue(t *testing.T) {
for name, src := range genTestEncodingValuesArrayValue() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewArrayValue()
CopyArrayValue(dest, src)
assert.Equal(t, src, dest)
CopyArrayValue(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyArrayValueSlice(t *testing.T) {
src := []ArrayValue{}
dest := []ArrayValue{}
// Test CopyTo empty
dest = CopyArrayValueSlice(dest, src)
assert.Equal(t, []ArrayValue{}, dest)
// Test CopyTo larger slice
src = GenTestArrayValueSlice()
dest = CopyArrayValueSlice(dest, src)
assert.Equal(t, GenTestArrayValueSlice(), dest)
// Test CopyTo same size slice
dest = CopyArrayValueSlice(dest, src)
assert.Equal(t, GenTestArrayValueSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyArrayValueSlice(dest, []ArrayValue{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyArrayValueSlice(dest, src)
assert.Equal(t, GenTestArrayValueSlice(), dest)
}
func TestCopyArrayValuePtrSlice(t *testing.T) {
src := []*ArrayValue{}
dest := []*ArrayValue{}
// Test CopyTo empty
dest = CopyArrayValuePtrSlice(dest, src)
assert.Equal(t, []*ArrayValue{}, dest)
// Test CopyTo larger slice
src = GenTestArrayValuePtrSlice()
dest = CopyArrayValuePtrSlice(dest, src)
assert.Equal(t, GenTestArrayValuePtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyArrayValuePtrSlice(dest, src)
assert.Equal(t, GenTestArrayValuePtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyArrayValuePtrSlice(dest, []*ArrayValue{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyArrayValuePtrSlice(dest, src)
assert.Equal(t, GenTestArrayValuePtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONArrayValueUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewArrayValue()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewArrayValue(), dest)
}
func TestMarshalAndUnmarshalJSONArrayValue(t *testing.T) {
for name, src := range genTestEncodingValuesArrayValue() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewArrayValue()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteArrayValue(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoArrayValueFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesArrayValue() {
t.Run(name, func(t *testing.T) {
dest := NewArrayValue()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoArrayValueUnknown(t *testing.T) {
dest := NewArrayValue()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewArrayValue(), dest)
}
func TestMarshalAndUnmarshalProtoArrayValue(t *testing.T) {
for name, src := range genTestEncodingValuesArrayValue() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewArrayValue()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteArrayValue(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufArrayValue(t *testing.T) {
for name, src := range genTestEncodingValuesArrayValue() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpcommon.ArrayValue{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewArrayValue()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesArrayValue() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"Values/wrong_wire_type": {0xc},
"Values/missing_value": {0xa},
}
}
func genTestEncodingValuesArrayValue() map[string]*ArrayValue {
return map[string]*ArrayValue{
"empty": NewArrayValue(),
"Values/test": {Values: []AnyValue{{}, *GenTestAnyValue()}},
}
}
================================================
FILE: pdata/internal/generated_proto_entityref.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
type EntityRef struct {
SchemaUrl string
Type string
IdKeys []string
DescriptionKeys []string
}
var (
protoPoolEntityRef = sync.Pool{
New: func() any {
return &EntityRef{}
},
}
)
func NewEntityRef() *EntityRef {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &EntityRef{}
}
return protoPoolEntityRef.Get().(*EntityRef)
}
func DeleteEntityRef(orig *EntityRef, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
orig.Reset()
if nullable {
protoPoolEntityRef.Put(orig)
}
}
func CopyEntityRef(dest, src *EntityRef) *EntityRef {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewEntityRef()
}
dest.SchemaUrl = src.SchemaUrl
dest.Type = src.Type
dest.IdKeys = append(dest.IdKeys[:0], src.IdKeys...)
dest.DescriptionKeys = append(dest.DescriptionKeys[:0], src.DescriptionKeys...)
return dest
}
func CopyEntityRefSlice(dest, src []EntityRef) []EntityRef {
var newDest []EntityRef
if cap(dest) < len(src) {
newDest = make([]EntityRef, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteEntityRef(&dest[i], false)
}
}
for i := range src {
CopyEntityRef(&newDest[i], &src[i])
}
return newDest
}
func CopyEntityRefPtrSlice(dest, src []*EntityRef) []*EntityRef {
var newDest []*EntityRef
if cap(dest) < len(src) {
newDest = make([]*EntityRef, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewEntityRef()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteEntityRef(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewEntityRef()
}
}
for i := range src {
CopyEntityRef(newDest[i], src[i])
}
return newDest
}
func (orig *EntityRef) Reset() {
*orig = EntityRef{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *EntityRef) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if orig.SchemaUrl != "" {
dest.WriteObjectField("schemaUrl")
dest.WriteString(orig.SchemaUrl)
}
if orig.Type != "" {
dest.WriteObjectField("type")
dest.WriteString(orig.Type)
}
if len(orig.IdKeys) > 0 {
dest.WriteObjectField("idKeys")
dest.WriteArrayStart()
dest.WriteString(orig.IdKeys[0])
for i := 1; i < len(orig.IdKeys); i++ {
dest.WriteMore()
dest.WriteString(orig.IdKeys[i])
}
dest.WriteArrayEnd()
}
if len(orig.DescriptionKeys) > 0 {
dest.WriteObjectField("descriptionKeys")
dest.WriteArrayStart()
dest.WriteString(orig.DescriptionKeys[0])
for i := 1; i < len(orig.DescriptionKeys); i++ {
dest.WriteMore()
dest.WriteString(orig.DescriptionKeys[i])
}
dest.WriteArrayEnd()
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *EntityRef) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "schemaUrl", "schema_url":
orig.SchemaUrl = iter.ReadString()
case "type":
orig.Type = iter.ReadString()
case "idKeys", "id_keys":
for iter.ReadArray() {
orig.IdKeys = append(orig.IdKeys, iter.ReadString())
}
case "descriptionKeys", "description_keys":
for iter.ReadArray() {
orig.DescriptionKeys = append(orig.DescriptionKeys, iter.ReadString())
}
default:
iter.Skip()
}
}
}
func (orig *EntityRef) SizeProto() int {
var n int
var l int
_ = l
l = len(orig.SchemaUrl)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
l = len(orig.Type)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
for _, s := range orig.IdKeys {
l = len(s)
n += 1 + proto.Sov(uint64(l)) + l
}
for _, s := range orig.DescriptionKeys {
l = len(s)
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *EntityRef) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
l = len(orig.SchemaUrl)
if l > 0 {
pos -= l
copy(buf[pos:], orig.SchemaUrl)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
}
l = len(orig.Type)
if l > 0 {
pos -= l
copy(buf[pos:], orig.Type)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
}
for i := len(orig.IdKeys) - 1; i >= 0; i-- {
l = len(orig.IdKeys[i])
pos -= l
copy(buf[pos:], orig.IdKeys[i])
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x1a
}
for i := len(orig.DescriptionKeys) - 1; i >= 0; i-- {
l = len(orig.DescriptionKeys[i])
pos -= l
copy(buf[pos:], orig.DescriptionKeys[i])
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x22
}
return len(buf) - pos
}
func (orig *EntityRef) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field SchemaUrl", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.SchemaUrl = string(buf[startPos:pos])
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Type = string(buf[startPos:pos])
case 3:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field IdKeys", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.IdKeys = append(orig.IdKeys, string(buf[startPos:pos]))
case 4:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field DescriptionKeys", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.DescriptionKeys = append(orig.DescriptionKeys, string(buf[startPos:pos]))
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestEntityRef() *EntityRef {
orig := NewEntityRef()
orig.SchemaUrl = "test_schemaurl"
orig.Type = "test_type"
orig.IdKeys = []string{"", "test_idkeys"}
orig.DescriptionKeys = []string{"", "test_descriptionkeys"}
return orig
}
func GenTestEntityRefPtrSlice() []*EntityRef {
orig := make([]*EntityRef, 5)
orig[0] = NewEntityRef()
orig[1] = GenTestEntityRef()
orig[2] = NewEntityRef()
orig[3] = GenTestEntityRef()
orig[4] = NewEntityRef()
return orig
}
func GenTestEntityRefSlice() []EntityRef {
orig := make([]EntityRef, 5)
orig[1] = *GenTestEntityRef()
orig[3] = *GenTestEntityRef()
return orig
}
================================================
FILE: pdata/internal/generated_proto_entityref_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpcommon "go.opentelemetry.io/proto/slim/otlp/common/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyEntityRef(t *testing.T) {
for name, src := range genTestEncodingValuesEntityRef() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewEntityRef()
CopyEntityRef(dest, src)
assert.Equal(t, src, dest)
CopyEntityRef(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyEntityRefSlice(t *testing.T) {
src := []EntityRef{}
dest := []EntityRef{}
// Test CopyTo empty
dest = CopyEntityRefSlice(dest, src)
assert.Equal(t, []EntityRef{}, dest)
// Test CopyTo larger slice
src = GenTestEntityRefSlice()
dest = CopyEntityRefSlice(dest, src)
assert.Equal(t, GenTestEntityRefSlice(), dest)
// Test CopyTo same size slice
dest = CopyEntityRefSlice(dest, src)
assert.Equal(t, GenTestEntityRefSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyEntityRefSlice(dest, []EntityRef{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyEntityRefSlice(dest, src)
assert.Equal(t, GenTestEntityRefSlice(), dest)
}
func TestCopyEntityRefPtrSlice(t *testing.T) {
src := []*EntityRef{}
dest := []*EntityRef{}
// Test CopyTo empty
dest = CopyEntityRefPtrSlice(dest, src)
assert.Equal(t, []*EntityRef{}, dest)
// Test CopyTo larger slice
src = GenTestEntityRefPtrSlice()
dest = CopyEntityRefPtrSlice(dest, src)
assert.Equal(t, GenTestEntityRefPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyEntityRefPtrSlice(dest, src)
assert.Equal(t, GenTestEntityRefPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyEntityRefPtrSlice(dest, []*EntityRef{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyEntityRefPtrSlice(dest, src)
assert.Equal(t, GenTestEntityRefPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONEntityRefUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewEntityRef()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewEntityRef(), dest)
}
func TestMarshalAndUnmarshalJSONEntityRef(t *testing.T) {
for name, src := range genTestEncodingValuesEntityRef() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewEntityRef()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteEntityRef(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoEntityRefFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesEntityRef() {
t.Run(name, func(t *testing.T) {
dest := NewEntityRef()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoEntityRefUnknown(t *testing.T) {
dest := NewEntityRef()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewEntityRef(), dest)
}
func TestMarshalAndUnmarshalProtoEntityRef(t *testing.T) {
for name, src := range genTestEncodingValuesEntityRef() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewEntityRef()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteEntityRef(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufEntityRef(t *testing.T) {
for name, src := range genTestEncodingValuesEntityRef() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpcommon.EntityRef{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewEntityRef()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesEntityRef() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"SchemaUrl/wrong_wire_type": {0xc},
"SchemaUrl/missing_value": {0xa},
"Type/wrong_wire_type": {0x14},
"Type/missing_value": {0x12},
"IdKeys/wrong_wire_type": {0x1c},
"IdKeys/missing_value": {0x1a},
"DescriptionKeys/wrong_wire_type": {0x24},
"DescriptionKeys/missing_value": {0x22},
}
}
func genTestEncodingValuesEntityRef() map[string]*EntityRef {
return map[string]*EntityRef{
"empty": NewEntityRef(),
"SchemaUrl/test": {SchemaUrl: "test_schemaurl"},
"Type/test": {Type: "test_type"},
"IdKeys/test": {IdKeys: []string{"", "test_idkeys"}},
"DescriptionKeys/test": {DescriptionKeys: []string{"", "test_descriptionkeys"}},
}
}
================================================
FILE: pdata/internal/generated_proto_exemplar.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"encoding/binary"
"fmt"
"math"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
func (m *Exemplar) GetValue() any {
if m != nil {
return m.Value
}
return nil
}
type Exemplar_AsDouble struct {
AsDouble float64
}
func (m *Exemplar) GetAsDouble() float64 {
if v, ok := m.GetValue().(*Exemplar_AsDouble); ok {
return v.AsDouble
}
return float64(0)
}
type Exemplar_AsInt struct {
AsInt int64
}
func (m *Exemplar) GetAsInt() int64 {
if v, ok := m.GetValue().(*Exemplar_AsInt); ok {
return v.AsInt
}
return int64(0)
}
// Exemplar 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 Exemplar struct {
Value any
FilteredAttributes []KeyValue
TimeUnixNano uint64
TraceId TraceID
SpanId SpanID
}
var (
protoPoolExemplar = sync.Pool{
New: func() any {
return &Exemplar{}
},
}
ProtoPoolExemplar_AsDouble = sync.Pool{
New: func() any {
return &Exemplar_AsDouble{}
},
}
ProtoPoolExemplar_AsInt = sync.Pool{
New: func() any {
return &Exemplar_AsInt{}
},
}
)
func NewExemplar() *Exemplar {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &Exemplar{}
}
return protoPoolExemplar.Get().(*Exemplar)
}
func DeleteExemplar(orig *Exemplar, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
for i := range orig.FilteredAttributes {
DeleteKeyValue(&orig.FilteredAttributes[i], false)
}
switch ov := orig.Value.(type) {
case *Exemplar_AsDouble:
if metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov.AsDouble = float64(0)
ProtoPoolExemplar_AsDouble.Put(ov)
}
case *Exemplar_AsInt:
if metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov.AsInt = int64(0)
ProtoPoolExemplar_AsInt.Put(ov)
}
}
DeleteTraceID(&orig.TraceId, false)
DeleteSpanID(&orig.SpanId, false)
orig.Reset()
if nullable {
protoPoolExemplar.Put(orig)
}
}
func CopyExemplar(dest, src *Exemplar) *Exemplar {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewExemplar()
}
dest.FilteredAttributes = CopyKeyValueSlice(dest.FilteredAttributes, src.FilteredAttributes)
dest.TimeUnixNano = src.TimeUnixNano
switch t := src.Value.(type) {
case *Exemplar_AsDouble:
var ov *Exemplar_AsDouble
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &Exemplar_AsDouble{}
} else {
ov = ProtoPoolExemplar_AsDouble.Get().(*Exemplar_AsDouble)
}
ov.AsDouble = t.AsDouble
dest.Value = ov
case *Exemplar_AsInt:
var ov *Exemplar_AsInt
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &Exemplar_AsInt{}
} else {
ov = ProtoPoolExemplar_AsInt.Get().(*Exemplar_AsInt)
}
ov.AsInt = t.AsInt
dest.Value = ov
default:
dest.Value = nil
}
CopyTraceID(&dest.TraceId, &src.TraceId)
CopySpanID(&dest.SpanId, &src.SpanId)
return dest
}
func CopyExemplarSlice(dest, src []Exemplar) []Exemplar {
var newDest []Exemplar
if cap(dest) < len(src) {
newDest = make([]Exemplar, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteExemplar(&dest[i], false)
}
}
for i := range src {
CopyExemplar(&newDest[i], &src[i])
}
return newDest
}
func CopyExemplarPtrSlice(dest, src []*Exemplar) []*Exemplar {
var newDest []*Exemplar
if cap(dest) < len(src) {
newDest = make([]*Exemplar, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewExemplar()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteExemplar(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewExemplar()
}
}
for i := range src {
CopyExemplar(newDest[i], src[i])
}
return newDest
}
func (orig *Exemplar) Reset() {
*orig = Exemplar{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *Exemplar) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if len(orig.FilteredAttributes) > 0 {
dest.WriteObjectField("filteredAttributes")
dest.WriteArrayStart()
orig.FilteredAttributes[0].MarshalJSON(dest)
for i := 1; i < len(orig.FilteredAttributes); i++ {
dest.WriteMore()
orig.FilteredAttributes[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if orig.TimeUnixNano != uint64(0) {
dest.WriteObjectField("timeUnixNano")
dest.WriteUint64(orig.TimeUnixNano)
}
switch orig := orig.Value.(type) {
case *Exemplar_AsDouble:
dest.WriteObjectField("asDouble")
dest.WriteFloat64(orig.AsDouble)
case *Exemplar_AsInt:
dest.WriteObjectField("asInt")
dest.WriteInt64(orig.AsInt)
}
if !orig.TraceId.IsEmpty() {
dest.WriteObjectField("traceId")
orig.TraceId.MarshalJSON(dest)
}
if !orig.SpanId.IsEmpty() {
dest.WriteObjectField("spanId")
orig.SpanId.MarshalJSON(dest)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *Exemplar) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "filteredAttributes", "filtered_attributes":
for iter.ReadArray() {
orig.FilteredAttributes = append(orig.FilteredAttributes, KeyValue{})
orig.FilteredAttributes[len(orig.FilteredAttributes)-1].UnmarshalJSON(iter)
}
case "timeUnixNano", "time_unix_nano":
orig.TimeUnixNano = iter.ReadUint64()
case "asDouble", "as_double":
{
var ov *Exemplar_AsDouble
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &Exemplar_AsDouble{}
} else {
ov = ProtoPoolExemplar_AsDouble.Get().(*Exemplar_AsDouble)
}
ov.AsDouble = iter.ReadFloat64()
orig.Value = ov
}
case "asInt", "as_int":
{
var ov *Exemplar_AsInt
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &Exemplar_AsInt{}
} else {
ov = ProtoPoolExemplar_AsInt.Get().(*Exemplar_AsInt)
}
ov.AsInt = iter.ReadInt64()
orig.Value = ov
}
case "traceId", "trace_id":
orig.TraceId.UnmarshalJSON(iter)
case "spanId", "span_id":
orig.SpanId.UnmarshalJSON(iter)
default:
iter.Skip()
}
}
}
func (orig *Exemplar) SizeProto() int {
var n int
var l int
_ = l
for i := range orig.FilteredAttributes {
l = orig.FilteredAttributes[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
if orig.TimeUnixNano != uint64(0) {
n += 9
}
switch orig := orig.Value.(type) {
case nil:
_ = orig
break
case *Exemplar_AsDouble:
n += 9
case *Exemplar_AsInt:
n += 9
}
l = orig.TraceId.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
l = orig.SpanId.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
return n
}
func (orig *Exemplar) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
for i := len(orig.FilteredAttributes) - 1; i >= 0; i-- {
l = orig.FilteredAttributes[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x3a
}
if orig.TimeUnixNano != uint64(0) {
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], uint64(orig.TimeUnixNano))
pos--
buf[pos] = 0x11
}
switch orig := orig.Value.(type) {
case *Exemplar_AsDouble:
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], math.Float64bits(orig.AsDouble))
pos--
buf[pos] = 0x19
case *Exemplar_AsInt:
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], uint64(orig.AsInt))
pos--
buf[pos] = 0x31
}
l = orig.TraceId.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x2a
l = orig.SpanId.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x22
return len(buf) - pos
}
func (orig *Exemplar) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 7:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field FilteredAttributes", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.FilteredAttributes = append(orig.FilteredAttributes, KeyValue{})
err = orig.FilteredAttributes[len(orig.FilteredAttributes)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 2:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field TimeUnixNano", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
orig.TimeUnixNano = uint64(num)
case 3:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field AsDouble", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
var ov *Exemplar_AsDouble
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &Exemplar_AsDouble{}
} else {
ov = ProtoPoolExemplar_AsDouble.Get().(*Exemplar_AsDouble)
}
ov.AsDouble = math.Float64frombits(num)
orig.Value = ov
case 6:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field AsInt", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
var ov *Exemplar_AsInt
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &Exemplar_AsInt{}
} else {
ov = ProtoPoolExemplar_AsInt.Get().(*Exemplar_AsInt)
}
ov.AsInt = int64(num)
orig.Value = ov
case 5:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field TraceId", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.TraceId.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 4:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field SpanId", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.SpanId.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestExemplar() *Exemplar {
orig := NewExemplar()
orig.FilteredAttributes = []KeyValue{{}, *GenTestKeyValue()}
orig.TimeUnixNano = uint64(13)
orig.Value = &Exemplar_AsDouble{AsDouble: float64(3.1415926)}
orig.TraceId = *GenTestTraceID()
orig.SpanId = *GenTestSpanID()
return orig
}
func GenTestExemplarPtrSlice() []*Exemplar {
orig := make([]*Exemplar, 5)
orig[0] = NewExemplar()
orig[1] = GenTestExemplar()
orig[2] = NewExemplar()
orig[3] = GenTestExemplar()
orig[4] = NewExemplar()
return orig
}
func GenTestExemplarSlice() []Exemplar {
orig := make([]Exemplar, 5)
orig[1] = *GenTestExemplar()
orig[3] = *GenTestExemplar()
return orig
}
================================================
FILE: pdata/internal/generated_proto_exemplar_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpmetrics "go.opentelemetry.io/proto/slim/otlp/metrics/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyExemplar(t *testing.T) {
for name, src := range genTestEncodingValuesExemplar() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewExemplar()
CopyExemplar(dest, src)
assert.Equal(t, src, dest)
CopyExemplar(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyExemplarSlice(t *testing.T) {
src := []Exemplar{}
dest := []Exemplar{}
// Test CopyTo empty
dest = CopyExemplarSlice(dest, src)
assert.Equal(t, []Exemplar{}, dest)
// Test CopyTo larger slice
src = GenTestExemplarSlice()
dest = CopyExemplarSlice(dest, src)
assert.Equal(t, GenTestExemplarSlice(), dest)
// Test CopyTo same size slice
dest = CopyExemplarSlice(dest, src)
assert.Equal(t, GenTestExemplarSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyExemplarSlice(dest, []Exemplar{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyExemplarSlice(dest, src)
assert.Equal(t, GenTestExemplarSlice(), dest)
}
func TestCopyExemplarPtrSlice(t *testing.T) {
src := []*Exemplar{}
dest := []*Exemplar{}
// Test CopyTo empty
dest = CopyExemplarPtrSlice(dest, src)
assert.Equal(t, []*Exemplar{}, dest)
// Test CopyTo larger slice
src = GenTestExemplarPtrSlice()
dest = CopyExemplarPtrSlice(dest, src)
assert.Equal(t, GenTestExemplarPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyExemplarPtrSlice(dest, src)
assert.Equal(t, GenTestExemplarPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyExemplarPtrSlice(dest, []*Exemplar{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyExemplarPtrSlice(dest, src)
assert.Equal(t, GenTestExemplarPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONExemplarUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewExemplar()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewExemplar(), dest)
}
func TestMarshalAndUnmarshalJSONExemplar(t *testing.T) {
for name, src := range genTestEncodingValuesExemplar() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewExemplar()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteExemplar(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoExemplarFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesExemplar() {
t.Run(name, func(t *testing.T) {
dest := NewExemplar()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoExemplarUnknown(t *testing.T) {
dest := NewExemplar()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewExemplar(), dest)
}
func TestMarshalAndUnmarshalProtoExemplar(t *testing.T) {
for name, src := range genTestEncodingValuesExemplar() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewExemplar()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteExemplar(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufExemplar(t *testing.T) {
for name, src := range genTestEncodingValuesExemplar() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpmetrics.Exemplar{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewExemplar()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesExemplar() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"FilteredAttributes/wrong_wire_type": {0x3c},
"FilteredAttributes/missing_value": {0x3a},
"TimeUnixNano/wrong_wire_type": {0x14},
"TimeUnixNano/missing_value": {0x11},
"AsDouble/wrong_wire_type": {0x1c},
"AsDouble/missing_value": {0x19},
"AsInt/wrong_wire_type": {0x34},
"AsInt/missing_value": {0x31},
"TraceId/wrong_wire_type": {0x2c},
"TraceId/missing_value": {0x2a},
"SpanId/wrong_wire_type": {0x24},
"SpanId/missing_value": {0x22},
}
}
func genTestEncodingValuesExemplar() map[string]*Exemplar {
return map[string]*Exemplar{
"empty": NewExemplar(),
"FilteredAttributes/test": {FilteredAttributes: []KeyValue{{}, *GenTestKeyValue()}},
"TimeUnixNano/test": {TimeUnixNano: uint64(13)},
"AsDouble/default": {Value: &Exemplar_AsDouble{AsDouble: float64(0)}},
"AsDouble/test": {Value: &Exemplar_AsDouble{AsDouble: float64(3.1415926)}}, "AsInt/default": {Value: &Exemplar_AsInt{AsInt: int64(0)}},
"AsInt/test": {Value: &Exemplar_AsInt{AsInt: int64(13)}},
"TraceId/test": {TraceId: *GenTestTraceID()},
"SpanId/test": {SpanId: *GenTestSpanID()},
}
}
================================================
FILE: pdata/internal/generated_proto_exponentialhistogram.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// ExponentialHistogram represents the type of a metric that is calculated by aggregating
// as a ExponentialHistogram of all reported double measurements over a time interval.
type ExponentialHistogram struct {
DataPoints []*ExponentialHistogramDataPoint
AggregationTemporality AggregationTemporality
}
var (
protoPoolExponentialHistogram = sync.Pool{
New: func() any {
return &ExponentialHistogram{}
},
}
)
func NewExponentialHistogram() *ExponentialHistogram {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &ExponentialHistogram{}
}
return protoPoolExponentialHistogram.Get().(*ExponentialHistogram)
}
func DeleteExponentialHistogram(orig *ExponentialHistogram, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
for i := range orig.DataPoints {
DeleteExponentialHistogramDataPoint(orig.DataPoints[i], true)
}
orig.Reset()
if nullable {
protoPoolExponentialHistogram.Put(orig)
}
}
func CopyExponentialHistogram(dest, src *ExponentialHistogram) *ExponentialHistogram {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewExponentialHistogram()
}
dest.DataPoints = CopyExponentialHistogramDataPointPtrSlice(dest.DataPoints, src.DataPoints)
dest.AggregationTemporality = src.AggregationTemporality
return dest
}
func CopyExponentialHistogramSlice(dest, src []ExponentialHistogram) []ExponentialHistogram {
var newDest []ExponentialHistogram
if cap(dest) < len(src) {
newDest = make([]ExponentialHistogram, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteExponentialHistogram(&dest[i], false)
}
}
for i := range src {
CopyExponentialHistogram(&newDest[i], &src[i])
}
return newDest
}
func CopyExponentialHistogramPtrSlice(dest, src []*ExponentialHistogram) []*ExponentialHistogram {
var newDest []*ExponentialHistogram
if cap(dest) < len(src) {
newDest = make([]*ExponentialHistogram, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewExponentialHistogram()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteExponentialHistogram(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewExponentialHistogram()
}
}
for i := range src {
CopyExponentialHistogram(newDest[i], src[i])
}
return newDest
}
func (orig *ExponentialHistogram) Reset() {
*orig = ExponentialHistogram{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *ExponentialHistogram) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if len(orig.DataPoints) > 0 {
dest.WriteObjectField("dataPoints")
dest.WriteArrayStart()
orig.DataPoints[0].MarshalJSON(dest)
for i := 1; i < len(orig.DataPoints); i++ {
dest.WriteMore()
orig.DataPoints[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if int32(orig.AggregationTemporality) != 0 {
dest.WriteObjectField("aggregationTemporality")
dest.WriteInt32(int32(orig.AggregationTemporality))
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *ExponentialHistogram) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "dataPoints", "data_points":
for iter.ReadArray() {
orig.DataPoints = append(orig.DataPoints, NewExponentialHistogramDataPoint())
orig.DataPoints[len(orig.DataPoints)-1].UnmarshalJSON(iter)
}
case "aggregationTemporality", "aggregation_temporality":
orig.AggregationTemporality = AggregationTemporality(iter.ReadEnumValue(AggregationTemporality_value))
default:
iter.Skip()
}
}
}
func (orig *ExponentialHistogram) SizeProto() int {
var n int
var l int
_ = l
for i := range orig.DataPoints {
l = orig.DataPoints[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
if orig.AggregationTemporality != AggregationTemporality(0) {
n += 1 + proto.Sov(uint64(orig.AggregationTemporality))
}
return n
}
func (orig *ExponentialHistogram) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
for i := len(orig.DataPoints) - 1; i >= 0; i-- {
l = orig.DataPoints[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
}
if orig.AggregationTemporality != AggregationTemporality(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.AggregationTemporality))
pos--
buf[pos] = 0x10
}
return len(buf) - pos
}
func (orig *ExponentialHistogram) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field DataPoints", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.DataPoints = append(orig.DataPoints, NewExponentialHistogramDataPoint())
err = orig.DataPoints[len(orig.DataPoints)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 2:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field AggregationTemporality", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.AggregationTemporality = AggregationTemporality(num)
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestExponentialHistogram() *ExponentialHistogram {
orig := NewExponentialHistogram()
orig.DataPoints = []*ExponentialHistogramDataPoint{{}, GenTestExponentialHistogramDataPoint()}
orig.AggregationTemporality = AggregationTemporality(13)
return orig
}
func GenTestExponentialHistogramPtrSlice() []*ExponentialHistogram {
orig := make([]*ExponentialHistogram, 5)
orig[0] = NewExponentialHistogram()
orig[1] = GenTestExponentialHistogram()
orig[2] = NewExponentialHistogram()
orig[3] = GenTestExponentialHistogram()
orig[4] = NewExponentialHistogram()
return orig
}
func GenTestExponentialHistogramSlice() []ExponentialHistogram {
orig := make([]ExponentialHistogram, 5)
orig[1] = *GenTestExponentialHistogram()
orig[3] = *GenTestExponentialHistogram()
return orig
}
================================================
FILE: pdata/internal/generated_proto_exponentialhistogram_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpmetrics "go.opentelemetry.io/proto/slim/otlp/metrics/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyExponentialHistogram(t *testing.T) {
for name, src := range genTestEncodingValuesExponentialHistogram() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewExponentialHistogram()
CopyExponentialHistogram(dest, src)
assert.Equal(t, src, dest)
CopyExponentialHistogram(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyExponentialHistogramSlice(t *testing.T) {
src := []ExponentialHistogram{}
dest := []ExponentialHistogram{}
// Test CopyTo empty
dest = CopyExponentialHistogramSlice(dest, src)
assert.Equal(t, []ExponentialHistogram{}, dest)
// Test CopyTo larger slice
src = GenTestExponentialHistogramSlice()
dest = CopyExponentialHistogramSlice(dest, src)
assert.Equal(t, GenTestExponentialHistogramSlice(), dest)
// Test CopyTo same size slice
dest = CopyExponentialHistogramSlice(dest, src)
assert.Equal(t, GenTestExponentialHistogramSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyExponentialHistogramSlice(dest, []ExponentialHistogram{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyExponentialHistogramSlice(dest, src)
assert.Equal(t, GenTestExponentialHistogramSlice(), dest)
}
func TestCopyExponentialHistogramPtrSlice(t *testing.T) {
src := []*ExponentialHistogram{}
dest := []*ExponentialHistogram{}
// Test CopyTo empty
dest = CopyExponentialHistogramPtrSlice(dest, src)
assert.Equal(t, []*ExponentialHistogram{}, dest)
// Test CopyTo larger slice
src = GenTestExponentialHistogramPtrSlice()
dest = CopyExponentialHistogramPtrSlice(dest, src)
assert.Equal(t, GenTestExponentialHistogramPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyExponentialHistogramPtrSlice(dest, src)
assert.Equal(t, GenTestExponentialHistogramPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyExponentialHistogramPtrSlice(dest, []*ExponentialHistogram{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyExponentialHistogramPtrSlice(dest, src)
assert.Equal(t, GenTestExponentialHistogramPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONExponentialHistogramUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewExponentialHistogram()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewExponentialHistogram(), dest)
}
func TestMarshalAndUnmarshalJSONExponentialHistogram(t *testing.T) {
for name, src := range genTestEncodingValuesExponentialHistogram() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewExponentialHistogram()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteExponentialHistogram(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoExponentialHistogramFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesExponentialHistogram() {
t.Run(name, func(t *testing.T) {
dest := NewExponentialHistogram()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoExponentialHistogramUnknown(t *testing.T) {
dest := NewExponentialHistogram()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewExponentialHistogram(), dest)
}
func TestMarshalAndUnmarshalProtoExponentialHistogram(t *testing.T) {
for name, src := range genTestEncodingValuesExponentialHistogram() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewExponentialHistogram()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteExponentialHistogram(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufExponentialHistogram(t *testing.T) {
for name, src := range genTestEncodingValuesExponentialHistogram() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpmetrics.ExponentialHistogram{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewExponentialHistogram()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesExponentialHistogram() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"DataPoints/wrong_wire_type": {0xc},
"DataPoints/missing_value": {0xa},
"AggregationTemporality/wrong_wire_type": {0x14},
"AggregationTemporality/missing_value": {0x10},
}
}
func genTestEncodingValuesExponentialHistogram() map[string]*ExponentialHistogram {
return map[string]*ExponentialHistogram{
"empty": NewExponentialHistogram(),
"DataPoints/test": {DataPoints: []*ExponentialHistogramDataPoint{{}, GenTestExponentialHistogramDataPoint()}},
"AggregationTemporality/test": {AggregationTemporality: AggregationTemporality(13)},
}
}
================================================
FILE: pdata/internal/generated_proto_exponentialhistogramdatapoint.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"encoding/binary"
"fmt"
"math"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// ExponentialHistogramDataPoint is a single data point in a timeseries that describes the
// time-varying values of a ExponentialHistogram of double values. A ExponentialHistogram contains
// summary statistics for a population of values, it may optionally contain the
// distribution of those values across a set of buckets.
type ExponentialHistogramDataPoint struct {
Positive ExponentialHistogramDataPointBuckets
Negative ExponentialHistogramDataPointBuckets
Attributes []KeyValue
Exemplars []Exemplar
StartTimeUnixNano uint64
TimeUnixNano uint64
Count uint64
Sum float64
ZeroCount uint64
Min float64
Max float64
ZeroThreshold float64
metadata [1]uint64
Scale int32
Flags uint32
}
var (
protoPoolExponentialHistogramDataPoint = sync.Pool{
New: func() any {
return &ExponentialHistogramDataPoint{}
},
}
)
func NewExponentialHistogramDataPoint() *ExponentialHistogramDataPoint {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &ExponentialHistogramDataPoint{}
}
return protoPoolExponentialHistogramDataPoint.Get().(*ExponentialHistogramDataPoint)
}
func DeleteExponentialHistogramDataPoint(orig *ExponentialHistogramDataPoint, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
for i := range orig.Attributes {
DeleteKeyValue(&orig.Attributes[i], false)
}
DeleteExponentialHistogramDataPointBuckets(&orig.Positive, false)
DeleteExponentialHistogramDataPointBuckets(&orig.Negative, false)
for i := range orig.Exemplars {
DeleteExemplar(&orig.Exemplars[i], false)
}
orig.Reset()
if nullable {
protoPoolExponentialHistogramDataPoint.Put(orig)
}
}
func CopyExponentialHistogramDataPoint(dest, src *ExponentialHistogramDataPoint) *ExponentialHistogramDataPoint {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewExponentialHistogramDataPoint()
}
dest.Attributes = CopyKeyValueSlice(dest.Attributes, src.Attributes)
dest.StartTimeUnixNano = src.StartTimeUnixNano
dest.TimeUnixNano = src.TimeUnixNano
dest.Count = src.Count
if src.HasSum() {
dest.SetSum(src.Sum)
} else {
dest.RemoveSum()
}
dest.Scale = src.Scale
dest.ZeroCount = src.ZeroCount
CopyExponentialHistogramDataPointBuckets(&dest.Positive, &src.Positive)
CopyExponentialHistogramDataPointBuckets(&dest.Negative, &src.Negative)
dest.Flags = src.Flags
dest.Exemplars = CopyExemplarSlice(dest.Exemplars, src.Exemplars)
if src.HasMin() {
dest.SetMin(src.Min)
} else {
dest.RemoveMin()
}
if src.HasMax() {
dest.SetMax(src.Max)
} else {
dest.RemoveMax()
}
dest.ZeroThreshold = src.ZeroThreshold
return dest
}
func CopyExponentialHistogramDataPointSlice(dest, src []ExponentialHistogramDataPoint) []ExponentialHistogramDataPoint {
var newDest []ExponentialHistogramDataPoint
if cap(dest) < len(src) {
newDest = make([]ExponentialHistogramDataPoint, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteExponentialHistogramDataPoint(&dest[i], false)
}
}
for i := range src {
CopyExponentialHistogramDataPoint(&newDest[i], &src[i])
}
return newDest
}
func CopyExponentialHistogramDataPointPtrSlice(dest, src []*ExponentialHistogramDataPoint) []*ExponentialHistogramDataPoint {
var newDest []*ExponentialHistogramDataPoint
if cap(dest) < len(src) {
newDest = make([]*ExponentialHistogramDataPoint, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewExponentialHistogramDataPoint()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteExponentialHistogramDataPoint(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewExponentialHistogramDataPoint()
}
}
for i := range src {
CopyExponentialHistogramDataPoint(newDest[i], src[i])
}
return newDest
}
func (orig *ExponentialHistogramDataPoint) Reset() {
*orig = ExponentialHistogramDataPoint{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *ExponentialHistogramDataPoint) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if len(orig.Attributes) > 0 {
dest.WriteObjectField("attributes")
dest.WriteArrayStart()
orig.Attributes[0].MarshalJSON(dest)
for i := 1; i < len(orig.Attributes); i++ {
dest.WriteMore()
orig.Attributes[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if orig.StartTimeUnixNano != uint64(0) {
dest.WriteObjectField("startTimeUnixNano")
dest.WriteUint64(orig.StartTimeUnixNano)
}
if orig.TimeUnixNano != uint64(0) {
dest.WriteObjectField("timeUnixNano")
dest.WriteUint64(orig.TimeUnixNano)
}
if orig.Count != uint64(0) {
dest.WriteObjectField("count")
dest.WriteUint64(orig.Count)
}
if orig.HasSum() {
dest.WriteObjectField("sum")
dest.WriteFloat64(orig.Sum)
}
if orig.Scale != int32(0) {
dest.WriteObjectField("scale")
dest.WriteInt32(orig.Scale)
}
if orig.ZeroCount != uint64(0) {
dest.WriteObjectField("zeroCount")
dest.WriteUint64(orig.ZeroCount)
}
dest.WriteObjectField("positive")
orig.Positive.MarshalJSON(dest)
dest.WriteObjectField("negative")
orig.Negative.MarshalJSON(dest)
if orig.Flags != uint32(0) {
dest.WriteObjectField("flags")
dest.WriteUint32(orig.Flags)
}
if len(orig.Exemplars) > 0 {
dest.WriteObjectField("exemplars")
dest.WriteArrayStart()
orig.Exemplars[0].MarshalJSON(dest)
for i := 1; i < len(orig.Exemplars); i++ {
dest.WriteMore()
orig.Exemplars[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if orig.HasMin() {
dest.WriteObjectField("min")
dest.WriteFloat64(orig.Min)
}
if orig.HasMax() {
dest.WriteObjectField("max")
dest.WriteFloat64(orig.Max)
}
if orig.ZeroThreshold != float64(0) {
dest.WriteObjectField("zeroThreshold")
dest.WriteFloat64(orig.ZeroThreshold)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *ExponentialHistogramDataPoint) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "attributes":
for iter.ReadArray() {
orig.Attributes = append(orig.Attributes, KeyValue{})
orig.Attributes[len(orig.Attributes)-1].UnmarshalJSON(iter)
}
case "startTimeUnixNano", "start_time_unix_nano":
orig.StartTimeUnixNano = iter.ReadUint64()
case "timeUnixNano", "time_unix_nano":
orig.TimeUnixNano = iter.ReadUint64()
case "count":
orig.Count = iter.ReadUint64()
case "sum":
orig.SetSum(iter.ReadFloat64())
case "scale":
orig.Scale = iter.ReadInt32()
case "zeroCount", "zero_count":
orig.ZeroCount = iter.ReadUint64()
case "positive":
orig.Positive.UnmarshalJSON(iter)
case "negative":
orig.Negative.UnmarshalJSON(iter)
case "flags":
orig.Flags = iter.ReadUint32()
case "exemplars":
for iter.ReadArray() {
orig.Exemplars = append(orig.Exemplars, Exemplar{})
orig.Exemplars[len(orig.Exemplars)-1].UnmarshalJSON(iter)
}
case "min":
orig.SetMin(iter.ReadFloat64())
case "max":
orig.SetMax(iter.ReadFloat64())
case "zeroThreshold", "zero_threshold":
orig.ZeroThreshold = iter.ReadFloat64()
default:
iter.Skip()
}
}
}
func (orig *ExponentialHistogramDataPoint) SizeProto() int {
var n int
var l int
_ = l
for i := range orig.Attributes {
l = orig.Attributes[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
if orig.StartTimeUnixNano != uint64(0) {
n += 9
}
if orig.TimeUnixNano != uint64(0) {
n += 9
}
if orig.Count != uint64(0) {
n += 9
}
if orig.HasSum() {
n += 9
}
if orig.Scale != int32(0) {
n += 1 + proto.Soz(uint64(orig.Scale))
}
if orig.ZeroCount != uint64(0) {
n += 9
}
l = orig.Positive.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
l = orig.Negative.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
if orig.Flags != uint32(0) {
n += 1 + proto.Sov(uint64(orig.Flags))
}
for i := range orig.Exemplars {
l = orig.Exemplars[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
if orig.HasMin() {
n += 9
}
if orig.HasMax() {
n += 9
}
if orig.ZeroThreshold != float64(0) {
n += 9
}
return n
}
func (orig *ExponentialHistogramDataPoint) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
for i := len(orig.Attributes) - 1; i >= 0; i-- {
l = orig.Attributes[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
}
if orig.StartTimeUnixNano != uint64(0) {
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], uint64(orig.StartTimeUnixNano))
pos--
buf[pos] = 0x11
}
if orig.TimeUnixNano != uint64(0) {
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], uint64(orig.TimeUnixNano))
pos--
buf[pos] = 0x19
}
if orig.Count != uint64(0) {
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], uint64(orig.Count))
pos--
buf[pos] = 0x21
}
if orig.HasSum() {
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], math.Float64bits(orig.Sum))
pos--
buf[pos] = 0x29
}
if orig.Scale != int32(0) {
pos = proto.EncodeVarint(buf, pos, uint64((uint32(orig.Scale)<<1)^uint32(orig.Scale>>31)))
pos--
buf[pos] = 0x30
}
if orig.ZeroCount != uint64(0) {
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], uint64(orig.ZeroCount))
pos--
buf[pos] = 0x39
}
l = orig.Positive.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x42
l = orig.Negative.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x4a
if orig.Flags != uint32(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.Flags))
pos--
buf[pos] = 0x50
}
for i := len(orig.Exemplars) - 1; i >= 0; i-- {
l = orig.Exemplars[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x5a
}
if orig.HasMin() {
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], math.Float64bits(orig.Min))
pos--
buf[pos] = 0x61
}
if orig.HasMax() {
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], math.Float64bits(orig.Max))
pos--
buf[pos] = 0x69
}
if orig.ZeroThreshold != float64(0) {
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], math.Float64bits(orig.ZeroThreshold))
pos--
buf[pos] = 0x71
}
return len(buf) - pos
}
func (orig *ExponentialHistogramDataPoint) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Attributes", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Attributes = append(orig.Attributes, KeyValue{})
err = orig.Attributes[len(orig.Attributes)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 2:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field StartTimeUnixNano", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
orig.StartTimeUnixNano = uint64(num)
case 3:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field TimeUnixNano", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
orig.TimeUnixNano = uint64(num)
case 4:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field Count", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
orig.Count = uint64(num)
case 5:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field Sum", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
orig.SetSum(math.Float64frombits(num))
case 6:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field Scale", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.Scale = int32(uint32(num>>1) ^ uint32(int32((num&1)<<31)>>31))
case 7:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field ZeroCount", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
orig.ZeroCount = uint64(num)
case 8:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Positive", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.Positive.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 9:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Negative", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.Negative.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 10:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field Flags", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.Flags = uint32(num)
case 11:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Exemplars", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Exemplars = append(orig.Exemplars, Exemplar{})
err = orig.Exemplars[len(orig.Exemplars)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 12:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field Min", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
orig.SetMin(math.Float64frombits(num))
case 13:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field Max", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
orig.SetMax(math.Float64frombits(num))
case 14:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field ZeroThreshold", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
orig.ZeroThreshold = math.Float64frombits(num)
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
const fieldBlockExponentialHistogramDataPointSum = uint64(0 >> 6)
const fieldBitExponentialHistogramDataPointSum = uint64(1 << 0 & 0x3F)
func (m *ExponentialHistogramDataPoint) SetSum(value float64) {
m.Sum = value
m.metadata[fieldBlockExponentialHistogramDataPointSum] |= fieldBitExponentialHistogramDataPointSum
}
func (m *ExponentialHistogramDataPoint) RemoveSum() {
m.Sum = float64(0)
m.metadata[fieldBlockExponentialHistogramDataPointSum] &^= fieldBitExponentialHistogramDataPointSum
}
func (m *ExponentialHistogramDataPoint) HasSum() bool {
return m.metadata[fieldBlockExponentialHistogramDataPointSum]&fieldBitExponentialHistogramDataPointSum != 0
}
const fieldBlockExponentialHistogramDataPointMin = uint64(1 >> 6)
const fieldBitExponentialHistogramDataPointMin = uint64(1 << 1 & 0x3F)
func (m *ExponentialHistogramDataPoint) SetMin(value float64) {
m.Min = value
m.metadata[fieldBlockExponentialHistogramDataPointMin] |= fieldBitExponentialHistogramDataPointMin
}
func (m *ExponentialHistogramDataPoint) RemoveMin() {
m.Min = float64(0)
m.metadata[fieldBlockExponentialHistogramDataPointMin] &^= fieldBitExponentialHistogramDataPointMin
}
func (m *ExponentialHistogramDataPoint) HasMin() bool {
return m.metadata[fieldBlockExponentialHistogramDataPointMin]&fieldBitExponentialHistogramDataPointMin != 0
}
const fieldBlockExponentialHistogramDataPointMax = uint64(2 >> 6)
const fieldBitExponentialHistogramDataPointMax = uint64(1 << 2 & 0x3F)
func (m *ExponentialHistogramDataPoint) SetMax(value float64) {
m.Max = value
m.metadata[fieldBlockExponentialHistogramDataPointMax] |= fieldBitExponentialHistogramDataPointMax
}
func (m *ExponentialHistogramDataPoint) RemoveMax() {
m.Max = float64(0)
m.metadata[fieldBlockExponentialHistogramDataPointMax] &^= fieldBitExponentialHistogramDataPointMax
}
func (m *ExponentialHistogramDataPoint) HasMax() bool {
return m.metadata[fieldBlockExponentialHistogramDataPointMax]&fieldBitExponentialHistogramDataPointMax != 0
}
func GenTestExponentialHistogramDataPoint() *ExponentialHistogramDataPoint {
orig := NewExponentialHistogramDataPoint()
orig.Attributes = []KeyValue{{}, *GenTestKeyValue()}
orig.StartTimeUnixNano = uint64(13)
orig.TimeUnixNano = uint64(13)
orig.Count = uint64(13)
orig.SetSum(float64(3.1415926))
orig.Scale = int32(13)
orig.ZeroCount = uint64(13)
orig.Positive = *GenTestExponentialHistogramDataPointBuckets()
orig.Negative = *GenTestExponentialHistogramDataPointBuckets()
orig.Flags = uint32(13)
orig.Exemplars = []Exemplar{{}, *GenTestExemplar()}
orig.SetMin(float64(3.1415926))
orig.SetMax(float64(3.1415926))
orig.ZeroThreshold = float64(3.1415926)
return orig
}
func GenTestExponentialHistogramDataPointPtrSlice() []*ExponentialHistogramDataPoint {
orig := make([]*ExponentialHistogramDataPoint, 5)
orig[0] = NewExponentialHistogramDataPoint()
orig[1] = GenTestExponentialHistogramDataPoint()
orig[2] = NewExponentialHistogramDataPoint()
orig[3] = GenTestExponentialHistogramDataPoint()
orig[4] = NewExponentialHistogramDataPoint()
return orig
}
func GenTestExponentialHistogramDataPointSlice() []ExponentialHistogramDataPoint {
orig := make([]ExponentialHistogramDataPoint, 5)
orig[1] = *GenTestExponentialHistogramDataPoint()
orig[3] = *GenTestExponentialHistogramDataPoint()
return orig
}
================================================
FILE: pdata/internal/generated_proto_exponentialhistogramdatapoint_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpmetrics "go.opentelemetry.io/proto/slim/otlp/metrics/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyExponentialHistogramDataPoint(t *testing.T) {
for name, src := range genTestEncodingValuesExponentialHistogramDataPoint() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewExponentialHistogramDataPoint()
CopyExponentialHistogramDataPoint(dest, src)
assert.Equal(t, src, dest)
CopyExponentialHistogramDataPoint(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyExponentialHistogramDataPointSlice(t *testing.T) {
src := []ExponentialHistogramDataPoint{}
dest := []ExponentialHistogramDataPoint{}
// Test CopyTo empty
dest = CopyExponentialHistogramDataPointSlice(dest, src)
assert.Equal(t, []ExponentialHistogramDataPoint{}, dest)
// Test CopyTo larger slice
src = GenTestExponentialHistogramDataPointSlice()
dest = CopyExponentialHistogramDataPointSlice(dest, src)
assert.Equal(t, GenTestExponentialHistogramDataPointSlice(), dest)
// Test CopyTo same size slice
dest = CopyExponentialHistogramDataPointSlice(dest, src)
assert.Equal(t, GenTestExponentialHistogramDataPointSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyExponentialHistogramDataPointSlice(dest, []ExponentialHistogramDataPoint{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyExponentialHistogramDataPointSlice(dest, src)
assert.Equal(t, GenTestExponentialHistogramDataPointSlice(), dest)
}
func TestCopyExponentialHistogramDataPointPtrSlice(t *testing.T) {
src := []*ExponentialHistogramDataPoint{}
dest := []*ExponentialHistogramDataPoint{}
// Test CopyTo empty
dest = CopyExponentialHistogramDataPointPtrSlice(dest, src)
assert.Equal(t, []*ExponentialHistogramDataPoint{}, dest)
// Test CopyTo larger slice
src = GenTestExponentialHistogramDataPointPtrSlice()
dest = CopyExponentialHistogramDataPointPtrSlice(dest, src)
assert.Equal(t, GenTestExponentialHistogramDataPointPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyExponentialHistogramDataPointPtrSlice(dest, src)
assert.Equal(t, GenTestExponentialHistogramDataPointPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyExponentialHistogramDataPointPtrSlice(dest, []*ExponentialHistogramDataPoint{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyExponentialHistogramDataPointPtrSlice(dest, src)
assert.Equal(t, GenTestExponentialHistogramDataPointPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONExponentialHistogramDataPointUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewExponentialHistogramDataPoint()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewExponentialHistogramDataPoint(), dest)
}
func TestMarshalAndUnmarshalJSONExponentialHistogramDataPoint(t *testing.T) {
for name, src := range genTestEncodingValuesExponentialHistogramDataPoint() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewExponentialHistogramDataPoint()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteExponentialHistogramDataPoint(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoExponentialHistogramDataPointFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesExponentialHistogramDataPoint() {
t.Run(name, func(t *testing.T) {
dest := NewExponentialHistogramDataPoint()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoExponentialHistogramDataPointUnknown(t *testing.T) {
dest := NewExponentialHistogramDataPoint()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewExponentialHistogramDataPoint(), dest)
}
func TestMarshalAndUnmarshalProtoExponentialHistogramDataPoint(t *testing.T) {
for name, src := range genTestEncodingValuesExponentialHistogramDataPoint() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewExponentialHistogramDataPoint()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteExponentialHistogramDataPoint(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufExponentialHistogramDataPoint(t *testing.T) {
for name, src := range genTestEncodingValuesExponentialHistogramDataPoint() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpmetrics.ExponentialHistogramDataPoint{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewExponentialHistogramDataPoint()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesExponentialHistogramDataPoint() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"Attributes/wrong_wire_type": {0xc},
"Attributes/missing_value": {0xa},
"StartTimeUnixNano/wrong_wire_type": {0x14},
"StartTimeUnixNano/missing_value": {0x11},
"TimeUnixNano/wrong_wire_type": {0x1c},
"TimeUnixNano/missing_value": {0x19},
"Count/wrong_wire_type": {0x24},
"Count/missing_value": {0x21},
"Sum/wrong_wire_type": {0x2c},
"Sum/missing_value": {0x29},
"Scale/wrong_wire_type": {0x34},
"Scale/missing_value": {0x30},
"ZeroCount/wrong_wire_type": {0x3c},
"ZeroCount/missing_value": {0x39},
"Positive/wrong_wire_type": {0x44},
"Positive/missing_value": {0x42},
"Negative/wrong_wire_type": {0x4c},
"Negative/missing_value": {0x4a},
"Flags/wrong_wire_type": {0x54},
"Flags/missing_value": {0x50},
"Exemplars/wrong_wire_type": {0x5c},
"Exemplars/missing_value": {0x5a},
"Min/wrong_wire_type": {0x64},
"Min/missing_value": {0x61},
"Max/wrong_wire_type": {0x6c},
"Max/missing_value": {0x69},
"ZeroThreshold/wrong_wire_type": {0x74},
"ZeroThreshold/missing_value": {0x71},
}
}
func genTestEncodingValuesExponentialHistogramDataPoint() map[string]*ExponentialHistogramDataPoint {
return map[string]*ExponentialHistogramDataPoint{
"empty": NewExponentialHistogramDataPoint(),
"Attributes/test": {Attributes: []KeyValue{{}, *GenTestKeyValue()}},
"StartTimeUnixNano/test": {StartTimeUnixNano: uint64(13)},
"TimeUnixNano/test": {TimeUnixNano: uint64(13)},
"Count/test": {Count: uint64(13)},
"Sum/test": func() *ExponentialHistogramDataPoint {
ms := NewExponentialHistogramDataPoint()
ms.SetSum(float64(3.1415926))
return ms
}(),
"Scale/test": {Scale: int32(13)},
"ZeroCount/test": {ZeroCount: uint64(13)},
"Positive/test": {Positive: *GenTestExponentialHistogramDataPointBuckets()},
"Negative/test": {Negative: *GenTestExponentialHistogramDataPointBuckets()},
"Flags/test": {Flags: uint32(13)},
"Exemplars/test": {Exemplars: []Exemplar{{}, *GenTestExemplar()}},
"Min/test": func() *ExponentialHistogramDataPoint {
ms := NewExponentialHistogramDataPoint()
ms.SetMin(float64(3.1415926))
return ms
}(),
"Max/test": func() *ExponentialHistogramDataPoint {
ms := NewExponentialHistogramDataPoint()
ms.SetMax(float64(3.1415926))
return ms
}(),
"ZeroThreshold/test": {ZeroThreshold: float64(3.1415926)},
}
}
================================================
FILE: pdata/internal/generated_proto_exponentialhistogramdatapointbuckets.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// ExponentialHistogramDataPointBuckets are a set of bucket counts, encoded in a contiguous array of counts.
type ExponentialHistogramDataPointBuckets struct {
BucketCounts []uint64
Offset int32
}
var (
protoPoolExponentialHistogramDataPointBuckets = sync.Pool{
New: func() any {
return &ExponentialHistogramDataPointBuckets{}
},
}
)
func NewExponentialHistogramDataPointBuckets() *ExponentialHistogramDataPointBuckets {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &ExponentialHistogramDataPointBuckets{}
}
return protoPoolExponentialHistogramDataPointBuckets.Get().(*ExponentialHistogramDataPointBuckets)
}
func DeleteExponentialHistogramDataPointBuckets(orig *ExponentialHistogramDataPointBuckets, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
orig.Reset()
if nullable {
protoPoolExponentialHistogramDataPointBuckets.Put(orig)
}
}
func CopyExponentialHistogramDataPointBuckets(dest, src *ExponentialHistogramDataPointBuckets) *ExponentialHistogramDataPointBuckets {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewExponentialHistogramDataPointBuckets()
}
dest.Offset = src.Offset
dest.BucketCounts = append(dest.BucketCounts[:0], src.BucketCounts...)
return dest
}
func CopyExponentialHistogramDataPointBucketsSlice(dest, src []ExponentialHistogramDataPointBuckets) []ExponentialHistogramDataPointBuckets {
var newDest []ExponentialHistogramDataPointBuckets
if cap(dest) < len(src) {
newDest = make([]ExponentialHistogramDataPointBuckets, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteExponentialHistogramDataPointBuckets(&dest[i], false)
}
}
for i := range src {
CopyExponentialHistogramDataPointBuckets(&newDest[i], &src[i])
}
return newDest
}
func CopyExponentialHistogramDataPointBucketsPtrSlice(dest, src []*ExponentialHistogramDataPointBuckets) []*ExponentialHistogramDataPointBuckets {
var newDest []*ExponentialHistogramDataPointBuckets
if cap(dest) < len(src) {
newDest = make([]*ExponentialHistogramDataPointBuckets, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewExponentialHistogramDataPointBuckets()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteExponentialHistogramDataPointBuckets(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewExponentialHistogramDataPointBuckets()
}
}
for i := range src {
CopyExponentialHistogramDataPointBuckets(newDest[i], src[i])
}
return newDest
}
func (orig *ExponentialHistogramDataPointBuckets) Reset() {
*orig = ExponentialHistogramDataPointBuckets{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *ExponentialHistogramDataPointBuckets) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if orig.Offset != int32(0) {
dest.WriteObjectField("offset")
dest.WriteInt32(orig.Offset)
}
if len(orig.BucketCounts) > 0 {
dest.WriteObjectField("bucketCounts")
dest.WriteArrayStart()
dest.WriteUint64(orig.BucketCounts[0])
for i := 1; i < len(orig.BucketCounts); i++ {
dest.WriteMore()
dest.WriteUint64(orig.BucketCounts[i])
}
dest.WriteArrayEnd()
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *ExponentialHistogramDataPointBuckets) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "offset":
orig.Offset = iter.ReadInt32()
case "bucketCounts", "bucket_counts":
for iter.ReadArray() {
orig.BucketCounts = append(orig.BucketCounts, iter.ReadUint64())
}
default:
iter.Skip()
}
}
}
func (orig *ExponentialHistogramDataPointBuckets) SizeProto() int {
var n int
var l int
_ = l
if orig.Offset != int32(0) {
n += 1 + proto.Soz(uint64(orig.Offset))
}
if len(orig.BucketCounts) > 0 {
l = 0
for _, e := range orig.BucketCounts {
l += proto.Sov(uint64(e))
}
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *ExponentialHistogramDataPointBuckets) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
if orig.Offset != int32(0) {
pos = proto.EncodeVarint(buf, pos, uint64((uint32(orig.Offset)<<1)^uint32(orig.Offset>>31)))
pos--
buf[pos] = 0x8
}
l = len(orig.BucketCounts)
if l > 0 {
endPos := pos
for i := l - 1; i >= 0; i-- {
pos = proto.EncodeVarint(buf, pos, uint64(orig.BucketCounts[i]))
}
pos = proto.EncodeVarint(buf, pos, uint64(endPos-pos))
pos--
buf[pos] = 0x12
}
return len(buf) - pos
}
func (orig *ExponentialHistogramDataPointBuckets) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field Offset", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.Offset = int32(uint32(num>>1) ^ uint32(int32((num&1)<<31)>>31))
case 2:
switch wireType {
case proto.WireTypeLen:
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
var num uint64
for startPos < pos {
num, startPos, err = proto.ConsumeVarint(buf[:pos], startPos)
if err != nil {
return err
}
orig.BucketCounts = append(orig.BucketCounts, uint64(num))
}
if startPos != pos {
return fmt.Errorf("proto: invalid field len = %d for field BucketCounts", pos-startPos)
}
case proto.WireTypeVarint:
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.BucketCounts = append(orig.BucketCounts, uint64(num))
default:
return fmt.Errorf("proto: wrong wireType = %d for field BucketCounts", wireType)
}
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestExponentialHistogramDataPointBuckets() *ExponentialHistogramDataPointBuckets {
orig := NewExponentialHistogramDataPointBuckets()
orig.Offset = int32(13)
orig.BucketCounts = []uint64{uint64(0), uint64(13)}
return orig
}
func GenTestExponentialHistogramDataPointBucketsPtrSlice() []*ExponentialHistogramDataPointBuckets {
orig := make([]*ExponentialHistogramDataPointBuckets, 5)
orig[0] = NewExponentialHistogramDataPointBuckets()
orig[1] = GenTestExponentialHistogramDataPointBuckets()
orig[2] = NewExponentialHistogramDataPointBuckets()
orig[3] = GenTestExponentialHistogramDataPointBuckets()
orig[4] = NewExponentialHistogramDataPointBuckets()
return orig
}
func GenTestExponentialHistogramDataPointBucketsSlice() []ExponentialHistogramDataPointBuckets {
orig := make([]ExponentialHistogramDataPointBuckets, 5)
orig[1] = *GenTestExponentialHistogramDataPointBuckets()
orig[3] = *GenTestExponentialHistogramDataPointBuckets()
return orig
}
================================================
FILE: pdata/internal/generated_proto_exponentialhistogramdatapointbuckets_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpmetrics "go.opentelemetry.io/proto/slim/otlp/metrics/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyExponentialHistogramDataPointBuckets(t *testing.T) {
for name, src := range genTestEncodingValuesExponentialHistogramDataPointBuckets() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewExponentialHistogramDataPointBuckets()
CopyExponentialHistogramDataPointBuckets(dest, src)
assert.Equal(t, src, dest)
CopyExponentialHistogramDataPointBuckets(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyExponentialHistogramDataPointBucketsSlice(t *testing.T) {
src := []ExponentialHistogramDataPointBuckets{}
dest := []ExponentialHistogramDataPointBuckets{}
// Test CopyTo empty
dest = CopyExponentialHistogramDataPointBucketsSlice(dest, src)
assert.Equal(t, []ExponentialHistogramDataPointBuckets{}, dest)
// Test CopyTo larger slice
src = GenTestExponentialHistogramDataPointBucketsSlice()
dest = CopyExponentialHistogramDataPointBucketsSlice(dest, src)
assert.Equal(t, GenTestExponentialHistogramDataPointBucketsSlice(), dest)
// Test CopyTo same size slice
dest = CopyExponentialHistogramDataPointBucketsSlice(dest, src)
assert.Equal(t, GenTestExponentialHistogramDataPointBucketsSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyExponentialHistogramDataPointBucketsSlice(dest, []ExponentialHistogramDataPointBuckets{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyExponentialHistogramDataPointBucketsSlice(dest, src)
assert.Equal(t, GenTestExponentialHistogramDataPointBucketsSlice(), dest)
}
func TestCopyExponentialHistogramDataPointBucketsPtrSlice(t *testing.T) {
src := []*ExponentialHistogramDataPointBuckets{}
dest := []*ExponentialHistogramDataPointBuckets{}
// Test CopyTo empty
dest = CopyExponentialHistogramDataPointBucketsPtrSlice(dest, src)
assert.Equal(t, []*ExponentialHistogramDataPointBuckets{}, dest)
// Test CopyTo larger slice
src = GenTestExponentialHistogramDataPointBucketsPtrSlice()
dest = CopyExponentialHistogramDataPointBucketsPtrSlice(dest, src)
assert.Equal(t, GenTestExponentialHistogramDataPointBucketsPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyExponentialHistogramDataPointBucketsPtrSlice(dest, src)
assert.Equal(t, GenTestExponentialHistogramDataPointBucketsPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyExponentialHistogramDataPointBucketsPtrSlice(dest, []*ExponentialHistogramDataPointBuckets{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyExponentialHistogramDataPointBucketsPtrSlice(dest, src)
assert.Equal(t, GenTestExponentialHistogramDataPointBucketsPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONExponentialHistogramDataPointBucketsUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewExponentialHistogramDataPointBuckets()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewExponentialHistogramDataPointBuckets(), dest)
}
func TestMarshalAndUnmarshalJSONExponentialHistogramDataPointBuckets(t *testing.T) {
for name, src := range genTestEncodingValuesExponentialHistogramDataPointBuckets() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewExponentialHistogramDataPointBuckets()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteExponentialHistogramDataPointBuckets(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoExponentialHistogramDataPointBucketsFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesExponentialHistogramDataPointBuckets() {
t.Run(name, func(t *testing.T) {
dest := NewExponentialHistogramDataPointBuckets()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoExponentialHistogramDataPointBucketsUnknown(t *testing.T) {
dest := NewExponentialHistogramDataPointBuckets()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewExponentialHistogramDataPointBuckets(), dest)
}
func TestMarshalAndUnmarshalProtoExponentialHistogramDataPointBuckets(t *testing.T) {
for name, src := range genTestEncodingValuesExponentialHistogramDataPointBuckets() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewExponentialHistogramDataPointBuckets()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteExponentialHistogramDataPointBuckets(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufExponentialHistogramDataPointBuckets(t *testing.T) {
for name, src := range genTestEncodingValuesExponentialHistogramDataPointBuckets() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpmetrics.ExponentialHistogramDataPoint_Buckets{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewExponentialHistogramDataPointBuckets()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesExponentialHistogramDataPointBuckets() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"Offset/wrong_wire_type": {0xc},
"Offset/missing_value": {0x8},
"BucketCounts/wrong_wire_type": {0x14},
"BucketCounts/missing_value": {0x12},
}
}
func genTestEncodingValuesExponentialHistogramDataPointBuckets() map[string]*ExponentialHistogramDataPointBuckets {
return map[string]*ExponentialHistogramDataPointBuckets{
"empty": NewExponentialHistogramDataPointBuckets(),
"Offset/test": {Offset: int32(13)},
"BucketCounts/test": {BucketCounts: []uint64{uint64(0), uint64(13)}},
}
}
================================================
FILE: pdata/internal/generated_proto_exportlogspartialsuccess.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// ExportPartialSuccess represents the details of a partially successful export request.
type ExportLogsPartialSuccess struct {
ErrorMessage string
RejectedLogRecords int64
}
var (
protoPoolExportLogsPartialSuccess = sync.Pool{
New: func() any {
return &ExportLogsPartialSuccess{}
},
}
)
func NewExportLogsPartialSuccess() *ExportLogsPartialSuccess {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &ExportLogsPartialSuccess{}
}
return protoPoolExportLogsPartialSuccess.Get().(*ExportLogsPartialSuccess)
}
func DeleteExportLogsPartialSuccess(orig *ExportLogsPartialSuccess, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
orig.Reset()
if nullable {
protoPoolExportLogsPartialSuccess.Put(orig)
}
}
func CopyExportLogsPartialSuccess(dest, src *ExportLogsPartialSuccess) *ExportLogsPartialSuccess {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewExportLogsPartialSuccess()
}
dest.RejectedLogRecords = src.RejectedLogRecords
dest.ErrorMessage = src.ErrorMessage
return dest
}
func CopyExportLogsPartialSuccessSlice(dest, src []ExportLogsPartialSuccess) []ExportLogsPartialSuccess {
var newDest []ExportLogsPartialSuccess
if cap(dest) < len(src) {
newDest = make([]ExportLogsPartialSuccess, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteExportLogsPartialSuccess(&dest[i], false)
}
}
for i := range src {
CopyExportLogsPartialSuccess(&newDest[i], &src[i])
}
return newDest
}
func CopyExportLogsPartialSuccessPtrSlice(dest, src []*ExportLogsPartialSuccess) []*ExportLogsPartialSuccess {
var newDest []*ExportLogsPartialSuccess
if cap(dest) < len(src) {
newDest = make([]*ExportLogsPartialSuccess, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewExportLogsPartialSuccess()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteExportLogsPartialSuccess(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewExportLogsPartialSuccess()
}
}
for i := range src {
CopyExportLogsPartialSuccess(newDest[i], src[i])
}
return newDest
}
func (orig *ExportLogsPartialSuccess) Reset() {
*orig = ExportLogsPartialSuccess{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *ExportLogsPartialSuccess) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if orig.RejectedLogRecords != int64(0) {
dest.WriteObjectField("rejectedLogRecords")
dest.WriteInt64(orig.RejectedLogRecords)
}
if orig.ErrorMessage != "" {
dest.WriteObjectField("errorMessage")
dest.WriteString(orig.ErrorMessage)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *ExportLogsPartialSuccess) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "rejectedLogRecords", "rejected_log_records":
orig.RejectedLogRecords = iter.ReadInt64()
case "errorMessage", "error_message":
orig.ErrorMessage = iter.ReadString()
default:
iter.Skip()
}
}
}
func (orig *ExportLogsPartialSuccess) SizeProto() int {
var n int
var l int
_ = l
if orig.RejectedLogRecords != int64(0) {
n += 1 + proto.Sov(uint64(orig.RejectedLogRecords))
}
l = len(orig.ErrorMessage)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *ExportLogsPartialSuccess) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
if orig.RejectedLogRecords != int64(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.RejectedLogRecords))
pos--
buf[pos] = 0x8
}
l = len(orig.ErrorMessage)
if l > 0 {
pos -= l
copy(buf[pos:], orig.ErrorMessage)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
}
return len(buf) - pos
}
func (orig *ExportLogsPartialSuccess) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field RejectedLogRecords", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.RejectedLogRecords = int64(num)
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field ErrorMessage", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.ErrorMessage = string(buf[startPos:pos])
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestExportLogsPartialSuccess() *ExportLogsPartialSuccess {
orig := NewExportLogsPartialSuccess()
orig.RejectedLogRecords = int64(13)
orig.ErrorMessage = "test_errormessage"
return orig
}
func GenTestExportLogsPartialSuccessPtrSlice() []*ExportLogsPartialSuccess {
orig := make([]*ExportLogsPartialSuccess, 5)
orig[0] = NewExportLogsPartialSuccess()
orig[1] = GenTestExportLogsPartialSuccess()
orig[2] = NewExportLogsPartialSuccess()
orig[3] = GenTestExportLogsPartialSuccess()
orig[4] = NewExportLogsPartialSuccess()
return orig
}
func GenTestExportLogsPartialSuccessSlice() []ExportLogsPartialSuccess {
orig := make([]ExportLogsPartialSuccess, 5)
orig[1] = *GenTestExportLogsPartialSuccess()
orig[3] = *GenTestExportLogsPartialSuccess()
return orig
}
================================================
FILE: pdata/internal/generated_proto_exportlogspartialsuccess_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpcollectorlogs "go.opentelemetry.io/proto/slim/otlp/collector/logs/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyExportLogsPartialSuccess(t *testing.T) {
for name, src := range genTestEncodingValuesExportLogsPartialSuccess() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewExportLogsPartialSuccess()
CopyExportLogsPartialSuccess(dest, src)
assert.Equal(t, src, dest)
CopyExportLogsPartialSuccess(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyExportLogsPartialSuccessSlice(t *testing.T) {
src := []ExportLogsPartialSuccess{}
dest := []ExportLogsPartialSuccess{}
// Test CopyTo empty
dest = CopyExportLogsPartialSuccessSlice(dest, src)
assert.Equal(t, []ExportLogsPartialSuccess{}, dest)
// Test CopyTo larger slice
src = GenTestExportLogsPartialSuccessSlice()
dest = CopyExportLogsPartialSuccessSlice(dest, src)
assert.Equal(t, GenTestExportLogsPartialSuccessSlice(), dest)
// Test CopyTo same size slice
dest = CopyExportLogsPartialSuccessSlice(dest, src)
assert.Equal(t, GenTestExportLogsPartialSuccessSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyExportLogsPartialSuccessSlice(dest, []ExportLogsPartialSuccess{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyExportLogsPartialSuccessSlice(dest, src)
assert.Equal(t, GenTestExportLogsPartialSuccessSlice(), dest)
}
func TestCopyExportLogsPartialSuccessPtrSlice(t *testing.T) {
src := []*ExportLogsPartialSuccess{}
dest := []*ExportLogsPartialSuccess{}
// Test CopyTo empty
dest = CopyExportLogsPartialSuccessPtrSlice(dest, src)
assert.Equal(t, []*ExportLogsPartialSuccess{}, dest)
// Test CopyTo larger slice
src = GenTestExportLogsPartialSuccessPtrSlice()
dest = CopyExportLogsPartialSuccessPtrSlice(dest, src)
assert.Equal(t, GenTestExportLogsPartialSuccessPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyExportLogsPartialSuccessPtrSlice(dest, src)
assert.Equal(t, GenTestExportLogsPartialSuccessPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyExportLogsPartialSuccessPtrSlice(dest, []*ExportLogsPartialSuccess{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyExportLogsPartialSuccessPtrSlice(dest, src)
assert.Equal(t, GenTestExportLogsPartialSuccessPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONExportLogsPartialSuccessUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewExportLogsPartialSuccess()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewExportLogsPartialSuccess(), dest)
}
func TestMarshalAndUnmarshalJSONExportLogsPartialSuccess(t *testing.T) {
for name, src := range genTestEncodingValuesExportLogsPartialSuccess() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewExportLogsPartialSuccess()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteExportLogsPartialSuccess(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoExportLogsPartialSuccessFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesExportLogsPartialSuccess() {
t.Run(name, func(t *testing.T) {
dest := NewExportLogsPartialSuccess()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoExportLogsPartialSuccessUnknown(t *testing.T) {
dest := NewExportLogsPartialSuccess()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewExportLogsPartialSuccess(), dest)
}
func TestMarshalAndUnmarshalProtoExportLogsPartialSuccess(t *testing.T) {
for name, src := range genTestEncodingValuesExportLogsPartialSuccess() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewExportLogsPartialSuccess()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteExportLogsPartialSuccess(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufExportLogsPartialSuccess(t *testing.T) {
for name, src := range genTestEncodingValuesExportLogsPartialSuccess() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpcollectorlogs.ExportLogsPartialSuccess{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewExportLogsPartialSuccess()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesExportLogsPartialSuccess() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"RejectedLogRecords/wrong_wire_type": {0xc},
"RejectedLogRecords/missing_value": {0x8},
"ErrorMessage/wrong_wire_type": {0x14},
"ErrorMessage/missing_value": {0x12},
}
}
func genTestEncodingValuesExportLogsPartialSuccess() map[string]*ExportLogsPartialSuccess {
return map[string]*ExportLogsPartialSuccess{
"empty": NewExportLogsPartialSuccess(),
"RejectedLogRecords/test": {RejectedLogRecords: int64(13)},
"ErrorMessage/test": {ErrorMessage: "test_errormessage"},
}
}
================================================
FILE: pdata/internal/generated_proto_exportlogsservicerequest.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// Logs is the top-level struct that is propagated through the logs pipeline.
// Use NewLogs to create new instance, zero-initialized instance is not valid for use.
type ExportLogsServiceRequest struct {
ResourceLogs []*ResourceLogs
}
var (
protoPoolExportLogsServiceRequest = sync.Pool{
New: func() any {
return &ExportLogsServiceRequest{}
},
}
)
func NewExportLogsServiceRequest() *ExportLogsServiceRequest {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &ExportLogsServiceRequest{}
}
return protoPoolExportLogsServiceRequest.Get().(*ExportLogsServiceRequest)
}
func DeleteExportLogsServiceRequest(orig *ExportLogsServiceRequest, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
for i := range orig.ResourceLogs {
DeleteResourceLogs(orig.ResourceLogs[i], true)
}
orig.Reset()
if nullable {
protoPoolExportLogsServiceRequest.Put(orig)
}
}
func CopyExportLogsServiceRequest(dest, src *ExportLogsServiceRequest) *ExportLogsServiceRequest {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewExportLogsServiceRequest()
}
dest.ResourceLogs = CopyResourceLogsPtrSlice(dest.ResourceLogs, src.ResourceLogs)
return dest
}
func CopyExportLogsServiceRequestSlice(dest, src []ExportLogsServiceRequest) []ExportLogsServiceRequest {
var newDest []ExportLogsServiceRequest
if cap(dest) < len(src) {
newDest = make([]ExportLogsServiceRequest, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteExportLogsServiceRequest(&dest[i], false)
}
}
for i := range src {
CopyExportLogsServiceRequest(&newDest[i], &src[i])
}
return newDest
}
func CopyExportLogsServiceRequestPtrSlice(dest, src []*ExportLogsServiceRequest) []*ExportLogsServiceRequest {
var newDest []*ExportLogsServiceRequest
if cap(dest) < len(src) {
newDest = make([]*ExportLogsServiceRequest, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewExportLogsServiceRequest()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteExportLogsServiceRequest(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewExportLogsServiceRequest()
}
}
for i := range src {
CopyExportLogsServiceRequest(newDest[i], src[i])
}
return newDest
}
func (orig *ExportLogsServiceRequest) Reset() {
*orig = ExportLogsServiceRequest{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *ExportLogsServiceRequest) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if len(orig.ResourceLogs) > 0 {
dest.WriteObjectField("resourceLogs")
dest.WriteArrayStart()
orig.ResourceLogs[0].MarshalJSON(dest)
for i := 1; i < len(orig.ResourceLogs); i++ {
dest.WriteMore()
orig.ResourceLogs[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *ExportLogsServiceRequest) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "resourceLogs", "resource_logs":
for iter.ReadArray() {
orig.ResourceLogs = append(orig.ResourceLogs, NewResourceLogs())
orig.ResourceLogs[len(orig.ResourceLogs)-1].UnmarshalJSON(iter)
}
default:
iter.Skip()
}
}
}
func (orig *ExportLogsServiceRequest) SizeProto() int {
var n int
var l int
_ = l
for i := range orig.ResourceLogs {
l = orig.ResourceLogs[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *ExportLogsServiceRequest) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
for i := len(orig.ResourceLogs) - 1; i >= 0; i-- {
l = orig.ResourceLogs[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
}
return len(buf) - pos
}
func (orig *ExportLogsServiceRequest) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field ResourceLogs", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.ResourceLogs = append(orig.ResourceLogs, NewResourceLogs())
err = orig.ResourceLogs[len(orig.ResourceLogs)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestExportLogsServiceRequest() *ExportLogsServiceRequest {
orig := NewExportLogsServiceRequest()
orig.ResourceLogs = []*ResourceLogs{{}, GenTestResourceLogs()}
return orig
}
func GenTestExportLogsServiceRequestPtrSlice() []*ExportLogsServiceRequest {
orig := make([]*ExportLogsServiceRequest, 5)
orig[0] = NewExportLogsServiceRequest()
orig[1] = GenTestExportLogsServiceRequest()
orig[2] = NewExportLogsServiceRequest()
orig[3] = GenTestExportLogsServiceRequest()
orig[4] = NewExportLogsServiceRequest()
return orig
}
func GenTestExportLogsServiceRequestSlice() []ExportLogsServiceRequest {
orig := make([]ExportLogsServiceRequest, 5)
orig[1] = *GenTestExportLogsServiceRequest()
orig[3] = *GenTestExportLogsServiceRequest()
return orig
}
================================================
FILE: pdata/internal/generated_proto_exportlogsservicerequest_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpcollectorlogs "go.opentelemetry.io/proto/slim/otlp/collector/logs/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyExportLogsServiceRequest(t *testing.T) {
for name, src := range genTestEncodingValuesExportLogsServiceRequest() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewExportLogsServiceRequest()
CopyExportLogsServiceRequest(dest, src)
assert.Equal(t, src, dest)
CopyExportLogsServiceRequest(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyExportLogsServiceRequestSlice(t *testing.T) {
src := []ExportLogsServiceRequest{}
dest := []ExportLogsServiceRequest{}
// Test CopyTo empty
dest = CopyExportLogsServiceRequestSlice(dest, src)
assert.Equal(t, []ExportLogsServiceRequest{}, dest)
// Test CopyTo larger slice
src = GenTestExportLogsServiceRequestSlice()
dest = CopyExportLogsServiceRequestSlice(dest, src)
assert.Equal(t, GenTestExportLogsServiceRequestSlice(), dest)
// Test CopyTo same size slice
dest = CopyExportLogsServiceRequestSlice(dest, src)
assert.Equal(t, GenTestExportLogsServiceRequestSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyExportLogsServiceRequestSlice(dest, []ExportLogsServiceRequest{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyExportLogsServiceRequestSlice(dest, src)
assert.Equal(t, GenTestExportLogsServiceRequestSlice(), dest)
}
func TestCopyExportLogsServiceRequestPtrSlice(t *testing.T) {
src := []*ExportLogsServiceRequest{}
dest := []*ExportLogsServiceRequest{}
// Test CopyTo empty
dest = CopyExportLogsServiceRequestPtrSlice(dest, src)
assert.Equal(t, []*ExportLogsServiceRequest{}, dest)
// Test CopyTo larger slice
src = GenTestExportLogsServiceRequestPtrSlice()
dest = CopyExportLogsServiceRequestPtrSlice(dest, src)
assert.Equal(t, GenTestExportLogsServiceRequestPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyExportLogsServiceRequestPtrSlice(dest, src)
assert.Equal(t, GenTestExportLogsServiceRequestPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyExportLogsServiceRequestPtrSlice(dest, []*ExportLogsServiceRequest{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyExportLogsServiceRequestPtrSlice(dest, src)
assert.Equal(t, GenTestExportLogsServiceRequestPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONExportLogsServiceRequestUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewExportLogsServiceRequest()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewExportLogsServiceRequest(), dest)
}
func TestMarshalAndUnmarshalJSONExportLogsServiceRequest(t *testing.T) {
for name, src := range genTestEncodingValuesExportLogsServiceRequest() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewExportLogsServiceRequest()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteExportLogsServiceRequest(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoExportLogsServiceRequestFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesExportLogsServiceRequest() {
t.Run(name, func(t *testing.T) {
dest := NewExportLogsServiceRequest()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoExportLogsServiceRequestUnknown(t *testing.T) {
dest := NewExportLogsServiceRequest()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewExportLogsServiceRequest(), dest)
}
func TestMarshalAndUnmarshalProtoExportLogsServiceRequest(t *testing.T) {
for name, src := range genTestEncodingValuesExportLogsServiceRequest() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewExportLogsServiceRequest()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteExportLogsServiceRequest(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufExportLogsServiceRequest(t *testing.T) {
for name, src := range genTestEncodingValuesExportLogsServiceRequest() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpcollectorlogs.ExportLogsServiceRequest{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewExportLogsServiceRequest()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesExportLogsServiceRequest() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"ResourceLogs/wrong_wire_type": {0xc},
"ResourceLogs/missing_value": {0xa},
}
}
func genTestEncodingValuesExportLogsServiceRequest() map[string]*ExportLogsServiceRequest {
return map[string]*ExportLogsServiceRequest{
"empty": NewExportLogsServiceRequest(),
"ResourceLogs/test": {ResourceLogs: []*ResourceLogs{{}, GenTestResourceLogs()}},
}
}
================================================
FILE: pdata/internal/generated_proto_exportlogsserviceresponse.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// ExportResponse represents the response for gRPC/HTTP client/server.
type ExportLogsServiceResponse struct {
PartialSuccess ExportLogsPartialSuccess
}
var (
protoPoolExportLogsServiceResponse = sync.Pool{
New: func() any {
return &ExportLogsServiceResponse{}
},
}
)
func NewExportLogsServiceResponse() *ExportLogsServiceResponse {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &ExportLogsServiceResponse{}
}
return protoPoolExportLogsServiceResponse.Get().(*ExportLogsServiceResponse)
}
func DeleteExportLogsServiceResponse(orig *ExportLogsServiceResponse, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
DeleteExportLogsPartialSuccess(&orig.PartialSuccess, false)
orig.Reset()
if nullable {
protoPoolExportLogsServiceResponse.Put(orig)
}
}
func CopyExportLogsServiceResponse(dest, src *ExportLogsServiceResponse) *ExportLogsServiceResponse {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewExportLogsServiceResponse()
}
CopyExportLogsPartialSuccess(&dest.PartialSuccess, &src.PartialSuccess)
return dest
}
func CopyExportLogsServiceResponseSlice(dest, src []ExportLogsServiceResponse) []ExportLogsServiceResponse {
var newDest []ExportLogsServiceResponse
if cap(dest) < len(src) {
newDest = make([]ExportLogsServiceResponse, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteExportLogsServiceResponse(&dest[i], false)
}
}
for i := range src {
CopyExportLogsServiceResponse(&newDest[i], &src[i])
}
return newDest
}
func CopyExportLogsServiceResponsePtrSlice(dest, src []*ExportLogsServiceResponse) []*ExportLogsServiceResponse {
var newDest []*ExportLogsServiceResponse
if cap(dest) < len(src) {
newDest = make([]*ExportLogsServiceResponse, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewExportLogsServiceResponse()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteExportLogsServiceResponse(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewExportLogsServiceResponse()
}
}
for i := range src {
CopyExportLogsServiceResponse(newDest[i], src[i])
}
return newDest
}
func (orig *ExportLogsServiceResponse) Reset() {
*orig = ExportLogsServiceResponse{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *ExportLogsServiceResponse) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
dest.WriteObjectField("partialSuccess")
orig.PartialSuccess.MarshalJSON(dest)
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *ExportLogsServiceResponse) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "partialSuccess", "partial_success":
orig.PartialSuccess.UnmarshalJSON(iter)
default:
iter.Skip()
}
}
}
func (orig *ExportLogsServiceResponse) SizeProto() int {
var n int
var l int
_ = l
l = orig.PartialSuccess.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
return n
}
func (orig *ExportLogsServiceResponse) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
l = orig.PartialSuccess.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
return len(buf) - pos
}
func (orig *ExportLogsServiceResponse) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field PartialSuccess", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.PartialSuccess.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestExportLogsServiceResponse() *ExportLogsServiceResponse {
orig := NewExportLogsServiceResponse()
orig.PartialSuccess = *GenTestExportLogsPartialSuccess()
return orig
}
func GenTestExportLogsServiceResponsePtrSlice() []*ExportLogsServiceResponse {
orig := make([]*ExportLogsServiceResponse, 5)
orig[0] = NewExportLogsServiceResponse()
orig[1] = GenTestExportLogsServiceResponse()
orig[2] = NewExportLogsServiceResponse()
orig[3] = GenTestExportLogsServiceResponse()
orig[4] = NewExportLogsServiceResponse()
return orig
}
func GenTestExportLogsServiceResponseSlice() []ExportLogsServiceResponse {
orig := make([]ExportLogsServiceResponse, 5)
orig[1] = *GenTestExportLogsServiceResponse()
orig[3] = *GenTestExportLogsServiceResponse()
return orig
}
================================================
FILE: pdata/internal/generated_proto_exportlogsserviceresponse_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpcollectorlogs "go.opentelemetry.io/proto/slim/otlp/collector/logs/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyExportLogsServiceResponse(t *testing.T) {
for name, src := range genTestEncodingValuesExportLogsServiceResponse() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewExportLogsServiceResponse()
CopyExportLogsServiceResponse(dest, src)
assert.Equal(t, src, dest)
CopyExportLogsServiceResponse(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyExportLogsServiceResponseSlice(t *testing.T) {
src := []ExportLogsServiceResponse{}
dest := []ExportLogsServiceResponse{}
// Test CopyTo empty
dest = CopyExportLogsServiceResponseSlice(dest, src)
assert.Equal(t, []ExportLogsServiceResponse{}, dest)
// Test CopyTo larger slice
src = GenTestExportLogsServiceResponseSlice()
dest = CopyExportLogsServiceResponseSlice(dest, src)
assert.Equal(t, GenTestExportLogsServiceResponseSlice(), dest)
// Test CopyTo same size slice
dest = CopyExportLogsServiceResponseSlice(dest, src)
assert.Equal(t, GenTestExportLogsServiceResponseSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyExportLogsServiceResponseSlice(dest, []ExportLogsServiceResponse{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyExportLogsServiceResponseSlice(dest, src)
assert.Equal(t, GenTestExportLogsServiceResponseSlice(), dest)
}
func TestCopyExportLogsServiceResponsePtrSlice(t *testing.T) {
src := []*ExportLogsServiceResponse{}
dest := []*ExportLogsServiceResponse{}
// Test CopyTo empty
dest = CopyExportLogsServiceResponsePtrSlice(dest, src)
assert.Equal(t, []*ExportLogsServiceResponse{}, dest)
// Test CopyTo larger slice
src = GenTestExportLogsServiceResponsePtrSlice()
dest = CopyExportLogsServiceResponsePtrSlice(dest, src)
assert.Equal(t, GenTestExportLogsServiceResponsePtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyExportLogsServiceResponsePtrSlice(dest, src)
assert.Equal(t, GenTestExportLogsServiceResponsePtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyExportLogsServiceResponsePtrSlice(dest, []*ExportLogsServiceResponse{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyExportLogsServiceResponsePtrSlice(dest, src)
assert.Equal(t, GenTestExportLogsServiceResponsePtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONExportLogsServiceResponseUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewExportLogsServiceResponse()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewExportLogsServiceResponse(), dest)
}
func TestMarshalAndUnmarshalJSONExportLogsServiceResponse(t *testing.T) {
for name, src := range genTestEncodingValuesExportLogsServiceResponse() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewExportLogsServiceResponse()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteExportLogsServiceResponse(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoExportLogsServiceResponseFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesExportLogsServiceResponse() {
t.Run(name, func(t *testing.T) {
dest := NewExportLogsServiceResponse()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoExportLogsServiceResponseUnknown(t *testing.T) {
dest := NewExportLogsServiceResponse()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewExportLogsServiceResponse(), dest)
}
func TestMarshalAndUnmarshalProtoExportLogsServiceResponse(t *testing.T) {
for name, src := range genTestEncodingValuesExportLogsServiceResponse() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewExportLogsServiceResponse()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteExportLogsServiceResponse(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufExportLogsServiceResponse(t *testing.T) {
for name, src := range genTestEncodingValuesExportLogsServiceResponse() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpcollectorlogs.ExportLogsServiceResponse{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewExportLogsServiceResponse()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesExportLogsServiceResponse() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"PartialSuccess/wrong_wire_type": {0xc},
"PartialSuccess/missing_value": {0xa},
}
}
func genTestEncodingValuesExportLogsServiceResponse() map[string]*ExportLogsServiceResponse {
return map[string]*ExportLogsServiceResponse{
"empty": NewExportLogsServiceResponse(),
"PartialSuccess/test": {PartialSuccess: *GenTestExportLogsPartialSuccess()},
}
}
================================================
FILE: pdata/internal/generated_proto_exportmetricspartialsuccess.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// ExportPartialSuccess represents the details of a partially successful export request.
type ExportMetricsPartialSuccess struct {
ErrorMessage string
RejectedDataPoints int64
}
var (
protoPoolExportMetricsPartialSuccess = sync.Pool{
New: func() any {
return &ExportMetricsPartialSuccess{}
},
}
)
func NewExportMetricsPartialSuccess() *ExportMetricsPartialSuccess {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &ExportMetricsPartialSuccess{}
}
return protoPoolExportMetricsPartialSuccess.Get().(*ExportMetricsPartialSuccess)
}
func DeleteExportMetricsPartialSuccess(orig *ExportMetricsPartialSuccess, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
orig.Reset()
if nullable {
protoPoolExportMetricsPartialSuccess.Put(orig)
}
}
func CopyExportMetricsPartialSuccess(dest, src *ExportMetricsPartialSuccess) *ExportMetricsPartialSuccess {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewExportMetricsPartialSuccess()
}
dest.RejectedDataPoints = src.RejectedDataPoints
dest.ErrorMessage = src.ErrorMessage
return dest
}
func CopyExportMetricsPartialSuccessSlice(dest, src []ExportMetricsPartialSuccess) []ExportMetricsPartialSuccess {
var newDest []ExportMetricsPartialSuccess
if cap(dest) < len(src) {
newDest = make([]ExportMetricsPartialSuccess, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteExportMetricsPartialSuccess(&dest[i], false)
}
}
for i := range src {
CopyExportMetricsPartialSuccess(&newDest[i], &src[i])
}
return newDest
}
func CopyExportMetricsPartialSuccessPtrSlice(dest, src []*ExportMetricsPartialSuccess) []*ExportMetricsPartialSuccess {
var newDest []*ExportMetricsPartialSuccess
if cap(dest) < len(src) {
newDest = make([]*ExportMetricsPartialSuccess, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewExportMetricsPartialSuccess()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteExportMetricsPartialSuccess(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewExportMetricsPartialSuccess()
}
}
for i := range src {
CopyExportMetricsPartialSuccess(newDest[i], src[i])
}
return newDest
}
func (orig *ExportMetricsPartialSuccess) Reset() {
*orig = ExportMetricsPartialSuccess{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *ExportMetricsPartialSuccess) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if orig.RejectedDataPoints != int64(0) {
dest.WriteObjectField("rejectedDataPoints")
dest.WriteInt64(orig.RejectedDataPoints)
}
if orig.ErrorMessage != "" {
dest.WriteObjectField("errorMessage")
dest.WriteString(orig.ErrorMessage)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *ExportMetricsPartialSuccess) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "rejectedDataPoints", "rejected_data_points":
orig.RejectedDataPoints = iter.ReadInt64()
case "errorMessage", "error_message":
orig.ErrorMessage = iter.ReadString()
default:
iter.Skip()
}
}
}
func (orig *ExportMetricsPartialSuccess) SizeProto() int {
var n int
var l int
_ = l
if orig.RejectedDataPoints != int64(0) {
n += 1 + proto.Sov(uint64(orig.RejectedDataPoints))
}
l = len(orig.ErrorMessage)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *ExportMetricsPartialSuccess) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
if orig.RejectedDataPoints != int64(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.RejectedDataPoints))
pos--
buf[pos] = 0x8
}
l = len(orig.ErrorMessage)
if l > 0 {
pos -= l
copy(buf[pos:], orig.ErrorMessage)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
}
return len(buf) - pos
}
func (orig *ExportMetricsPartialSuccess) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field RejectedDataPoints", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.RejectedDataPoints = int64(num)
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field ErrorMessage", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.ErrorMessage = string(buf[startPos:pos])
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestExportMetricsPartialSuccess() *ExportMetricsPartialSuccess {
orig := NewExportMetricsPartialSuccess()
orig.RejectedDataPoints = int64(13)
orig.ErrorMessage = "test_errormessage"
return orig
}
func GenTestExportMetricsPartialSuccessPtrSlice() []*ExportMetricsPartialSuccess {
orig := make([]*ExportMetricsPartialSuccess, 5)
orig[0] = NewExportMetricsPartialSuccess()
orig[1] = GenTestExportMetricsPartialSuccess()
orig[2] = NewExportMetricsPartialSuccess()
orig[3] = GenTestExportMetricsPartialSuccess()
orig[4] = NewExportMetricsPartialSuccess()
return orig
}
func GenTestExportMetricsPartialSuccessSlice() []ExportMetricsPartialSuccess {
orig := make([]ExportMetricsPartialSuccess, 5)
orig[1] = *GenTestExportMetricsPartialSuccess()
orig[3] = *GenTestExportMetricsPartialSuccess()
return orig
}
================================================
FILE: pdata/internal/generated_proto_exportmetricspartialsuccess_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpcollectormetrics "go.opentelemetry.io/proto/slim/otlp/collector/metrics/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyExportMetricsPartialSuccess(t *testing.T) {
for name, src := range genTestEncodingValuesExportMetricsPartialSuccess() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewExportMetricsPartialSuccess()
CopyExportMetricsPartialSuccess(dest, src)
assert.Equal(t, src, dest)
CopyExportMetricsPartialSuccess(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyExportMetricsPartialSuccessSlice(t *testing.T) {
src := []ExportMetricsPartialSuccess{}
dest := []ExportMetricsPartialSuccess{}
// Test CopyTo empty
dest = CopyExportMetricsPartialSuccessSlice(dest, src)
assert.Equal(t, []ExportMetricsPartialSuccess{}, dest)
// Test CopyTo larger slice
src = GenTestExportMetricsPartialSuccessSlice()
dest = CopyExportMetricsPartialSuccessSlice(dest, src)
assert.Equal(t, GenTestExportMetricsPartialSuccessSlice(), dest)
// Test CopyTo same size slice
dest = CopyExportMetricsPartialSuccessSlice(dest, src)
assert.Equal(t, GenTestExportMetricsPartialSuccessSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyExportMetricsPartialSuccessSlice(dest, []ExportMetricsPartialSuccess{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyExportMetricsPartialSuccessSlice(dest, src)
assert.Equal(t, GenTestExportMetricsPartialSuccessSlice(), dest)
}
func TestCopyExportMetricsPartialSuccessPtrSlice(t *testing.T) {
src := []*ExportMetricsPartialSuccess{}
dest := []*ExportMetricsPartialSuccess{}
// Test CopyTo empty
dest = CopyExportMetricsPartialSuccessPtrSlice(dest, src)
assert.Equal(t, []*ExportMetricsPartialSuccess{}, dest)
// Test CopyTo larger slice
src = GenTestExportMetricsPartialSuccessPtrSlice()
dest = CopyExportMetricsPartialSuccessPtrSlice(dest, src)
assert.Equal(t, GenTestExportMetricsPartialSuccessPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyExportMetricsPartialSuccessPtrSlice(dest, src)
assert.Equal(t, GenTestExportMetricsPartialSuccessPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyExportMetricsPartialSuccessPtrSlice(dest, []*ExportMetricsPartialSuccess{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyExportMetricsPartialSuccessPtrSlice(dest, src)
assert.Equal(t, GenTestExportMetricsPartialSuccessPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONExportMetricsPartialSuccessUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewExportMetricsPartialSuccess()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewExportMetricsPartialSuccess(), dest)
}
func TestMarshalAndUnmarshalJSONExportMetricsPartialSuccess(t *testing.T) {
for name, src := range genTestEncodingValuesExportMetricsPartialSuccess() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewExportMetricsPartialSuccess()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteExportMetricsPartialSuccess(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoExportMetricsPartialSuccessFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesExportMetricsPartialSuccess() {
t.Run(name, func(t *testing.T) {
dest := NewExportMetricsPartialSuccess()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoExportMetricsPartialSuccessUnknown(t *testing.T) {
dest := NewExportMetricsPartialSuccess()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewExportMetricsPartialSuccess(), dest)
}
func TestMarshalAndUnmarshalProtoExportMetricsPartialSuccess(t *testing.T) {
for name, src := range genTestEncodingValuesExportMetricsPartialSuccess() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewExportMetricsPartialSuccess()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteExportMetricsPartialSuccess(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufExportMetricsPartialSuccess(t *testing.T) {
for name, src := range genTestEncodingValuesExportMetricsPartialSuccess() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpcollectormetrics.ExportMetricsPartialSuccess{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewExportMetricsPartialSuccess()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesExportMetricsPartialSuccess() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"RejectedDataPoints/wrong_wire_type": {0xc},
"RejectedDataPoints/missing_value": {0x8},
"ErrorMessage/wrong_wire_type": {0x14},
"ErrorMessage/missing_value": {0x12},
}
}
func genTestEncodingValuesExportMetricsPartialSuccess() map[string]*ExportMetricsPartialSuccess {
return map[string]*ExportMetricsPartialSuccess{
"empty": NewExportMetricsPartialSuccess(),
"RejectedDataPoints/test": {RejectedDataPoints: int64(13)},
"ErrorMessage/test": {ErrorMessage: "test_errormessage"},
}
}
================================================
FILE: pdata/internal/generated_proto_exportmetricsservicerequest.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// Metrics is the top-level struct that is propagated through the metrics pipeline.
// Use NewMetrics to create new instance, zero-initialized instance is not valid for use.
type ExportMetricsServiceRequest struct {
ResourceMetrics []*ResourceMetrics
}
var (
protoPoolExportMetricsServiceRequest = sync.Pool{
New: func() any {
return &ExportMetricsServiceRequest{}
},
}
)
func NewExportMetricsServiceRequest() *ExportMetricsServiceRequest {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &ExportMetricsServiceRequest{}
}
return protoPoolExportMetricsServiceRequest.Get().(*ExportMetricsServiceRequest)
}
func DeleteExportMetricsServiceRequest(orig *ExportMetricsServiceRequest, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
for i := range orig.ResourceMetrics {
DeleteResourceMetrics(orig.ResourceMetrics[i], true)
}
orig.Reset()
if nullable {
protoPoolExportMetricsServiceRequest.Put(orig)
}
}
func CopyExportMetricsServiceRequest(dest, src *ExportMetricsServiceRequest) *ExportMetricsServiceRequest {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewExportMetricsServiceRequest()
}
dest.ResourceMetrics = CopyResourceMetricsPtrSlice(dest.ResourceMetrics, src.ResourceMetrics)
return dest
}
func CopyExportMetricsServiceRequestSlice(dest, src []ExportMetricsServiceRequest) []ExportMetricsServiceRequest {
var newDest []ExportMetricsServiceRequest
if cap(dest) < len(src) {
newDest = make([]ExportMetricsServiceRequest, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteExportMetricsServiceRequest(&dest[i], false)
}
}
for i := range src {
CopyExportMetricsServiceRequest(&newDest[i], &src[i])
}
return newDest
}
func CopyExportMetricsServiceRequestPtrSlice(dest, src []*ExportMetricsServiceRequest) []*ExportMetricsServiceRequest {
var newDest []*ExportMetricsServiceRequest
if cap(dest) < len(src) {
newDest = make([]*ExportMetricsServiceRequest, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewExportMetricsServiceRequest()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteExportMetricsServiceRequest(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewExportMetricsServiceRequest()
}
}
for i := range src {
CopyExportMetricsServiceRequest(newDest[i], src[i])
}
return newDest
}
func (orig *ExportMetricsServiceRequest) Reset() {
*orig = ExportMetricsServiceRequest{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *ExportMetricsServiceRequest) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if len(orig.ResourceMetrics) > 0 {
dest.WriteObjectField("resourceMetrics")
dest.WriteArrayStart()
orig.ResourceMetrics[0].MarshalJSON(dest)
for i := 1; i < len(orig.ResourceMetrics); i++ {
dest.WriteMore()
orig.ResourceMetrics[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *ExportMetricsServiceRequest) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "resourceMetrics", "resource_metrics":
for iter.ReadArray() {
orig.ResourceMetrics = append(orig.ResourceMetrics, NewResourceMetrics())
orig.ResourceMetrics[len(orig.ResourceMetrics)-1].UnmarshalJSON(iter)
}
default:
iter.Skip()
}
}
}
func (orig *ExportMetricsServiceRequest) SizeProto() int {
var n int
var l int
_ = l
for i := range orig.ResourceMetrics {
l = orig.ResourceMetrics[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *ExportMetricsServiceRequest) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
for i := len(orig.ResourceMetrics) - 1; i >= 0; i-- {
l = orig.ResourceMetrics[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
}
return len(buf) - pos
}
func (orig *ExportMetricsServiceRequest) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field ResourceMetrics", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.ResourceMetrics = append(orig.ResourceMetrics, NewResourceMetrics())
err = orig.ResourceMetrics[len(orig.ResourceMetrics)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestExportMetricsServiceRequest() *ExportMetricsServiceRequest {
orig := NewExportMetricsServiceRequest()
orig.ResourceMetrics = []*ResourceMetrics{{}, GenTestResourceMetrics()}
return orig
}
func GenTestExportMetricsServiceRequestPtrSlice() []*ExportMetricsServiceRequest {
orig := make([]*ExportMetricsServiceRequest, 5)
orig[0] = NewExportMetricsServiceRequest()
orig[1] = GenTestExportMetricsServiceRequest()
orig[2] = NewExportMetricsServiceRequest()
orig[3] = GenTestExportMetricsServiceRequest()
orig[4] = NewExportMetricsServiceRequest()
return orig
}
func GenTestExportMetricsServiceRequestSlice() []ExportMetricsServiceRequest {
orig := make([]ExportMetricsServiceRequest, 5)
orig[1] = *GenTestExportMetricsServiceRequest()
orig[3] = *GenTestExportMetricsServiceRequest()
return orig
}
================================================
FILE: pdata/internal/generated_proto_exportmetricsservicerequest_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpcollectormetrics "go.opentelemetry.io/proto/slim/otlp/collector/metrics/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyExportMetricsServiceRequest(t *testing.T) {
for name, src := range genTestEncodingValuesExportMetricsServiceRequest() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewExportMetricsServiceRequest()
CopyExportMetricsServiceRequest(dest, src)
assert.Equal(t, src, dest)
CopyExportMetricsServiceRequest(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyExportMetricsServiceRequestSlice(t *testing.T) {
src := []ExportMetricsServiceRequest{}
dest := []ExportMetricsServiceRequest{}
// Test CopyTo empty
dest = CopyExportMetricsServiceRequestSlice(dest, src)
assert.Equal(t, []ExportMetricsServiceRequest{}, dest)
// Test CopyTo larger slice
src = GenTestExportMetricsServiceRequestSlice()
dest = CopyExportMetricsServiceRequestSlice(dest, src)
assert.Equal(t, GenTestExportMetricsServiceRequestSlice(), dest)
// Test CopyTo same size slice
dest = CopyExportMetricsServiceRequestSlice(dest, src)
assert.Equal(t, GenTestExportMetricsServiceRequestSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyExportMetricsServiceRequestSlice(dest, []ExportMetricsServiceRequest{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyExportMetricsServiceRequestSlice(dest, src)
assert.Equal(t, GenTestExportMetricsServiceRequestSlice(), dest)
}
func TestCopyExportMetricsServiceRequestPtrSlice(t *testing.T) {
src := []*ExportMetricsServiceRequest{}
dest := []*ExportMetricsServiceRequest{}
// Test CopyTo empty
dest = CopyExportMetricsServiceRequestPtrSlice(dest, src)
assert.Equal(t, []*ExportMetricsServiceRequest{}, dest)
// Test CopyTo larger slice
src = GenTestExportMetricsServiceRequestPtrSlice()
dest = CopyExportMetricsServiceRequestPtrSlice(dest, src)
assert.Equal(t, GenTestExportMetricsServiceRequestPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyExportMetricsServiceRequestPtrSlice(dest, src)
assert.Equal(t, GenTestExportMetricsServiceRequestPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyExportMetricsServiceRequestPtrSlice(dest, []*ExportMetricsServiceRequest{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyExportMetricsServiceRequestPtrSlice(dest, src)
assert.Equal(t, GenTestExportMetricsServiceRequestPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONExportMetricsServiceRequestUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewExportMetricsServiceRequest()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewExportMetricsServiceRequest(), dest)
}
func TestMarshalAndUnmarshalJSONExportMetricsServiceRequest(t *testing.T) {
for name, src := range genTestEncodingValuesExportMetricsServiceRequest() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewExportMetricsServiceRequest()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteExportMetricsServiceRequest(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoExportMetricsServiceRequestFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesExportMetricsServiceRequest() {
t.Run(name, func(t *testing.T) {
dest := NewExportMetricsServiceRequest()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoExportMetricsServiceRequestUnknown(t *testing.T) {
dest := NewExportMetricsServiceRequest()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewExportMetricsServiceRequest(), dest)
}
func TestMarshalAndUnmarshalProtoExportMetricsServiceRequest(t *testing.T) {
for name, src := range genTestEncodingValuesExportMetricsServiceRequest() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewExportMetricsServiceRequest()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteExportMetricsServiceRequest(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufExportMetricsServiceRequest(t *testing.T) {
for name, src := range genTestEncodingValuesExportMetricsServiceRequest() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpcollectormetrics.ExportMetricsServiceRequest{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewExportMetricsServiceRequest()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesExportMetricsServiceRequest() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"ResourceMetrics/wrong_wire_type": {0xc},
"ResourceMetrics/missing_value": {0xa},
}
}
func genTestEncodingValuesExportMetricsServiceRequest() map[string]*ExportMetricsServiceRequest {
return map[string]*ExportMetricsServiceRequest{
"empty": NewExportMetricsServiceRequest(),
"ResourceMetrics/test": {ResourceMetrics: []*ResourceMetrics{{}, GenTestResourceMetrics()}},
}
}
================================================
FILE: pdata/internal/generated_proto_exportmetricsserviceresponse.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// ExportResponse represents the response for gRPC/HTTP client/server.
type ExportMetricsServiceResponse struct {
PartialSuccess ExportMetricsPartialSuccess
}
var (
protoPoolExportMetricsServiceResponse = sync.Pool{
New: func() any {
return &ExportMetricsServiceResponse{}
},
}
)
func NewExportMetricsServiceResponse() *ExportMetricsServiceResponse {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &ExportMetricsServiceResponse{}
}
return protoPoolExportMetricsServiceResponse.Get().(*ExportMetricsServiceResponse)
}
func DeleteExportMetricsServiceResponse(orig *ExportMetricsServiceResponse, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
DeleteExportMetricsPartialSuccess(&orig.PartialSuccess, false)
orig.Reset()
if nullable {
protoPoolExportMetricsServiceResponse.Put(orig)
}
}
func CopyExportMetricsServiceResponse(dest, src *ExportMetricsServiceResponse) *ExportMetricsServiceResponse {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewExportMetricsServiceResponse()
}
CopyExportMetricsPartialSuccess(&dest.PartialSuccess, &src.PartialSuccess)
return dest
}
func CopyExportMetricsServiceResponseSlice(dest, src []ExportMetricsServiceResponse) []ExportMetricsServiceResponse {
var newDest []ExportMetricsServiceResponse
if cap(dest) < len(src) {
newDest = make([]ExportMetricsServiceResponse, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteExportMetricsServiceResponse(&dest[i], false)
}
}
for i := range src {
CopyExportMetricsServiceResponse(&newDest[i], &src[i])
}
return newDest
}
func CopyExportMetricsServiceResponsePtrSlice(dest, src []*ExportMetricsServiceResponse) []*ExportMetricsServiceResponse {
var newDest []*ExportMetricsServiceResponse
if cap(dest) < len(src) {
newDest = make([]*ExportMetricsServiceResponse, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewExportMetricsServiceResponse()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteExportMetricsServiceResponse(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewExportMetricsServiceResponse()
}
}
for i := range src {
CopyExportMetricsServiceResponse(newDest[i], src[i])
}
return newDest
}
func (orig *ExportMetricsServiceResponse) Reset() {
*orig = ExportMetricsServiceResponse{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *ExportMetricsServiceResponse) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
dest.WriteObjectField("partialSuccess")
orig.PartialSuccess.MarshalJSON(dest)
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *ExportMetricsServiceResponse) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "partialSuccess", "partial_success":
orig.PartialSuccess.UnmarshalJSON(iter)
default:
iter.Skip()
}
}
}
func (orig *ExportMetricsServiceResponse) SizeProto() int {
var n int
var l int
_ = l
l = orig.PartialSuccess.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
return n
}
func (orig *ExportMetricsServiceResponse) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
l = orig.PartialSuccess.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
return len(buf) - pos
}
func (orig *ExportMetricsServiceResponse) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field PartialSuccess", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.PartialSuccess.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestExportMetricsServiceResponse() *ExportMetricsServiceResponse {
orig := NewExportMetricsServiceResponse()
orig.PartialSuccess = *GenTestExportMetricsPartialSuccess()
return orig
}
func GenTestExportMetricsServiceResponsePtrSlice() []*ExportMetricsServiceResponse {
orig := make([]*ExportMetricsServiceResponse, 5)
orig[0] = NewExportMetricsServiceResponse()
orig[1] = GenTestExportMetricsServiceResponse()
orig[2] = NewExportMetricsServiceResponse()
orig[3] = GenTestExportMetricsServiceResponse()
orig[4] = NewExportMetricsServiceResponse()
return orig
}
func GenTestExportMetricsServiceResponseSlice() []ExportMetricsServiceResponse {
orig := make([]ExportMetricsServiceResponse, 5)
orig[1] = *GenTestExportMetricsServiceResponse()
orig[3] = *GenTestExportMetricsServiceResponse()
return orig
}
================================================
FILE: pdata/internal/generated_proto_exportmetricsserviceresponse_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpcollectormetrics "go.opentelemetry.io/proto/slim/otlp/collector/metrics/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyExportMetricsServiceResponse(t *testing.T) {
for name, src := range genTestEncodingValuesExportMetricsServiceResponse() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewExportMetricsServiceResponse()
CopyExportMetricsServiceResponse(dest, src)
assert.Equal(t, src, dest)
CopyExportMetricsServiceResponse(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyExportMetricsServiceResponseSlice(t *testing.T) {
src := []ExportMetricsServiceResponse{}
dest := []ExportMetricsServiceResponse{}
// Test CopyTo empty
dest = CopyExportMetricsServiceResponseSlice(dest, src)
assert.Equal(t, []ExportMetricsServiceResponse{}, dest)
// Test CopyTo larger slice
src = GenTestExportMetricsServiceResponseSlice()
dest = CopyExportMetricsServiceResponseSlice(dest, src)
assert.Equal(t, GenTestExportMetricsServiceResponseSlice(), dest)
// Test CopyTo same size slice
dest = CopyExportMetricsServiceResponseSlice(dest, src)
assert.Equal(t, GenTestExportMetricsServiceResponseSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyExportMetricsServiceResponseSlice(dest, []ExportMetricsServiceResponse{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyExportMetricsServiceResponseSlice(dest, src)
assert.Equal(t, GenTestExportMetricsServiceResponseSlice(), dest)
}
func TestCopyExportMetricsServiceResponsePtrSlice(t *testing.T) {
src := []*ExportMetricsServiceResponse{}
dest := []*ExportMetricsServiceResponse{}
// Test CopyTo empty
dest = CopyExportMetricsServiceResponsePtrSlice(dest, src)
assert.Equal(t, []*ExportMetricsServiceResponse{}, dest)
// Test CopyTo larger slice
src = GenTestExportMetricsServiceResponsePtrSlice()
dest = CopyExportMetricsServiceResponsePtrSlice(dest, src)
assert.Equal(t, GenTestExportMetricsServiceResponsePtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyExportMetricsServiceResponsePtrSlice(dest, src)
assert.Equal(t, GenTestExportMetricsServiceResponsePtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyExportMetricsServiceResponsePtrSlice(dest, []*ExportMetricsServiceResponse{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyExportMetricsServiceResponsePtrSlice(dest, src)
assert.Equal(t, GenTestExportMetricsServiceResponsePtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONExportMetricsServiceResponseUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewExportMetricsServiceResponse()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewExportMetricsServiceResponse(), dest)
}
func TestMarshalAndUnmarshalJSONExportMetricsServiceResponse(t *testing.T) {
for name, src := range genTestEncodingValuesExportMetricsServiceResponse() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewExportMetricsServiceResponse()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteExportMetricsServiceResponse(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoExportMetricsServiceResponseFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesExportMetricsServiceResponse() {
t.Run(name, func(t *testing.T) {
dest := NewExportMetricsServiceResponse()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoExportMetricsServiceResponseUnknown(t *testing.T) {
dest := NewExportMetricsServiceResponse()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewExportMetricsServiceResponse(), dest)
}
func TestMarshalAndUnmarshalProtoExportMetricsServiceResponse(t *testing.T) {
for name, src := range genTestEncodingValuesExportMetricsServiceResponse() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewExportMetricsServiceResponse()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteExportMetricsServiceResponse(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufExportMetricsServiceResponse(t *testing.T) {
for name, src := range genTestEncodingValuesExportMetricsServiceResponse() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpcollectormetrics.ExportMetricsServiceResponse{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewExportMetricsServiceResponse()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesExportMetricsServiceResponse() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"PartialSuccess/wrong_wire_type": {0xc},
"PartialSuccess/missing_value": {0xa},
}
}
func genTestEncodingValuesExportMetricsServiceResponse() map[string]*ExportMetricsServiceResponse {
return map[string]*ExportMetricsServiceResponse{
"empty": NewExportMetricsServiceResponse(),
"PartialSuccess/test": {PartialSuccess: *GenTestExportMetricsPartialSuccess()},
}
}
================================================
FILE: pdata/internal/generated_proto_exportprofilespartialsuccess.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// ExportPartialSuccess represents the details of a partially successful export request.
type ExportProfilesPartialSuccess struct {
ErrorMessage string
RejectedProfiles int64
}
var (
protoPoolExportProfilesPartialSuccess = sync.Pool{
New: func() any {
return &ExportProfilesPartialSuccess{}
},
}
)
func NewExportProfilesPartialSuccess() *ExportProfilesPartialSuccess {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &ExportProfilesPartialSuccess{}
}
return protoPoolExportProfilesPartialSuccess.Get().(*ExportProfilesPartialSuccess)
}
func DeleteExportProfilesPartialSuccess(orig *ExportProfilesPartialSuccess, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
orig.Reset()
if nullable {
protoPoolExportProfilesPartialSuccess.Put(orig)
}
}
func CopyExportProfilesPartialSuccess(dest, src *ExportProfilesPartialSuccess) *ExportProfilesPartialSuccess {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewExportProfilesPartialSuccess()
}
dest.RejectedProfiles = src.RejectedProfiles
dest.ErrorMessage = src.ErrorMessage
return dest
}
func CopyExportProfilesPartialSuccessSlice(dest, src []ExportProfilesPartialSuccess) []ExportProfilesPartialSuccess {
var newDest []ExportProfilesPartialSuccess
if cap(dest) < len(src) {
newDest = make([]ExportProfilesPartialSuccess, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteExportProfilesPartialSuccess(&dest[i], false)
}
}
for i := range src {
CopyExportProfilesPartialSuccess(&newDest[i], &src[i])
}
return newDest
}
func CopyExportProfilesPartialSuccessPtrSlice(dest, src []*ExportProfilesPartialSuccess) []*ExportProfilesPartialSuccess {
var newDest []*ExportProfilesPartialSuccess
if cap(dest) < len(src) {
newDest = make([]*ExportProfilesPartialSuccess, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewExportProfilesPartialSuccess()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteExportProfilesPartialSuccess(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewExportProfilesPartialSuccess()
}
}
for i := range src {
CopyExportProfilesPartialSuccess(newDest[i], src[i])
}
return newDest
}
func (orig *ExportProfilesPartialSuccess) Reset() {
*orig = ExportProfilesPartialSuccess{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *ExportProfilesPartialSuccess) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if orig.RejectedProfiles != int64(0) {
dest.WriteObjectField("rejectedProfiles")
dest.WriteInt64(orig.RejectedProfiles)
}
if orig.ErrorMessage != "" {
dest.WriteObjectField("errorMessage")
dest.WriteString(orig.ErrorMessage)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *ExportProfilesPartialSuccess) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "rejectedProfiles", "rejected_profiles":
orig.RejectedProfiles = iter.ReadInt64()
case "errorMessage", "error_message":
orig.ErrorMessage = iter.ReadString()
default:
iter.Skip()
}
}
}
func (orig *ExportProfilesPartialSuccess) SizeProto() int {
var n int
var l int
_ = l
if orig.RejectedProfiles != int64(0) {
n += 1 + proto.Sov(uint64(orig.RejectedProfiles))
}
l = len(orig.ErrorMessage)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *ExportProfilesPartialSuccess) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
if orig.RejectedProfiles != int64(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.RejectedProfiles))
pos--
buf[pos] = 0x8
}
l = len(orig.ErrorMessage)
if l > 0 {
pos -= l
copy(buf[pos:], orig.ErrorMessage)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
}
return len(buf) - pos
}
func (orig *ExportProfilesPartialSuccess) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field RejectedProfiles", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.RejectedProfiles = int64(num)
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field ErrorMessage", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.ErrorMessage = string(buf[startPos:pos])
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestExportProfilesPartialSuccess() *ExportProfilesPartialSuccess {
orig := NewExportProfilesPartialSuccess()
orig.RejectedProfiles = int64(13)
orig.ErrorMessage = "test_errormessage"
return orig
}
func GenTestExportProfilesPartialSuccessPtrSlice() []*ExportProfilesPartialSuccess {
orig := make([]*ExportProfilesPartialSuccess, 5)
orig[0] = NewExportProfilesPartialSuccess()
orig[1] = GenTestExportProfilesPartialSuccess()
orig[2] = NewExportProfilesPartialSuccess()
orig[3] = GenTestExportProfilesPartialSuccess()
orig[4] = NewExportProfilesPartialSuccess()
return orig
}
func GenTestExportProfilesPartialSuccessSlice() []ExportProfilesPartialSuccess {
orig := make([]ExportProfilesPartialSuccess, 5)
orig[1] = *GenTestExportProfilesPartialSuccess()
orig[3] = *GenTestExportProfilesPartialSuccess()
return orig
}
================================================
FILE: pdata/internal/generated_proto_exportprofilespartialsuccess_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpcollectorprofiles "go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyExportProfilesPartialSuccess(t *testing.T) {
for name, src := range genTestEncodingValuesExportProfilesPartialSuccess() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewExportProfilesPartialSuccess()
CopyExportProfilesPartialSuccess(dest, src)
assert.Equal(t, src, dest)
CopyExportProfilesPartialSuccess(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyExportProfilesPartialSuccessSlice(t *testing.T) {
src := []ExportProfilesPartialSuccess{}
dest := []ExportProfilesPartialSuccess{}
// Test CopyTo empty
dest = CopyExportProfilesPartialSuccessSlice(dest, src)
assert.Equal(t, []ExportProfilesPartialSuccess{}, dest)
// Test CopyTo larger slice
src = GenTestExportProfilesPartialSuccessSlice()
dest = CopyExportProfilesPartialSuccessSlice(dest, src)
assert.Equal(t, GenTestExportProfilesPartialSuccessSlice(), dest)
// Test CopyTo same size slice
dest = CopyExportProfilesPartialSuccessSlice(dest, src)
assert.Equal(t, GenTestExportProfilesPartialSuccessSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyExportProfilesPartialSuccessSlice(dest, []ExportProfilesPartialSuccess{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyExportProfilesPartialSuccessSlice(dest, src)
assert.Equal(t, GenTestExportProfilesPartialSuccessSlice(), dest)
}
func TestCopyExportProfilesPartialSuccessPtrSlice(t *testing.T) {
src := []*ExportProfilesPartialSuccess{}
dest := []*ExportProfilesPartialSuccess{}
// Test CopyTo empty
dest = CopyExportProfilesPartialSuccessPtrSlice(dest, src)
assert.Equal(t, []*ExportProfilesPartialSuccess{}, dest)
// Test CopyTo larger slice
src = GenTestExportProfilesPartialSuccessPtrSlice()
dest = CopyExportProfilesPartialSuccessPtrSlice(dest, src)
assert.Equal(t, GenTestExportProfilesPartialSuccessPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyExportProfilesPartialSuccessPtrSlice(dest, src)
assert.Equal(t, GenTestExportProfilesPartialSuccessPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyExportProfilesPartialSuccessPtrSlice(dest, []*ExportProfilesPartialSuccess{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyExportProfilesPartialSuccessPtrSlice(dest, src)
assert.Equal(t, GenTestExportProfilesPartialSuccessPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONExportProfilesPartialSuccessUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewExportProfilesPartialSuccess()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewExportProfilesPartialSuccess(), dest)
}
func TestMarshalAndUnmarshalJSONExportProfilesPartialSuccess(t *testing.T) {
for name, src := range genTestEncodingValuesExportProfilesPartialSuccess() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewExportProfilesPartialSuccess()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteExportProfilesPartialSuccess(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoExportProfilesPartialSuccessFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesExportProfilesPartialSuccess() {
t.Run(name, func(t *testing.T) {
dest := NewExportProfilesPartialSuccess()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoExportProfilesPartialSuccessUnknown(t *testing.T) {
dest := NewExportProfilesPartialSuccess()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewExportProfilesPartialSuccess(), dest)
}
func TestMarshalAndUnmarshalProtoExportProfilesPartialSuccess(t *testing.T) {
for name, src := range genTestEncodingValuesExportProfilesPartialSuccess() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewExportProfilesPartialSuccess()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteExportProfilesPartialSuccess(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufExportProfilesPartialSuccess(t *testing.T) {
for name, src := range genTestEncodingValuesExportProfilesPartialSuccess() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpcollectorprofiles.ExportProfilesPartialSuccess{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewExportProfilesPartialSuccess()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesExportProfilesPartialSuccess() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"RejectedProfiles/wrong_wire_type": {0xc},
"RejectedProfiles/missing_value": {0x8},
"ErrorMessage/wrong_wire_type": {0x14},
"ErrorMessage/missing_value": {0x12},
}
}
func genTestEncodingValuesExportProfilesPartialSuccess() map[string]*ExportProfilesPartialSuccess {
return map[string]*ExportProfilesPartialSuccess{
"empty": NewExportProfilesPartialSuccess(),
"RejectedProfiles/test": {RejectedProfiles: int64(13)},
"ErrorMessage/test": {ErrorMessage: "test_errormessage"},
}
}
================================================
FILE: pdata/internal/generated_proto_exportprofilesservicerequest.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// Profiles is the top-level struct that is propagated through the profiles pipeline.
// Use NewProfiles to create new instance, zero-initialized instance is not valid for use.
type ExportProfilesServiceRequest struct {
ResourceProfiles []*ResourceProfiles
Dictionary ProfilesDictionary
}
var (
protoPoolExportProfilesServiceRequest = sync.Pool{
New: func() any {
return &ExportProfilesServiceRequest{}
},
}
)
func NewExportProfilesServiceRequest() *ExportProfilesServiceRequest {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &ExportProfilesServiceRequest{}
}
return protoPoolExportProfilesServiceRequest.Get().(*ExportProfilesServiceRequest)
}
func DeleteExportProfilesServiceRequest(orig *ExportProfilesServiceRequest, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
for i := range orig.ResourceProfiles {
DeleteResourceProfiles(orig.ResourceProfiles[i], true)
}
DeleteProfilesDictionary(&orig.Dictionary, false)
orig.Reset()
if nullable {
protoPoolExportProfilesServiceRequest.Put(orig)
}
}
func CopyExportProfilesServiceRequest(dest, src *ExportProfilesServiceRequest) *ExportProfilesServiceRequest {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewExportProfilesServiceRequest()
}
dest.ResourceProfiles = CopyResourceProfilesPtrSlice(dest.ResourceProfiles, src.ResourceProfiles)
CopyProfilesDictionary(&dest.Dictionary, &src.Dictionary)
return dest
}
func CopyExportProfilesServiceRequestSlice(dest, src []ExportProfilesServiceRequest) []ExportProfilesServiceRequest {
var newDest []ExportProfilesServiceRequest
if cap(dest) < len(src) {
newDest = make([]ExportProfilesServiceRequest, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteExportProfilesServiceRequest(&dest[i], false)
}
}
for i := range src {
CopyExportProfilesServiceRequest(&newDest[i], &src[i])
}
return newDest
}
func CopyExportProfilesServiceRequestPtrSlice(dest, src []*ExportProfilesServiceRequest) []*ExportProfilesServiceRequest {
var newDest []*ExportProfilesServiceRequest
if cap(dest) < len(src) {
newDest = make([]*ExportProfilesServiceRequest, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewExportProfilesServiceRequest()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteExportProfilesServiceRequest(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewExportProfilesServiceRequest()
}
}
for i := range src {
CopyExportProfilesServiceRequest(newDest[i], src[i])
}
return newDest
}
func (orig *ExportProfilesServiceRequest) Reset() {
*orig = ExportProfilesServiceRequest{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *ExportProfilesServiceRequest) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if len(orig.ResourceProfiles) > 0 {
dest.WriteObjectField("resourceProfiles")
dest.WriteArrayStart()
orig.ResourceProfiles[0].MarshalJSON(dest)
for i := 1; i < len(orig.ResourceProfiles); i++ {
dest.WriteMore()
orig.ResourceProfiles[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
dest.WriteObjectField("dictionary")
orig.Dictionary.MarshalJSON(dest)
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *ExportProfilesServiceRequest) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "resourceProfiles", "resource_profiles":
for iter.ReadArray() {
orig.ResourceProfiles = append(orig.ResourceProfiles, NewResourceProfiles())
orig.ResourceProfiles[len(orig.ResourceProfiles)-1].UnmarshalJSON(iter)
}
case "dictionary":
orig.Dictionary.UnmarshalJSON(iter)
default:
iter.Skip()
}
}
}
func (orig *ExportProfilesServiceRequest) SizeProto() int {
var n int
var l int
_ = l
for i := range orig.ResourceProfiles {
l = orig.ResourceProfiles[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
l = orig.Dictionary.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
return n
}
func (orig *ExportProfilesServiceRequest) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
for i := len(orig.ResourceProfiles) - 1; i >= 0; i-- {
l = orig.ResourceProfiles[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
}
l = orig.Dictionary.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
return len(buf) - pos
}
func (orig *ExportProfilesServiceRequest) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field ResourceProfiles", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.ResourceProfiles = append(orig.ResourceProfiles, NewResourceProfiles())
err = orig.ResourceProfiles[len(orig.ResourceProfiles)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Dictionary", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.Dictionary.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestExportProfilesServiceRequest() *ExportProfilesServiceRequest {
orig := NewExportProfilesServiceRequest()
orig.ResourceProfiles = []*ResourceProfiles{{}, GenTestResourceProfiles()}
orig.Dictionary = *GenTestProfilesDictionary()
return orig
}
func GenTestExportProfilesServiceRequestPtrSlice() []*ExportProfilesServiceRequest {
orig := make([]*ExportProfilesServiceRequest, 5)
orig[0] = NewExportProfilesServiceRequest()
orig[1] = GenTestExportProfilesServiceRequest()
orig[2] = NewExportProfilesServiceRequest()
orig[3] = GenTestExportProfilesServiceRequest()
orig[4] = NewExportProfilesServiceRequest()
return orig
}
func GenTestExportProfilesServiceRequestSlice() []ExportProfilesServiceRequest {
orig := make([]ExportProfilesServiceRequest, 5)
orig[1] = *GenTestExportProfilesServiceRequest()
orig[3] = *GenTestExportProfilesServiceRequest()
return orig
}
================================================
FILE: pdata/internal/generated_proto_exportprofilesservicerequest_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpcollectorprofiles "go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyExportProfilesServiceRequest(t *testing.T) {
for name, src := range genTestEncodingValuesExportProfilesServiceRequest() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewExportProfilesServiceRequest()
CopyExportProfilesServiceRequest(dest, src)
assert.Equal(t, src, dest)
CopyExportProfilesServiceRequest(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyExportProfilesServiceRequestSlice(t *testing.T) {
src := []ExportProfilesServiceRequest{}
dest := []ExportProfilesServiceRequest{}
// Test CopyTo empty
dest = CopyExportProfilesServiceRequestSlice(dest, src)
assert.Equal(t, []ExportProfilesServiceRequest{}, dest)
// Test CopyTo larger slice
src = GenTestExportProfilesServiceRequestSlice()
dest = CopyExportProfilesServiceRequestSlice(dest, src)
assert.Equal(t, GenTestExportProfilesServiceRequestSlice(), dest)
// Test CopyTo same size slice
dest = CopyExportProfilesServiceRequestSlice(dest, src)
assert.Equal(t, GenTestExportProfilesServiceRequestSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyExportProfilesServiceRequestSlice(dest, []ExportProfilesServiceRequest{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyExportProfilesServiceRequestSlice(dest, src)
assert.Equal(t, GenTestExportProfilesServiceRequestSlice(), dest)
}
func TestCopyExportProfilesServiceRequestPtrSlice(t *testing.T) {
src := []*ExportProfilesServiceRequest{}
dest := []*ExportProfilesServiceRequest{}
// Test CopyTo empty
dest = CopyExportProfilesServiceRequestPtrSlice(dest, src)
assert.Equal(t, []*ExportProfilesServiceRequest{}, dest)
// Test CopyTo larger slice
src = GenTestExportProfilesServiceRequestPtrSlice()
dest = CopyExportProfilesServiceRequestPtrSlice(dest, src)
assert.Equal(t, GenTestExportProfilesServiceRequestPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyExportProfilesServiceRequestPtrSlice(dest, src)
assert.Equal(t, GenTestExportProfilesServiceRequestPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyExportProfilesServiceRequestPtrSlice(dest, []*ExportProfilesServiceRequest{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyExportProfilesServiceRequestPtrSlice(dest, src)
assert.Equal(t, GenTestExportProfilesServiceRequestPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONExportProfilesServiceRequestUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewExportProfilesServiceRequest()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewExportProfilesServiceRequest(), dest)
}
func TestMarshalAndUnmarshalJSONExportProfilesServiceRequest(t *testing.T) {
for name, src := range genTestEncodingValuesExportProfilesServiceRequest() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewExportProfilesServiceRequest()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteExportProfilesServiceRequest(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoExportProfilesServiceRequestFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesExportProfilesServiceRequest() {
t.Run(name, func(t *testing.T) {
dest := NewExportProfilesServiceRequest()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoExportProfilesServiceRequestUnknown(t *testing.T) {
dest := NewExportProfilesServiceRequest()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewExportProfilesServiceRequest(), dest)
}
func TestMarshalAndUnmarshalProtoExportProfilesServiceRequest(t *testing.T) {
for name, src := range genTestEncodingValuesExportProfilesServiceRequest() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewExportProfilesServiceRequest()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteExportProfilesServiceRequest(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufExportProfilesServiceRequest(t *testing.T) {
for name, src := range genTestEncodingValuesExportProfilesServiceRequest() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpcollectorprofiles.ExportProfilesServiceRequest{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewExportProfilesServiceRequest()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesExportProfilesServiceRequest() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"ResourceProfiles/wrong_wire_type": {0xc},
"ResourceProfiles/missing_value": {0xa},
"Dictionary/wrong_wire_type": {0x14},
"Dictionary/missing_value": {0x12},
}
}
func genTestEncodingValuesExportProfilesServiceRequest() map[string]*ExportProfilesServiceRequest {
return map[string]*ExportProfilesServiceRequest{
"empty": NewExportProfilesServiceRequest(),
"ResourceProfiles/test": {ResourceProfiles: []*ResourceProfiles{{}, GenTestResourceProfiles()}},
"Dictionary/test": {Dictionary: *GenTestProfilesDictionary()},
}
}
================================================
FILE: pdata/internal/generated_proto_exportprofilesserviceresponse.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// ExportResponse represents the response for gRPC/HTTP client/server.
type ExportProfilesServiceResponse struct {
PartialSuccess ExportProfilesPartialSuccess
}
var (
protoPoolExportProfilesServiceResponse = sync.Pool{
New: func() any {
return &ExportProfilesServiceResponse{}
},
}
)
func NewExportProfilesServiceResponse() *ExportProfilesServiceResponse {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &ExportProfilesServiceResponse{}
}
return protoPoolExportProfilesServiceResponse.Get().(*ExportProfilesServiceResponse)
}
func DeleteExportProfilesServiceResponse(orig *ExportProfilesServiceResponse, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
DeleteExportProfilesPartialSuccess(&orig.PartialSuccess, false)
orig.Reset()
if nullable {
protoPoolExportProfilesServiceResponse.Put(orig)
}
}
func CopyExportProfilesServiceResponse(dest, src *ExportProfilesServiceResponse) *ExportProfilesServiceResponse {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewExportProfilesServiceResponse()
}
CopyExportProfilesPartialSuccess(&dest.PartialSuccess, &src.PartialSuccess)
return dest
}
func CopyExportProfilesServiceResponseSlice(dest, src []ExportProfilesServiceResponse) []ExportProfilesServiceResponse {
var newDest []ExportProfilesServiceResponse
if cap(dest) < len(src) {
newDest = make([]ExportProfilesServiceResponse, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteExportProfilesServiceResponse(&dest[i], false)
}
}
for i := range src {
CopyExportProfilesServiceResponse(&newDest[i], &src[i])
}
return newDest
}
func CopyExportProfilesServiceResponsePtrSlice(dest, src []*ExportProfilesServiceResponse) []*ExportProfilesServiceResponse {
var newDest []*ExportProfilesServiceResponse
if cap(dest) < len(src) {
newDest = make([]*ExportProfilesServiceResponse, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewExportProfilesServiceResponse()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteExportProfilesServiceResponse(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewExportProfilesServiceResponse()
}
}
for i := range src {
CopyExportProfilesServiceResponse(newDest[i], src[i])
}
return newDest
}
func (orig *ExportProfilesServiceResponse) Reset() {
*orig = ExportProfilesServiceResponse{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *ExportProfilesServiceResponse) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
dest.WriteObjectField("partialSuccess")
orig.PartialSuccess.MarshalJSON(dest)
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *ExportProfilesServiceResponse) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "partialSuccess", "partial_success":
orig.PartialSuccess.UnmarshalJSON(iter)
default:
iter.Skip()
}
}
}
func (orig *ExportProfilesServiceResponse) SizeProto() int {
var n int
var l int
_ = l
l = orig.PartialSuccess.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
return n
}
func (orig *ExportProfilesServiceResponse) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
l = orig.PartialSuccess.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
return len(buf) - pos
}
func (orig *ExportProfilesServiceResponse) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field PartialSuccess", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.PartialSuccess.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestExportProfilesServiceResponse() *ExportProfilesServiceResponse {
orig := NewExportProfilesServiceResponse()
orig.PartialSuccess = *GenTestExportProfilesPartialSuccess()
return orig
}
func GenTestExportProfilesServiceResponsePtrSlice() []*ExportProfilesServiceResponse {
orig := make([]*ExportProfilesServiceResponse, 5)
orig[0] = NewExportProfilesServiceResponse()
orig[1] = GenTestExportProfilesServiceResponse()
orig[2] = NewExportProfilesServiceResponse()
orig[3] = GenTestExportProfilesServiceResponse()
orig[4] = NewExportProfilesServiceResponse()
return orig
}
func GenTestExportProfilesServiceResponseSlice() []ExportProfilesServiceResponse {
orig := make([]ExportProfilesServiceResponse, 5)
orig[1] = *GenTestExportProfilesServiceResponse()
orig[3] = *GenTestExportProfilesServiceResponse()
return orig
}
================================================
FILE: pdata/internal/generated_proto_exportprofilesserviceresponse_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpcollectorprofiles "go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyExportProfilesServiceResponse(t *testing.T) {
for name, src := range genTestEncodingValuesExportProfilesServiceResponse() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewExportProfilesServiceResponse()
CopyExportProfilesServiceResponse(dest, src)
assert.Equal(t, src, dest)
CopyExportProfilesServiceResponse(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyExportProfilesServiceResponseSlice(t *testing.T) {
src := []ExportProfilesServiceResponse{}
dest := []ExportProfilesServiceResponse{}
// Test CopyTo empty
dest = CopyExportProfilesServiceResponseSlice(dest, src)
assert.Equal(t, []ExportProfilesServiceResponse{}, dest)
// Test CopyTo larger slice
src = GenTestExportProfilesServiceResponseSlice()
dest = CopyExportProfilesServiceResponseSlice(dest, src)
assert.Equal(t, GenTestExportProfilesServiceResponseSlice(), dest)
// Test CopyTo same size slice
dest = CopyExportProfilesServiceResponseSlice(dest, src)
assert.Equal(t, GenTestExportProfilesServiceResponseSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyExportProfilesServiceResponseSlice(dest, []ExportProfilesServiceResponse{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyExportProfilesServiceResponseSlice(dest, src)
assert.Equal(t, GenTestExportProfilesServiceResponseSlice(), dest)
}
func TestCopyExportProfilesServiceResponsePtrSlice(t *testing.T) {
src := []*ExportProfilesServiceResponse{}
dest := []*ExportProfilesServiceResponse{}
// Test CopyTo empty
dest = CopyExportProfilesServiceResponsePtrSlice(dest, src)
assert.Equal(t, []*ExportProfilesServiceResponse{}, dest)
// Test CopyTo larger slice
src = GenTestExportProfilesServiceResponsePtrSlice()
dest = CopyExportProfilesServiceResponsePtrSlice(dest, src)
assert.Equal(t, GenTestExportProfilesServiceResponsePtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyExportProfilesServiceResponsePtrSlice(dest, src)
assert.Equal(t, GenTestExportProfilesServiceResponsePtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyExportProfilesServiceResponsePtrSlice(dest, []*ExportProfilesServiceResponse{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyExportProfilesServiceResponsePtrSlice(dest, src)
assert.Equal(t, GenTestExportProfilesServiceResponsePtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONExportProfilesServiceResponseUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewExportProfilesServiceResponse()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewExportProfilesServiceResponse(), dest)
}
func TestMarshalAndUnmarshalJSONExportProfilesServiceResponse(t *testing.T) {
for name, src := range genTestEncodingValuesExportProfilesServiceResponse() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewExportProfilesServiceResponse()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteExportProfilesServiceResponse(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoExportProfilesServiceResponseFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesExportProfilesServiceResponse() {
t.Run(name, func(t *testing.T) {
dest := NewExportProfilesServiceResponse()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoExportProfilesServiceResponseUnknown(t *testing.T) {
dest := NewExportProfilesServiceResponse()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewExportProfilesServiceResponse(), dest)
}
func TestMarshalAndUnmarshalProtoExportProfilesServiceResponse(t *testing.T) {
for name, src := range genTestEncodingValuesExportProfilesServiceResponse() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewExportProfilesServiceResponse()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteExportProfilesServiceResponse(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufExportProfilesServiceResponse(t *testing.T) {
for name, src := range genTestEncodingValuesExportProfilesServiceResponse() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpcollectorprofiles.ExportProfilesServiceResponse{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewExportProfilesServiceResponse()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesExportProfilesServiceResponse() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"PartialSuccess/wrong_wire_type": {0xc},
"PartialSuccess/missing_value": {0xa},
}
}
func genTestEncodingValuesExportProfilesServiceResponse() map[string]*ExportProfilesServiceResponse {
return map[string]*ExportProfilesServiceResponse{
"empty": NewExportProfilesServiceResponse(),
"PartialSuccess/test": {PartialSuccess: *GenTestExportProfilesPartialSuccess()},
}
}
================================================
FILE: pdata/internal/generated_proto_exporttracepartialsuccess.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// ExportPartialSuccess represents the details of a partially successful export request.
type ExportTracePartialSuccess struct {
ErrorMessage string
RejectedSpans int64
}
var (
protoPoolExportTracePartialSuccess = sync.Pool{
New: func() any {
return &ExportTracePartialSuccess{}
},
}
)
func NewExportTracePartialSuccess() *ExportTracePartialSuccess {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &ExportTracePartialSuccess{}
}
return protoPoolExportTracePartialSuccess.Get().(*ExportTracePartialSuccess)
}
func DeleteExportTracePartialSuccess(orig *ExportTracePartialSuccess, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
orig.Reset()
if nullable {
protoPoolExportTracePartialSuccess.Put(orig)
}
}
func CopyExportTracePartialSuccess(dest, src *ExportTracePartialSuccess) *ExportTracePartialSuccess {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewExportTracePartialSuccess()
}
dest.RejectedSpans = src.RejectedSpans
dest.ErrorMessage = src.ErrorMessage
return dest
}
func CopyExportTracePartialSuccessSlice(dest, src []ExportTracePartialSuccess) []ExportTracePartialSuccess {
var newDest []ExportTracePartialSuccess
if cap(dest) < len(src) {
newDest = make([]ExportTracePartialSuccess, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteExportTracePartialSuccess(&dest[i], false)
}
}
for i := range src {
CopyExportTracePartialSuccess(&newDest[i], &src[i])
}
return newDest
}
func CopyExportTracePartialSuccessPtrSlice(dest, src []*ExportTracePartialSuccess) []*ExportTracePartialSuccess {
var newDest []*ExportTracePartialSuccess
if cap(dest) < len(src) {
newDest = make([]*ExportTracePartialSuccess, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewExportTracePartialSuccess()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteExportTracePartialSuccess(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewExportTracePartialSuccess()
}
}
for i := range src {
CopyExportTracePartialSuccess(newDest[i], src[i])
}
return newDest
}
func (orig *ExportTracePartialSuccess) Reset() {
*orig = ExportTracePartialSuccess{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *ExportTracePartialSuccess) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if orig.RejectedSpans != int64(0) {
dest.WriteObjectField("rejectedSpans")
dest.WriteInt64(orig.RejectedSpans)
}
if orig.ErrorMessage != "" {
dest.WriteObjectField("errorMessage")
dest.WriteString(orig.ErrorMessage)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *ExportTracePartialSuccess) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "rejectedSpans", "rejected_spans":
orig.RejectedSpans = iter.ReadInt64()
case "errorMessage", "error_message":
orig.ErrorMessage = iter.ReadString()
default:
iter.Skip()
}
}
}
func (orig *ExportTracePartialSuccess) SizeProto() int {
var n int
var l int
_ = l
if orig.RejectedSpans != int64(0) {
n += 1 + proto.Sov(uint64(orig.RejectedSpans))
}
l = len(orig.ErrorMessage)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *ExportTracePartialSuccess) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
if orig.RejectedSpans != int64(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.RejectedSpans))
pos--
buf[pos] = 0x8
}
l = len(orig.ErrorMessage)
if l > 0 {
pos -= l
copy(buf[pos:], orig.ErrorMessage)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
}
return len(buf) - pos
}
func (orig *ExportTracePartialSuccess) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field RejectedSpans", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.RejectedSpans = int64(num)
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field ErrorMessage", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.ErrorMessage = string(buf[startPos:pos])
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestExportTracePartialSuccess() *ExportTracePartialSuccess {
orig := NewExportTracePartialSuccess()
orig.RejectedSpans = int64(13)
orig.ErrorMessage = "test_errormessage"
return orig
}
func GenTestExportTracePartialSuccessPtrSlice() []*ExportTracePartialSuccess {
orig := make([]*ExportTracePartialSuccess, 5)
orig[0] = NewExportTracePartialSuccess()
orig[1] = GenTestExportTracePartialSuccess()
orig[2] = NewExportTracePartialSuccess()
orig[3] = GenTestExportTracePartialSuccess()
orig[4] = NewExportTracePartialSuccess()
return orig
}
func GenTestExportTracePartialSuccessSlice() []ExportTracePartialSuccess {
orig := make([]ExportTracePartialSuccess, 5)
orig[1] = *GenTestExportTracePartialSuccess()
orig[3] = *GenTestExportTracePartialSuccess()
return orig
}
================================================
FILE: pdata/internal/generated_proto_exporttracepartialsuccess_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpcollectortrace "go.opentelemetry.io/proto/slim/otlp/collector/trace/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyExportTracePartialSuccess(t *testing.T) {
for name, src := range genTestEncodingValuesExportTracePartialSuccess() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewExportTracePartialSuccess()
CopyExportTracePartialSuccess(dest, src)
assert.Equal(t, src, dest)
CopyExportTracePartialSuccess(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyExportTracePartialSuccessSlice(t *testing.T) {
src := []ExportTracePartialSuccess{}
dest := []ExportTracePartialSuccess{}
// Test CopyTo empty
dest = CopyExportTracePartialSuccessSlice(dest, src)
assert.Equal(t, []ExportTracePartialSuccess{}, dest)
// Test CopyTo larger slice
src = GenTestExportTracePartialSuccessSlice()
dest = CopyExportTracePartialSuccessSlice(dest, src)
assert.Equal(t, GenTestExportTracePartialSuccessSlice(), dest)
// Test CopyTo same size slice
dest = CopyExportTracePartialSuccessSlice(dest, src)
assert.Equal(t, GenTestExportTracePartialSuccessSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyExportTracePartialSuccessSlice(dest, []ExportTracePartialSuccess{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyExportTracePartialSuccessSlice(dest, src)
assert.Equal(t, GenTestExportTracePartialSuccessSlice(), dest)
}
func TestCopyExportTracePartialSuccessPtrSlice(t *testing.T) {
src := []*ExportTracePartialSuccess{}
dest := []*ExportTracePartialSuccess{}
// Test CopyTo empty
dest = CopyExportTracePartialSuccessPtrSlice(dest, src)
assert.Equal(t, []*ExportTracePartialSuccess{}, dest)
// Test CopyTo larger slice
src = GenTestExportTracePartialSuccessPtrSlice()
dest = CopyExportTracePartialSuccessPtrSlice(dest, src)
assert.Equal(t, GenTestExportTracePartialSuccessPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyExportTracePartialSuccessPtrSlice(dest, src)
assert.Equal(t, GenTestExportTracePartialSuccessPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyExportTracePartialSuccessPtrSlice(dest, []*ExportTracePartialSuccess{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyExportTracePartialSuccessPtrSlice(dest, src)
assert.Equal(t, GenTestExportTracePartialSuccessPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONExportTracePartialSuccessUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewExportTracePartialSuccess()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewExportTracePartialSuccess(), dest)
}
func TestMarshalAndUnmarshalJSONExportTracePartialSuccess(t *testing.T) {
for name, src := range genTestEncodingValuesExportTracePartialSuccess() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewExportTracePartialSuccess()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteExportTracePartialSuccess(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoExportTracePartialSuccessFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesExportTracePartialSuccess() {
t.Run(name, func(t *testing.T) {
dest := NewExportTracePartialSuccess()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoExportTracePartialSuccessUnknown(t *testing.T) {
dest := NewExportTracePartialSuccess()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewExportTracePartialSuccess(), dest)
}
func TestMarshalAndUnmarshalProtoExportTracePartialSuccess(t *testing.T) {
for name, src := range genTestEncodingValuesExportTracePartialSuccess() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewExportTracePartialSuccess()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteExportTracePartialSuccess(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufExportTracePartialSuccess(t *testing.T) {
for name, src := range genTestEncodingValuesExportTracePartialSuccess() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpcollectortrace.ExportTracePartialSuccess{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewExportTracePartialSuccess()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesExportTracePartialSuccess() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"RejectedSpans/wrong_wire_type": {0xc},
"RejectedSpans/missing_value": {0x8},
"ErrorMessage/wrong_wire_type": {0x14},
"ErrorMessage/missing_value": {0x12},
}
}
func genTestEncodingValuesExportTracePartialSuccess() map[string]*ExportTracePartialSuccess {
return map[string]*ExportTracePartialSuccess{
"empty": NewExportTracePartialSuccess(),
"RejectedSpans/test": {RejectedSpans: int64(13)},
"ErrorMessage/test": {ErrorMessage: "test_errormessage"},
}
}
================================================
FILE: pdata/internal/generated_proto_exporttraceservicerequest.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// Traces is the top-level struct that is propagated through the traces pipeline.
// Use NewTraces to create new instance, zero-initialized instance is not valid for use.
type ExportTraceServiceRequest struct {
ResourceSpans []*ResourceSpans
}
var (
protoPoolExportTraceServiceRequest = sync.Pool{
New: func() any {
return &ExportTraceServiceRequest{}
},
}
)
func NewExportTraceServiceRequest() *ExportTraceServiceRequest {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &ExportTraceServiceRequest{}
}
return protoPoolExportTraceServiceRequest.Get().(*ExportTraceServiceRequest)
}
func DeleteExportTraceServiceRequest(orig *ExportTraceServiceRequest, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
for i := range orig.ResourceSpans {
DeleteResourceSpans(orig.ResourceSpans[i], true)
}
orig.Reset()
if nullable {
protoPoolExportTraceServiceRequest.Put(orig)
}
}
func CopyExportTraceServiceRequest(dest, src *ExportTraceServiceRequest) *ExportTraceServiceRequest {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewExportTraceServiceRequest()
}
dest.ResourceSpans = CopyResourceSpansPtrSlice(dest.ResourceSpans, src.ResourceSpans)
return dest
}
func CopyExportTraceServiceRequestSlice(dest, src []ExportTraceServiceRequest) []ExportTraceServiceRequest {
var newDest []ExportTraceServiceRequest
if cap(dest) < len(src) {
newDest = make([]ExportTraceServiceRequest, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteExportTraceServiceRequest(&dest[i], false)
}
}
for i := range src {
CopyExportTraceServiceRequest(&newDest[i], &src[i])
}
return newDest
}
func CopyExportTraceServiceRequestPtrSlice(dest, src []*ExportTraceServiceRequest) []*ExportTraceServiceRequest {
var newDest []*ExportTraceServiceRequest
if cap(dest) < len(src) {
newDest = make([]*ExportTraceServiceRequest, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewExportTraceServiceRequest()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteExportTraceServiceRequest(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewExportTraceServiceRequest()
}
}
for i := range src {
CopyExportTraceServiceRequest(newDest[i], src[i])
}
return newDest
}
func (orig *ExportTraceServiceRequest) Reset() {
*orig = ExportTraceServiceRequest{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *ExportTraceServiceRequest) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if len(orig.ResourceSpans) > 0 {
dest.WriteObjectField("resourceSpans")
dest.WriteArrayStart()
orig.ResourceSpans[0].MarshalJSON(dest)
for i := 1; i < len(orig.ResourceSpans); i++ {
dest.WriteMore()
orig.ResourceSpans[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *ExportTraceServiceRequest) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "resourceSpans", "resource_spans":
for iter.ReadArray() {
orig.ResourceSpans = append(orig.ResourceSpans, NewResourceSpans())
orig.ResourceSpans[len(orig.ResourceSpans)-1].UnmarshalJSON(iter)
}
default:
iter.Skip()
}
}
}
func (orig *ExportTraceServiceRequest) SizeProto() int {
var n int
var l int
_ = l
for i := range orig.ResourceSpans {
l = orig.ResourceSpans[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *ExportTraceServiceRequest) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
for i := len(orig.ResourceSpans) - 1; i >= 0; i-- {
l = orig.ResourceSpans[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
}
return len(buf) - pos
}
func (orig *ExportTraceServiceRequest) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field ResourceSpans", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.ResourceSpans = append(orig.ResourceSpans, NewResourceSpans())
err = orig.ResourceSpans[len(orig.ResourceSpans)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestExportTraceServiceRequest() *ExportTraceServiceRequest {
orig := NewExportTraceServiceRequest()
orig.ResourceSpans = []*ResourceSpans{{}, GenTestResourceSpans()}
return orig
}
func GenTestExportTraceServiceRequestPtrSlice() []*ExportTraceServiceRequest {
orig := make([]*ExportTraceServiceRequest, 5)
orig[0] = NewExportTraceServiceRequest()
orig[1] = GenTestExportTraceServiceRequest()
orig[2] = NewExportTraceServiceRequest()
orig[3] = GenTestExportTraceServiceRequest()
orig[4] = NewExportTraceServiceRequest()
return orig
}
func GenTestExportTraceServiceRequestSlice() []ExportTraceServiceRequest {
orig := make([]ExportTraceServiceRequest, 5)
orig[1] = *GenTestExportTraceServiceRequest()
orig[3] = *GenTestExportTraceServiceRequest()
return orig
}
================================================
FILE: pdata/internal/generated_proto_exporttraceservicerequest_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpcollectortrace "go.opentelemetry.io/proto/slim/otlp/collector/trace/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyExportTraceServiceRequest(t *testing.T) {
for name, src := range genTestEncodingValuesExportTraceServiceRequest() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewExportTraceServiceRequest()
CopyExportTraceServiceRequest(dest, src)
assert.Equal(t, src, dest)
CopyExportTraceServiceRequest(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyExportTraceServiceRequestSlice(t *testing.T) {
src := []ExportTraceServiceRequest{}
dest := []ExportTraceServiceRequest{}
// Test CopyTo empty
dest = CopyExportTraceServiceRequestSlice(dest, src)
assert.Equal(t, []ExportTraceServiceRequest{}, dest)
// Test CopyTo larger slice
src = GenTestExportTraceServiceRequestSlice()
dest = CopyExportTraceServiceRequestSlice(dest, src)
assert.Equal(t, GenTestExportTraceServiceRequestSlice(), dest)
// Test CopyTo same size slice
dest = CopyExportTraceServiceRequestSlice(dest, src)
assert.Equal(t, GenTestExportTraceServiceRequestSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyExportTraceServiceRequestSlice(dest, []ExportTraceServiceRequest{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyExportTraceServiceRequestSlice(dest, src)
assert.Equal(t, GenTestExportTraceServiceRequestSlice(), dest)
}
func TestCopyExportTraceServiceRequestPtrSlice(t *testing.T) {
src := []*ExportTraceServiceRequest{}
dest := []*ExportTraceServiceRequest{}
// Test CopyTo empty
dest = CopyExportTraceServiceRequestPtrSlice(dest, src)
assert.Equal(t, []*ExportTraceServiceRequest{}, dest)
// Test CopyTo larger slice
src = GenTestExportTraceServiceRequestPtrSlice()
dest = CopyExportTraceServiceRequestPtrSlice(dest, src)
assert.Equal(t, GenTestExportTraceServiceRequestPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyExportTraceServiceRequestPtrSlice(dest, src)
assert.Equal(t, GenTestExportTraceServiceRequestPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyExportTraceServiceRequestPtrSlice(dest, []*ExportTraceServiceRequest{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyExportTraceServiceRequestPtrSlice(dest, src)
assert.Equal(t, GenTestExportTraceServiceRequestPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONExportTraceServiceRequestUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewExportTraceServiceRequest()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewExportTraceServiceRequest(), dest)
}
func TestMarshalAndUnmarshalJSONExportTraceServiceRequest(t *testing.T) {
for name, src := range genTestEncodingValuesExportTraceServiceRequest() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewExportTraceServiceRequest()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteExportTraceServiceRequest(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoExportTraceServiceRequestFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesExportTraceServiceRequest() {
t.Run(name, func(t *testing.T) {
dest := NewExportTraceServiceRequest()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoExportTraceServiceRequestUnknown(t *testing.T) {
dest := NewExportTraceServiceRequest()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewExportTraceServiceRequest(), dest)
}
func TestMarshalAndUnmarshalProtoExportTraceServiceRequest(t *testing.T) {
for name, src := range genTestEncodingValuesExportTraceServiceRequest() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewExportTraceServiceRequest()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteExportTraceServiceRequest(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufExportTraceServiceRequest(t *testing.T) {
for name, src := range genTestEncodingValuesExportTraceServiceRequest() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpcollectortrace.ExportTraceServiceRequest{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewExportTraceServiceRequest()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesExportTraceServiceRequest() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"ResourceSpans/wrong_wire_type": {0xc},
"ResourceSpans/missing_value": {0xa},
}
}
func genTestEncodingValuesExportTraceServiceRequest() map[string]*ExportTraceServiceRequest {
return map[string]*ExportTraceServiceRequest{
"empty": NewExportTraceServiceRequest(),
"ResourceSpans/test": {ResourceSpans: []*ResourceSpans{{}, GenTestResourceSpans()}},
}
}
================================================
FILE: pdata/internal/generated_proto_exporttraceserviceresponse.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// ExportResponse represents the response for gRPC/HTTP client/server.
type ExportTraceServiceResponse struct {
PartialSuccess ExportTracePartialSuccess
}
var (
protoPoolExportTraceServiceResponse = sync.Pool{
New: func() any {
return &ExportTraceServiceResponse{}
},
}
)
func NewExportTraceServiceResponse() *ExportTraceServiceResponse {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &ExportTraceServiceResponse{}
}
return protoPoolExportTraceServiceResponse.Get().(*ExportTraceServiceResponse)
}
func DeleteExportTraceServiceResponse(orig *ExportTraceServiceResponse, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
DeleteExportTracePartialSuccess(&orig.PartialSuccess, false)
orig.Reset()
if nullable {
protoPoolExportTraceServiceResponse.Put(orig)
}
}
func CopyExportTraceServiceResponse(dest, src *ExportTraceServiceResponse) *ExportTraceServiceResponse {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewExportTraceServiceResponse()
}
CopyExportTracePartialSuccess(&dest.PartialSuccess, &src.PartialSuccess)
return dest
}
func CopyExportTraceServiceResponseSlice(dest, src []ExportTraceServiceResponse) []ExportTraceServiceResponse {
var newDest []ExportTraceServiceResponse
if cap(dest) < len(src) {
newDest = make([]ExportTraceServiceResponse, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteExportTraceServiceResponse(&dest[i], false)
}
}
for i := range src {
CopyExportTraceServiceResponse(&newDest[i], &src[i])
}
return newDest
}
func CopyExportTraceServiceResponsePtrSlice(dest, src []*ExportTraceServiceResponse) []*ExportTraceServiceResponse {
var newDest []*ExportTraceServiceResponse
if cap(dest) < len(src) {
newDest = make([]*ExportTraceServiceResponse, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewExportTraceServiceResponse()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteExportTraceServiceResponse(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewExportTraceServiceResponse()
}
}
for i := range src {
CopyExportTraceServiceResponse(newDest[i], src[i])
}
return newDest
}
func (orig *ExportTraceServiceResponse) Reset() {
*orig = ExportTraceServiceResponse{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *ExportTraceServiceResponse) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
dest.WriteObjectField("partialSuccess")
orig.PartialSuccess.MarshalJSON(dest)
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *ExportTraceServiceResponse) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "partialSuccess", "partial_success":
orig.PartialSuccess.UnmarshalJSON(iter)
default:
iter.Skip()
}
}
}
func (orig *ExportTraceServiceResponse) SizeProto() int {
var n int
var l int
_ = l
l = orig.PartialSuccess.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
return n
}
func (orig *ExportTraceServiceResponse) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
l = orig.PartialSuccess.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
return len(buf) - pos
}
func (orig *ExportTraceServiceResponse) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field PartialSuccess", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.PartialSuccess.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestExportTraceServiceResponse() *ExportTraceServiceResponse {
orig := NewExportTraceServiceResponse()
orig.PartialSuccess = *GenTestExportTracePartialSuccess()
return orig
}
func GenTestExportTraceServiceResponsePtrSlice() []*ExportTraceServiceResponse {
orig := make([]*ExportTraceServiceResponse, 5)
orig[0] = NewExportTraceServiceResponse()
orig[1] = GenTestExportTraceServiceResponse()
orig[2] = NewExportTraceServiceResponse()
orig[3] = GenTestExportTraceServiceResponse()
orig[4] = NewExportTraceServiceResponse()
return orig
}
func GenTestExportTraceServiceResponseSlice() []ExportTraceServiceResponse {
orig := make([]ExportTraceServiceResponse, 5)
orig[1] = *GenTestExportTraceServiceResponse()
orig[3] = *GenTestExportTraceServiceResponse()
return orig
}
================================================
FILE: pdata/internal/generated_proto_exporttraceserviceresponse_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpcollectortrace "go.opentelemetry.io/proto/slim/otlp/collector/trace/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyExportTraceServiceResponse(t *testing.T) {
for name, src := range genTestEncodingValuesExportTraceServiceResponse() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewExportTraceServiceResponse()
CopyExportTraceServiceResponse(dest, src)
assert.Equal(t, src, dest)
CopyExportTraceServiceResponse(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyExportTraceServiceResponseSlice(t *testing.T) {
src := []ExportTraceServiceResponse{}
dest := []ExportTraceServiceResponse{}
// Test CopyTo empty
dest = CopyExportTraceServiceResponseSlice(dest, src)
assert.Equal(t, []ExportTraceServiceResponse{}, dest)
// Test CopyTo larger slice
src = GenTestExportTraceServiceResponseSlice()
dest = CopyExportTraceServiceResponseSlice(dest, src)
assert.Equal(t, GenTestExportTraceServiceResponseSlice(), dest)
// Test CopyTo same size slice
dest = CopyExportTraceServiceResponseSlice(dest, src)
assert.Equal(t, GenTestExportTraceServiceResponseSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyExportTraceServiceResponseSlice(dest, []ExportTraceServiceResponse{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyExportTraceServiceResponseSlice(dest, src)
assert.Equal(t, GenTestExportTraceServiceResponseSlice(), dest)
}
func TestCopyExportTraceServiceResponsePtrSlice(t *testing.T) {
src := []*ExportTraceServiceResponse{}
dest := []*ExportTraceServiceResponse{}
// Test CopyTo empty
dest = CopyExportTraceServiceResponsePtrSlice(dest, src)
assert.Equal(t, []*ExportTraceServiceResponse{}, dest)
// Test CopyTo larger slice
src = GenTestExportTraceServiceResponsePtrSlice()
dest = CopyExportTraceServiceResponsePtrSlice(dest, src)
assert.Equal(t, GenTestExportTraceServiceResponsePtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyExportTraceServiceResponsePtrSlice(dest, src)
assert.Equal(t, GenTestExportTraceServiceResponsePtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyExportTraceServiceResponsePtrSlice(dest, []*ExportTraceServiceResponse{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyExportTraceServiceResponsePtrSlice(dest, src)
assert.Equal(t, GenTestExportTraceServiceResponsePtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONExportTraceServiceResponseUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewExportTraceServiceResponse()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewExportTraceServiceResponse(), dest)
}
func TestMarshalAndUnmarshalJSONExportTraceServiceResponse(t *testing.T) {
for name, src := range genTestEncodingValuesExportTraceServiceResponse() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewExportTraceServiceResponse()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteExportTraceServiceResponse(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoExportTraceServiceResponseFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesExportTraceServiceResponse() {
t.Run(name, func(t *testing.T) {
dest := NewExportTraceServiceResponse()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoExportTraceServiceResponseUnknown(t *testing.T) {
dest := NewExportTraceServiceResponse()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewExportTraceServiceResponse(), dest)
}
func TestMarshalAndUnmarshalProtoExportTraceServiceResponse(t *testing.T) {
for name, src := range genTestEncodingValuesExportTraceServiceResponse() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewExportTraceServiceResponse()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteExportTraceServiceResponse(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufExportTraceServiceResponse(t *testing.T) {
for name, src := range genTestEncodingValuesExportTraceServiceResponse() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpcollectortrace.ExportTraceServiceResponse{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewExportTraceServiceResponse()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesExportTraceServiceResponse() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"PartialSuccess/wrong_wire_type": {0xc},
"PartialSuccess/missing_value": {0xa},
}
}
func genTestEncodingValuesExportTraceServiceResponse() map[string]*ExportTraceServiceResponse {
return map[string]*ExportTraceServiceResponse{
"empty": NewExportTraceServiceResponse(),
"PartialSuccess/test": {PartialSuccess: *GenTestExportTracePartialSuccess()},
}
}
================================================
FILE: pdata/internal/generated_proto_function.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// Function describes a function, including its human-readable name, system name, source file, and starting line number in the source.
type Function struct {
NameStrindex int32
SystemNameStrindex int32
FilenameStrindex int32
StartLine int64
}
var (
protoPoolFunction = sync.Pool{
New: func() any {
return &Function{}
},
}
)
func NewFunction() *Function {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &Function{}
}
return protoPoolFunction.Get().(*Function)
}
func DeleteFunction(orig *Function, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
orig.Reset()
if nullable {
protoPoolFunction.Put(orig)
}
}
func CopyFunction(dest, src *Function) *Function {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewFunction()
}
dest.NameStrindex = src.NameStrindex
dest.SystemNameStrindex = src.SystemNameStrindex
dest.FilenameStrindex = src.FilenameStrindex
dest.StartLine = src.StartLine
return dest
}
func CopyFunctionSlice(dest, src []Function) []Function {
var newDest []Function
if cap(dest) < len(src) {
newDest = make([]Function, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteFunction(&dest[i], false)
}
}
for i := range src {
CopyFunction(&newDest[i], &src[i])
}
return newDest
}
func CopyFunctionPtrSlice(dest, src []*Function) []*Function {
var newDest []*Function
if cap(dest) < len(src) {
newDest = make([]*Function, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewFunction()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteFunction(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewFunction()
}
}
for i := range src {
CopyFunction(newDest[i], src[i])
}
return newDest
}
func (orig *Function) Reset() {
*orig = Function{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *Function) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if orig.NameStrindex != int32(0) {
dest.WriteObjectField("nameStrindex")
dest.WriteInt32(orig.NameStrindex)
}
if orig.SystemNameStrindex != int32(0) {
dest.WriteObjectField("systemNameStrindex")
dest.WriteInt32(orig.SystemNameStrindex)
}
if orig.FilenameStrindex != int32(0) {
dest.WriteObjectField("filenameStrindex")
dest.WriteInt32(orig.FilenameStrindex)
}
if orig.StartLine != int64(0) {
dest.WriteObjectField("startLine")
dest.WriteInt64(orig.StartLine)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *Function) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "nameStrindex", "name_strindex":
orig.NameStrindex = iter.ReadInt32()
case "systemNameStrindex", "system_name_strindex":
orig.SystemNameStrindex = iter.ReadInt32()
case "filenameStrindex", "filename_strindex":
orig.FilenameStrindex = iter.ReadInt32()
case "startLine", "start_line":
orig.StartLine = iter.ReadInt64()
default:
iter.Skip()
}
}
}
func (orig *Function) SizeProto() int {
var n int
var l int
_ = l
if orig.NameStrindex != int32(0) {
n += 1 + proto.Sov(uint64(orig.NameStrindex))
}
if orig.SystemNameStrindex != int32(0) {
n += 1 + proto.Sov(uint64(orig.SystemNameStrindex))
}
if orig.FilenameStrindex != int32(0) {
n += 1 + proto.Sov(uint64(orig.FilenameStrindex))
}
if orig.StartLine != int64(0) {
n += 1 + proto.Sov(uint64(orig.StartLine))
}
return n
}
func (orig *Function) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
if orig.NameStrindex != int32(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.NameStrindex))
pos--
buf[pos] = 0x8
}
if orig.SystemNameStrindex != int32(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.SystemNameStrindex))
pos--
buf[pos] = 0x10
}
if orig.FilenameStrindex != int32(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.FilenameStrindex))
pos--
buf[pos] = 0x18
}
if orig.StartLine != int64(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.StartLine))
pos--
buf[pos] = 0x20
}
return len(buf) - pos
}
func (orig *Function) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field NameStrindex", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.NameStrindex = int32(num)
case 2:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field SystemNameStrindex", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.SystemNameStrindex = int32(num)
case 3:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field FilenameStrindex", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.FilenameStrindex = int32(num)
case 4:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field StartLine", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.StartLine = int64(num)
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestFunction() *Function {
orig := NewFunction()
orig.NameStrindex = int32(13)
orig.SystemNameStrindex = int32(13)
orig.FilenameStrindex = int32(13)
orig.StartLine = int64(13)
return orig
}
func GenTestFunctionPtrSlice() []*Function {
orig := make([]*Function, 5)
orig[0] = NewFunction()
orig[1] = GenTestFunction()
orig[2] = NewFunction()
orig[3] = GenTestFunction()
orig[4] = NewFunction()
return orig
}
func GenTestFunctionSlice() []Function {
orig := make([]Function, 5)
orig[1] = *GenTestFunction()
orig[3] = *GenTestFunction()
return orig
}
================================================
FILE: pdata/internal/generated_proto_function_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpprofiles "go.opentelemetry.io/proto/slim/otlp/profiles/v1development"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyFunction(t *testing.T) {
for name, src := range genTestEncodingValuesFunction() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewFunction()
CopyFunction(dest, src)
assert.Equal(t, src, dest)
CopyFunction(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyFunctionSlice(t *testing.T) {
src := []Function{}
dest := []Function{}
// Test CopyTo empty
dest = CopyFunctionSlice(dest, src)
assert.Equal(t, []Function{}, dest)
// Test CopyTo larger slice
src = GenTestFunctionSlice()
dest = CopyFunctionSlice(dest, src)
assert.Equal(t, GenTestFunctionSlice(), dest)
// Test CopyTo same size slice
dest = CopyFunctionSlice(dest, src)
assert.Equal(t, GenTestFunctionSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyFunctionSlice(dest, []Function{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyFunctionSlice(dest, src)
assert.Equal(t, GenTestFunctionSlice(), dest)
}
func TestCopyFunctionPtrSlice(t *testing.T) {
src := []*Function{}
dest := []*Function{}
// Test CopyTo empty
dest = CopyFunctionPtrSlice(dest, src)
assert.Equal(t, []*Function{}, dest)
// Test CopyTo larger slice
src = GenTestFunctionPtrSlice()
dest = CopyFunctionPtrSlice(dest, src)
assert.Equal(t, GenTestFunctionPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyFunctionPtrSlice(dest, src)
assert.Equal(t, GenTestFunctionPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyFunctionPtrSlice(dest, []*Function{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyFunctionPtrSlice(dest, src)
assert.Equal(t, GenTestFunctionPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONFunctionUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewFunction()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewFunction(), dest)
}
func TestMarshalAndUnmarshalJSONFunction(t *testing.T) {
for name, src := range genTestEncodingValuesFunction() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewFunction()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteFunction(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoFunctionFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesFunction() {
t.Run(name, func(t *testing.T) {
dest := NewFunction()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoFunctionUnknown(t *testing.T) {
dest := NewFunction()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewFunction(), dest)
}
func TestMarshalAndUnmarshalProtoFunction(t *testing.T) {
for name, src := range genTestEncodingValuesFunction() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewFunction()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteFunction(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufFunction(t *testing.T) {
for name, src := range genTestEncodingValuesFunction() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpprofiles.Function{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewFunction()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesFunction() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"NameStrindex/wrong_wire_type": {0xc},
"NameStrindex/missing_value": {0x8},
"SystemNameStrindex/wrong_wire_type": {0x14},
"SystemNameStrindex/missing_value": {0x10},
"FilenameStrindex/wrong_wire_type": {0x1c},
"FilenameStrindex/missing_value": {0x18},
"StartLine/wrong_wire_type": {0x24},
"StartLine/missing_value": {0x20},
}
}
func genTestEncodingValuesFunction() map[string]*Function {
return map[string]*Function{
"empty": NewFunction(),
"NameStrindex/test": {NameStrindex: int32(13)},
"SystemNameStrindex/test": {SystemNameStrindex: int32(13)},
"FilenameStrindex/test": {FilenameStrindex: int32(13)},
"StartLine/test": {StartLine: int64(13)},
}
}
================================================
FILE: pdata/internal/generated_proto_gauge.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// Gauge represents the type of a numeric metric that always exports the "current value" for every data point.
type Gauge struct {
DataPoints []*NumberDataPoint
}
var (
protoPoolGauge = sync.Pool{
New: func() any {
return &Gauge{}
},
}
)
func NewGauge() *Gauge {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &Gauge{}
}
return protoPoolGauge.Get().(*Gauge)
}
func DeleteGauge(orig *Gauge, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
for i := range orig.DataPoints {
DeleteNumberDataPoint(orig.DataPoints[i], true)
}
orig.Reset()
if nullable {
protoPoolGauge.Put(orig)
}
}
func CopyGauge(dest, src *Gauge) *Gauge {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewGauge()
}
dest.DataPoints = CopyNumberDataPointPtrSlice(dest.DataPoints, src.DataPoints)
return dest
}
func CopyGaugeSlice(dest, src []Gauge) []Gauge {
var newDest []Gauge
if cap(dest) < len(src) {
newDest = make([]Gauge, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteGauge(&dest[i], false)
}
}
for i := range src {
CopyGauge(&newDest[i], &src[i])
}
return newDest
}
func CopyGaugePtrSlice(dest, src []*Gauge) []*Gauge {
var newDest []*Gauge
if cap(dest) < len(src) {
newDest = make([]*Gauge, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewGauge()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteGauge(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewGauge()
}
}
for i := range src {
CopyGauge(newDest[i], src[i])
}
return newDest
}
func (orig *Gauge) Reset() {
*orig = Gauge{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *Gauge) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if len(orig.DataPoints) > 0 {
dest.WriteObjectField("dataPoints")
dest.WriteArrayStart()
orig.DataPoints[0].MarshalJSON(dest)
for i := 1; i < len(orig.DataPoints); i++ {
dest.WriteMore()
orig.DataPoints[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *Gauge) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "dataPoints", "data_points":
for iter.ReadArray() {
orig.DataPoints = append(orig.DataPoints, NewNumberDataPoint())
orig.DataPoints[len(orig.DataPoints)-1].UnmarshalJSON(iter)
}
default:
iter.Skip()
}
}
}
func (orig *Gauge) SizeProto() int {
var n int
var l int
_ = l
for i := range orig.DataPoints {
l = orig.DataPoints[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *Gauge) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
for i := len(orig.DataPoints) - 1; i >= 0; i-- {
l = orig.DataPoints[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
}
return len(buf) - pos
}
func (orig *Gauge) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field DataPoints", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.DataPoints = append(orig.DataPoints, NewNumberDataPoint())
err = orig.DataPoints[len(orig.DataPoints)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestGauge() *Gauge {
orig := NewGauge()
orig.DataPoints = []*NumberDataPoint{{}, GenTestNumberDataPoint()}
return orig
}
func GenTestGaugePtrSlice() []*Gauge {
orig := make([]*Gauge, 5)
orig[0] = NewGauge()
orig[1] = GenTestGauge()
orig[2] = NewGauge()
orig[3] = GenTestGauge()
orig[4] = NewGauge()
return orig
}
func GenTestGaugeSlice() []Gauge {
orig := make([]Gauge, 5)
orig[1] = *GenTestGauge()
orig[3] = *GenTestGauge()
return orig
}
================================================
FILE: pdata/internal/generated_proto_gauge_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpmetrics "go.opentelemetry.io/proto/slim/otlp/metrics/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyGauge(t *testing.T) {
for name, src := range genTestEncodingValuesGauge() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewGauge()
CopyGauge(dest, src)
assert.Equal(t, src, dest)
CopyGauge(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyGaugeSlice(t *testing.T) {
src := []Gauge{}
dest := []Gauge{}
// Test CopyTo empty
dest = CopyGaugeSlice(dest, src)
assert.Equal(t, []Gauge{}, dest)
// Test CopyTo larger slice
src = GenTestGaugeSlice()
dest = CopyGaugeSlice(dest, src)
assert.Equal(t, GenTestGaugeSlice(), dest)
// Test CopyTo same size slice
dest = CopyGaugeSlice(dest, src)
assert.Equal(t, GenTestGaugeSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyGaugeSlice(dest, []Gauge{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyGaugeSlice(dest, src)
assert.Equal(t, GenTestGaugeSlice(), dest)
}
func TestCopyGaugePtrSlice(t *testing.T) {
src := []*Gauge{}
dest := []*Gauge{}
// Test CopyTo empty
dest = CopyGaugePtrSlice(dest, src)
assert.Equal(t, []*Gauge{}, dest)
// Test CopyTo larger slice
src = GenTestGaugePtrSlice()
dest = CopyGaugePtrSlice(dest, src)
assert.Equal(t, GenTestGaugePtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyGaugePtrSlice(dest, src)
assert.Equal(t, GenTestGaugePtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyGaugePtrSlice(dest, []*Gauge{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyGaugePtrSlice(dest, src)
assert.Equal(t, GenTestGaugePtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONGaugeUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewGauge()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewGauge(), dest)
}
func TestMarshalAndUnmarshalJSONGauge(t *testing.T) {
for name, src := range genTestEncodingValuesGauge() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewGauge()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteGauge(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoGaugeFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesGauge() {
t.Run(name, func(t *testing.T) {
dest := NewGauge()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoGaugeUnknown(t *testing.T) {
dest := NewGauge()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewGauge(), dest)
}
func TestMarshalAndUnmarshalProtoGauge(t *testing.T) {
for name, src := range genTestEncodingValuesGauge() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewGauge()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteGauge(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufGauge(t *testing.T) {
for name, src := range genTestEncodingValuesGauge() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpmetrics.Gauge{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewGauge()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesGauge() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"DataPoints/wrong_wire_type": {0xc},
"DataPoints/missing_value": {0xa},
}
}
func genTestEncodingValuesGauge() map[string]*Gauge {
return map[string]*Gauge{
"empty": NewGauge(),
"DataPoints/test": {DataPoints: []*NumberDataPoint{{}, GenTestNumberDataPoint()}},
}
}
================================================
FILE: pdata/internal/generated_proto_histogram.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// Histogram represents the type of a metric that is calculated by aggregating as a Histogram of all reported measurements over a time interval.
type Histogram struct {
DataPoints []*HistogramDataPoint
AggregationTemporality AggregationTemporality
}
var (
protoPoolHistogram = sync.Pool{
New: func() any {
return &Histogram{}
},
}
)
func NewHistogram() *Histogram {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &Histogram{}
}
return protoPoolHistogram.Get().(*Histogram)
}
func DeleteHistogram(orig *Histogram, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
for i := range orig.DataPoints {
DeleteHistogramDataPoint(orig.DataPoints[i], true)
}
orig.Reset()
if nullable {
protoPoolHistogram.Put(orig)
}
}
func CopyHistogram(dest, src *Histogram) *Histogram {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewHistogram()
}
dest.DataPoints = CopyHistogramDataPointPtrSlice(dest.DataPoints, src.DataPoints)
dest.AggregationTemporality = src.AggregationTemporality
return dest
}
func CopyHistogramSlice(dest, src []Histogram) []Histogram {
var newDest []Histogram
if cap(dest) < len(src) {
newDest = make([]Histogram, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteHistogram(&dest[i], false)
}
}
for i := range src {
CopyHistogram(&newDest[i], &src[i])
}
return newDest
}
func CopyHistogramPtrSlice(dest, src []*Histogram) []*Histogram {
var newDest []*Histogram
if cap(dest) < len(src) {
newDest = make([]*Histogram, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewHistogram()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteHistogram(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewHistogram()
}
}
for i := range src {
CopyHistogram(newDest[i], src[i])
}
return newDest
}
func (orig *Histogram) Reset() {
*orig = Histogram{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *Histogram) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if len(orig.DataPoints) > 0 {
dest.WriteObjectField("dataPoints")
dest.WriteArrayStart()
orig.DataPoints[0].MarshalJSON(dest)
for i := 1; i < len(orig.DataPoints); i++ {
dest.WriteMore()
orig.DataPoints[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if int32(orig.AggregationTemporality) != 0 {
dest.WriteObjectField("aggregationTemporality")
dest.WriteInt32(int32(orig.AggregationTemporality))
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *Histogram) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "dataPoints", "data_points":
for iter.ReadArray() {
orig.DataPoints = append(orig.DataPoints, NewHistogramDataPoint())
orig.DataPoints[len(orig.DataPoints)-1].UnmarshalJSON(iter)
}
case "aggregationTemporality", "aggregation_temporality":
orig.AggregationTemporality = AggregationTemporality(iter.ReadEnumValue(AggregationTemporality_value))
default:
iter.Skip()
}
}
}
func (orig *Histogram) SizeProto() int {
var n int
var l int
_ = l
for i := range orig.DataPoints {
l = orig.DataPoints[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
if orig.AggregationTemporality != AggregationTemporality(0) {
n += 1 + proto.Sov(uint64(orig.AggregationTemporality))
}
return n
}
func (orig *Histogram) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
for i := len(orig.DataPoints) - 1; i >= 0; i-- {
l = orig.DataPoints[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
}
if orig.AggregationTemporality != AggregationTemporality(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.AggregationTemporality))
pos--
buf[pos] = 0x10
}
return len(buf) - pos
}
func (orig *Histogram) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field DataPoints", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.DataPoints = append(orig.DataPoints, NewHistogramDataPoint())
err = orig.DataPoints[len(orig.DataPoints)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 2:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field AggregationTemporality", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.AggregationTemporality = AggregationTemporality(num)
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestHistogram() *Histogram {
orig := NewHistogram()
orig.DataPoints = []*HistogramDataPoint{{}, GenTestHistogramDataPoint()}
orig.AggregationTemporality = AggregationTemporality(13)
return orig
}
func GenTestHistogramPtrSlice() []*Histogram {
orig := make([]*Histogram, 5)
orig[0] = NewHistogram()
orig[1] = GenTestHistogram()
orig[2] = NewHistogram()
orig[3] = GenTestHistogram()
orig[4] = NewHistogram()
return orig
}
func GenTestHistogramSlice() []Histogram {
orig := make([]Histogram, 5)
orig[1] = *GenTestHistogram()
orig[3] = *GenTestHistogram()
return orig
}
================================================
FILE: pdata/internal/generated_proto_histogram_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpmetrics "go.opentelemetry.io/proto/slim/otlp/metrics/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyHistogram(t *testing.T) {
for name, src := range genTestEncodingValuesHistogram() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewHistogram()
CopyHistogram(dest, src)
assert.Equal(t, src, dest)
CopyHistogram(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyHistogramSlice(t *testing.T) {
src := []Histogram{}
dest := []Histogram{}
// Test CopyTo empty
dest = CopyHistogramSlice(dest, src)
assert.Equal(t, []Histogram{}, dest)
// Test CopyTo larger slice
src = GenTestHistogramSlice()
dest = CopyHistogramSlice(dest, src)
assert.Equal(t, GenTestHistogramSlice(), dest)
// Test CopyTo same size slice
dest = CopyHistogramSlice(dest, src)
assert.Equal(t, GenTestHistogramSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyHistogramSlice(dest, []Histogram{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyHistogramSlice(dest, src)
assert.Equal(t, GenTestHistogramSlice(), dest)
}
func TestCopyHistogramPtrSlice(t *testing.T) {
src := []*Histogram{}
dest := []*Histogram{}
// Test CopyTo empty
dest = CopyHistogramPtrSlice(dest, src)
assert.Equal(t, []*Histogram{}, dest)
// Test CopyTo larger slice
src = GenTestHistogramPtrSlice()
dest = CopyHistogramPtrSlice(dest, src)
assert.Equal(t, GenTestHistogramPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyHistogramPtrSlice(dest, src)
assert.Equal(t, GenTestHistogramPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyHistogramPtrSlice(dest, []*Histogram{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyHistogramPtrSlice(dest, src)
assert.Equal(t, GenTestHistogramPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONHistogramUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewHistogram()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewHistogram(), dest)
}
func TestMarshalAndUnmarshalJSONHistogram(t *testing.T) {
for name, src := range genTestEncodingValuesHistogram() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewHistogram()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteHistogram(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoHistogramFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesHistogram() {
t.Run(name, func(t *testing.T) {
dest := NewHistogram()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoHistogramUnknown(t *testing.T) {
dest := NewHistogram()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewHistogram(), dest)
}
func TestMarshalAndUnmarshalProtoHistogram(t *testing.T) {
for name, src := range genTestEncodingValuesHistogram() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewHistogram()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteHistogram(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufHistogram(t *testing.T) {
for name, src := range genTestEncodingValuesHistogram() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpmetrics.Histogram{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewHistogram()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesHistogram() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"DataPoints/wrong_wire_type": {0xc},
"DataPoints/missing_value": {0xa},
"AggregationTemporality/wrong_wire_type": {0x14},
"AggregationTemporality/missing_value": {0x10},
}
}
func genTestEncodingValuesHistogram() map[string]*Histogram {
return map[string]*Histogram{
"empty": NewHistogram(),
"DataPoints/test": {DataPoints: []*HistogramDataPoint{{}, GenTestHistogramDataPoint()}},
"AggregationTemporality/test": {AggregationTemporality: AggregationTemporality(13)},
}
}
================================================
FILE: pdata/internal/generated_proto_histogramdatapoint.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"encoding/binary"
"fmt"
"math"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// HistogramDataPoint is a single data point in a timeseries that describes the time-varying values of a Histogram of values.
type HistogramDataPoint struct {
Attributes []KeyValue
BucketCounts []uint64
ExplicitBounds []float64
Exemplars []Exemplar
StartTimeUnixNano uint64
TimeUnixNano uint64
Count uint64
Sum float64
Min float64
Max float64
metadata [1]uint64
Flags uint32
}
var (
protoPoolHistogramDataPoint = sync.Pool{
New: func() any {
return &HistogramDataPoint{}
},
}
)
func NewHistogramDataPoint() *HistogramDataPoint {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &HistogramDataPoint{}
}
return protoPoolHistogramDataPoint.Get().(*HistogramDataPoint)
}
func DeleteHistogramDataPoint(orig *HistogramDataPoint, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
for i := range orig.Attributes {
DeleteKeyValue(&orig.Attributes[i], false)
}
for i := range orig.Exemplars {
DeleteExemplar(&orig.Exemplars[i], false)
}
orig.Reset()
if nullable {
protoPoolHistogramDataPoint.Put(orig)
}
}
func CopyHistogramDataPoint(dest, src *HistogramDataPoint) *HistogramDataPoint {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewHistogramDataPoint()
}
dest.Attributes = CopyKeyValueSlice(dest.Attributes, src.Attributes)
dest.StartTimeUnixNano = src.StartTimeUnixNano
dest.TimeUnixNano = src.TimeUnixNano
dest.Count = src.Count
if src.HasSum() {
dest.SetSum(src.Sum)
} else {
dest.RemoveSum()
}
dest.BucketCounts = append(dest.BucketCounts[:0], src.BucketCounts...)
dest.ExplicitBounds = append(dest.ExplicitBounds[:0], src.ExplicitBounds...)
dest.Exemplars = CopyExemplarSlice(dest.Exemplars, src.Exemplars)
dest.Flags = src.Flags
if src.HasMin() {
dest.SetMin(src.Min)
} else {
dest.RemoveMin()
}
if src.HasMax() {
dest.SetMax(src.Max)
} else {
dest.RemoveMax()
}
return dest
}
func CopyHistogramDataPointSlice(dest, src []HistogramDataPoint) []HistogramDataPoint {
var newDest []HistogramDataPoint
if cap(dest) < len(src) {
newDest = make([]HistogramDataPoint, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteHistogramDataPoint(&dest[i], false)
}
}
for i := range src {
CopyHistogramDataPoint(&newDest[i], &src[i])
}
return newDest
}
func CopyHistogramDataPointPtrSlice(dest, src []*HistogramDataPoint) []*HistogramDataPoint {
var newDest []*HistogramDataPoint
if cap(dest) < len(src) {
newDest = make([]*HistogramDataPoint, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewHistogramDataPoint()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteHistogramDataPoint(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewHistogramDataPoint()
}
}
for i := range src {
CopyHistogramDataPoint(newDest[i], src[i])
}
return newDest
}
func (orig *HistogramDataPoint) Reset() {
*orig = HistogramDataPoint{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *HistogramDataPoint) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if len(orig.Attributes) > 0 {
dest.WriteObjectField("attributes")
dest.WriteArrayStart()
orig.Attributes[0].MarshalJSON(dest)
for i := 1; i < len(orig.Attributes); i++ {
dest.WriteMore()
orig.Attributes[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if orig.StartTimeUnixNano != uint64(0) {
dest.WriteObjectField("startTimeUnixNano")
dest.WriteUint64(orig.StartTimeUnixNano)
}
if orig.TimeUnixNano != uint64(0) {
dest.WriteObjectField("timeUnixNano")
dest.WriteUint64(orig.TimeUnixNano)
}
if orig.Count != uint64(0) {
dest.WriteObjectField("count")
dest.WriteUint64(orig.Count)
}
if orig.HasSum() {
dest.WriteObjectField("sum")
dest.WriteFloat64(orig.Sum)
}
if len(orig.BucketCounts) > 0 {
dest.WriteObjectField("bucketCounts")
dest.WriteArrayStart()
dest.WriteUint64(orig.BucketCounts[0])
for i := 1; i < len(orig.BucketCounts); i++ {
dest.WriteMore()
dest.WriteUint64(orig.BucketCounts[i])
}
dest.WriteArrayEnd()
}
if len(orig.ExplicitBounds) > 0 {
dest.WriteObjectField("explicitBounds")
dest.WriteArrayStart()
dest.WriteFloat64(orig.ExplicitBounds[0])
for i := 1; i < len(orig.ExplicitBounds); i++ {
dest.WriteMore()
dest.WriteFloat64(orig.ExplicitBounds[i])
}
dest.WriteArrayEnd()
}
if len(orig.Exemplars) > 0 {
dest.WriteObjectField("exemplars")
dest.WriteArrayStart()
orig.Exemplars[0].MarshalJSON(dest)
for i := 1; i < len(orig.Exemplars); i++ {
dest.WriteMore()
orig.Exemplars[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if orig.Flags != uint32(0) {
dest.WriteObjectField("flags")
dest.WriteUint32(orig.Flags)
}
if orig.HasMin() {
dest.WriteObjectField("min")
dest.WriteFloat64(orig.Min)
}
if orig.HasMax() {
dest.WriteObjectField("max")
dest.WriteFloat64(orig.Max)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *HistogramDataPoint) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "attributes":
for iter.ReadArray() {
orig.Attributes = append(orig.Attributes, KeyValue{})
orig.Attributes[len(orig.Attributes)-1].UnmarshalJSON(iter)
}
case "startTimeUnixNano", "start_time_unix_nano":
orig.StartTimeUnixNano = iter.ReadUint64()
case "timeUnixNano", "time_unix_nano":
orig.TimeUnixNano = iter.ReadUint64()
case "count":
orig.Count = iter.ReadUint64()
case "sum":
orig.SetSum(iter.ReadFloat64())
case "bucketCounts", "bucket_counts":
for iter.ReadArray() {
orig.BucketCounts = append(orig.BucketCounts, iter.ReadUint64())
}
case "explicitBounds", "explicit_bounds":
for iter.ReadArray() {
orig.ExplicitBounds = append(orig.ExplicitBounds, iter.ReadFloat64())
}
case "exemplars":
for iter.ReadArray() {
orig.Exemplars = append(orig.Exemplars, Exemplar{})
orig.Exemplars[len(orig.Exemplars)-1].UnmarshalJSON(iter)
}
case "flags":
orig.Flags = iter.ReadUint32()
case "min":
orig.SetMin(iter.ReadFloat64())
case "max":
orig.SetMax(iter.ReadFloat64())
default:
iter.Skip()
}
}
}
func (orig *HistogramDataPoint) SizeProto() int {
var n int
var l int
_ = l
for i := range orig.Attributes {
l = orig.Attributes[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
if orig.StartTimeUnixNano != uint64(0) {
n += 9
}
if orig.TimeUnixNano != uint64(0) {
n += 9
}
if orig.Count != uint64(0) {
n += 9
}
if orig.HasSum() {
n += 9
}
l = len(orig.BucketCounts)
if l > 0 {
l *= 8
n += 1 + proto.Sov(uint64(l)) + l
}
l = len(orig.ExplicitBounds)
if l > 0 {
l *= 8
n += 1 + proto.Sov(uint64(l)) + l
}
for i := range orig.Exemplars {
l = orig.Exemplars[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
if orig.Flags != uint32(0) {
n += 1 + proto.Sov(uint64(orig.Flags))
}
if orig.HasMin() {
n += 9
}
if orig.HasMax() {
n += 9
}
return n
}
func (orig *HistogramDataPoint) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
for i := len(orig.Attributes) - 1; i >= 0; i-- {
l = orig.Attributes[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x4a
}
if orig.StartTimeUnixNano != uint64(0) {
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], uint64(orig.StartTimeUnixNano))
pos--
buf[pos] = 0x11
}
if orig.TimeUnixNano != uint64(0) {
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], uint64(orig.TimeUnixNano))
pos--
buf[pos] = 0x19
}
if orig.Count != uint64(0) {
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], uint64(orig.Count))
pos--
buf[pos] = 0x21
}
if orig.HasSum() {
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], math.Float64bits(orig.Sum))
pos--
buf[pos] = 0x29
}
l = len(orig.BucketCounts)
if l > 0 {
for i := l - 1; i >= 0; i-- {
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], uint64(orig.BucketCounts[i]))
}
pos = proto.EncodeVarint(buf, pos, uint64(l*8))
pos--
buf[pos] = 0x32
}
l = len(orig.ExplicitBounds)
if l > 0 {
for i := l - 1; i >= 0; i-- {
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], math.Float64bits(orig.ExplicitBounds[i]))
}
pos = proto.EncodeVarint(buf, pos, uint64(l*8))
pos--
buf[pos] = 0x3a
}
for i := len(orig.Exemplars) - 1; i >= 0; i-- {
l = orig.Exemplars[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x42
}
if orig.Flags != uint32(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.Flags))
pos--
buf[pos] = 0x50
}
if orig.HasMin() {
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], math.Float64bits(orig.Min))
pos--
buf[pos] = 0x59
}
if orig.HasMax() {
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], math.Float64bits(orig.Max))
pos--
buf[pos] = 0x61
}
return len(buf) - pos
}
func (orig *HistogramDataPoint) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 9:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Attributes", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Attributes = append(orig.Attributes, KeyValue{})
err = orig.Attributes[len(orig.Attributes)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 2:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field StartTimeUnixNano", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
orig.StartTimeUnixNano = uint64(num)
case 3:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field TimeUnixNano", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
orig.TimeUnixNano = uint64(num)
case 4:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field Count", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
orig.Count = uint64(num)
case 5:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field Sum", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
orig.SetSum(math.Float64frombits(num))
case 6:
switch wireType {
case proto.WireTypeLen:
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
size := length / 8
orig.BucketCounts = make([]uint64, size)
var num uint64
for i := 0; i < size; i++ {
num, startPos, err = proto.ConsumeI64(buf[:pos], startPos)
if err != nil {
return err
}
orig.BucketCounts[i] = uint64(num)
}
if startPos != pos {
return fmt.Errorf("proto: invalid field len = %d for field BucketCounts", pos-startPos)
}
case proto.WireTypeI64:
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
orig.BucketCounts = append(orig.BucketCounts, uint64(num))
default:
return fmt.Errorf("proto: wrong wireType = %d for field BucketCounts", wireType)
}
case 7:
switch wireType {
case proto.WireTypeLen:
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
size := length / 8
orig.ExplicitBounds = make([]float64, size)
var num uint64
for i := 0; i < size; i++ {
num, startPos, err = proto.ConsumeI64(buf[:pos], startPos)
if err != nil {
return err
}
orig.ExplicitBounds[i] = math.Float64frombits(num)
}
if startPos != pos {
return fmt.Errorf("proto: invalid field len = %d for field ExplicitBounds", pos-startPos)
}
case proto.WireTypeI64:
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
orig.ExplicitBounds = append(orig.ExplicitBounds, math.Float64frombits(num))
default:
return fmt.Errorf("proto: wrong wireType = %d for field ExplicitBounds", wireType)
}
case 8:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Exemplars", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Exemplars = append(orig.Exemplars, Exemplar{})
err = orig.Exemplars[len(orig.Exemplars)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 10:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field Flags", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.Flags = uint32(num)
case 11:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field Min", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
orig.SetMin(math.Float64frombits(num))
case 12:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field Max", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
orig.SetMax(math.Float64frombits(num))
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
const fieldBlockHistogramDataPointSum = uint64(0 >> 6)
const fieldBitHistogramDataPointSum = uint64(1 << 0 & 0x3F)
func (m *HistogramDataPoint) SetSum(value float64) {
m.Sum = value
m.metadata[fieldBlockHistogramDataPointSum] |= fieldBitHistogramDataPointSum
}
func (m *HistogramDataPoint) RemoveSum() {
m.Sum = float64(0)
m.metadata[fieldBlockHistogramDataPointSum] &^= fieldBitHistogramDataPointSum
}
func (m *HistogramDataPoint) HasSum() bool {
return m.metadata[fieldBlockHistogramDataPointSum]&fieldBitHistogramDataPointSum != 0
}
const fieldBlockHistogramDataPointMin = uint64(1 >> 6)
const fieldBitHistogramDataPointMin = uint64(1 << 1 & 0x3F)
func (m *HistogramDataPoint) SetMin(value float64) {
m.Min = value
m.metadata[fieldBlockHistogramDataPointMin] |= fieldBitHistogramDataPointMin
}
func (m *HistogramDataPoint) RemoveMin() {
m.Min = float64(0)
m.metadata[fieldBlockHistogramDataPointMin] &^= fieldBitHistogramDataPointMin
}
func (m *HistogramDataPoint) HasMin() bool {
return m.metadata[fieldBlockHistogramDataPointMin]&fieldBitHistogramDataPointMin != 0
}
const fieldBlockHistogramDataPointMax = uint64(2 >> 6)
const fieldBitHistogramDataPointMax = uint64(1 << 2 & 0x3F)
func (m *HistogramDataPoint) SetMax(value float64) {
m.Max = value
m.metadata[fieldBlockHistogramDataPointMax] |= fieldBitHistogramDataPointMax
}
func (m *HistogramDataPoint) RemoveMax() {
m.Max = float64(0)
m.metadata[fieldBlockHistogramDataPointMax] &^= fieldBitHistogramDataPointMax
}
func (m *HistogramDataPoint) HasMax() bool {
return m.metadata[fieldBlockHistogramDataPointMax]&fieldBitHistogramDataPointMax != 0
}
func GenTestHistogramDataPoint() *HistogramDataPoint {
orig := NewHistogramDataPoint()
orig.Attributes = []KeyValue{{}, *GenTestKeyValue()}
orig.StartTimeUnixNano = uint64(13)
orig.TimeUnixNano = uint64(13)
orig.Count = uint64(13)
orig.SetSum(float64(3.1415926))
orig.BucketCounts = []uint64{uint64(0), uint64(13)}
orig.ExplicitBounds = []float64{float64(0), float64(3.1415926)}
orig.Exemplars = []Exemplar{{}, *GenTestExemplar()}
orig.Flags = uint32(13)
orig.SetMin(float64(3.1415926))
orig.SetMax(float64(3.1415926))
return orig
}
func GenTestHistogramDataPointPtrSlice() []*HistogramDataPoint {
orig := make([]*HistogramDataPoint, 5)
orig[0] = NewHistogramDataPoint()
orig[1] = GenTestHistogramDataPoint()
orig[2] = NewHistogramDataPoint()
orig[3] = GenTestHistogramDataPoint()
orig[4] = NewHistogramDataPoint()
return orig
}
func GenTestHistogramDataPointSlice() []HistogramDataPoint {
orig := make([]HistogramDataPoint, 5)
orig[1] = *GenTestHistogramDataPoint()
orig[3] = *GenTestHistogramDataPoint()
return orig
}
================================================
FILE: pdata/internal/generated_proto_histogramdatapoint_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpmetrics "go.opentelemetry.io/proto/slim/otlp/metrics/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyHistogramDataPoint(t *testing.T) {
for name, src := range genTestEncodingValuesHistogramDataPoint() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewHistogramDataPoint()
CopyHistogramDataPoint(dest, src)
assert.Equal(t, src, dest)
CopyHistogramDataPoint(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyHistogramDataPointSlice(t *testing.T) {
src := []HistogramDataPoint{}
dest := []HistogramDataPoint{}
// Test CopyTo empty
dest = CopyHistogramDataPointSlice(dest, src)
assert.Equal(t, []HistogramDataPoint{}, dest)
// Test CopyTo larger slice
src = GenTestHistogramDataPointSlice()
dest = CopyHistogramDataPointSlice(dest, src)
assert.Equal(t, GenTestHistogramDataPointSlice(), dest)
// Test CopyTo same size slice
dest = CopyHistogramDataPointSlice(dest, src)
assert.Equal(t, GenTestHistogramDataPointSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyHistogramDataPointSlice(dest, []HistogramDataPoint{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyHistogramDataPointSlice(dest, src)
assert.Equal(t, GenTestHistogramDataPointSlice(), dest)
}
func TestCopyHistogramDataPointPtrSlice(t *testing.T) {
src := []*HistogramDataPoint{}
dest := []*HistogramDataPoint{}
// Test CopyTo empty
dest = CopyHistogramDataPointPtrSlice(dest, src)
assert.Equal(t, []*HistogramDataPoint{}, dest)
// Test CopyTo larger slice
src = GenTestHistogramDataPointPtrSlice()
dest = CopyHistogramDataPointPtrSlice(dest, src)
assert.Equal(t, GenTestHistogramDataPointPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyHistogramDataPointPtrSlice(dest, src)
assert.Equal(t, GenTestHistogramDataPointPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyHistogramDataPointPtrSlice(dest, []*HistogramDataPoint{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyHistogramDataPointPtrSlice(dest, src)
assert.Equal(t, GenTestHistogramDataPointPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONHistogramDataPointUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewHistogramDataPoint()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewHistogramDataPoint(), dest)
}
func TestMarshalAndUnmarshalJSONHistogramDataPoint(t *testing.T) {
for name, src := range genTestEncodingValuesHistogramDataPoint() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewHistogramDataPoint()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteHistogramDataPoint(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoHistogramDataPointFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesHistogramDataPoint() {
t.Run(name, func(t *testing.T) {
dest := NewHistogramDataPoint()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoHistogramDataPointUnknown(t *testing.T) {
dest := NewHistogramDataPoint()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewHistogramDataPoint(), dest)
}
func TestMarshalAndUnmarshalProtoHistogramDataPoint(t *testing.T) {
for name, src := range genTestEncodingValuesHistogramDataPoint() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewHistogramDataPoint()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteHistogramDataPoint(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufHistogramDataPoint(t *testing.T) {
for name, src := range genTestEncodingValuesHistogramDataPoint() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpmetrics.HistogramDataPoint{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewHistogramDataPoint()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesHistogramDataPoint() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"Attributes/wrong_wire_type": {0x4c},
"Attributes/missing_value": {0x4a},
"StartTimeUnixNano/wrong_wire_type": {0x14},
"StartTimeUnixNano/missing_value": {0x11},
"TimeUnixNano/wrong_wire_type": {0x1c},
"TimeUnixNano/missing_value": {0x19},
"Count/wrong_wire_type": {0x24},
"Count/missing_value": {0x21},
"Sum/wrong_wire_type": {0x2c},
"Sum/missing_value": {0x29},
"BucketCounts/wrong_wire_type": {0x34},
"BucketCounts/missing_value": {0x32},
"ExplicitBounds/wrong_wire_type": {0x3c},
"ExplicitBounds/missing_value": {0x3a},
"Exemplars/wrong_wire_type": {0x44},
"Exemplars/missing_value": {0x42},
"Flags/wrong_wire_type": {0x54},
"Flags/missing_value": {0x50},
"Min/wrong_wire_type": {0x5c},
"Min/missing_value": {0x59},
"Max/wrong_wire_type": {0x64},
"Max/missing_value": {0x61},
}
}
func genTestEncodingValuesHistogramDataPoint() map[string]*HistogramDataPoint {
return map[string]*HistogramDataPoint{
"empty": NewHistogramDataPoint(),
"Attributes/test": {Attributes: []KeyValue{{}, *GenTestKeyValue()}},
"StartTimeUnixNano/test": {StartTimeUnixNano: uint64(13)},
"TimeUnixNano/test": {TimeUnixNano: uint64(13)},
"Count/test": {Count: uint64(13)},
"Sum/test": func() *HistogramDataPoint {
ms := NewHistogramDataPoint()
ms.SetSum(float64(3.1415926))
return ms
}(),
"BucketCounts/test": {BucketCounts: []uint64{uint64(0), uint64(13)}},
"ExplicitBounds/test": {ExplicitBounds: []float64{float64(0), float64(3.1415926)}},
"Exemplars/test": {Exemplars: []Exemplar{{}, *GenTestExemplar()}},
"Flags/test": {Flags: uint32(13)},
"Min/test": func() *HistogramDataPoint {
ms := NewHistogramDataPoint()
ms.SetMin(float64(3.1415926))
return ms
}(),
"Max/test": func() *HistogramDataPoint {
ms := NewHistogramDataPoint()
ms.SetMax(float64(3.1415926))
return ms
}(),
}
}
================================================
FILE: pdata/internal/generated_proto_instrumentationscope.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// InstrumentationScope is a message representing the instrumentation scope information.
type InstrumentationScope struct {
Name string
Version string
Attributes []KeyValue
DroppedAttributesCount uint32
}
var (
protoPoolInstrumentationScope = sync.Pool{
New: func() any {
return &InstrumentationScope{}
},
}
)
func NewInstrumentationScope() *InstrumentationScope {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &InstrumentationScope{}
}
return protoPoolInstrumentationScope.Get().(*InstrumentationScope)
}
func DeleteInstrumentationScope(orig *InstrumentationScope, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
for i := range orig.Attributes {
DeleteKeyValue(&orig.Attributes[i], false)
}
orig.Reset()
if nullable {
protoPoolInstrumentationScope.Put(orig)
}
}
func CopyInstrumentationScope(dest, src *InstrumentationScope) *InstrumentationScope {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewInstrumentationScope()
}
dest.Name = src.Name
dest.Version = src.Version
dest.Attributes = CopyKeyValueSlice(dest.Attributes, src.Attributes)
dest.DroppedAttributesCount = src.DroppedAttributesCount
return dest
}
func CopyInstrumentationScopeSlice(dest, src []InstrumentationScope) []InstrumentationScope {
var newDest []InstrumentationScope
if cap(dest) < len(src) {
newDest = make([]InstrumentationScope, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteInstrumentationScope(&dest[i], false)
}
}
for i := range src {
CopyInstrumentationScope(&newDest[i], &src[i])
}
return newDest
}
func CopyInstrumentationScopePtrSlice(dest, src []*InstrumentationScope) []*InstrumentationScope {
var newDest []*InstrumentationScope
if cap(dest) < len(src) {
newDest = make([]*InstrumentationScope, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewInstrumentationScope()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteInstrumentationScope(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewInstrumentationScope()
}
}
for i := range src {
CopyInstrumentationScope(newDest[i], src[i])
}
return newDest
}
func (orig *InstrumentationScope) Reset() {
*orig = InstrumentationScope{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *InstrumentationScope) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if orig.Name != "" {
dest.WriteObjectField("name")
dest.WriteString(orig.Name)
}
if orig.Version != "" {
dest.WriteObjectField("version")
dest.WriteString(orig.Version)
}
if len(orig.Attributes) > 0 {
dest.WriteObjectField("attributes")
dest.WriteArrayStart()
orig.Attributes[0].MarshalJSON(dest)
for i := 1; i < len(orig.Attributes); i++ {
dest.WriteMore()
orig.Attributes[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if orig.DroppedAttributesCount != uint32(0) {
dest.WriteObjectField("droppedAttributesCount")
dest.WriteUint32(orig.DroppedAttributesCount)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *InstrumentationScope) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "name":
orig.Name = iter.ReadString()
case "version":
orig.Version = iter.ReadString()
case "attributes":
for iter.ReadArray() {
orig.Attributes = append(orig.Attributes, KeyValue{})
orig.Attributes[len(orig.Attributes)-1].UnmarshalJSON(iter)
}
case "droppedAttributesCount", "dropped_attributes_count":
orig.DroppedAttributesCount = iter.ReadUint32()
default:
iter.Skip()
}
}
}
func (orig *InstrumentationScope) SizeProto() int {
var n int
var l int
_ = l
l = len(orig.Name)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
l = len(orig.Version)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
for i := range orig.Attributes {
l = orig.Attributes[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
if orig.DroppedAttributesCount != uint32(0) {
n += 1 + proto.Sov(uint64(orig.DroppedAttributesCount))
}
return n
}
func (orig *InstrumentationScope) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
l = len(orig.Name)
if l > 0 {
pos -= l
copy(buf[pos:], orig.Name)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
}
l = len(orig.Version)
if l > 0 {
pos -= l
copy(buf[pos:], orig.Version)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
}
for i := len(orig.Attributes) - 1; i >= 0; i-- {
l = orig.Attributes[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x1a
}
if orig.DroppedAttributesCount != uint32(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.DroppedAttributesCount))
pos--
buf[pos] = 0x20
}
return len(buf) - pos
}
func (orig *InstrumentationScope) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Name = string(buf[startPos:pos])
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Version = string(buf[startPos:pos])
case 3:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Attributes", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Attributes = append(orig.Attributes, KeyValue{})
err = orig.Attributes[len(orig.Attributes)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 4:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field DroppedAttributesCount", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.DroppedAttributesCount = uint32(num)
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestInstrumentationScope() *InstrumentationScope {
orig := NewInstrumentationScope()
orig.Name = "test_name"
orig.Version = "test_version"
orig.Attributes = []KeyValue{{}, *GenTestKeyValue()}
orig.DroppedAttributesCount = uint32(13)
return orig
}
func GenTestInstrumentationScopePtrSlice() []*InstrumentationScope {
orig := make([]*InstrumentationScope, 5)
orig[0] = NewInstrumentationScope()
orig[1] = GenTestInstrumentationScope()
orig[2] = NewInstrumentationScope()
orig[3] = GenTestInstrumentationScope()
orig[4] = NewInstrumentationScope()
return orig
}
func GenTestInstrumentationScopeSlice() []InstrumentationScope {
orig := make([]InstrumentationScope, 5)
orig[1] = *GenTestInstrumentationScope()
orig[3] = *GenTestInstrumentationScope()
return orig
}
================================================
FILE: pdata/internal/generated_proto_instrumentationscope_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpcommon "go.opentelemetry.io/proto/slim/otlp/common/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyInstrumentationScope(t *testing.T) {
for name, src := range genTestEncodingValuesInstrumentationScope() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewInstrumentationScope()
CopyInstrumentationScope(dest, src)
assert.Equal(t, src, dest)
CopyInstrumentationScope(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyInstrumentationScopeSlice(t *testing.T) {
src := []InstrumentationScope{}
dest := []InstrumentationScope{}
// Test CopyTo empty
dest = CopyInstrumentationScopeSlice(dest, src)
assert.Equal(t, []InstrumentationScope{}, dest)
// Test CopyTo larger slice
src = GenTestInstrumentationScopeSlice()
dest = CopyInstrumentationScopeSlice(dest, src)
assert.Equal(t, GenTestInstrumentationScopeSlice(), dest)
// Test CopyTo same size slice
dest = CopyInstrumentationScopeSlice(dest, src)
assert.Equal(t, GenTestInstrumentationScopeSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyInstrumentationScopeSlice(dest, []InstrumentationScope{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyInstrumentationScopeSlice(dest, src)
assert.Equal(t, GenTestInstrumentationScopeSlice(), dest)
}
func TestCopyInstrumentationScopePtrSlice(t *testing.T) {
src := []*InstrumentationScope{}
dest := []*InstrumentationScope{}
// Test CopyTo empty
dest = CopyInstrumentationScopePtrSlice(dest, src)
assert.Equal(t, []*InstrumentationScope{}, dest)
// Test CopyTo larger slice
src = GenTestInstrumentationScopePtrSlice()
dest = CopyInstrumentationScopePtrSlice(dest, src)
assert.Equal(t, GenTestInstrumentationScopePtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyInstrumentationScopePtrSlice(dest, src)
assert.Equal(t, GenTestInstrumentationScopePtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyInstrumentationScopePtrSlice(dest, []*InstrumentationScope{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyInstrumentationScopePtrSlice(dest, src)
assert.Equal(t, GenTestInstrumentationScopePtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONInstrumentationScopeUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewInstrumentationScope()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewInstrumentationScope(), dest)
}
func TestMarshalAndUnmarshalJSONInstrumentationScope(t *testing.T) {
for name, src := range genTestEncodingValuesInstrumentationScope() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewInstrumentationScope()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteInstrumentationScope(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoInstrumentationScopeFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesInstrumentationScope() {
t.Run(name, func(t *testing.T) {
dest := NewInstrumentationScope()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoInstrumentationScopeUnknown(t *testing.T) {
dest := NewInstrumentationScope()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewInstrumentationScope(), dest)
}
func TestMarshalAndUnmarshalProtoInstrumentationScope(t *testing.T) {
for name, src := range genTestEncodingValuesInstrumentationScope() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewInstrumentationScope()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteInstrumentationScope(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufInstrumentationScope(t *testing.T) {
for name, src := range genTestEncodingValuesInstrumentationScope() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpcommon.InstrumentationScope{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewInstrumentationScope()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesInstrumentationScope() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"Name/wrong_wire_type": {0xc},
"Name/missing_value": {0xa},
"Version/wrong_wire_type": {0x14},
"Version/missing_value": {0x12},
"Attributes/wrong_wire_type": {0x1c},
"Attributes/missing_value": {0x1a},
"DroppedAttributesCount/wrong_wire_type": {0x24},
"DroppedAttributesCount/missing_value": {0x20},
}
}
func genTestEncodingValuesInstrumentationScope() map[string]*InstrumentationScope {
return map[string]*InstrumentationScope{
"empty": NewInstrumentationScope(),
"Name/test": {Name: "test_name"},
"Version/test": {Version: "test_version"},
"Attributes/test": {Attributes: []KeyValue{{}, *GenTestKeyValue()}},
"DroppedAttributesCount/test": {DroppedAttributesCount: uint32(13)},
}
}
================================================
FILE: pdata/internal/generated_proto_ipaddr.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
type IPAddr struct {
Zone string
IP []byte
}
var (
protoPoolIPAddr = sync.Pool{
New: func() any {
return &IPAddr{}
},
}
)
func NewIPAddr() *IPAddr {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &IPAddr{}
}
return protoPoolIPAddr.Get().(*IPAddr)
}
func DeleteIPAddr(orig *IPAddr, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
orig.Reset()
if nullable {
protoPoolIPAddr.Put(orig)
}
}
func CopyIPAddr(dest, src *IPAddr) *IPAddr {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewIPAddr()
}
dest.IP = src.IP
dest.Zone = src.Zone
return dest
}
func CopyIPAddrSlice(dest, src []IPAddr) []IPAddr {
var newDest []IPAddr
if cap(dest) < len(src) {
newDest = make([]IPAddr, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteIPAddr(&dest[i], false)
}
}
for i := range src {
CopyIPAddr(&newDest[i], &src[i])
}
return newDest
}
func CopyIPAddrPtrSlice(dest, src []*IPAddr) []*IPAddr {
var newDest []*IPAddr
if cap(dest) < len(src) {
newDest = make([]*IPAddr, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewIPAddr()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteIPAddr(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewIPAddr()
}
}
for i := range src {
CopyIPAddr(newDest[i], src[i])
}
return newDest
}
func (orig *IPAddr) Reset() {
*orig = IPAddr{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *IPAddr) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if len(orig.IP) > 0 {
dest.WriteObjectField("iP")
dest.WriteBytes(orig.IP)
}
if orig.Zone != "" {
dest.WriteObjectField("zone")
dest.WriteString(orig.Zone)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *IPAddr) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "iP":
orig.IP = iter.ReadBytes()
case "zone":
orig.Zone = iter.ReadString()
default:
iter.Skip()
}
}
}
func (orig *IPAddr) SizeProto() int {
var n int
var l int
_ = l
l = len(orig.IP)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
l = len(orig.Zone)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *IPAddr) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
l = len(orig.IP)
if l > 0 {
pos -= l
copy(buf[pos:], orig.IP)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
}
l = len(orig.Zone)
if l > 0 {
pos -= l
copy(buf[pos:], orig.Zone)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
}
return len(buf) - pos
}
func (orig *IPAddr) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field IP", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
if length != 0 {
orig.IP = make([]byte, length)
copy(orig.IP, buf[startPos:pos])
}
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Zone", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Zone = string(buf[startPos:pos])
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestIPAddr() *IPAddr {
orig := NewIPAddr()
orig.IP = []byte{1, 2, 3}
orig.Zone = "test_zone"
return orig
}
func GenTestIPAddrPtrSlice() []*IPAddr {
orig := make([]*IPAddr, 5)
orig[0] = NewIPAddr()
orig[1] = GenTestIPAddr()
orig[2] = NewIPAddr()
orig[3] = GenTestIPAddr()
orig[4] = NewIPAddr()
return orig
}
func GenTestIPAddrSlice() []IPAddr {
orig := make([]IPAddr, 5)
orig[1] = *GenTestIPAddr()
orig[3] = *GenTestIPAddr()
return orig
}
================================================
FILE: pdata/internal/generated_proto_ipaddr_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/emptypb"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyIPAddr(t *testing.T) {
for name, src := range genTestEncodingValuesIPAddr() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewIPAddr()
CopyIPAddr(dest, src)
assert.Equal(t, src, dest)
CopyIPAddr(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyIPAddrSlice(t *testing.T) {
src := []IPAddr{}
dest := []IPAddr{}
// Test CopyTo empty
dest = CopyIPAddrSlice(dest, src)
assert.Equal(t, []IPAddr{}, dest)
// Test CopyTo larger slice
src = GenTestIPAddrSlice()
dest = CopyIPAddrSlice(dest, src)
assert.Equal(t, GenTestIPAddrSlice(), dest)
// Test CopyTo same size slice
dest = CopyIPAddrSlice(dest, src)
assert.Equal(t, GenTestIPAddrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyIPAddrSlice(dest, []IPAddr{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyIPAddrSlice(dest, src)
assert.Equal(t, GenTestIPAddrSlice(), dest)
}
func TestCopyIPAddrPtrSlice(t *testing.T) {
src := []*IPAddr{}
dest := []*IPAddr{}
// Test CopyTo empty
dest = CopyIPAddrPtrSlice(dest, src)
assert.Equal(t, []*IPAddr{}, dest)
// Test CopyTo larger slice
src = GenTestIPAddrPtrSlice()
dest = CopyIPAddrPtrSlice(dest, src)
assert.Equal(t, GenTestIPAddrPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyIPAddrPtrSlice(dest, src)
assert.Equal(t, GenTestIPAddrPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyIPAddrPtrSlice(dest, []*IPAddr{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyIPAddrPtrSlice(dest, src)
assert.Equal(t, GenTestIPAddrPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONIPAddrUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewIPAddr()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewIPAddr(), dest)
}
func TestMarshalAndUnmarshalJSONIPAddr(t *testing.T) {
for name, src := range genTestEncodingValuesIPAddr() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewIPAddr()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteIPAddr(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoIPAddrFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesIPAddr() {
t.Run(name, func(t *testing.T) {
dest := NewIPAddr()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoIPAddrUnknown(t *testing.T) {
dest := NewIPAddr()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewIPAddr(), dest)
}
func TestMarshalAndUnmarshalProtoIPAddr(t *testing.T) {
for name, src := range genTestEncodingValuesIPAddr() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewIPAddr()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteIPAddr(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufIPAddr(t *testing.T) {
for name, src := range genTestEncodingValuesIPAddr() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &emptypb.Empty{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewIPAddr()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesIPAddr() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"IP/wrong_wire_type": {0xc},
"IP/missing_value": {0xa},
"Zone/wrong_wire_type": {0x14},
"Zone/missing_value": {0x12},
}
}
func genTestEncodingValuesIPAddr() map[string]*IPAddr {
return map[string]*IPAddr{
"empty": NewIPAddr(),
"IP/test": {IP: []byte{1, 2, 3}},
"Zone/test": {Zone: "test_zone"},
}
}
================================================
FILE: pdata/internal/generated_proto_keyvalue.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
type KeyValue struct {
Value AnyValue
Key string
KeyStrindex int32
}
var (
protoPoolKeyValue = sync.Pool{
New: func() any {
return &KeyValue{}
},
}
)
func NewKeyValue() *KeyValue {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &KeyValue{}
}
return protoPoolKeyValue.Get().(*KeyValue)
}
func DeleteKeyValue(orig *KeyValue, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
DeleteAnyValue(&orig.Value, false)
orig.Reset()
if nullable {
protoPoolKeyValue.Put(orig)
}
}
func CopyKeyValue(dest, src *KeyValue) *KeyValue {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewKeyValue()
}
dest.Key = src.Key
CopyAnyValue(&dest.Value, &src.Value)
dest.KeyStrindex = src.KeyStrindex
return dest
}
func CopyKeyValueSlice(dest, src []KeyValue) []KeyValue {
var newDest []KeyValue
if cap(dest) < len(src) {
newDest = make([]KeyValue, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteKeyValue(&dest[i], false)
}
}
for i := range src {
CopyKeyValue(&newDest[i], &src[i])
}
return newDest
}
func CopyKeyValuePtrSlice(dest, src []*KeyValue) []*KeyValue {
var newDest []*KeyValue
if cap(dest) < len(src) {
newDest = make([]*KeyValue, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewKeyValue()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteKeyValue(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewKeyValue()
}
}
for i := range src {
CopyKeyValue(newDest[i], src[i])
}
return newDest
}
func (orig *KeyValue) Reset() {
*orig = KeyValue{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *KeyValue) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if orig.Key != "" {
dest.WriteObjectField("key")
dest.WriteString(orig.Key)
}
dest.WriteObjectField("value")
orig.Value.MarshalJSON(dest)
if orig.KeyStrindex != int32(0) {
dest.WriteObjectField("keyStrindex")
dest.WriteInt32(orig.KeyStrindex)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *KeyValue) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "key":
orig.Key = iter.ReadString()
case "value":
orig.Value.UnmarshalJSON(iter)
case "keyStrindex", "key_strindex":
orig.KeyStrindex = iter.ReadInt32()
default:
iter.Skip()
}
}
}
func (orig *KeyValue) SizeProto() int {
var n int
var l int
_ = l
l = len(orig.Key)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
l = orig.Value.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
if orig.KeyStrindex != int32(0) {
n += 1 + proto.Sov(uint64(orig.KeyStrindex))
}
return n
}
func (orig *KeyValue) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
l = len(orig.Key)
if l > 0 {
pos -= l
copy(buf[pos:], orig.Key)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
}
l = orig.Value.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
if orig.KeyStrindex != int32(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.KeyStrindex))
pos--
buf[pos] = 0x18
}
return len(buf) - pos
}
func (orig *KeyValue) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Key = string(buf[startPos:pos])
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.Value.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 3:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field KeyStrindex", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.KeyStrindex = int32(num)
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestKeyValue() *KeyValue {
orig := NewKeyValue()
orig.Key = "test_key"
orig.Value = *GenTestAnyValue()
orig.KeyStrindex = int32(13)
return orig
}
func GenTestKeyValuePtrSlice() []*KeyValue {
orig := make([]*KeyValue, 5)
orig[0] = NewKeyValue()
orig[1] = GenTestKeyValue()
orig[2] = NewKeyValue()
orig[3] = GenTestKeyValue()
orig[4] = NewKeyValue()
return orig
}
func GenTestKeyValueSlice() []KeyValue {
orig := make([]KeyValue, 5)
orig[1] = *GenTestKeyValue()
orig[3] = *GenTestKeyValue()
return orig
}
================================================
FILE: pdata/internal/generated_proto_keyvalue_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpcommon "go.opentelemetry.io/proto/slim/otlp/common/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyKeyValue(t *testing.T) {
for name, src := range genTestEncodingValuesKeyValue() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewKeyValue()
CopyKeyValue(dest, src)
assert.Equal(t, src, dest)
CopyKeyValue(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyKeyValueSlice(t *testing.T) {
src := []KeyValue{}
dest := []KeyValue{}
// Test CopyTo empty
dest = CopyKeyValueSlice(dest, src)
assert.Equal(t, []KeyValue{}, dest)
// Test CopyTo larger slice
src = GenTestKeyValueSlice()
dest = CopyKeyValueSlice(dest, src)
assert.Equal(t, GenTestKeyValueSlice(), dest)
// Test CopyTo same size slice
dest = CopyKeyValueSlice(dest, src)
assert.Equal(t, GenTestKeyValueSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyKeyValueSlice(dest, []KeyValue{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyKeyValueSlice(dest, src)
assert.Equal(t, GenTestKeyValueSlice(), dest)
}
func TestCopyKeyValuePtrSlice(t *testing.T) {
src := []*KeyValue{}
dest := []*KeyValue{}
// Test CopyTo empty
dest = CopyKeyValuePtrSlice(dest, src)
assert.Equal(t, []*KeyValue{}, dest)
// Test CopyTo larger slice
src = GenTestKeyValuePtrSlice()
dest = CopyKeyValuePtrSlice(dest, src)
assert.Equal(t, GenTestKeyValuePtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyKeyValuePtrSlice(dest, src)
assert.Equal(t, GenTestKeyValuePtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyKeyValuePtrSlice(dest, []*KeyValue{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyKeyValuePtrSlice(dest, src)
assert.Equal(t, GenTestKeyValuePtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONKeyValueUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewKeyValue()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewKeyValue(), dest)
}
func TestMarshalAndUnmarshalJSONKeyValue(t *testing.T) {
for name, src := range genTestEncodingValuesKeyValue() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewKeyValue()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteKeyValue(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoKeyValueFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesKeyValue() {
t.Run(name, func(t *testing.T) {
dest := NewKeyValue()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoKeyValueUnknown(t *testing.T) {
dest := NewKeyValue()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewKeyValue(), dest)
}
func TestMarshalAndUnmarshalProtoKeyValue(t *testing.T) {
for name, src := range genTestEncodingValuesKeyValue() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewKeyValue()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteKeyValue(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufKeyValue(t *testing.T) {
for name, src := range genTestEncodingValuesKeyValue() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpcommon.KeyValue{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewKeyValue()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesKeyValue() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"Key/wrong_wire_type": {0xc},
"Key/missing_value": {0xa},
"Value/wrong_wire_type": {0x14},
"Value/missing_value": {0x12},
"KeyStrindex/wrong_wire_type": {0x1c},
"KeyStrindex/missing_value": {0x18},
}
}
func genTestEncodingValuesKeyValue() map[string]*KeyValue {
return map[string]*KeyValue{
"empty": NewKeyValue(),
"Key/test": {Key: "test_key"},
"Value/test": {Value: *GenTestAnyValue()},
"KeyStrindex/test": {KeyStrindex: int32(13)},
}
}
================================================
FILE: pdata/internal/generated_proto_keyvalueandunit.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// KeyValueAndUnit represents a custom 'dictionary native'
// style of encoding attributes which is more convenient
// for profiles than opentelemetry.proto.common.v1.KeyValue.
type KeyValueAndUnit struct {
Value AnyValue
KeyStrindex int32
UnitStrindex int32
}
var (
protoPoolKeyValueAndUnit = sync.Pool{
New: func() any {
return &KeyValueAndUnit{}
},
}
)
func NewKeyValueAndUnit() *KeyValueAndUnit {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &KeyValueAndUnit{}
}
return protoPoolKeyValueAndUnit.Get().(*KeyValueAndUnit)
}
func DeleteKeyValueAndUnit(orig *KeyValueAndUnit, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
DeleteAnyValue(&orig.Value, false)
orig.Reset()
if nullable {
protoPoolKeyValueAndUnit.Put(orig)
}
}
func CopyKeyValueAndUnit(dest, src *KeyValueAndUnit) *KeyValueAndUnit {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewKeyValueAndUnit()
}
dest.KeyStrindex = src.KeyStrindex
CopyAnyValue(&dest.Value, &src.Value)
dest.UnitStrindex = src.UnitStrindex
return dest
}
func CopyKeyValueAndUnitSlice(dest, src []KeyValueAndUnit) []KeyValueAndUnit {
var newDest []KeyValueAndUnit
if cap(dest) < len(src) {
newDest = make([]KeyValueAndUnit, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteKeyValueAndUnit(&dest[i], false)
}
}
for i := range src {
CopyKeyValueAndUnit(&newDest[i], &src[i])
}
return newDest
}
func CopyKeyValueAndUnitPtrSlice(dest, src []*KeyValueAndUnit) []*KeyValueAndUnit {
var newDest []*KeyValueAndUnit
if cap(dest) < len(src) {
newDest = make([]*KeyValueAndUnit, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewKeyValueAndUnit()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteKeyValueAndUnit(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewKeyValueAndUnit()
}
}
for i := range src {
CopyKeyValueAndUnit(newDest[i], src[i])
}
return newDest
}
func (orig *KeyValueAndUnit) Reset() {
*orig = KeyValueAndUnit{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *KeyValueAndUnit) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if orig.KeyStrindex != int32(0) {
dest.WriteObjectField("keyStrindex")
dest.WriteInt32(orig.KeyStrindex)
}
dest.WriteObjectField("value")
orig.Value.MarshalJSON(dest)
if orig.UnitStrindex != int32(0) {
dest.WriteObjectField("unitStrindex")
dest.WriteInt32(orig.UnitStrindex)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *KeyValueAndUnit) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "keyStrindex", "key_strindex":
orig.KeyStrindex = iter.ReadInt32()
case "value":
orig.Value.UnmarshalJSON(iter)
case "unitStrindex", "unit_strindex":
orig.UnitStrindex = iter.ReadInt32()
default:
iter.Skip()
}
}
}
func (orig *KeyValueAndUnit) SizeProto() int {
var n int
var l int
_ = l
if orig.KeyStrindex != int32(0) {
n += 1 + proto.Sov(uint64(orig.KeyStrindex))
}
l = orig.Value.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
if orig.UnitStrindex != int32(0) {
n += 1 + proto.Sov(uint64(orig.UnitStrindex))
}
return n
}
func (orig *KeyValueAndUnit) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
if orig.KeyStrindex != int32(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.KeyStrindex))
pos--
buf[pos] = 0x8
}
l = orig.Value.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
if orig.UnitStrindex != int32(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.UnitStrindex))
pos--
buf[pos] = 0x18
}
return len(buf) - pos
}
func (orig *KeyValueAndUnit) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field KeyStrindex", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.KeyStrindex = int32(num)
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.Value.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 3:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field UnitStrindex", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.UnitStrindex = int32(num)
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestKeyValueAndUnit() *KeyValueAndUnit {
orig := NewKeyValueAndUnit()
orig.KeyStrindex = int32(13)
orig.Value = *GenTestAnyValue()
orig.UnitStrindex = int32(13)
return orig
}
func GenTestKeyValueAndUnitPtrSlice() []*KeyValueAndUnit {
orig := make([]*KeyValueAndUnit, 5)
orig[0] = NewKeyValueAndUnit()
orig[1] = GenTestKeyValueAndUnit()
orig[2] = NewKeyValueAndUnit()
orig[3] = GenTestKeyValueAndUnit()
orig[4] = NewKeyValueAndUnit()
return orig
}
func GenTestKeyValueAndUnitSlice() []KeyValueAndUnit {
orig := make([]KeyValueAndUnit, 5)
orig[1] = *GenTestKeyValueAndUnit()
orig[3] = *GenTestKeyValueAndUnit()
return orig
}
================================================
FILE: pdata/internal/generated_proto_keyvalueandunit_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpprofiles "go.opentelemetry.io/proto/slim/otlp/profiles/v1development"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyKeyValueAndUnit(t *testing.T) {
for name, src := range genTestEncodingValuesKeyValueAndUnit() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewKeyValueAndUnit()
CopyKeyValueAndUnit(dest, src)
assert.Equal(t, src, dest)
CopyKeyValueAndUnit(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyKeyValueAndUnitSlice(t *testing.T) {
src := []KeyValueAndUnit{}
dest := []KeyValueAndUnit{}
// Test CopyTo empty
dest = CopyKeyValueAndUnitSlice(dest, src)
assert.Equal(t, []KeyValueAndUnit{}, dest)
// Test CopyTo larger slice
src = GenTestKeyValueAndUnitSlice()
dest = CopyKeyValueAndUnitSlice(dest, src)
assert.Equal(t, GenTestKeyValueAndUnitSlice(), dest)
// Test CopyTo same size slice
dest = CopyKeyValueAndUnitSlice(dest, src)
assert.Equal(t, GenTestKeyValueAndUnitSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyKeyValueAndUnitSlice(dest, []KeyValueAndUnit{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyKeyValueAndUnitSlice(dest, src)
assert.Equal(t, GenTestKeyValueAndUnitSlice(), dest)
}
func TestCopyKeyValueAndUnitPtrSlice(t *testing.T) {
src := []*KeyValueAndUnit{}
dest := []*KeyValueAndUnit{}
// Test CopyTo empty
dest = CopyKeyValueAndUnitPtrSlice(dest, src)
assert.Equal(t, []*KeyValueAndUnit{}, dest)
// Test CopyTo larger slice
src = GenTestKeyValueAndUnitPtrSlice()
dest = CopyKeyValueAndUnitPtrSlice(dest, src)
assert.Equal(t, GenTestKeyValueAndUnitPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyKeyValueAndUnitPtrSlice(dest, src)
assert.Equal(t, GenTestKeyValueAndUnitPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyKeyValueAndUnitPtrSlice(dest, []*KeyValueAndUnit{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyKeyValueAndUnitPtrSlice(dest, src)
assert.Equal(t, GenTestKeyValueAndUnitPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONKeyValueAndUnitUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewKeyValueAndUnit()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewKeyValueAndUnit(), dest)
}
func TestMarshalAndUnmarshalJSONKeyValueAndUnit(t *testing.T) {
for name, src := range genTestEncodingValuesKeyValueAndUnit() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewKeyValueAndUnit()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteKeyValueAndUnit(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoKeyValueAndUnitFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesKeyValueAndUnit() {
t.Run(name, func(t *testing.T) {
dest := NewKeyValueAndUnit()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoKeyValueAndUnitUnknown(t *testing.T) {
dest := NewKeyValueAndUnit()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewKeyValueAndUnit(), dest)
}
func TestMarshalAndUnmarshalProtoKeyValueAndUnit(t *testing.T) {
for name, src := range genTestEncodingValuesKeyValueAndUnit() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewKeyValueAndUnit()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteKeyValueAndUnit(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufKeyValueAndUnit(t *testing.T) {
for name, src := range genTestEncodingValuesKeyValueAndUnit() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpprofiles.KeyValueAndUnit{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewKeyValueAndUnit()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesKeyValueAndUnit() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"KeyStrindex/wrong_wire_type": {0xc},
"KeyStrindex/missing_value": {0x8},
"Value/wrong_wire_type": {0x14},
"Value/missing_value": {0x12},
"UnitStrindex/wrong_wire_type": {0x1c},
"UnitStrindex/missing_value": {0x18},
}
}
func genTestEncodingValuesKeyValueAndUnit() map[string]*KeyValueAndUnit {
return map[string]*KeyValueAndUnit{
"empty": NewKeyValueAndUnit(),
"KeyStrindex/test": {KeyStrindex: int32(13)},
"Value/test": {Value: *GenTestAnyValue()},
"UnitStrindex/test": {UnitStrindex: int32(13)},
}
}
================================================
FILE: pdata/internal/generated_proto_keyvaluelist.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// KeyValueList is a list of KeyValue messages. We need KeyValueList as a message since oneof in AnyValue does not allow repeated fields.
type KeyValueList struct {
Values []KeyValue
}
var (
protoPoolKeyValueList = sync.Pool{
New: func() any {
return &KeyValueList{}
},
}
)
func NewKeyValueList() *KeyValueList {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &KeyValueList{}
}
return protoPoolKeyValueList.Get().(*KeyValueList)
}
func DeleteKeyValueList(orig *KeyValueList, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
for i := range orig.Values {
DeleteKeyValue(&orig.Values[i], false)
}
orig.Reset()
if nullable {
protoPoolKeyValueList.Put(orig)
}
}
func CopyKeyValueList(dest, src *KeyValueList) *KeyValueList {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewKeyValueList()
}
dest.Values = CopyKeyValueSlice(dest.Values, src.Values)
return dest
}
func CopyKeyValueListSlice(dest, src []KeyValueList) []KeyValueList {
var newDest []KeyValueList
if cap(dest) < len(src) {
newDest = make([]KeyValueList, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteKeyValueList(&dest[i], false)
}
}
for i := range src {
CopyKeyValueList(&newDest[i], &src[i])
}
return newDest
}
func CopyKeyValueListPtrSlice(dest, src []*KeyValueList) []*KeyValueList {
var newDest []*KeyValueList
if cap(dest) < len(src) {
newDest = make([]*KeyValueList, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewKeyValueList()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteKeyValueList(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewKeyValueList()
}
}
for i := range src {
CopyKeyValueList(newDest[i], src[i])
}
return newDest
}
func (orig *KeyValueList) Reset() {
*orig = KeyValueList{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *KeyValueList) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if len(orig.Values) > 0 {
dest.WriteObjectField("values")
dest.WriteArrayStart()
orig.Values[0].MarshalJSON(dest)
for i := 1; i < len(orig.Values); i++ {
dest.WriteMore()
orig.Values[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *KeyValueList) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "values":
for iter.ReadArray() {
orig.Values = append(orig.Values, KeyValue{})
orig.Values[len(orig.Values)-1].UnmarshalJSON(iter)
}
default:
iter.Skip()
}
}
}
func (orig *KeyValueList) SizeProto() int {
var n int
var l int
_ = l
for i := range orig.Values {
l = orig.Values[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *KeyValueList) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
for i := len(orig.Values) - 1; i >= 0; i-- {
l = orig.Values[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
}
return len(buf) - pos
}
func (orig *KeyValueList) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Values", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Values = append(orig.Values, KeyValue{})
err = orig.Values[len(orig.Values)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestKeyValueList() *KeyValueList {
orig := NewKeyValueList()
orig.Values = []KeyValue{{}, *GenTestKeyValue()}
return orig
}
func GenTestKeyValueListPtrSlice() []*KeyValueList {
orig := make([]*KeyValueList, 5)
orig[0] = NewKeyValueList()
orig[1] = GenTestKeyValueList()
orig[2] = NewKeyValueList()
orig[3] = GenTestKeyValueList()
orig[4] = NewKeyValueList()
return orig
}
func GenTestKeyValueListSlice() []KeyValueList {
orig := make([]KeyValueList, 5)
orig[1] = *GenTestKeyValueList()
orig[3] = *GenTestKeyValueList()
return orig
}
================================================
FILE: pdata/internal/generated_proto_keyvaluelist_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpcommon "go.opentelemetry.io/proto/slim/otlp/common/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyKeyValueList(t *testing.T) {
for name, src := range genTestEncodingValuesKeyValueList() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewKeyValueList()
CopyKeyValueList(dest, src)
assert.Equal(t, src, dest)
CopyKeyValueList(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyKeyValueListSlice(t *testing.T) {
src := []KeyValueList{}
dest := []KeyValueList{}
// Test CopyTo empty
dest = CopyKeyValueListSlice(dest, src)
assert.Equal(t, []KeyValueList{}, dest)
// Test CopyTo larger slice
src = GenTestKeyValueListSlice()
dest = CopyKeyValueListSlice(dest, src)
assert.Equal(t, GenTestKeyValueListSlice(), dest)
// Test CopyTo same size slice
dest = CopyKeyValueListSlice(dest, src)
assert.Equal(t, GenTestKeyValueListSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyKeyValueListSlice(dest, []KeyValueList{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyKeyValueListSlice(dest, src)
assert.Equal(t, GenTestKeyValueListSlice(), dest)
}
func TestCopyKeyValueListPtrSlice(t *testing.T) {
src := []*KeyValueList{}
dest := []*KeyValueList{}
// Test CopyTo empty
dest = CopyKeyValueListPtrSlice(dest, src)
assert.Equal(t, []*KeyValueList{}, dest)
// Test CopyTo larger slice
src = GenTestKeyValueListPtrSlice()
dest = CopyKeyValueListPtrSlice(dest, src)
assert.Equal(t, GenTestKeyValueListPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyKeyValueListPtrSlice(dest, src)
assert.Equal(t, GenTestKeyValueListPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyKeyValueListPtrSlice(dest, []*KeyValueList{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyKeyValueListPtrSlice(dest, src)
assert.Equal(t, GenTestKeyValueListPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONKeyValueListUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewKeyValueList()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewKeyValueList(), dest)
}
func TestMarshalAndUnmarshalJSONKeyValueList(t *testing.T) {
for name, src := range genTestEncodingValuesKeyValueList() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewKeyValueList()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteKeyValueList(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoKeyValueListFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesKeyValueList() {
t.Run(name, func(t *testing.T) {
dest := NewKeyValueList()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoKeyValueListUnknown(t *testing.T) {
dest := NewKeyValueList()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewKeyValueList(), dest)
}
func TestMarshalAndUnmarshalProtoKeyValueList(t *testing.T) {
for name, src := range genTestEncodingValuesKeyValueList() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewKeyValueList()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteKeyValueList(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufKeyValueList(t *testing.T) {
for name, src := range genTestEncodingValuesKeyValueList() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpcommon.KeyValueList{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewKeyValueList()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesKeyValueList() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"Values/wrong_wire_type": {0xc},
"Values/missing_value": {0xa},
}
}
func genTestEncodingValuesKeyValueList() map[string]*KeyValueList {
return map[string]*KeyValueList{
"empty": NewKeyValueList(),
"Values/test": {Values: []KeyValue{{}, *GenTestKeyValue()}},
}
}
================================================
FILE: pdata/internal/generated_proto_line.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// Line details a specific line in a source code, linked to a function.
type Line struct {
FunctionIndex int32
Line int64
Column int64
}
var (
protoPoolLine = sync.Pool{
New: func() any {
return &Line{}
},
}
)
func NewLine() *Line {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &Line{}
}
return protoPoolLine.Get().(*Line)
}
func DeleteLine(orig *Line, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
orig.Reset()
if nullable {
protoPoolLine.Put(orig)
}
}
func CopyLine(dest, src *Line) *Line {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewLine()
}
dest.FunctionIndex = src.FunctionIndex
dest.Line = src.Line
dest.Column = src.Column
return dest
}
func CopyLineSlice(dest, src []Line) []Line {
var newDest []Line
if cap(dest) < len(src) {
newDest = make([]Line, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteLine(&dest[i], false)
}
}
for i := range src {
CopyLine(&newDest[i], &src[i])
}
return newDest
}
func CopyLinePtrSlice(dest, src []*Line) []*Line {
var newDest []*Line
if cap(dest) < len(src) {
newDest = make([]*Line, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewLine()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteLine(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewLine()
}
}
for i := range src {
CopyLine(newDest[i], src[i])
}
return newDest
}
func (orig *Line) Reset() {
*orig = Line{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *Line) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if orig.FunctionIndex != int32(0) {
dest.WriteObjectField("functionIndex")
dest.WriteInt32(orig.FunctionIndex)
}
if orig.Line != int64(0) {
dest.WriteObjectField("line")
dest.WriteInt64(orig.Line)
}
if orig.Column != int64(0) {
dest.WriteObjectField("column")
dest.WriteInt64(orig.Column)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *Line) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "functionIndex", "function_index":
orig.FunctionIndex = iter.ReadInt32()
case "line":
orig.Line = iter.ReadInt64()
case "column":
orig.Column = iter.ReadInt64()
default:
iter.Skip()
}
}
}
func (orig *Line) SizeProto() int {
var n int
var l int
_ = l
if orig.FunctionIndex != int32(0) {
n += 1 + proto.Sov(uint64(orig.FunctionIndex))
}
if orig.Line != int64(0) {
n += 1 + proto.Sov(uint64(orig.Line))
}
if orig.Column != int64(0) {
n += 1 + proto.Sov(uint64(orig.Column))
}
return n
}
func (orig *Line) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
if orig.FunctionIndex != int32(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.FunctionIndex))
pos--
buf[pos] = 0x8
}
if orig.Line != int64(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.Line))
pos--
buf[pos] = 0x10
}
if orig.Column != int64(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.Column))
pos--
buf[pos] = 0x18
}
return len(buf) - pos
}
func (orig *Line) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field FunctionIndex", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.FunctionIndex = int32(num)
case 2:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field Line", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.Line = int64(num)
case 3:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field Column", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.Column = int64(num)
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestLine() *Line {
orig := NewLine()
orig.FunctionIndex = int32(13)
orig.Line = int64(13)
orig.Column = int64(13)
return orig
}
func GenTestLinePtrSlice() []*Line {
orig := make([]*Line, 5)
orig[0] = NewLine()
orig[1] = GenTestLine()
orig[2] = NewLine()
orig[3] = GenTestLine()
orig[4] = NewLine()
return orig
}
func GenTestLineSlice() []Line {
orig := make([]Line, 5)
orig[1] = *GenTestLine()
orig[3] = *GenTestLine()
return orig
}
================================================
FILE: pdata/internal/generated_proto_line_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpprofiles "go.opentelemetry.io/proto/slim/otlp/profiles/v1development"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyLine(t *testing.T) {
for name, src := range genTestEncodingValuesLine() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewLine()
CopyLine(dest, src)
assert.Equal(t, src, dest)
CopyLine(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyLineSlice(t *testing.T) {
src := []Line{}
dest := []Line{}
// Test CopyTo empty
dest = CopyLineSlice(dest, src)
assert.Equal(t, []Line{}, dest)
// Test CopyTo larger slice
src = GenTestLineSlice()
dest = CopyLineSlice(dest, src)
assert.Equal(t, GenTestLineSlice(), dest)
// Test CopyTo same size slice
dest = CopyLineSlice(dest, src)
assert.Equal(t, GenTestLineSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyLineSlice(dest, []Line{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyLineSlice(dest, src)
assert.Equal(t, GenTestLineSlice(), dest)
}
func TestCopyLinePtrSlice(t *testing.T) {
src := []*Line{}
dest := []*Line{}
// Test CopyTo empty
dest = CopyLinePtrSlice(dest, src)
assert.Equal(t, []*Line{}, dest)
// Test CopyTo larger slice
src = GenTestLinePtrSlice()
dest = CopyLinePtrSlice(dest, src)
assert.Equal(t, GenTestLinePtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyLinePtrSlice(dest, src)
assert.Equal(t, GenTestLinePtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyLinePtrSlice(dest, []*Line{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyLinePtrSlice(dest, src)
assert.Equal(t, GenTestLinePtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONLineUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewLine()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewLine(), dest)
}
func TestMarshalAndUnmarshalJSONLine(t *testing.T) {
for name, src := range genTestEncodingValuesLine() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewLine()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteLine(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoLineFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesLine() {
t.Run(name, func(t *testing.T) {
dest := NewLine()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoLineUnknown(t *testing.T) {
dest := NewLine()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewLine(), dest)
}
func TestMarshalAndUnmarshalProtoLine(t *testing.T) {
for name, src := range genTestEncodingValuesLine() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewLine()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteLine(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufLine(t *testing.T) {
for name, src := range genTestEncodingValuesLine() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpprofiles.Line{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewLine()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesLine() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"FunctionIndex/wrong_wire_type": {0xc},
"FunctionIndex/missing_value": {0x8},
"Line/wrong_wire_type": {0x14},
"Line/missing_value": {0x10},
"Column/wrong_wire_type": {0x1c},
"Column/missing_value": {0x18},
}
}
func genTestEncodingValuesLine() map[string]*Line {
return map[string]*Line{
"empty": NewLine(),
"FunctionIndex/test": {FunctionIndex: int32(13)},
"Line/test": {Line: int64(13)},
"Column/test": {Column: int64(13)},
}
}
================================================
FILE: pdata/internal/generated_proto_link.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// Link represents a pointer from a profile Sample to a trace Span.
type Link struct {
TraceId TraceID
SpanId SpanID
}
var (
protoPoolLink = sync.Pool{
New: func() any {
return &Link{}
},
}
)
func NewLink() *Link {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &Link{}
}
return protoPoolLink.Get().(*Link)
}
func DeleteLink(orig *Link, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
DeleteTraceID(&orig.TraceId, false)
DeleteSpanID(&orig.SpanId, false)
orig.Reset()
if nullable {
protoPoolLink.Put(orig)
}
}
func CopyLink(dest, src *Link) *Link {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewLink()
}
CopyTraceID(&dest.TraceId, &src.TraceId)
CopySpanID(&dest.SpanId, &src.SpanId)
return dest
}
func CopyLinkSlice(dest, src []Link) []Link {
var newDest []Link
if cap(dest) < len(src) {
newDest = make([]Link, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteLink(&dest[i], false)
}
}
for i := range src {
CopyLink(&newDest[i], &src[i])
}
return newDest
}
func CopyLinkPtrSlice(dest, src []*Link) []*Link {
var newDest []*Link
if cap(dest) < len(src) {
newDest = make([]*Link, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewLink()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteLink(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewLink()
}
}
for i := range src {
CopyLink(newDest[i], src[i])
}
return newDest
}
func (orig *Link) Reset() {
*orig = Link{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *Link) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if !orig.TraceId.IsEmpty() {
dest.WriteObjectField("traceId")
orig.TraceId.MarshalJSON(dest)
}
if !orig.SpanId.IsEmpty() {
dest.WriteObjectField("spanId")
orig.SpanId.MarshalJSON(dest)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *Link) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "traceId", "trace_id":
orig.TraceId.UnmarshalJSON(iter)
case "spanId", "span_id":
orig.SpanId.UnmarshalJSON(iter)
default:
iter.Skip()
}
}
}
func (orig *Link) SizeProto() int {
var n int
var l int
_ = l
l = orig.TraceId.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
l = orig.SpanId.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
return n
}
func (orig *Link) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
l = orig.TraceId.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
l = orig.SpanId.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
return len(buf) - pos
}
func (orig *Link) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field TraceId", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.TraceId.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field SpanId", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.SpanId.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestLink() *Link {
orig := NewLink()
orig.TraceId = *GenTestTraceID()
orig.SpanId = *GenTestSpanID()
return orig
}
func GenTestLinkPtrSlice() []*Link {
orig := make([]*Link, 5)
orig[0] = NewLink()
orig[1] = GenTestLink()
orig[2] = NewLink()
orig[3] = GenTestLink()
orig[4] = NewLink()
return orig
}
func GenTestLinkSlice() []Link {
orig := make([]Link, 5)
orig[1] = *GenTestLink()
orig[3] = *GenTestLink()
return orig
}
================================================
FILE: pdata/internal/generated_proto_link_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpprofiles "go.opentelemetry.io/proto/slim/otlp/profiles/v1development"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyLink(t *testing.T) {
for name, src := range genTestEncodingValuesLink() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewLink()
CopyLink(dest, src)
assert.Equal(t, src, dest)
CopyLink(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyLinkSlice(t *testing.T) {
src := []Link{}
dest := []Link{}
// Test CopyTo empty
dest = CopyLinkSlice(dest, src)
assert.Equal(t, []Link{}, dest)
// Test CopyTo larger slice
src = GenTestLinkSlice()
dest = CopyLinkSlice(dest, src)
assert.Equal(t, GenTestLinkSlice(), dest)
// Test CopyTo same size slice
dest = CopyLinkSlice(dest, src)
assert.Equal(t, GenTestLinkSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyLinkSlice(dest, []Link{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyLinkSlice(dest, src)
assert.Equal(t, GenTestLinkSlice(), dest)
}
func TestCopyLinkPtrSlice(t *testing.T) {
src := []*Link{}
dest := []*Link{}
// Test CopyTo empty
dest = CopyLinkPtrSlice(dest, src)
assert.Equal(t, []*Link{}, dest)
// Test CopyTo larger slice
src = GenTestLinkPtrSlice()
dest = CopyLinkPtrSlice(dest, src)
assert.Equal(t, GenTestLinkPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyLinkPtrSlice(dest, src)
assert.Equal(t, GenTestLinkPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyLinkPtrSlice(dest, []*Link{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyLinkPtrSlice(dest, src)
assert.Equal(t, GenTestLinkPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONLinkUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewLink()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewLink(), dest)
}
func TestMarshalAndUnmarshalJSONLink(t *testing.T) {
for name, src := range genTestEncodingValuesLink() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewLink()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteLink(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoLinkFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesLink() {
t.Run(name, func(t *testing.T) {
dest := NewLink()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoLinkUnknown(t *testing.T) {
dest := NewLink()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewLink(), dest)
}
func TestMarshalAndUnmarshalProtoLink(t *testing.T) {
for name, src := range genTestEncodingValuesLink() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewLink()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteLink(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufLink(t *testing.T) {
for name, src := range genTestEncodingValuesLink() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpprofiles.Link{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewLink()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesLink() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"TraceId/wrong_wire_type": {0xc},
"TraceId/missing_value": {0xa},
"SpanId/wrong_wire_type": {0x14},
"SpanId/missing_value": {0x12},
}
}
func genTestEncodingValuesLink() map[string]*Link {
return map[string]*Link{
"empty": NewLink(),
"TraceId/test": {TraceId: *GenTestTraceID()},
"SpanId/test": {SpanId: *GenTestSpanID()},
}
}
================================================
FILE: pdata/internal/generated_proto_location.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// Location describes function and line table debug information.
type Location struct {
Lines []*Line
AttributeIndices []int32
Address uint64
MappingIndex int32
}
var (
protoPoolLocation = sync.Pool{
New: func() any {
return &Location{}
},
}
)
func NewLocation() *Location {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &Location{}
}
return protoPoolLocation.Get().(*Location)
}
func DeleteLocation(orig *Location, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
for i := range orig.Lines {
DeleteLine(orig.Lines[i], true)
}
orig.Reset()
if nullable {
protoPoolLocation.Put(orig)
}
}
func CopyLocation(dest, src *Location) *Location {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewLocation()
}
dest.MappingIndex = src.MappingIndex
dest.Address = src.Address
dest.Lines = CopyLinePtrSlice(dest.Lines, src.Lines)
dest.AttributeIndices = append(dest.AttributeIndices[:0], src.AttributeIndices...)
return dest
}
func CopyLocationSlice(dest, src []Location) []Location {
var newDest []Location
if cap(dest) < len(src) {
newDest = make([]Location, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteLocation(&dest[i], false)
}
}
for i := range src {
CopyLocation(&newDest[i], &src[i])
}
return newDest
}
func CopyLocationPtrSlice(dest, src []*Location) []*Location {
var newDest []*Location
if cap(dest) < len(src) {
newDest = make([]*Location, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewLocation()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteLocation(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewLocation()
}
}
for i := range src {
CopyLocation(newDest[i], src[i])
}
return newDest
}
func (orig *Location) Reset() {
*orig = Location{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *Location) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if orig.MappingIndex != int32(0) {
dest.WriteObjectField("mappingIndex")
dest.WriteInt32(orig.MappingIndex)
}
if orig.Address != uint64(0) {
dest.WriteObjectField("address")
dest.WriteUint64(orig.Address)
}
if len(orig.Lines) > 0 {
dest.WriteObjectField("lines")
dest.WriteArrayStart()
orig.Lines[0].MarshalJSON(dest)
for i := 1; i < len(orig.Lines); i++ {
dest.WriteMore()
orig.Lines[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if len(orig.AttributeIndices) > 0 {
dest.WriteObjectField("attributeIndices")
dest.WriteArrayStart()
dest.WriteInt32(orig.AttributeIndices[0])
for i := 1; i < len(orig.AttributeIndices); i++ {
dest.WriteMore()
dest.WriteInt32(orig.AttributeIndices[i])
}
dest.WriteArrayEnd()
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *Location) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "mappingIndex", "mapping_index":
orig.MappingIndex = iter.ReadInt32()
case "address":
orig.Address = iter.ReadUint64()
case "lines":
for iter.ReadArray() {
orig.Lines = append(orig.Lines, NewLine())
orig.Lines[len(orig.Lines)-1].UnmarshalJSON(iter)
}
case "attributeIndices", "attribute_indices":
for iter.ReadArray() {
orig.AttributeIndices = append(orig.AttributeIndices, iter.ReadInt32())
}
default:
iter.Skip()
}
}
}
func (orig *Location) SizeProto() int {
var n int
var l int
_ = l
if orig.MappingIndex != int32(0) {
n += 1 + proto.Sov(uint64(orig.MappingIndex))
}
if orig.Address != uint64(0) {
n += 1 + proto.Sov(uint64(orig.Address))
}
for i := range orig.Lines {
l = orig.Lines[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
if len(orig.AttributeIndices) > 0 {
l = 0
for _, e := range orig.AttributeIndices {
l += proto.Sov(uint64(e))
}
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *Location) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
if orig.MappingIndex != int32(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.MappingIndex))
pos--
buf[pos] = 0x8
}
if orig.Address != uint64(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.Address))
pos--
buf[pos] = 0x10
}
for i := len(orig.Lines) - 1; i >= 0; i-- {
l = orig.Lines[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x1a
}
l = len(orig.AttributeIndices)
if l > 0 {
endPos := pos
for i := l - 1; i >= 0; i-- {
pos = proto.EncodeVarint(buf, pos, uint64(orig.AttributeIndices[i]))
}
pos = proto.EncodeVarint(buf, pos, uint64(endPos-pos))
pos--
buf[pos] = 0x22
}
return len(buf) - pos
}
func (orig *Location) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field MappingIndex", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.MappingIndex = int32(num)
case 2:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field Address", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.Address = uint64(num)
case 3:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Lines", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Lines = append(orig.Lines, NewLine())
err = orig.Lines[len(orig.Lines)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 4:
switch wireType {
case proto.WireTypeLen:
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
var num uint64
for startPos < pos {
num, startPos, err = proto.ConsumeVarint(buf[:pos], startPos)
if err != nil {
return err
}
orig.AttributeIndices = append(orig.AttributeIndices, int32(num))
}
if startPos != pos {
return fmt.Errorf("proto: invalid field len = %d for field AttributeIndices", pos-startPos)
}
case proto.WireTypeVarint:
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.AttributeIndices = append(orig.AttributeIndices, int32(num))
default:
return fmt.Errorf("proto: wrong wireType = %d for field AttributeIndices", wireType)
}
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestLocation() *Location {
orig := NewLocation()
orig.MappingIndex = int32(13)
orig.Address = uint64(13)
orig.Lines = []*Line{{}, GenTestLine()}
orig.AttributeIndices = []int32{int32(0), int32(13)}
return orig
}
func GenTestLocationPtrSlice() []*Location {
orig := make([]*Location, 5)
orig[0] = NewLocation()
orig[1] = GenTestLocation()
orig[2] = NewLocation()
orig[3] = GenTestLocation()
orig[4] = NewLocation()
return orig
}
func GenTestLocationSlice() []Location {
orig := make([]Location, 5)
orig[1] = *GenTestLocation()
orig[3] = *GenTestLocation()
return orig
}
================================================
FILE: pdata/internal/generated_proto_location_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpprofiles "go.opentelemetry.io/proto/slim/otlp/profiles/v1development"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyLocation(t *testing.T) {
for name, src := range genTestEncodingValuesLocation() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewLocation()
CopyLocation(dest, src)
assert.Equal(t, src, dest)
CopyLocation(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyLocationSlice(t *testing.T) {
src := []Location{}
dest := []Location{}
// Test CopyTo empty
dest = CopyLocationSlice(dest, src)
assert.Equal(t, []Location{}, dest)
// Test CopyTo larger slice
src = GenTestLocationSlice()
dest = CopyLocationSlice(dest, src)
assert.Equal(t, GenTestLocationSlice(), dest)
// Test CopyTo same size slice
dest = CopyLocationSlice(dest, src)
assert.Equal(t, GenTestLocationSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyLocationSlice(dest, []Location{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyLocationSlice(dest, src)
assert.Equal(t, GenTestLocationSlice(), dest)
}
func TestCopyLocationPtrSlice(t *testing.T) {
src := []*Location{}
dest := []*Location{}
// Test CopyTo empty
dest = CopyLocationPtrSlice(dest, src)
assert.Equal(t, []*Location{}, dest)
// Test CopyTo larger slice
src = GenTestLocationPtrSlice()
dest = CopyLocationPtrSlice(dest, src)
assert.Equal(t, GenTestLocationPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyLocationPtrSlice(dest, src)
assert.Equal(t, GenTestLocationPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyLocationPtrSlice(dest, []*Location{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyLocationPtrSlice(dest, src)
assert.Equal(t, GenTestLocationPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONLocationUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewLocation()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewLocation(), dest)
}
func TestMarshalAndUnmarshalJSONLocation(t *testing.T) {
for name, src := range genTestEncodingValuesLocation() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewLocation()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteLocation(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoLocationFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesLocation() {
t.Run(name, func(t *testing.T) {
dest := NewLocation()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoLocationUnknown(t *testing.T) {
dest := NewLocation()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewLocation(), dest)
}
func TestMarshalAndUnmarshalProtoLocation(t *testing.T) {
for name, src := range genTestEncodingValuesLocation() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewLocation()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteLocation(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufLocation(t *testing.T) {
for name, src := range genTestEncodingValuesLocation() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpprofiles.Location{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewLocation()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesLocation() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"MappingIndex/wrong_wire_type": {0xc},
"MappingIndex/missing_value": {0x8},
"Address/wrong_wire_type": {0x14},
"Address/missing_value": {0x10},
"Lines/wrong_wire_type": {0x1c},
"Lines/missing_value": {0x1a},
"AttributeIndices/wrong_wire_type": {0x24},
"AttributeIndices/missing_value": {0x22},
}
}
func genTestEncodingValuesLocation() map[string]*Location {
return map[string]*Location{
"empty": NewLocation(),
"MappingIndex/test": {MappingIndex: int32(13)},
"Address/test": {Address: uint64(13)},
"Lines/test": {Lines: []*Line{{}, GenTestLine()}},
"AttributeIndices/test": {AttributeIndices: []int32{int32(0), int32(13)}},
}
}
================================================
FILE: pdata/internal/generated_proto_logrecord.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"encoding/binary"
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// LogRecord are experimental implementation of OpenTelemetry Log Data Model.
type LogRecord struct {
Body AnyValue
SeverityText string
EventName string
Attributes []KeyValue
TimeUnixNano uint64
ObservedTimeUnixNano uint64
SeverityNumber SeverityNumber
DroppedAttributesCount uint32
Flags uint32
TraceId TraceID
SpanId SpanID
}
var (
protoPoolLogRecord = sync.Pool{
New: func() any {
return &LogRecord{}
},
}
)
func NewLogRecord() *LogRecord {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &LogRecord{}
}
return protoPoolLogRecord.Get().(*LogRecord)
}
func DeleteLogRecord(orig *LogRecord, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
DeleteAnyValue(&orig.Body, false)
for i := range orig.Attributes {
DeleteKeyValue(&orig.Attributes[i], false)
}
DeleteTraceID(&orig.TraceId, false)
DeleteSpanID(&orig.SpanId, false)
orig.Reset()
if nullable {
protoPoolLogRecord.Put(orig)
}
}
func CopyLogRecord(dest, src *LogRecord) *LogRecord {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewLogRecord()
}
dest.TimeUnixNano = src.TimeUnixNano
dest.ObservedTimeUnixNano = src.ObservedTimeUnixNano
dest.SeverityNumber = src.SeverityNumber
dest.SeverityText = src.SeverityText
CopyAnyValue(&dest.Body, &src.Body)
dest.Attributes = CopyKeyValueSlice(dest.Attributes, src.Attributes)
dest.DroppedAttributesCount = src.DroppedAttributesCount
dest.Flags = src.Flags
CopyTraceID(&dest.TraceId, &src.TraceId)
CopySpanID(&dest.SpanId, &src.SpanId)
dest.EventName = src.EventName
return dest
}
func CopyLogRecordSlice(dest, src []LogRecord) []LogRecord {
var newDest []LogRecord
if cap(dest) < len(src) {
newDest = make([]LogRecord, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteLogRecord(&dest[i], false)
}
}
for i := range src {
CopyLogRecord(&newDest[i], &src[i])
}
return newDest
}
func CopyLogRecordPtrSlice(dest, src []*LogRecord) []*LogRecord {
var newDest []*LogRecord
if cap(dest) < len(src) {
newDest = make([]*LogRecord, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewLogRecord()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteLogRecord(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewLogRecord()
}
}
for i := range src {
CopyLogRecord(newDest[i], src[i])
}
return newDest
}
func (orig *LogRecord) Reset() {
*orig = LogRecord{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *LogRecord) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if orig.TimeUnixNano != uint64(0) {
dest.WriteObjectField("timeUnixNano")
dest.WriteUint64(orig.TimeUnixNano)
}
if orig.ObservedTimeUnixNano != uint64(0) {
dest.WriteObjectField("observedTimeUnixNano")
dest.WriteUint64(orig.ObservedTimeUnixNano)
}
if int32(orig.SeverityNumber) != 0 {
dest.WriteObjectField("severityNumber")
dest.WriteInt32(int32(orig.SeverityNumber))
}
if orig.SeverityText != "" {
dest.WriteObjectField("severityText")
dest.WriteString(orig.SeverityText)
}
dest.WriteObjectField("body")
orig.Body.MarshalJSON(dest)
if len(orig.Attributes) > 0 {
dest.WriteObjectField("attributes")
dest.WriteArrayStart()
orig.Attributes[0].MarshalJSON(dest)
for i := 1; i < len(orig.Attributes); i++ {
dest.WriteMore()
orig.Attributes[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if orig.DroppedAttributesCount != uint32(0) {
dest.WriteObjectField("droppedAttributesCount")
dest.WriteUint32(orig.DroppedAttributesCount)
}
if orig.Flags != uint32(0) {
dest.WriteObjectField("flags")
dest.WriteUint32(orig.Flags)
}
if !orig.TraceId.IsEmpty() {
dest.WriteObjectField("traceId")
orig.TraceId.MarshalJSON(dest)
}
if !orig.SpanId.IsEmpty() {
dest.WriteObjectField("spanId")
orig.SpanId.MarshalJSON(dest)
}
if orig.EventName != "" {
dest.WriteObjectField("eventName")
dest.WriteString(orig.EventName)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *LogRecord) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "timeUnixNano", "time_unix_nano":
orig.TimeUnixNano = iter.ReadUint64()
case "observedTimeUnixNano", "observed_time_unix_nano":
orig.ObservedTimeUnixNano = iter.ReadUint64()
case "severityNumber", "severity_number":
orig.SeverityNumber = SeverityNumber(iter.ReadEnumValue(SeverityNumber_value))
case "severityText", "severity_text":
orig.SeverityText = iter.ReadString()
case "body":
orig.Body.UnmarshalJSON(iter)
case "attributes":
for iter.ReadArray() {
orig.Attributes = append(orig.Attributes, KeyValue{})
orig.Attributes[len(orig.Attributes)-1].UnmarshalJSON(iter)
}
case "droppedAttributesCount", "dropped_attributes_count":
orig.DroppedAttributesCount = iter.ReadUint32()
case "flags":
orig.Flags = iter.ReadUint32()
case "traceId", "trace_id":
orig.TraceId.UnmarshalJSON(iter)
case "spanId", "span_id":
orig.SpanId.UnmarshalJSON(iter)
case "eventName", "event_name":
orig.EventName = iter.ReadString()
default:
iter.Skip()
}
}
}
func (orig *LogRecord) SizeProto() int {
var n int
var l int
_ = l
if orig.TimeUnixNano != uint64(0) {
n += 9
}
if orig.ObservedTimeUnixNano != uint64(0) {
n += 9
}
if orig.SeverityNumber != SeverityNumber(0) {
n += 1 + proto.Sov(uint64(orig.SeverityNumber))
}
l = len(orig.SeverityText)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
l = orig.Body.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
for i := range orig.Attributes {
l = orig.Attributes[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
if orig.DroppedAttributesCount != uint32(0) {
n += 1 + proto.Sov(uint64(orig.DroppedAttributesCount))
}
if orig.Flags != uint32(0) {
n += 5
}
l = orig.TraceId.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
l = orig.SpanId.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
l = len(orig.EventName)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *LogRecord) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
if orig.TimeUnixNano != uint64(0) {
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], uint64(orig.TimeUnixNano))
pos--
buf[pos] = 0x9
}
if orig.ObservedTimeUnixNano != uint64(0) {
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], uint64(orig.ObservedTimeUnixNano))
pos--
buf[pos] = 0x59
}
if orig.SeverityNumber != SeverityNumber(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.SeverityNumber))
pos--
buf[pos] = 0x10
}
l = len(orig.SeverityText)
if l > 0 {
pos -= l
copy(buf[pos:], orig.SeverityText)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x1a
}
l = orig.Body.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x2a
for i := len(orig.Attributes) - 1; i >= 0; i-- {
l = orig.Attributes[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x32
}
if orig.DroppedAttributesCount != uint32(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.DroppedAttributesCount))
pos--
buf[pos] = 0x38
}
if orig.Flags != uint32(0) {
pos -= 4
binary.LittleEndian.PutUint32(buf[pos:], uint32(orig.Flags))
pos--
buf[pos] = 0x45
}
l = orig.TraceId.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x4a
l = orig.SpanId.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x52
l = len(orig.EventName)
if l > 0 {
pos -= l
copy(buf[pos:], orig.EventName)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x62
}
return len(buf) - pos
}
func (orig *LogRecord) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field TimeUnixNano", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
orig.TimeUnixNano = uint64(num)
case 11:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field ObservedTimeUnixNano", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
orig.ObservedTimeUnixNano = uint64(num)
case 2:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field SeverityNumber", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.SeverityNumber = SeverityNumber(num)
case 3:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field SeverityText", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.SeverityText = string(buf[startPos:pos])
case 5:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Body", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.Body.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 6:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Attributes", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Attributes = append(orig.Attributes, KeyValue{})
err = orig.Attributes[len(orig.Attributes)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 7:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field DroppedAttributesCount", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.DroppedAttributesCount = uint32(num)
case 8:
if wireType != proto.WireTypeI32 {
return fmt.Errorf("proto: wrong wireType = %d for field Flags", wireType)
}
var num uint32
num, pos, err = proto.ConsumeI32(buf, pos)
if err != nil {
return err
}
orig.Flags = uint32(num)
case 9:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field TraceId", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.TraceId.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 10:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field SpanId", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.SpanId.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 12:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field EventName", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.EventName = string(buf[startPos:pos])
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestLogRecord() *LogRecord {
orig := NewLogRecord()
orig.TimeUnixNano = uint64(13)
orig.ObservedTimeUnixNano = uint64(13)
orig.SeverityNumber = SeverityNumber(13)
orig.SeverityText = "test_severitytext"
orig.Body = *GenTestAnyValue()
orig.Attributes = []KeyValue{{}, *GenTestKeyValue()}
orig.DroppedAttributesCount = uint32(13)
orig.Flags = uint32(13)
orig.TraceId = *GenTestTraceID()
orig.SpanId = *GenTestSpanID()
orig.EventName = "test_eventname"
return orig
}
func GenTestLogRecordPtrSlice() []*LogRecord {
orig := make([]*LogRecord, 5)
orig[0] = NewLogRecord()
orig[1] = GenTestLogRecord()
orig[2] = NewLogRecord()
orig[3] = GenTestLogRecord()
orig[4] = NewLogRecord()
return orig
}
func GenTestLogRecordSlice() []LogRecord {
orig := make([]LogRecord, 5)
orig[1] = *GenTestLogRecord()
orig[3] = *GenTestLogRecord()
return orig
}
================================================
FILE: pdata/internal/generated_proto_logrecord_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlplogs "go.opentelemetry.io/proto/slim/otlp/logs/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyLogRecord(t *testing.T) {
for name, src := range genTestEncodingValuesLogRecord() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewLogRecord()
CopyLogRecord(dest, src)
assert.Equal(t, src, dest)
CopyLogRecord(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyLogRecordSlice(t *testing.T) {
src := []LogRecord{}
dest := []LogRecord{}
// Test CopyTo empty
dest = CopyLogRecordSlice(dest, src)
assert.Equal(t, []LogRecord{}, dest)
// Test CopyTo larger slice
src = GenTestLogRecordSlice()
dest = CopyLogRecordSlice(dest, src)
assert.Equal(t, GenTestLogRecordSlice(), dest)
// Test CopyTo same size slice
dest = CopyLogRecordSlice(dest, src)
assert.Equal(t, GenTestLogRecordSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyLogRecordSlice(dest, []LogRecord{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyLogRecordSlice(dest, src)
assert.Equal(t, GenTestLogRecordSlice(), dest)
}
func TestCopyLogRecordPtrSlice(t *testing.T) {
src := []*LogRecord{}
dest := []*LogRecord{}
// Test CopyTo empty
dest = CopyLogRecordPtrSlice(dest, src)
assert.Equal(t, []*LogRecord{}, dest)
// Test CopyTo larger slice
src = GenTestLogRecordPtrSlice()
dest = CopyLogRecordPtrSlice(dest, src)
assert.Equal(t, GenTestLogRecordPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyLogRecordPtrSlice(dest, src)
assert.Equal(t, GenTestLogRecordPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyLogRecordPtrSlice(dest, []*LogRecord{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyLogRecordPtrSlice(dest, src)
assert.Equal(t, GenTestLogRecordPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONLogRecordUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewLogRecord()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewLogRecord(), dest)
}
func TestMarshalAndUnmarshalJSONLogRecord(t *testing.T) {
for name, src := range genTestEncodingValuesLogRecord() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewLogRecord()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteLogRecord(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoLogRecordFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesLogRecord() {
t.Run(name, func(t *testing.T) {
dest := NewLogRecord()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoLogRecordUnknown(t *testing.T) {
dest := NewLogRecord()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewLogRecord(), dest)
}
func TestMarshalAndUnmarshalProtoLogRecord(t *testing.T) {
for name, src := range genTestEncodingValuesLogRecord() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewLogRecord()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteLogRecord(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufLogRecord(t *testing.T) {
for name, src := range genTestEncodingValuesLogRecord() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlplogs.LogRecord{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewLogRecord()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesLogRecord() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"TimeUnixNano/wrong_wire_type": {0xc},
"TimeUnixNano/missing_value": {0x9},
"ObservedTimeUnixNano/wrong_wire_type": {0x5c},
"ObservedTimeUnixNano/missing_value": {0x59},
"SeverityNumber/wrong_wire_type": {0x14},
"SeverityNumber/missing_value": {0x10},
"SeverityText/wrong_wire_type": {0x1c},
"SeverityText/missing_value": {0x1a},
"Body/wrong_wire_type": {0x2c},
"Body/missing_value": {0x2a},
"Attributes/wrong_wire_type": {0x34},
"Attributes/missing_value": {0x32},
"DroppedAttributesCount/wrong_wire_type": {0x3c},
"DroppedAttributesCount/missing_value": {0x38},
"Flags/wrong_wire_type": {0x44},
"Flags/missing_value": {0x45},
"TraceId/wrong_wire_type": {0x4c},
"TraceId/missing_value": {0x4a},
"SpanId/wrong_wire_type": {0x54},
"SpanId/missing_value": {0x52},
"EventName/wrong_wire_type": {0x64},
"EventName/missing_value": {0x62},
}
}
func genTestEncodingValuesLogRecord() map[string]*LogRecord {
return map[string]*LogRecord{
"empty": NewLogRecord(),
"TimeUnixNano/test": {TimeUnixNano: uint64(13)},
"ObservedTimeUnixNano/test": {ObservedTimeUnixNano: uint64(13)},
"SeverityNumber/test": {SeverityNumber: SeverityNumber(13)},
"SeverityText/test": {SeverityText: "test_severitytext"},
"Body/test": {Body: *GenTestAnyValue()},
"Attributes/test": {Attributes: []KeyValue{{}, *GenTestKeyValue()}},
"DroppedAttributesCount/test": {DroppedAttributesCount: uint32(13)},
"Flags/test": {Flags: uint32(13)},
"TraceId/test": {TraceId: *GenTestTraceID()},
"SpanId/test": {SpanId: *GenTestSpanID()},
"EventName/test": {EventName: "test_eventname"},
}
}
================================================
FILE: pdata/internal/generated_proto_logsdata.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// LogsData represents the logs data that can be stored in a persistent storage,
// OR can be embedded by other protocols that transfer OTLP logs data but do not
// implement the OTLP protocol.
type LogsData struct {
ResourceLogs []*ResourceLogs
}
var (
protoPoolLogsData = sync.Pool{
New: func() any {
return &LogsData{}
},
}
)
func NewLogsData() *LogsData {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &LogsData{}
}
return protoPoolLogsData.Get().(*LogsData)
}
func DeleteLogsData(orig *LogsData, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
for i := range orig.ResourceLogs {
DeleteResourceLogs(orig.ResourceLogs[i], true)
}
orig.Reset()
if nullable {
protoPoolLogsData.Put(orig)
}
}
func CopyLogsData(dest, src *LogsData) *LogsData {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewLogsData()
}
dest.ResourceLogs = CopyResourceLogsPtrSlice(dest.ResourceLogs, src.ResourceLogs)
return dest
}
func CopyLogsDataSlice(dest, src []LogsData) []LogsData {
var newDest []LogsData
if cap(dest) < len(src) {
newDest = make([]LogsData, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteLogsData(&dest[i], false)
}
}
for i := range src {
CopyLogsData(&newDest[i], &src[i])
}
return newDest
}
func CopyLogsDataPtrSlice(dest, src []*LogsData) []*LogsData {
var newDest []*LogsData
if cap(dest) < len(src) {
newDest = make([]*LogsData, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewLogsData()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteLogsData(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewLogsData()
}
}
for i := range src {
CopyLogsData(newDest[i], src[i])
}
return newDest
}
func (orig *LogsData) Reset() {
*orig = LogsData{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *LogsData) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if len(orig.ResourceLogs) > 0 {
dest.WriteObjectField("resourceLogs")
dest.WriteArrayStart()
orig.ResourceLogs[0].MarshalJSON(dest)
for i := 1; i < len(orig.ResourceLogs); i++ {
dest.WriteMore()
orig.ResourceLogs[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *LogsData) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "resourceLogs", "resource_logs":
for iter.ReadArray() {
orig.ResourceLogs = append(orig.ResourceLogs, NewResourceLogs())
orig.ResourceLogs[len(orig.ResourceLogs)-1].UnmarshalJSON(iter)
}
default:
iter.Skip()
}
}
}
func (orig *LogsData) SizeProto() int {
var n int
var l int
_ = l
for i := range orig.ResourceLogs {
l = orig.ResourceLogs[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *LogsData) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
for i := len(orig.ResourceLogs) - 1; i >= 0; i-- {
l = orig.ResourceLogs[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
}
return len(buf) - pos
}
func (orig *LogsData) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field ResourceLogs", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.ResourceLogs = append(orig.ResourceLogs, NewResourceLogs())
err = orig.ResourceLogs[len(orig.ResourceLogs)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestLogsData() *LogsData {
orig := NewLogsData()
orig.ResourceLogs = []*ResourceLogs{{}, GenTestResourceLogs()}
return orig
}
func GenTestLogsDataPtrSlice() []*LogsData {
orig := make([]*LogsData, 5)
orig[0] = NewLogsData()
orig[1] = GenTestLogsData()
orig[2] = NewLogsData()
orig[3] = GenTestLogsData()
orig[4] = NewLogsData()
return orig
}
func GenTestLogsDataSlice() []LogsData {
orig := make([]LogsData, 5)
orig[1] = *GenTestLogsData()
orig[3] = *GenTestLogsData()
return orig
}
================================================
FILE: pdata/internal/generated_proto_logsdata_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlplogs "go.opentelemetry.io/proto/slim/otlp/logs/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyLogsData(t *testing.T) {
for name, src := range genTestEncodingValuesLogsData() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewLogsData()
CopyLogsData(dest, src)
assert.Equal(t, src, dest)
CopyLogsData(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyLogsDataSlice(t *testing.T) {
src := []LogsData{}
dest := []LogsData{}
// Test CopyTo empty
dest = CopyLogsDataSlice(dest, src)
assert.Equal(t, []LogsData{}, dest)
// Test CopyTo larger slice
src = GenTestLogsDataSlice()
dest = CopyLogsDataSlice(dest, src)
assert.Equal(t, GenTestLogsDataSlice(), dest)
// Test CopyTo same size slice
dest = CopyLogsDataSlice(dest, src)
assert.Equal(t, GenTestLogsDataSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyLogsDataSlice(dest, []LogsData{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyLogsDataSlice(dest, src)
assert.Equal(t, GenTestLogsDataSlice(), dest)
}
func TestCopyLogsDataPtrSlice(t *testing.T) {
src := []*LogsData{}
dest := []*LogsData{}
// Test CopyTo empty
dest = CopyLogsDataPtrSlice(dest, src)
assert.Equal(t, []*LogsData{}, dest)
// Test CopyTo larger slice
src = GenTestLogsDataPtrSlice()
dest = CopyLogsDataPtrSlice(dest, src)
assert.Equal(t, GenTestLogsDataPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyLogsDataPtrSlice(dest, src)
assert.Equal(t, GenTestLogsDataPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyLogsDataPtrSlice(dest, []*LogsData{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyLogsDataPtrSlice(dest, src)
assert.Equal(t, GenTestLogsDataPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONLogsDataUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewLogsData()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewLogsData(), dest)
}
func TestMarshalAndUnmarshalJSONLogsData(t *testing.T) {
for name, src := range genTestEncodingValuesLogsData() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewLogsData()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteLogsData(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoLogsDataFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesLogsData() {
t.Run(name, func(t *testing.T) {
dest := NewLogsData()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoLogsDataUnknown(t *testing.T) {
dest := NewLogsData()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewLogsData(), dest)
}
func TestMarshalAndUnmarshalProtoLogsData(t *testing.T) {
for name, src := range genTestEncodingValuesLogsData() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewLogsData()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteLogsData(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufLogsData(t *testing.T) {
for name, src := range genTestEncodingValuesLogsData() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlplogs.LogsData{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewLogsData()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesLogsData() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"ResourceLogs/wrong_wire_type": {0xc},
"ResourceLogs/missing_value": {0xa},
}
}
func genTestEncodingValuesLogsData() map[string]*LogsData {
return map[string]*LogsData{
"empty": NewLogsData(),
"ResourceLogs/test": {ResourceLogs: []*ResourceLogs{{}, GenTestResourceLogs()}},
}
}
================================================
FILE: pdata/internal/generated_proto_logsrequest.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"encoding/binary"
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
type LogsRequest struct {
RequestContext *RequestContext
LogsData LogsData
FormatVersion uint32
}
var (
protoPoolLogsRequest = sync.Pool{
New: func() any {
return &LogsRequest{}
},
}
)
func NewLogsRequest() *LogsRequest {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &LogsRequest{}
}
return protoPoolLogsRequest.Get().(*LogsRequest)
}
func DeleteLogsRequest(orig *LogsRequest, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
DeleteRequestContext(orig.RequestContext, true)
DeleteLogsData(&orig.LogsData, false)
orig.Reset()
if nullable {
protoPoolLogsRequest.Put(orig)
}
}
func CopyLogsRequest(dest, src *LogsRequest) *LogsRequest {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewLogsRequest()
}
dest.RequestContext = CopyRequestContext(dest.RequestContext, src.RequestContext)
CopyLogsData(&dest.LogsData, &src.LogsData)
dest.FormatVersion = src.FormatVersion
return dest
}
func CopyLogsRequestSlice(dest, src []LogsRequest) []LogsRequest {
var newDest []LogsRequest
if cap(dest) < len(src) {
newDest = make([]LogsRequest, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteLogsRequest(&dest[i], false)
}
}
for i := range src {
CopyLogsRequest(&newDest[i], &src[i])
}
return newDest
}
func CopyLogsRequestPtrSlice(dest, src []*LogsRequest) []*LogsRequest {
var newDest []*LogsRequest
if cap(dest) < len(src) {
newDest = make([]*LogsRequest, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewLogsRequest()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteLogsRequest(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewLogsRequest()
}
}
for i := range src {
CopyLogsRequest(newDest[i], src[i])
}
return newDest
}
func (orig *LogsRequest) Reset() {
*orig = LogsRequest{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *LogsRequest) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if orig.RequestContext != nil {
dest.WriteObjectField("requestContext")
orig.RequestContext.MarshalJSON(dest)
}
dest.WriteObjectField("logsData")
orig.LogsData.MarshalJSON(dest)
if orig.FormatVersion != uint32(0) {
dest.WriteObjectField("formatVersion")
dest.WriteUint32(orig.FormatVersion)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *LogsRequest) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "requestContext", "request_context":
orig.RequestContext = NewRequestContext()
orig.RequestContext.UnmarshalJSON(iter)
case "logsData", "logs_data":
orig.LogsData.UnmarshalJSON(iter)
case "formatVersion", "format_version":
orig.FormatVersion = iter.ReadUint32()
default:
iter.Skip()
}
}
}
func (orig *LogsRequest) SizeProto() int {
var n int
var l int
_ = l
if orig.RequestContext != nil {
l = orig.RequestContext.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
l = orig.LogsData.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
if orig.FormatVersion != uint32(0) {
n += 5
}
return n
}
func (orig *LogsRequest) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
if orig.RequestContext != nil {
l = orig.RequestContext.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
}
l = orig.LogsData.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x1a
if orig.FormatVersion != uint32(0) {
pos -= 4
binary.LittleEndian.PutUint32(buf[pos:], uint32(orig.FormatVersion))
pos--
buf[pos] = 0xd
}
return len(buf) - pos
}
func (orig *LogsRequest) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field RequestContext", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.RequestContext = NewRequestContext()
err = orig.RequestContext.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 3:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field LogsData", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.LogsData.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 1:
if wireType != proto.WireTypeI32 {
return fmt.Errorf("proto: wrong wireType = %d for field FormatVersion", wireType)
}
var num uint32
num, pos, err = proto.ConsumeI32(buf, pos)
if err != nil {
return err
}
orig.FormatVersion = uint32(num)
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestLogsRequest() *LogsRequest {
orig := NewLogsRequest()
orig.RequestContext = GenTestRequestContext()
orig.LogsData = *GenTestLogsData()
orig.FormatVersion = uint32(13)
return orig
}
func GenTestLogsRequestPtrSlice() []*LogsRequest {
orig := make([]*LogsRequest, 5)
orig[0] = NewLogsRequest()
orig[1] = GenTestLogsRequest()
orig[2] = NewLogsRequest()
orig[3] = GenTestLogsRequest()
orig[4] = NewLogsRequest()
return orig
}
func GenTestLogsRequestSlice() []LogsRequest {
orig := make([]LogsRequest, 5)
orig[1] = *GenTestLogsRequest()
orig[3] = *GenTestLogsRequest()
return orig
}
================================================
FILE: pdata/internal/generated_proto_logsrequest_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/emptypb"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyLogsRequest(t *testing.T) {
for name, src := range genTestEncodingValuesLogsRequest() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewLogsRequest()
CopyLogsRequest(dest, src)
assert.Equal(t, src, dest)
CopyLogsRequest(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyLogsRequestSlice(t *testing.T) {
src := []LogsRequest{}
dest := []LogsRequest{}
// Test CopyTo empty
dest = CopyLogsRequestSlice(dest, src)
assert.Equal(t, []LogsRequest{}, dest)
// Test CopyTo larger slice
src = GenTestLogsRequestSlice()
dest = CopyLogsRequestSlice(dest, src)
assert.Equal(t, GenTestLogsRequestSlice(), dest)
// Test CopyTo same size slice
dest = CopyLogsRequestSlice(dest, src)
assert.Equal(t, GenTestLogsRequestSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyLogsRequestSlice(dest, []LogsRequest{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyLogsRequestSlice(dest, src)
assert.Equal(t, GenTestLogsRequestSlice(), dest)
}
func TestCopyLogsRequestPtrSlice(t *testing.T) {
src := []*LogsRequest{}
dest := []*LogsRequest{}
// Test CopyTo empty
dest = CopyLogsRequestPtrSlice(dest, src)
assert.Equal(t, []*LogsRequest{}, dest)
// Test CopyTo larger slice
src = GenTestLogsRequestPtrSlice()
dest = CopyLogsRequestPtrSlice(dest, src)
assert.Equal(t, GenTestLogsRequestPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyLogsRequestPtrSlice(dest, src)
assert.Equal(t, GenTestLogsRequestPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyLogsRequestPtrSlice(dest, []*LogsRequest{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyLogsRequestPtrSlice(dest, src)
assert.Equal(t, GenTestLogsRequestPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONLogsRequestUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewLogsRequest()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewLogsRequest(), dest)
}
func TestMarshalAndUnmarshalJSONLogsRequest(t *testing.T) {
for name, src := range genTestEncodingValuesLogsRequest() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewLogsRequest()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteLogsRequest(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoLogsRequestFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesLogsRequest() {
t.Run(name, func(t *testing.T) {
dest := NewLogsRequest()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoLogsRequestUnknown(t *testing.T) {
dest := NewLogsRequest()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewLogsRequest(), dest)
}
func TestMarshalAndUnmarshalProtoLogsRequest(t *testing.T) {
for name, src := range genTestEncodingValuesLogsRequest() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewLogsRequest()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteLogsRequest(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufLogsRequest(t *testing.T) {
for name, src := range genTestEncodingValuesLogsRequest() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &emptypb.Empty{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewLogsRequest()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesLogsRequest() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"RequestContext/wrong_wire_type": {0x14},
"RequestContext/missing_value": {0x12},
"LogsData/wrong_wire_type": {0x1c},
"LogsData/missing_value": {0x1a},
"FormatVersion/wrong_wire_type": {0xc},
"FormatVersion/missing_value": {0xd},
}
}
func genTestEncodingValuesLogsRequest() map[string]*LogsRequest {
return map[string]*LogsRequest{
"empty": NewLogsRequest(),
"RequestContext/test": {RequestContext: GenTestRequestContext()},
"LogsData/test": {LogsData: *GenTestLogsData()},
"FormatVersion/test": {FormatVersion: uint32(13)},
}
}
================================================
FILE: pdata/internal/generated_proto_mapping.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// Mapping describes the mapping of a binary in memory, including its address range, file offset, and metadata like build ID
type Mapping struct {
AttributeIndices []int32
MemoryStart uint64
MemoryLimit uint64
FileOffset uint64
FilenameStrindex int32
}
var (
protoPoolMapping = sync.Pool{
New: func() any {
return &Mapping{}
},
}
)
func NewMapping() *Mapping {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &Mapping{}
}
return protoPoolMapping.Get().(*Mapping)
}
func DeleteMapping(orig *Mapping, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
orig.Reset()
if nullable {
protoPoolMapping.Put(orig)
}
}
func CopyMapping(dest, src *Mapping) *Mapping {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewMapping()
}
dest.MemoryStart = src.MemoryStart
dest.MemoryLimit = src.MemoryLimit
dest.FileOffset = src.FileOffset
dest.FilenameStrindex = src.FilenameStrindex
dest.AttributeIndices = append(dest.AttributeIndices[:0], src.AttributeIndices...)
return dest
}
func CopyMappingSlice(dest, src []Mapping) []Mapping {
var newDest []Mapping
if cap(dest) < len(src) {
newDest = make([]Mapping, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteMapping(&dest[i], false)
}
}
for i := range src {
CopyMapping(&newDest[i], &src[i])
}
return newDest
}
func CopyMappingPtrSlice(dest, src []*Mapping) []*Mapping {
var newDest []*Mapping
if cap(dest) < len(src) {
newDest = make([]*Mapping, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewMapping()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteMapping(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewMapping()
}
}
for i := range src {
CopyMapping(newDest[i], src[i])
}
return newDest
}
func (orig *Mapping) Reset() {
*orig = Mapping{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *Mapping) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if orig.MemoryStart != uint64(0) {
dest.WriteObjectField("memoryStart")
dest.WriteUint64(orig.MemoryStart)
}
if orig.MemoryLimit != uint64(0) {
dest.WriteObjectField("memoryLimit")
dest.WriteUint64(orig.MemoryLimit)
}
if orig.FileOffset != uint64(0) {
dest.WriteObjectField("fileOffset")
dest.WriteUint64(orig.FileOffset)
}
if orig.FilenameStrindex != int32(0) {
dest.WriteObjectField("filenameStrindex")
dest.WriteInt32(orig.FilenameStrindex)
}
if len(orig.AttributeIndices) > 0 {
dest.WriteObjectField("attributeIndices")
dest.WriteArrayStart()
dest.WriteInt32(orig.AttributeIndices[0])
for i := 1; i < len(orig.AttributeIndices); i++ {
dest.WriteMore()
dest.WriteInt32(orig.AttributeIndices[i])
}
dest.WriteArrayEnd()
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *Mapping) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "memoryStart", "memory_start":
orig.MemoryStart = iter.ReadUint64()
case "memoryLimit", "memory_limit":
orig.MemoryLimit = iter.ReadUint64()
case "fileOffset", "file_offset":
orig.FileOffset = iter.ReadUint64()
case "filenameStrindex", "filename_strindex":
orig.FilenameStrindex = iter.ReadInt32()
case "attributeIndices", "attribute_indices":
for iter.ReadArray() {
orig.AttributeIndices = append(orig.AttributeIndices, iter.ReadInt32())
}
default:
iter.Skip()
}
}
}
func (orig *Mapping) SizeProto() int {
var n int
var l int
_ = l
if orig.MemoryStart != uint64(0) {
n += 1 + proto.Sov(uint64(orig.MemoryStart))
}
if orig.MemoryLimit != uint64(0) {
n += 1 + proto.Sov(uint64(orig.MemoryLimit))
}
if orig.FileOffset != uint64(0) {
n += 1 + proto.Sov(uint64(orig.FileOffset))
}
if orig.FilenameStrindex != int32(0) {
n += 1 + proto.Sov(uint64(orig.FilenameStrindex))
}
if len(orig.AttributeIndices) > 0 {
l = 0
for _, e := range orig.AttributeIndices {
l += proto.Sov(uint64(e))
}
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *Mapping) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
if orig.MemoryStart != uint64(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.MemoryStart))
pos--
buf[pos] = 0x8
}
if orig.MemoryLimit != uint64(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.MemoryLimit))
pos--
buf[pos] = 0x10
}
if orig.FileOffset != uint64(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.FileOffset))
pos--
buf[pos] = 0x18
}
if orig.FilenameStrindex != int32(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.FilenameStrindex))
pos--
buf[pos] = 0x20
}
l = len(orig.AttributeIndices)
if l > 0 {
endPos := pos
for i := l - 1; i >= 0; i-- {
pos = proto.EncodeVarint(buf, pos, uint64(orig.AttributeIndices[i]))
}
pos = proto.EncodeVarint(buf, pos, uint64(endPos-pos))
pos--
buf[pos] = 0x2a
}
return len(buf) - pos
}
func (orig *Mapping) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field MemoryStart", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.MemoryStart = uint64(num)
case 2:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field MemoryLimit", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.MemoryLimit = uint64(num)
case 3:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field FileOffset", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.FileOffset = uint64(num)
case 4:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field FilenameStrindex", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.FilenameStrindex = int32(num)
case 5:
switch wireType {
case proto.WireTypeLen:
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
var num uint64
for startPos < pos {
num, startPos, err = proto.ConsumeVarint(buf[:pos], startPos)
if err != nil {
return err
}
orig.AttributeIndices = append(orig.AttributeIndices, int32(num))
}
if startPos != pos {
return fmt.Errorf("proto: invalid field len = %d for field AttributeIndices", pos-startPos)
}
case proto.WireTypeVarint:
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.AttributeIndices = append(orig.AttributeIndices, int32(num))
default:
return fmt.Errorf("proto: wrong wireType = %d for field AttributeIndices", wireType)
}
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestMapping() *Mapping {
orig := NewMapping()
orig.MemoryStart = uint64(13)
orig.MemoryLimit = uint64(13)
orig.FileOffset = uint64(13)
orig.FilenameStrindex = int32(13)
orig.AttributeIndices = []int32{int32(0), int32(13)}
return orig
}
func GenTestMappingPtrSlice() []*Mapping {
orig := make([]*Mapping, 5)
orig[0] = NewMapping()
orig[1] = GenTestMapping()
orig[2] = NewMapping()
orig[3] = GenTestMapping()
orig[4] = NewMapping()
return orig
}
func GenTestMappingSlice() []Mapping {
orig := make([]Mapping, 5)
orig[1] = *GenTestMapping()
orig[3] = *GenTestMapping()
return orig
}
================================================
FILE: pdata/internal/generated_proto_mapping_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpprofiles "go.opentelemetry.io/proto/slim/otlp/profiles/v1development"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyMapping(t *testing.T) {
for name, src := range genTestEncodingValuesMapping() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewMapping()
CopyMapping(dest, src)
assert.Equal(t, src, dest)
CopyMapping(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyMappingSlice(t *testing.T) {
src := []Mapping{}
dest := []Mapping{}
// Test CopyTo empty
dest = CopyMappingSlice(dest, src)
assert.Equal(t, []Mapping{}, dest)
// Test CopyTo larger slice
src = GenTestMappingSlice()
dest = CopyMappingSlice(dest, src)
assert.Equal(t, GenTestMappingSlice(), dest)
// Test CopyTo same size slice
dest = CopyMappingSlice(dest, src)
assert.Equal(t, GenTestMappingSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyMappingSlice(dest, []Mapping{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyMappingSlice(dest, src)
assert.Equal(t, GenTestMappingSlice(), dest)
}
func TestCopyMappingPtrSlice(t *testing.T) {
src := []*Mapping{}
dest := []*Mapping{}
// Test CopyTo empty
dest = CopyMappingPtrSlice(dest, src)
assert.Equal(t, []*Mapping{}, dest)
// Test CopyTo larger slice
src = GenTestMappingPtrSlice()
dest = CopyMappingPtrSlice(dest, src)
assert.Equal(t, GenTestMappingPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyMappingPtrSlice(dest, src)
assert.Equal(t, GenTestMappingPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyMappingPtrSlice(dest, []*Mapping{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyMappingPtrSlice(dest, src)
assert.Equal(t, GenTestMappingPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONMappingUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewMapping()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewMapping(), dest)
}
func TestMarshalAndUnmarshalJSONMapping(t *testing.T) {
for name, src := range genTestEncodingValuesMapping() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewMapping()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteMapping(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoMappingFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesMapping() {
t.Run(name, func(t *testing.T) {
dest := NewMapping()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoMappingUnknown(t *testing.T) {
dest := NewMapping()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewMapping(), dest)
}
func TestMarshalAndUnmarshalProtoMapping(t *testing.T) {
for name, src := range genTestEncodingValuesMapping() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewMapping()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteMapping(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufMapping(t *testing.T) {
for name, src := range genTestEncodingValuesMapping() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpprofiles.Mapping{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewMapping()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesMapping() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"MemoryStart/wrong_wire_type": {0xc},
"MemoryStart/missing_value": {0x8},
"MemoryLimit/wrong_wire_type": {0x14},
"MemoryLimit/missing_value": {0x10},
"FileOffset/wrong_wire_type": {0x1c},
"FileOffset/missing_value": {0x18},
"FilenameStrindex/wrong_wire_type": {0x24},
"FilenameStrindex/missing_value": {0x20},
"AttributeIndices/wrong_wire_type": {0x2c},
"AttributeIndices/missing_value": {0x2a},
}
}
func genTestEncodingValuesMapping() map[string]*Mapping {
return map[string]*Mapping{
"empty": NewMapping(),
"MemoryStart/test": {MemoryStart: uint64(13)},
"MemoryLimit/test": {MemoryLimit: uint64(13)},
"FileOffset/test": {FileOffset: uint64(13)},
"FilenameStrindex/test": {FilenameStrindex: int32(13)},
"AttributeIndices/test": {AttributeIndices: []int32{int32(0), int32(13)}},
}
}
================================================
FILE: pdata/internal/generated_proto_metric.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
func (m *Metric) GetData() any {
if m != nil {
return m.Data
}
return nil
}
type Metric_Gauge struct {
Gauge *Gauge
}
func (m *Metric) GetGauge() *Gauge {
if v, ok := m.GetData().(*Metric_Gauge); ok {
return v.Gauge
}
return nil
}
type Metric_Sum struct {
Sum *Sum
}
func (m *Metric) GetSum() *Sum {
if v, ok := m.GetData().(*Metric_Sum); ok {
return v.Sum
}
return nil
}
type Metric_Histogram struct {
Histogram *Histogram
}
func (m *Metric) GetHistogram() *Histogram {
if v, ok := m.GetData().(*Metric_Histogram); ok {
return v.Histogram
}
return nil
}
type Metric_ExponentialHistogram struct {
ExponentialHistogram *ExponentialHistogram
}
func (m *Metric) GetExponentialHistogram() *ExponentialHistogram {
if v, ok := m.GetData().(*Metric_ExponentialHistogram); ok {
return v.ExponentialHistogram
}
return nil
}
type Metric_Summary struct {
Summary *Summary
}
func (m *Metric) GetSummary() *Summary {
if v, ok := m.GetData().(*Metric_Summary); ok {
return v.Summary
}
return nil
}
// Metric represents one metric as a collection of datapoints.
// See Metric definition in OTLP: https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/metrics/v1/metrics.proto
type Metric struct {
Name string
Description string
Unit string
Data any
Metadata []KeyValue
}
var (
protoPoolMetric = sync.Pool{
New: func() any {
return &Metric{}
},
}
ProtoPoolMetric_Gauge = sync.Pool{
New: func() any {
return &Metric_Gauge{}
},
}
ProtoPoolMetric_Sum = sync.Pool{
New: func() any {
return &Metric_Sum{}
},
}
ProtoPoolMetric_Histogram = sync.Pool{
New: func() any {
return &Metric_Histogram{}
},
}
ProtoPoolMetric_ExponentialHistogram = sync.Pool{
New: func() any {
return &Metric_ExponentialHistogram{}
},
}
ProtoPoolMetric_Summary = sync.Pool{
New: func() any {
return &Metric_Summary{}
},
}
)
func NewMetric() *Metric {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &Metric{}
}
return protoPoolMetric.Get().(*Metric)
}
func DeleteMetric(orig *Metric, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
switch ov := orig.Data.(type) {
case *Metric_Gauge:
DeleteGauge(ov.Gauge, true)
ov.Gauge = nil
ProtoPoolMetric_Gauge.Put(ov)
case *Metric_Sum:
DeleteSum(ov.Sum, true)
ov.Sum = nil
ProtoPoolMetric_Sum.Put(ov)
case *Metric_Histogram:
DeleteHistogram(ov.Histogram, true)
ov.Histogram = nil
ProtoPoolMetric_Histogram.Put(ov)
case *Metric_ExponentialHistogram:
DeleteExponentialHistogram(ov.ExponentialHistogram, true)
ov.ExponentialHistogram = nil
ProtoPoolMetric_ExponentialHistogram.Put(ov)
case *Metric_Summary:
DeleteSummary(ov.Summary, true)
ov.Summary = nil
ProtoPoolMetric_Summary.Put(ov)
}
for i := range orig.Metadata {
DeleteKeyValue(&orig.Metadata[i], false)
}
orig.Reset()
if nullable {
protoPoolMetric.Put(orig)
}
}
func CopyMetric(dest, src *Metric) *Metric {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewMetric()
}
dest.Name = src.Name
dest.Description = src.Description
dest.Unit = src.Unit
switch t := src.Data.(type) {
case *Metric_Gauge:
var ov *Metric_Gauge
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &Metric_Gauge{}
} else {
ov = ProtoPoolMetric_Gauge.Get().(*Metric_Gauge)
}
ov.Gauge = NewGauge()
CopyGauge(ov.Gauge, t.Gauge)
dest.Data = ov
case *Metric_Sum:
var ov *Metric_Sum
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &Metric_Sum{}
} else {
ov = ProtoPoolMetric_Sum.Get().(*Metric_Sum)
}
ov.Sum = NewSum()
CopySum(ov.Sum, t.Sum)
dest.Data = ov
case *Metric_Histogram:
var ov *Metric_Histogram
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &Metric_Histogram{}
} else {
ov = ProtoPoolMetric_Histogram.Get().(*Metric_Histogram)
}
ov.Histogram = NewHistogram()
CopyHistogram(ov.Histogram, t.Histogram)
dest.Data = ov
case *Metric_ExponentialHistogram:
var ov *Metric_ExponentialHistogram
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &Metric_ExponentialHistogram{}
} else {
ov = ProtoPoolMetric_ExponentialHistogram.Get().(*Metric_ExponentialHistogram)
}
ov.ExponentialHistogram = NewExponentialHistogram()
CopyExponentialHistogram(ov.ExponentialHistogram, t.ExponentialHistogram)
dest.Data = ov
case *Metric_Summary:
var ov *Metric_Summary
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &Metric_Summary{}
} else {
ov = ProtoPoolMetric_Summary.Get().(*Metric_Summary)
}
ov.Summary = NewSummary()
CopySummary(ov.Summary, t.Summary)
dest.Data = ov
default:
dest.Data = nil
}
dest.Metadata = CopyKeyValueSlice(dest.Metadata, src.Metadata)
return dest
}
func CopyMetricSlice(dest, src []Metric) []Metric {
var newDest []Metric
if cap(dest) < len(src) {
newDest = make([]Metric, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteMetric(&dest[i], false)
}
}
for i := range src {
CopyMetric(&newDest[i], &src[i])
}
return newDest
}
func CopyMetricPtrSlice(dest, src []*Metric) []*Metric {
var newDest []*Metric
if cap(dest) < len(src) {
newDest = make([]*Metric, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewMetric()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteMetric(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewMetric()
}
}
for i := range src {
CopyMetric(newDest[i], src[i])
}
return newDest
}
func (orig *Metric) Reset() {
*orig = Metric{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *Metric) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if orig.Name != "" {
dest.WriteObjectField("name")
dest.WriteString(orig.Name)
}
if orig.Description != "" {
dest.WriteObjectField("description")
dest.WriteString(orig.Description)
}
if orig.Unit != "" {
dest.WriteObjectField("unit")
dest.WriteString(orig.Unit)
}
switch orig := orig.Data.(type) {
case *Metric_Gauge:
if orig.Gauge != nil {
dest.WriteObjectField("gauge")
orig.Gauge.MarshalJSON(dest)
}
case *Metric_Sum:
if orig.Sum != nil {
dest.WriteObjectField("sum")
orig.Sum.MarshalJSON(dest)
}
case *Metric_Histogram:
if orig.Histogram != nil {
dest.WriteObjectField("histogram")
orig.Histogram.MarshalJSON(dest)
}
case *Metric_ExponentialHistogram:
if orig.ExponentialHistogram != nil {
dest.WriteObjectField("exponentialHistogram")
orig.ExponentialHistogram.MarshalJSON(dest)
}
case *Metric_Summary:
if orig.Summary != nil {
dest.WriteObjectField("summary")
orig.Summary.MarshalJSON(dest)
}
}
if len(orig.Metadata) > 0 {
dest.WriteObjectField("metadata")
dest.WriteArrayStart()
orig.Metadata[0].MarshalJSON(dest)
for i := 1; i < len(orig.Metadata); i++ {
dest.WriteMore()
orig.Metadata[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *Metric) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "name":
orig.Name = iter.ReadString()
case "description":
orig.Description = iter.ReadString()
case "unit":
orig.Unit = iter.ReadString()
case "gauge":
{
var ov *Metric_Gauge
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &Metric_Gauge{}
} else {
ov = ProtoPoolMetric_Gauge.Get().(*Metric_Gauge)
}
ov.Gauge = NewGauge()
ov.Gauge.UnmarshalJSON(iter)
orig.Data = ov
}
case "sum":
{
var ov *Metric_Sum
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &Metric_Sum{}
} else {
ov = ProtoPoolMetric_Sum.Get().(*Metric_Sum)
}
ov.Sum = NewSum()
ov.Sum.UnmarshalJSON(iter)
orig.Data = ov
}
case "histogram":
{
var ov *Metric_Histogram
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &Metric_Histogram{}
} else {
ov = ProtoPoolMetric_Histogram.Get().(*Metric_Histogram)
}
ov.Histogram = NewHistogram()
ov.Histogram.UnmarshalJSON(iter)
orig.Data = ov
}
case "exponentialHistogram", "exponential_histogram":
{
var ov *Metric_ExponentialHistogram
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &Metric_ExponentialHistogram{}
} else {
ov = ProtoPoolMetric_ExponentialHistogram.Get().(*Metric_ExponentialHistogram)
}
ov.ExponentialHistogram = NewExponentialHistogram()
ov.ExponentialHistogram.UnmarshalJSON(iter)
orig.Data = ov
}
case "summary":
{
var ov *Metric_Summary
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &Metric_Summary{}
} else {
ov = ProtoPoolMetric_Summary.Get().(*Metric_Summary)
}
ov.Summary = NewSummary()
ov.Summary.UnmarshalJSON(iter)
orig.Data = ov
}
case "metadata":
for iter.ReadArray() {
orig.Metadata = append(orig.Metadata, KeyValue{})
orig.Metadata[len(orig.Metadata)-1].UnmarshalJSON(iter)
}
default:
iter.Skip()
}
}
}
func (orig *Metric) SizeProto() int {
var n int
var l int
_ = l
l = len(orig.Name)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
l = len(orig.Description)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
l = len(orig.Unit)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
switch orig := orig.Data.(type) {
case nil:
_ = orig
break
case *Metric_Gauge:
if orig.Gauge != nil {
l = orig.Gauge.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
case *Metric_Sum:
if orig.Sum != nil {
l = orig.Sum.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
case *Metric_Histogram:
if orig.Histogram != nil {
l = orig.Histogram.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
case *Metric_ExponentialHistogram:
if orig.ExponentialHistogram != nil {
l = orig.ExponentialHistogram.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
case *Metric_Summary:
if orig.Summary != nil {
l = orig.Summary.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
}
for i := range orig.Metadata {
l = orig.Metadata[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *Metric) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
l = len(orig.Name)
if l > 0 {
pos -= l
copy(buf[pos:], orig.Name)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
}
l = len(orig.Description)
if l > 0 {
pos -= l
copy(buf[pos:], orig.Description)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
}
l = len(orig.Unit)
if l > 0 {
pos -= l
copy(buf[pos:], orig.Unit)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x1a
}
switch orig := orig.Data.(type) {
case *Metric_Gauge:
if orig.Gauge != nil {
l = orig.Gauge.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x2a
}
case *Metric_Sum:
if orig.Sum != nil {
l = orig.Sum.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x3a
}
case *Metric_Histogram:
if orig.Histogram != nil {
l = orig.Histogram.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x4a
}
case *Metric_ExponentialHistogram:
if orig.ExponentialHistogram != nil {
l = orig.ExponentialHistogram.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x52
}
case *Metric_Summary:
if orig.Summary != nil {
l = orig.Summary.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x5a
}
}
for i := len(orig.Metadata) - 1; i >= 0; i-- {
l = orig.Metadata[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x62
}
return len(buf) - pos
}
func (orig *Metric) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Name = string(buf[startPos:pos])
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Description = string(buf[startPos:pos])
case 3:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Unit", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Unit = string(buf[startPos:pos])
case 5:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Gauge", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
var ov *Metric_Gauge
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &Metric_Gauge{}
} else {
ov = ProtoPoolMetric_Gauge.Get().(*Metric_Gauge)
}
ov.Gauge = NewGauge()
err = ov.Gauge.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
orig.Data = ov
case 7:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Sum", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
var ov *Metric_Sum
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &Metric_Sum{}
} else {
ov = ProtoPoolMetric_Sum.Get().(*Metric_Sum)
}
ov.Sum = NewSum()
err = ov.Sum.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
orig.Data = ov
case 9:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Histogram", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
var ov *Metric_Histogram
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &Metric_Histogram{}
} else {
ov = ProtoPoolMetric_Histogram.Get().(*Metric_Histogram)
}
ov.Histogram = NewHistogram()
err = ov.Histogram.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
orig.Data = ov
case 10:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field ExponentialHistogram", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
var ov *Metric_ExponentialHistogram
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &Metric_ExponentialHistogram{}
} else {
ov = ProtoPoolMetric_ExponentialHistogram.Get().(*Metric_ExponentialHistogram)
}
ov.ExponentialHistogram = NewExponentialHistogram()
err = ov.ExponentialHistogram.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
orig.Data = ov
case 11:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Summary", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
var ov *Metric_Summary
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &Metric_Summary{}
} else {
ov = ProtoPoolMetric_Summary.Get().(*Metric_Summary)
}
ov.Summary = NewSummary()
err = ov.Summary.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
orig.Data = ov
case 12:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Metadata = append(orig.Metadata, KeyValue{})
err = orig.Metadata[len(orig.Metadata)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestMetric() *Metric {
orig := NewMetric()
orig.Name = "test_name"
orig.Description = "test_description"
orig.Unit = "test_unit"
orig.Data = &Metric_Gauge{Gauge: GenTestGauge()}
orig.Metadata = []KeyValue{{}, *GenTestKeyValue()}
return orig
}
func GenTestMetricPtrSlice() []*Metric {
orig := make([]*Metric, 5)
orig[0] = NewMetric()
orig[1] = GenTestMetric()
orig[2] = NewMetric()
orig[3] = GenTestMetric()
orig[4] = NewMetric()
return orig
}
func GenTestMetricSlice() []Metric {
orig := make([]Metric, 5)
orig[1] = *GenTestMetric()
orig[3] = *GenTestMetric()
return orig
}
================================================
FILE: pdata/internal/generated_proto_metric_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpmetrics "go.opentelemetry.io/proto/slim/otlp/metrics/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyMetric(t *testing.T) {
for name, src := range genTestEncodingValuesMetric() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewMetric()
CopyMetric(dest, src)
assert.Equal(t, src, dest)
CopyMetric(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyMetricSlice(t *testing.T) {
src := []Metric{}
dest := []Metric{}
// Test CopyTo empty
dest = CopyMetricSlice(dest, src)
assert.Equal(t, []Metric{}, dest)
// Test CopyTo larger slice
src = GenTestMetricSlice()
dest = CopyMetricSlice(dest, src)
assert.Equal(t, GenTestMetricSlice(), dest)
// Test CopyTo same size slice
dest = CopyMetricSlice(dest, src)
assert.Equal(t, GenTestMetricSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyMetricSlice(dest, []Metric{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyMetricSlice(dest, src)
assert.Equal(t, GenTestMetricSlice(), dest)
}
func TestCopyMetricPtrSlice(t *testing.T) {
src := []*Metric{}
dest := []*Metric{}
// Test CopyTo empty
dest = CopyMetricPtrSlice(dest, src)
assert.Equal(t, []*Metric{}, dest)
// Test CopyTo larger slice
src = GenTestMetricPtrSlice()
dest = CopyMetricPtrSlice(dest, src)
assert.Equal(t, GenTestMetricPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyMetricPtrSlice(dest, src)
assert.Equal(t, GenTestMetricPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyMetricPtrSlice(dest, []*Metric{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyMetricPtrSlice(dest, src)
assert.Equal(t, GenTestMetricPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONMetricUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewMetric()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewMetric(), dest)
}
func TestMarshalAndUnmarshalJSONMetric(t *testing.T) {
for name, src := range genTestEncodingValuesMetric() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewMetric()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteMetric(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoMetricFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesMetric() {
t.Run(name, func(t *testing.T) {
dest := NewMetric()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoMetricUnknown(t *testing.T) {
dest := NewMetric()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewMetric(), dest)
}
func TestMarshalAndUnmarshalProtoMetric(t *testing.T) {
for name, src := range genTestEncodingValuesMetric() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewMetric()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteMetric(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufMetric(t *testing.T) {
for name, src := range genTestEncodingValuesMetric() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpmetrics.Metric{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewMetric()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesMetric() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"Name/wrong_wire_type": {0xc},
"Name/missing_value": {0xa},
"Description/wrong_wire_type": {0x14},
"Description/missing_value": {0x12},
"Unit/wrong_wire_type": {0x1c},
"Unit/missing_value": {0x1a},
"Gauge/wrong_wire_type": {0x2c},
"Gauge/missing_value": {0x2a},
"Sum/wrong_wire_type": {0x3c},
"Sum/missing_value": {0x3a},
"Histogram/wrong_wire_type": {0x4c},
"Histogram/missing_value": {0x4a},
"ExponentialHistogram/wrong_wire_type": {0x54},
"ExponentialHistogram/missing_value": {0x52},
"Summary/wrong_wire_type": {0x5c},
"Summary/missing_value": {0x5a},
"Metadata/wrong_wire_type": {0x64},
"Metadata/missing_value": {0x62},
}
}
func genTestEncodingValuesMetric() map[string]*Metric {
return map[string]*Metric{
"empty": NewMetric(),
"Name/test": {Name: "test_name"},
"Description/test": {Description: "test_description"},
"Unit/test": {Unit: "test_unit"},
"Gauge/default": {Data: &Metric_Gauge{Gauge: &Gauge{}}},
"Gauge/test": {Data: &Metric_Gauge{Gauge: GenTestGauge()}}, "Sum/default": {Data: &Metric_Sum{Sum: &Sum{}}},
"Sum/test": {Data: &Metric_Sum{Sum: GenTestSum()}}, "Histogram/default": {Data: &Metric_Histogram{Histogram: &Histogram{}}},
"Histogram/test": {Data: &Metric_Histogram{Histogram: GenTestHistogram()}}, "ExponentialHistogram/default": {Data: &Metric_ExponentialHistogram{ExponentialHistogram: &ExponentialHistogram{}}},
"ExponentialHistogram/test": {Data: &Metric_ExponentialHistogram{ExponentialHistogram: GenTestExponentialHistogram()}}, "Summary/default": {Data: &Metric_Summary{Summary: &Summary{}}},
"Summary/test": {Data: &Metric_Summary{Summary: GenTestSummary()}},
"Metadata/test": {Metadata: []KeyValue{{}, *GenTestKeyValue()}},
}
}
================================================
FILE: pdata/internal/generated_proto_metricsdata.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// MetricsData represents the metrics data that can be stored in a persistent storage,
// OR can be embedded by other protocols that transfer OTLP metrics data but do not
// implement the OTLP protocol..
type MetricsData struct {
ResourceMetrics []*ResourceMetrics
}
var (
protoPoolMetricsData = sync.Pool{
New: func() any {
return &MetricsData{}
},
}
)
func NewMetricsData() *MetricsData {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &MetricsData{}
}
return protoPoolMetricsData.Get().(*MetricsData)
}
func DeleteMetricsData(orig *MetricsData, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
for i := range orig.ResourceMetrics {
DeleteResourceMetrics(orig.ResourceMetrics[i], true)
}
orig.Reset()
if nullable {
protoPoolMetricsData.Put(orig)
}
}
func CopyMetricsData(dest, src *MetricsData) *MetricsData {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewMetricsData()
}
dest.ResourceMetrics = CopyResourceMetricsPtrSlice(dest.ResourceMetrics, src.ResourceMetrics)
return dest
}
func CopyMetricsDataSlice(dest, src []MetricsData) []MetricsData {
var newDest []MetricsData
if cap(dest) < len(src) {
newDest = make([]MetricsData, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteMetricsData(&dest[i], false)
}
}
for i := range src {
CopyMetricsData(&newDest[i], &src[i])
}
return newDest
}
func CopyMetricsDataPtrSlice(dest, src []*MetricsData) []*MetricsData {
var newDest []*MetricsData
if cap(dest) < len(src) {
newDest = make([]*MetricsData, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewMetricsData()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteMetricsData(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewMetricsData()
}
}
for i := range src {
CopyMetricsData(newDest[i], src[i])
}
return newDest
}
func (orig *MetricsData) Reset() {
*orig = MetricsData{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *MetricsData) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if len(orig.ResourceMetrics) > 0 {
dest.WriteObjectField("resourceMetrics")
dest.WriteArrayStart()
orig.ResourceMetrics[0].MarshalJSON(dest)
for i := 1; i < len(orig.ResourceMetrics); i++ {
dest.WriteMore()
orig.ResourceMetrics[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *MetricsData) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "resourceMetrics", "resource_metrics":
for iter.ReadArray() {
orig.ResourceMetrics = append(orig.ResourceMetrics, NewResourceMetrics())
orig.ResourceMetrics[len(orig.ResourceMetrics)-1].UnmarshalJSON(iter)
}
default:
iter.Skip()
}
}
}
func (orig *MetricsData) SizeProto() int {
var n int
var l int
_ = l
for i := range orig.ResourceMetrics {
l = orig.ResourceMetrics[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *MetricsData) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
for i := len(orig.ResourceMetrics) - 1; i >= 0; i-- {
l = orig.ResourceMetrics[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
}
return len(buf) - pos
}
func (orig *MetricsData) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field ResourceMetrics", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.ResourceMetrics = append(orig.ResourceMetrics, NewResourceMetrics())
err = orig.ResourceMetrics[len(orig.ResourceMetrics)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestMetricsData() *MetricsData {
orig := NewMetricsData()
orig.ResourceMetrics = []*ResourceMetrics{{}, GenTestResourceMetrics()}
return orig
}
func GenTestMetricsDataPtrSlice() []*MetricsData {
orig := make([]*MetricsData, 5)
orig[0] = NewMetricsData()
orig[1] = GenTestMetricsData()
orig[2] = NewMetricsData()
orig[3] = GenTestMetricsData()
orig[4] = NewMetricsData()
return orig
}
func GenTestMetricsDataSlice() []MetricsData {
orig := make([]MetricsData, 5)
orig[1] = *GenTestMetricsData()
orig[3] = *GenTestMetricsData()
return orig
}
================================================
FILE: pdata/internal/generated_proto_metricsdata_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpmetrics "go.opentelemetry.io/proto/slim/otlp/metrics/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyMetricsData(t *testing.T) {
for name, src := range genTestEncodingValuesMetricsData() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewMetricsData()
CopyMetricsData(dest, src)
assert.Equal(t, src, dest)
CopyMetricsData(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyMetricsDataSlice(t *testing.T) {
src := []MetricsData{}
dest := []MetricsData{}
// Test CopyTo empty
dest = CopyMetricsDataSlice(dest, src)
assert.Equal(t, []MetricsData{}, dest)
// Test CopyTo larger slice
src = GenTestMetricsDataSlice()
dest = CopyMetricsDataSlice(dest, src)
assert.Equal(t, GenTestMetricsDataSlice(), dest)
// Test CopyTo same size slice
dest = CopyMetricsDataSlice(dest, src)
assert.Equal(t, GenTestMetricsDataSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyMetricsDataSlice(dest, []MetricsData{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyMetricsDataSlice(dest, src)
assert.Equal(t, GenTestMetricsDataSlice(), dest)
}
func TestCopyMetricsDataPtrSlice(t *testing.T) {
src := []*MetricsData{}
dest := []*MetricsData{}
// Test CopyTo empty
dest = CopyMetricsDataPtrSlice(dest, src)
assert.Equal(t, []*MetricsData{}, dest)
// Test CopyTo larger slice
src = GenTestMetricsDataPtrSlice()
dest = CopyMetricsDataPtrSlice(dest, src)
assert.Equal(t, GenTestMetricsDataPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyMetricsDataPtrSlice(dest, src)
assert.Equal(t, GenTestMetricsDataPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyMetricsDataPtrSlice(dest, []*MetricsData{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyMetricsDataPtrSlice(dest, src)
assert.Equal(t, GenTestMetricsDataPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONMetricsDataUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewMetricsData()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewMetricsData(), dest)
}
func TestMarshalAndUnmarshalJSONMetricsData(t *testing.T) {
for name, src := range genTestEncodingValuesMetricsData() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewMetricsData()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteMetricsData(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoMetricsDataFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesMetricsData() {
t.Run(name, func(t *testing.T) {
dest := NewMetricsData()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoMetricsDataUnknown(t *testing.T) {
dest := NewMetricsData()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewMetricsData(), dest)
}
func TestMarshalAndUnmarshalProtoMetricsData(t *testing.T) {
for name, src := range genTestEncodingValuesMetricsData() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewMetricsData()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteMetricsData(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufMetricsData(t *testing.T) {
for name, src := range genTestEncodingValuesMetricsData() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpmetrics.MetricsData{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewMetricsData()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesMetricsData() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"ResourceMetrics/wrong_wire_type": {0xc},
"ResourceMetrics/missing_value": {0xa},
}
}
func genTestEncodingValuesMetricsData() map[string]*MetricsData {
return map[string]*MetricsData{
"empty": NewMetricsData(),
"ResourceMetrics/test": {ResourceMetrics: []*ResourceMetrics{{}, GenTestResourceMetrics()}},
}
}
================================================
FILE: pdata/internal/generated_proto_metricsrequest.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"encoding/binary"
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
type MetricsRequest struct {
RequestContext *RequestContext
MetricsData MetricsData
FormatVersion uint32
}
var (
protoPoolMetricsRequest = sync.Pool{
New: func() any {
return &MetricsRequest{}
},
}
)
func NewMetricsRequest() *MetricsRequest {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &MetricsRequest{}
}
return protoPoolMetricsRequest.Get().(*MetricsRequest)
}
func DeleteMetricsRequest(orig *MetricsRequest, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
DeleteRequestContext(orig.RequestContext, true)
DeleteMetricsData(&orig.MetricsData, false)
orig.Reset()
if nullable {
protoPoolMetricsRequest.Put(orig)
}
}
func CopyMetricsRequest(dest, src *MetricsRequest) *MetricsRequest {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewMetricsRequest()
}
dest.RequestContext = CopyRequestContext(dest.RequestContext, src.RequestContext)
CopyMetricsData(&dest.MetricsData, &src.MetricsData)
dest.FormatVersion = src.FormatVersion
return dest
}
func CopyMetricsRequestSlice(dest, src []MetricsRequest) []MetricsRequest {
var newDest []MetricsRequest
if cap(dest) < len(src) {
newDest = make([]MetricsRequest, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteMetricsRequest(&dest[i], false)
}
}
for i := range src {
CopyMetricsRequest(&newDest[i], &src[i])
}
return newDest
}
func CopyMetricsRequestPtrSlice(dest, src []*MetricsRequest) []*MetricsRequest {
var newDest []*MetricsRequest
if cap(dest) < len(src) {
newDest = make([]*MetricsRequest, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewMetricsRequest()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteMetricsRequest(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewMetricsRequest()
}
}
for i := range src {
CopyMetricsRequest(newDest[i], src[i])
}
return newDest
}
func (orig *MetricsRequest) Reset() {
*orig = MetricsRequest{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *MetricsRequest) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if orig.RequestContext != nil {
dest.WriteObjectField("requestContext")
orig.RequestContext.MarshalJSON(dest)
}
dest.WriteObjectField("metricsData")
orig.MetricsData.MarshalJSON(dest)
if orig.FormatVersion != uint32(0) {
dest.WriteObjectField("formatVersion")
dest.WriteUint32(orig.FormatVersion)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *MetricsRequest) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "requestContext", "request_context":
orig.RequestContext = NewRequestContext()
orig.RequestContext.UnmarshalJSON(iter)
case "metricsData", "metrics_data":
orig.MetricsData.UnmarshalJSON(iter)
case "formatVersion", "format_version":
orig.FormatVersion = iter.ReadUint32()
default:
iter.Skip()
}
}
}
func (orig *MetricsRequest) SizeProto() int {
var n int
var l int
_ = l
if orig.RequestContext != nil {
l = orig.RequestContext.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
l = orig.MetricsData.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
if orig.FormatVersion != uint32(0) {
n += 5
}
return n
}
func (orig *MetricsRequest) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
if orig.RequestContext != nil {
l = orig.RequestContext.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
}
l = orig.MetricsData.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x1a
if orig.FormatVersion != uint32(0) {
pos -= 4
binary.LittleEndian.PutUint32(buf[pos:], uint32(orig.FormatVersion))
pos--
buf[pos] = 0xd
}
return len(buf) - pos
}
func (orig *MetricsRequest) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field RequestContext", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.RequestContext = NewRequestContext()
err = orig.RequestContext.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 3:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field MetricsData", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.MetricsData.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 1:
if wireType != proto.WireTypeI32 {
return fmt.Errorf("proto: wrong wireType = %d for field FormatVersion", wireType)
}
var num uint32
num, pos, err = proto.ConsumeI32(buf, pos)
if err != nil {
return err
}
orig.FormatVersion = uint32(num)
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestMetricsRequest() *MetricsRequest {
orig := NewMetricsRequest()
orig.RequestContext = GenTestRequestContext()
orig.MetricsData = *GenTestMetricsData()
orig.FormatVersion = uint32(13)
return orig
}
func GenTestMetricsRequestPtrSlice() []*MetricsRequest {
orig := make([]*MetricsRequest, 5)
orig[0] = NewMetricsRequest()
orig[1] = GenTestMetricsRequest()
orig[2] = NewMetricsRequest()
orig[3] = GenTestMetricsRequest()
orig[4] = NewMetricsRequest()
return orig
}
func GenTestMetricsRequestSlice() []MetricsRequest {
orig := make([]MetricsRequest, 5)
orig[1] = *GenTestMetricsRequest()
orig[3] = *GenTestMetricsRequest()
return orig
}
================================================
FILE: pdata/internal/generated_proto_metricsrequest_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/emptypb"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyMetricsRequest(t *testing.T) {
for name, src := range genTestEncodingValuesMetricsRequest() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewMetricsRequest()
CopyMetricsRequest(dest, src)
assert.Equal(t, src, dest)
CopyMetricsRequest(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyMetricsRequestSlice(t *testing.T) {
src := []MetricsRequest{}
dest := []MetricsRequest{}
// Test CopyTo empty
dest = CopyMetricsRequestSlice(dest, src)
assert.Equal(t, []MetricsRequest{}, dest)
// Test CopyTo larger slice
src = GenTestMetricsRequestSlice()
dest = CopyMetricsRequestSlice(dest, src)
assert.Equal(t, GenTestMetricsRequestSlice(), dest)
// Test CopyTo same size slice
dest = CopyMetricsRequestSlice(dest, src)
assert.Equal(t, GenTestMetricsRequestSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyMetricsRequestSlice(dest, []MetricsRequest{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyMetricsRequestSlice(dest, src)
assert.Equal(t, GenTestMetricsRequestSlice(), dest)
}
func TestCopyMetricsRequestPtrSlice(t *testing.T) {
src := []*MetricsRequest{}
dest := []*MetricsRequest{}
// Test CopyTo empty
dest = CopyMetricsRequestPtrSlice(dest, src)
assert.Equal(t, []*MetricsRequest{}, dest)
// Test CopyTo larger slice
src = GenTestMetricsRequestPtrSlice()
dest = CopyMetricsRequestPtrSlice(dest, src)
assert.Equal(t, GenTestMetricsRequestPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyMetricsRequestPtrSlice(dest, src)
assert.Equal(t, GenTestMetricsRequestPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyMetricsRequestPtrSlice(dest, []*MetricsRequest{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyMetricsRequestPtrSlice(dest, src)
assert.Equal(t, GenTestMetricsRequestPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONMetricsRequestUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewMetricsRequest()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewMetricsRequest(), dest)
}
func TestMarshalAndUnmarshalJSONMetricsRequest(t *testing.T) {
for name, src := range genTestEncodingValuesMetricsRequest() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewMetricsRequest()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteMetricsRequest(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoMetricsRequestFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesMetricsRequest() {
t.Run(name, func(t *testing.T) {
dest := NewMetricsRequest()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoMetricsRequestUnknown(t *testing.T) {
dest := NewMetricsRequest()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewMetricsRequest(), dest)
}
func TestMarshalAndUnmarshalProtoMetricsRequest(t *testing.T) {
for name, src := range genTestEncodingValuesMetricsRequest() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewMetricsRequest()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteMetricsRequest(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufMetricsRequest(t *testing.T) {
for name, src := range genTestEncodingValuesMetricsRequest() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &emptypb.Empty{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewMetricsRequest()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesMetricsRequest() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"RequestContext/wrong_wire_type": {0x14},
"RequestContext/missing_value": {0x12},
"MetricsData/wrong_wire_type": {0x1c},
"MetricsData/missing_value": {0x1a},
"FormatVersion/wrong_wire_type": {0xc},
"FormatVersion/missing_value": {0xd},
}
}
func genTestEncodingValuesMetricsRequest() map[string]*MetricsRequest {
return map[string]*MetricsRequest{
"empty": NewMetricsRequest(),
"RequestContext/test": {RequestContext: GenTestRequestContext()},
"MetricsData/test": {MetricsData: *GenTestMetricsData()},
"FormatVersion/test": {FormatVersion: uint32(13)},
}
}
================================================
FILE: pdata/internal/generated_proto_numberdatapoint.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"encoding/binary"
"fmt"
"math"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
func (m *NumberDataPoint) GetValue() any {
if m != nil {
return m.Value
}
return nil
}
type NumberDataPoint_AsDouble struct {
AsDouble float64
}
func (m *NumberDataPoint) GetAsDouble() float64 {
if v, ok := m.GetValue().(*NumberDataPoint_AsDouble); ok {
return v.AsDouble
}
return float64(0)
}
type NumberDataPoint_AsInt struct {
AsInt int64
}
func (m *NumberDataPoint) GetAsInt() int64 {
if v, ok := m.GetValue().(*NumberDataPoint_AsInt); ok {
return v.AsInt
}
return int64(0)
}
// NumberDataPoint is a single data point in a timeseries that describes the time-varying value of a number metric.
type NumberDataPoint struct {
Value any
Attributes []KeyValue
Exemplars []Exemplar
StartTimeUnixNano uint64
TimeUnixNano uint64
Flags uint32
}
var (
protoPoolNumberDataPoint = sync.Pool{
New: func() any {
return &NumberDataPoint{}
},
}
ProtoPoolNumberDataPoint_AsDouble = sync.Pool{
New: func() any {
return &NumberDataPoint_AsDouble{}
},
}
ProtoPoolNumberDataPoint_AsInt = sync.Pool{
New: func() any {
return &NumberDataPoint_AsInt{}
},
}
)
func NewNumberDataPoint() *NumberDataPoint {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &NumberDataPoint{}
}
return protoPoolNumberDataPoint.Get().(*NumberDataPoint)
}
func DeleteNumberDataPoint(orig *NumberDataPoint, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
for i := range orig.Attributes {
DeleteKeyValue(&orig.Attributes[i], false)
}
switch ov := orig.Value.(type) {
case *NumberDataPoint_AsDouble:
if metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov.AsDouble = float64(0)
ProtoPoolNumberDataPoint_AsDouble.Put(ov)
}
case *NumberDataPoint_AsInt:
if metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov.AsInt = int64(0)
ProtoPoolNumberDataPoint_AsInt.Put(ov)
}
}
for i := range orig.Exemplars {
DeleteExemplar(&orig.Exemplars[i], false)
}
orig.Reset()
if nullable {
protoPoolNumberDataPoint.Put(orig)
}
}
func CopyNumberDataPoint(dest, src *NumberDataPoint) *NumberDataPoint {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewNumberDataPoint()
}
dest.Attributes = CopyKeyValueSlice(dest.Attributes, src.Attributes)
dest.StartTimeUnixNano = src.StartTimeUnixNano
dest.TimeUnixNano = src.TimeUnixNano
switch t := src.Value.(type) {
case *NumberDataPoint_AsDouble:
var ov *NumberDataPoint_AsDouble
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &NumberDataPoint_AsDouble{}
} else {
ov = ProtoPoolNumberDataPoint_AsDouble.Get().(*NumberDataPoint_AsDouble)
}
ov.AsDouble = t.AsDouble
dest.Value = ov
case *NumberDataPoint_AsInt:
var ov *NumberDataPoint_AsInt
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &NumberDataPoint_AsInt{}
} else {
ov = ProtoPoolNumberDataPoint_AsInt.Get().(*NumberDataPoint_AsInt)
}
ov.AsInt = t.AsInt
dest.Value = ov
default:
dest.Value = nil
}
dest.Exemplars = CopyExemplarSlice(dest.Exemplars, src.Exemplars)
dest.Flags = src.Flags
return dest
}
func CopyNumberDataPointSlice(dest, src []NumberDataPoint) []NumberDataPoint {
var newDest []NumberDataPoint
if cap(dest) < len(src) {
newDest = make([]NumberDataPoint, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteNumberDataPoint(&dest[i], false)
}
}
for i := range src {
CopyNumberDataPoint(&newDest[i], &src[i])
}
return newDest
}
func CopyNumberDataPointPtrSlice(dest, src []*NumberDataPoint) []*NumberDataPoint {
var newDest []*NumberDataPoint
if cap(dest) < len(src) {
newDest = make([]*NumberDataPoint, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewNumberDataPoint()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteNumberDataPoint(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewNumberDataPoint()
}
}
for i := range src {
CopyNumberDataPoint(newDest[i], src[i])
}
return newDest
}
func (orig *NumberDataPoint) Reset() {
*orig = NumberDataPoint{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *NumberDataPoint) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if len(orig.Attributes) > 0 {
dest.WriteObjectField("attributes")
dest.WriteArrayStart()
orig.Attributes[0].MarshalJSON(dest)
for i := 1; i < len(orig.Attributes); i++ {
dest.WriteMore()
orig.Attributes[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if orig.StartTimeUnixNano != uint64(0) {
dest.WriteObjectField("startTimeUnixNano")
dest.WriteUint64(orig.StartTimeUnixNano)
}
if orig.TimeUnixNano != uint64(0) {
dest.WriteObjectField("timeUnixNano")
dest.WriteUint64(orig.TimeUnixNano)
}
switch orig := orig.Value.(type) {
case *NumberDataPoint_AsDouble:
dest.WriteObjectField("asDouble")
dest.WriteFloat64(orig.AsDouble)
case *NumberDataPoint_AsInt:
dest.WriteObjectField("asInt")
dest.WriteInt64(orig.AsInt)
}
if len(orig.Exemplars) > 0 {
dest.WriteObjectField("exemplars")
dest.WriteArrayStart()
orig.Exemplars[0].MarshalJSON(dest)
for i := 1; i < len(orig.Exemplars); i++ {
dest.WriteMore()
orig.Exemplars[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if orig.Flags != uint32(0) {
dest.WriteObjectField("flags")
dest.WriteUint32(orig.Flags)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *NumberDataPoint) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "attributes":
for iter.ReadArray() {
orig.Attributes = append(orig.Attributes, KeyValue{})
orig.Attributes[len(orig.Attributes)-1].UnmarshalJSON(iter)
}
case "startTimeUnixNano", "start_time_unix_nano":
orig.StartTimeUnixNano = iter.ReadUint64()
case "timeUnixNano", "time_unix_nano":
orig.TimeUnixNano = iter.ReadUint64()
case "asDouble", "as_double":
{
var ov *NumberDataPoint_AsDouble
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &NumberDataPoint_AsDouble{}
} else {
ov = ProtoPoolNumberDataPoint_AsDouble.Get().(*NumberDataPoint_AsDouble)
}
ov.AsDouble = iter.ReadFloat64()
orig.Value = ov
}
case "asInt", "as_int":
{
var ov *NumberDataPoint_AsInt
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &NumberDataPoint_AsInt{}
} else {
ov = ProtoPoolNumberDataPoint_AsInt.Get().(*NumberDataPoint_AsInt)
}
ov.AsInt = iter.ReadInt64()
orig.Value = ov
}
case "exemplars":
for iter.ReadArray() {
orig.Exemplars = append(orig.Exemplars, Exemplar{})
orig.Exemplars[len(orig.Exemplars)-1].UnmarshalJSON(iter)
}
case "flags":
orig.Flags = iter.ReadUint32()
default:
iter.Skip()
}
}
}
func (orig *NumberDataPoint) SizeProto() int {
var n int
var l int
_ = l
for i := range orig.Attributes {
l = orig.Attributes[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
if orig.StartTimeUnixNano != uint64(0) {
n += 9
}
if orig.TimeUnixNano != uint64(0) {
n += 9
}
switch orig := orig.Value.(type) {
case nil:
_ = orig
break
case *NumberDataPoint_AsDouble:
n += 9
case *NumberDataPoint_AsInt:
n += 9
}
for i := range orig.Exemplars {
l = orig.Exemplars[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
if orig.Flags != uint32(0) {
n += 1 + proto.Sov(uint64(orig.Flags))
}
return n
}
func (orig *NumberDataPoint) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
for i := len(orig.Attributes) - 1; i >= 0; i-- {
l = orig.Attributes[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x3a
}
if orig.StartTimeUnixNano != uint64(0) {
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], uint64(orig.StartTimeUnixNano))
pos--
buf[pos] = 0x11
}
if orig.TimeUnixNano != uint64(0) {
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], uint64(orig.TimeUnixNano))
pos--
buf[pos] = 0x19
}
switch orig := orig.Value.(type) {
case *NumberDataPoint_AsDouble:
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], math.Float64bits(orig.AsDouble))
pos--
buf[pos] = 0x21
case *NumberDataPoint_AsInt:
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], uint64(orig.AsInt))
pos--
buf[pos] = 0x31
}
for i := len(orig.Exemplars) - 1; i >= 0; i-- {
l = orig.Exemplars[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x2a
}
if orig.Flags != uint32(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.Flags))
pos--
buf[pos] = 0x40
}
return len(buf) - pos
}
func (orig *NumberDataPoint) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 7:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Attributes", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Attributes = append(orig.Attributes, KeyValue{})
err = orig.Attributes[len(orig.Attributes)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 2:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field StartTimeUnixNano", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
orig.StartTimeUnixNano = uint64(num)
case 3:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field TimeUnixNano", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
orig.TimeUnixNano = uint64(num)
case 4:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field AsDouble", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
var ov *NumberDataPoint_AsDouble
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &NumberDataPoint_AsDouble{}
} else {
ov = ProtoPoolNumberDataPoint_AsDouble.Get().(*NumberDataPoint_AsDouble)
}
ov.AsDouble = math.Float64frombits(num)
orig.Value = ov
case 6:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field AsInt", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
var ov *NumberDataPoint_AsInt
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &NumberDataPoint_AsInt{}
} else {
ov = ProtoPoolNumberDataPoint_AsInt.Get().(*NumberDataPoint_AsInt)
}
ov.AsInt = int64(num)
orig.Value = ov
case 5:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Exemplars", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Exemplars = append(orig.Exemplars, Exemplar{})
err = orig.Exemplars[len(orig.Exemplars)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 8:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field Flags", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.Flags = uint32(num)
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestNumberDataPoint() *NumberDataPoint {
orig := NewNumberDataPoint()
orig.Attributes = []KeyValue{{}, *GenTestKeyValue()}
orig.StartTimeUnixNano = uint64(13)
orig.TimeUnixNano = uint64(13)
orig.Value = &NumberDataPoint_AsDouble{AsDouble: float64(3.1415926)}
orig.Exemplars = []Exemplar{{}, *GenTestExemplar()}
orig.Flags = uint32(13)
return orig
}
func GenTestNumberDataPointPtrSlice() []*NumberDataPoint {
orig := make([]*NumberDataPoint, 5)
orig[0] = NewNumberDataPoint()
orig[1] = GenTestNumberDataPoint()
orig[2] = NewNumberDataPoint()
orig[3] = GenTestNumberDataPoint()
orig[4] = NewNumberDataPoint()
return orig
}
func GenTestNumberDataPointSlice() []NumberDataPoint {
orig := make([]NumberDataPoint, 5)
orig[1] = *GenTestNumberDataPoint()
orig[3] = *GenTestNumberDataPoint()
return orig
}
================================================
FILE: pdata/internal/generated_proto_numberdatapoint_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpmetrics "go.opentelemetry.io/proto/slim/otlp/metrics/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyNumberDataPoint(t *testing.T) {
for name, src := range genTestEncodingValuesNumberDataPoint() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewNumberDataPoint()
CopyNumberDataPoint(dest, src)
assert.Equal(t, src, dest)
CopyNumberDataPoint(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyNumberDataPointSlice(t *testing.T) {
src := []NumberDataPoint{}
dest := []NumberDataPoint{}
// Test CopyTo empty
dest = CopyNumberDataPointSlice(dest, src)
assert.Equal(t, []NumberDataPoint{}, dest)
// Test CopyTo larger slice
src = GenTestNumberDataPointSlice()
dest = CopyNumberDataPointSlice(dest, src)
assert.Equal(t, GenTestNumberDataPointSlice(), dest)
// Test CopyTo same size slice
dest = CopyNumberDataPointSlice(dest, src)
assert.Equal(t, GenTestNumberDataPointSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyNumberDataPointSlice(dest, []NumberDataPoint{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyNumberDataPointSlice(dest, src)
assert.Equal(t, GenTestNumberDataPointSlice(), dest)
}
func TestCopyNumberDataPointPtrSlice(t *testing.T) {
src := []*NumberDataPoint{}
dest := []*NumberDataPoint{}
// Test CopyTo empty
dest = CopyNumberDataPointPtrSlice(dest, src)
assert.Equal(t, []*NumberDataPoint{}, dest)
// Test CopyTo larger slice
src = GenTestNumberDataPointPtrSlice()
dest = CopyNumberDataPointPtrSlice(dest, src)
assert.Equal(t, GenTestNumberDataPointPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyNumberDataPointPtrSlice(dest, src)
assert.Equal(t, GenTestNumberDataPointPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyNumberDataPointPtrSlice(dest, []*NumberDataPoint{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyNumberDataPointPtrSlice(dest, src)
assert.Equal(t, GenTestNumberDataPointPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONNumberDataPointUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewNumberDataPoint()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewNumberDataPoint(), dest)
}
func TestMarshalAndUnmarshalJSONNumberDataPoint(t *testing.T) {
for name, src := range genTestEncodingValuesNumberDataPoint() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewNumberDataPoint()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteNumberDataPoint(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoNumberDataPointFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesNumberDataPoint() {
t.Run(name, func(t *testing.T) {
dest := NewNumberDataPoint()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoNumberDataPointUnknown(t *testing.T) {
dest := NewNumberDataPoint()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewNumberDataPoint(), dest)
}
func TestMarshalAndUnmarshalProtoNumberDataPoint(t *testing.T) {
for name, src := range genTestEncodingValuesNumberDataPoint() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewNumberDataPoint()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteNumberDataPoint(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufNumberDataPoint(t *testing.T) {
for name, src := range genTestEncodingValuesNumberDataPoint() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpmetrics.NumberDataPoint{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewNumberDataPoint()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesNumberDataPoint() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"Attributes/wrong_wire_type": {0x3c},
"Attributes/missing_value": {0x3a},
"StartTimeUnixNano/wrong_wire_type": {0x14},
"StartTimeUnixNano/missing_value": {0x11},
"TimeUnixNano/wrong_wire_type": {0x1c},
"TimeUnixNano/missing_value": {0x19},
"AsDouble/wrong_wire_type": {0x24},
"AsDouble/missing_value": {0x21},
"AsInt/wrong_wire_type": {0x34},
"AsInt/missing_value": {0x31},
"Exemplars/wrong_wire_type": {0x2c},
"Exemplars/missing_value": {0x2a},
"Flags/wrong_wire_type": {0x44},
"Flags/missing_value": {0x40},
}
}
func genTestEncodingValuesNumberDataPoint() map[string]*NumberDataPoint {
return map[string]*NumberDataPoint{
"empty": NewNumberDataPoint(),
"Attributes/test": {Attributes: []KeyValue{{}, *GenTestKeyValue()}},
"StartTimeUnixNano/test": {StartTimeUnixNano: uint64(13)},
"TimeUnixNano/test": {TimeUnixNano: uint64(13)},
"AsDouble/default": {Value: &NumberDataPoint_AsDouble{AsDouble: float64(0)}},
"AsDouble/test": {Value: &NumberDataPoint_AsDouble{AsDouble: float64(3.1415926)}}, "AsInt/default": {Value: &NumberDataPoint_AsInt{AsInt: int64(0)}},
"AsInt/test": {Value: &NumberDataPoint_AsInt{AsInt: int64(13)}},
"Exemplars/test": {Exemplars: []Exemplar{{}, *GenTestExemplar()}},
"Flags/test": {Flags: uint32(13)},
}
}
================================================
FILE: pdata/internal/generated_proto_profile.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"encoding/binary"
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// Profile are an implementation of the pprofextended data model.
type Profile struct {
OriginalPayloadFormat string
Samples []*Sample
OriginalPayload []byte
AttributeIndices []int32
TimeUnixNano uint64
DurationNano uint64
Period int64
SampleType ValueType
PeriodType ValueType
DroppedAttributesCount uint32
ProfileId ProfileID
}
var (
protoPoolProfile = sync.Pool{
New: func() any {
return &Profile{}
},
}
)
func NewProfile() *Profile {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &Profile{}
}
return protoPoolProfile.Get().(*Profile)
}
func DeleteProfile(orig *Profile, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
DeleteValueType(&orig.SampleType, false)
for i := range orig.Samples {
DeleteSample(orig.Samples[i], true)
}
DeleteValueType(&orig.PeriodType, false)
DeleteProfileID(&orig.ProfileId, false)
orig.Reset()
if nullable {
protoPoolProfile.Put(orig)
}
}
func CopyProfile(dest, src *Profile) *Profile {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewProfile()
}
CopyValueType(&dest.SampleType, &src.SampleType)
dest.Samples = CopySamplePtrSlice(dest.Samples, src.Samples)
dest.TimeUnixNano = src.TimeUnixNano
dest.DurationNano = src.DurationNano
CopyValueType(&dest.PeriodType, &src.PeriodType)
dest.Period = src.Period
CopyProfileID(&dest.ProfileId, &src.ProfileId)
dest.DroppedAttributesCount = src.DroppedAttributesCount
dest.OriginalPayloadFormat = src.OriginalPayloadFormat
dest.OriginalPayload = src.OriginalPayload
dest.AttributeIndices = append(dest.AttributeIndices[:0], src.AttributeIndices...)
return dest
}
func CopyProfileSlice(dest, src []Profile) []Profile {
var newDest []Profile
if cap(dest) < len(src) {
newDest = make([]Profile, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteProfile(&dest[i], false)
}
}
for i := range src {
CopyProfile(&newDest[i], &src[i])
}
return newDest
}
func CopyProfilePtrSlice(dest, src []*Profile) []*Profile {
var newDest []*Profile
if cap(dest) < len(src) {
newDest = make([]*Profile, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewProfile()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteProfile(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewProfile()
}
}
for i := range src {
CopyProfile(newDest[i], src[i])
}
return newDest
}
func (orig *Profile) Reset() {
*orig = Profile{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *Profile) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
dest.WriteObjectField("sampleType")
orig.SampleType.MarshalJSON(dest)
if len(orig.Samples) > 0 {
dest.WriteObjectField("samples")
dest.WriteArrayStart()
orig.Samples[0].MarshalJSON(dest)
for i := 1; i < len(orig.Samples); i++ {
dest.WriteMore()
orig.Samples[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if orig.TimeUnixNano != uint64(0) {
dest.WriteObjectField("timeUnixNano")
dest.WriteUint64(orig.TimeUnixNano)
}
if orig.DurationNano != uint64(0) {
dest.WriteObjectField("durationNano")
dest.WriteUint64(orig.DurationNano)
}
dest.WriteObjectField("periodType")
orig.PeriodType.MarshalJSON(dest)
if orig.Period != int64(0) {
dest.WriteObjectField("period")
dest.WriteInt64(orig.Period)
}
if !orig.ProfileId.IsEmpty() {
dest.WriteObjectField("profileId")
orig.ProfileId.MarshalJSON(dest)
}
if orig.DroppedAttributesCount != uint32(0) {
dest.WriteObjectField("droppedAttributesCount")
dest.WriteUint32(orig.DroppedAttributesCount)
}
if orig.OriginalPayloadFormat != "" {
dest.WriteObjectField("originalPayloadFormat")
dest.WriteString(orig.OriginalPayloadFormat)
}
if len(orig.OriginalPayload) > 0 {
dest.WriteObjectField("originalPayload")
dest.WriteBytes(orig.OriginalPayload)
}
if len(orig.AttributeIndices) > 0 {
dest.WriteObjectField("attributeIndices")
dest.WriteArrayStart()
dest.WriteInt32(orig.AttributeIndices[0])
for i := 1; i < len(orig.AttributeIndices); i++ {
dest.WriteMore()
dest.WriteInt32(orig.AttributeIndices[i])
}
dest.WriteArrayEnd()
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *Profile) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "sampleType", "sample_type":
orig.SampleType.UnmarshalJSON(iter)
case "samples":
for iter.ReadArray() {
orig.Samples = append(orig.Samples, NewSample())
orig.Samples[len(orig.Samples)-1].UnmarshalJSON(iter)
}
case "timeUnixNano", "time_unix_nano":
orig.TimeUnixNano = iter.ReadUint64()
case "durationNano", "duration_nano":
orig.DurationNano = iter.ReadUint64()
case "periodType", "period_type":
orig.PeriodType.UnmarshalJSON(iter)
case "period":
orig.Period = iter.ReadInt64()
case "profileId", "profile_id":
orig.ProfileId.UnmarshalJSON(iter)
case "droppedAttributesCount", "dropped_attributes_count":
orig.DroppedAttributesCount = iter.ReadUint32()
case "originalPayloadFormat", "original_payload_format":
orig.OriginalPayloadFormat = iter.ReadString()
case "originalPayload", "original_payload":
orig.OriginalPayload = iter.ReadBytes()
case "attributeIndices", "attribute_indices":
for iter.ReadArray() {
orig.AttributeIndices = append(orig.AttributeIndices, iter.ReadInt32())
}
default:
iter.Skip()
}
}
}
func (orig *Profile) SizeProto() int {
var n int
var l int
_ = l
l = orig.SampleType.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
for i := range orig.Samples {
l = orig.Samples[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
if orig.TimeUnixNano != uint64(0) {
n += 9
}
if orig.DurationNano != uint64(0) {
n += 1 + proto.Sov(uint64(orig.DurationNano))
}
l = orig.PeriodType.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
if orig.Period != int64(0) {
n += 1 + proto.Sov(uint64(orig.Period))
}
l = orig.ProfileId.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
if orig.DroppedAttributesCount != uint32(0) {
n += 1 + proto.Sov(uint64(orig.DroppedAttributesCount))
}
l = len(orig.OriginalPayloadFormat)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
l = len(orig.OriginalPayload)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
if len(orig.AttributeIndices) > 0 {
l = 0
for _, e := range orig.AttributeIndices {
l += proto.Sov(uint64(e))
}
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *Profile) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
l = orig.SampleType.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
for i := len(orig.Samples) - 1; i >= 0; i-- {
l = orig.Samples[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
}
if orig.TimeUnixNano != uint64(0) {
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], uint64(orig.TimeUnixNano))
pos--
buf[pos] = 0x19
}
if orig.DurationNano != uint64(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.DurationNano))
pos--
buf[pos] = 0x20
}
l = orig.PeriodType.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x2a
if orig.Period != int64(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.Period))
pos--
buf[pos] = 0x30
}
l = orig.ProfileId.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x3a
if orig.DroppedAttributesCount != uint32(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.DroppedAttributesCount))
pos--
buf[pos] = 0x40
}
l = len(orig.OriginalPayloadFormat)
if l > 0 {
pos -= l
copy(buf[pos:], orig.OriginalPayloadFormat)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x4a
}
l = len(orig.OriginalPayload)
if l > 0 {
pos -= l
copy(buf[pos:], orig.OriginalPayload)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x52
}
l = len(orig.AttributeIndices)
if l > 0 {
endPos := pos
for i := l - 1; i >= 0; i-- {
pos = proto.EncodeVarint(buf, pos, uint64(orig.AttributeIndices[i]))
}
pos = proto.EncodeVarint(buf, pos, uint64(endPos-pos))
pos--
buf[pos] = 0x5a
}
return len(buf) - pos
}
func (orig *Profile) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field SampleType", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.SampleType.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Samples", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Samples = append(orig.Samples, NewSample())
err = orig.Samples[len(orig.Samples)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 3:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field TimeUnixNano", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
orig.TimeUnixNano = uint64(num)
case 4:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field DurationNano", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.DurationNano = uint64(num)
case 5:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field PeriodType", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.PeriodType.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 6:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field Period", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.Period = int64(num)
case 7:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field ProfileId", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.ProfileId.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 8:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field DroppedAttributesCount", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.DroppedAttributesCount = uint32(num)
case 9:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field OriginalPayloadFormat", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.OriginalPayloadFormat = string(buf[startPos:pos])
case 10:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field OriginalPayload", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
if length != 0 {
orig.OriginalPayload = make([]byte, length)
copy(orig.OriginalPayload, buf[startPos:pos])
}
case 11:
switch wireType {
case proto.WireTypeLen:
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
var num uint64
for startPos < pos {
num, startPos, err = proto.ConsumeVarint(buf[:pos], startPos)
if err != nil {
return err
}
orig.AttributeIndices = append(orig.AttributeIndices, int32(num))
}
if startPos != pos {
return fmt.Errorf("proto: invalid field len = %d for field AttributeIndices", pos-startPos)
}
case proto.WireTypeVarint:
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.AttributeIndices = append(orig.AttributeIndices, int32(num))
default:
return fmt.Errorf("proto: wrong wireType = %d for field AttributeIndices", wireType)
}
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestProfile() *Profile {
orig := NewProfile()
orig.SampleType = *GenTestValueType()
orig.Samples = []*Sample{{}, GenTestSample()}
orig.TimeUnixNano = uint64(13)
orig.DurationNano = uint64(13)
orig.PeriodType = *GenTestValueType()
orig.Period = int64(13)
orig.ProfileId = *GenTestProfileID()
orig.DroppedAttributesCount = uint32(13)
orig.OriginalPayloadFormat = "test_originalpayloadformat"
orig.OriginalPayload = []byte{1, 2, 3}
orig.AttributeIndices = []int32{int32(0), int32(13)}
return orig
}
func GenTestProfilePtrSlice() []*Profile {
orig := make([]*Profile, 5)
orig[0] = NewProfile()
orig[1] = GenTestProfile()
orig[2] = NewProfile()
orig[3] = GenTestProfile()
orig[4] = NewProfile()
return orig
}
func GenTestProfileSlice() []Profile {
orig := make([]Profile, 5)
orig[1] = *GenTestProfile()
orig[3] = *GenTestProfile()
return orig
}
================================================
FILE: pdata/internal/generated_proto_profile_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpprofiles "go.opentelemetry.io/proto/slim/otlp/profiles/v1development"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyProfile(t *testing.T) {
for name, src := range genTestEncodingValuesProfile() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewProfile()
CopyProfile(dest, src)
assert.Equal(t, src, dest)
CopyProfile(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyProfileSlice(t *testing.T) {
src := []Profile{}
dest := []Profile{}
// Test CopyTo empty
dest = CopyProfileSlice(dest, src)
assert.Equal(t, []Profile{}, dest)
// Test CopyTo larger slice
src = GenTestProfileSlice()
dest = CopyProfileSlice(dest, src)
assert.Equal(t, GenTestProfileSlice(), dest)
// Test CopyTo same size slice
dest = CopyProfileSlice(dest, src)
assert.Equal(t, GenTestProfileSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyProfileSlice(dest, []Profile{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyProfileSlice(dest, src)
assert.Equal(t, GenTestProfileSlice(), dest)
}
func TestCopyProfilePtrSlice(t *testing.T) {
src := []*Profile{}
dest := []*Profile{}
// Test CopyTo empty
dest = CopyProfilePtrSlice(dest, src)
assert.Equal(t, []*Profile{}, dest)
// Test CopyTo larger slice
src = GenTestProfilePtrSlice()
dest = CopyProfilePtrSlice(dest, src)
assert.Equal(t, GenTestProfilePtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyProfilePtrSlice(dest, src)
assert.Equal(t, GenTestProfilePtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyProfilePtrSlice(dest, []*Profile{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyProfilePtrSlice(dest, src)
assert.Equal(t, GenTestProfilePtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONProfileUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewProfile()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewProfile(), dest)
}
func TestMarshalAndUnmarshalJSONProfile(t *testing.T) {
for name, src := range genTestEncodingValuesProfile() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewProfile()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteProfile(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoProfileFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesProfile() {
t.Run(name, func(t *testing.T) {
dest := NewProfile()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoProfileUnknown(t *testing.T) {
dest := NewProfile()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewProfile(), dest)
}
func TestMarshalAndUnmarshalProtoProfile(t *testing.T) {
for name, src := range genTestEncodingValuesProfile() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewProfile()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteProfile(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufProfile(t *testing.T) {
for name, src := range genTestEncodingValuesProfile() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpprofiles.Profile{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewProfile()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesProfile() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"SampleType/wrong_wire_type": {0xc},
"SampleType/missing_value": {0xa},
"Samples/wrong_wire_type": {0x14},
"Samples/missing_value": {0x12},
"TimeUnixNano/wrong_wire_type": {0x1c},
"TimeUnixNano/missing_value": {0x19},
"DurationNano/wrong_wire_type": {0x24},
"DurationNano/missing_value": {0x20},
"PeriodType/wrong_wire_type": {0x2c},
"PeriodType/missing_value": {0x2a},
"Period/wrong_wire_type": {0x34},
"Period/missing_value": {0x30},
"ProfileId/wrong_wire_type": {0x3c},
"ProfileId/missing_value": {0x3a},
"DroppedAttributesCount/wrong_wire_type": {0x44},
"DroppedAttributesCount/missing_value": {0x40},
"OriginalPayloadFormat/wrong_wire_type": {0x4c},
"OriginalPayloadFormat/missing_value": {0x4a},
"OriginalPayload/wrong_wire_type": {0x54},
"OriginalPayload/missing_value": {0x52},
"AttributeIndices/wrong_wire_type": {0x5c},
"AttributeIndices/missing_value": {0x5a},
}
}
func genTestEncodingValuesProfile() map[string]*Profile {
return map[string]*Profile{
"empty": NewProfile(),
"SampleType/test": {SampleType: *GenTestValueType()},
"Samples/test": {Samples: []*Sample{{}, GenTestSample()}},
"TimeUnixNano/test": {TimeUnixNano: uint64(13)},
"DurationNano/test": {DurationNano: uint64(13)},
"PeriodType/test": {PeriodType: *GenTestValueType()},
"Period/test": {Period: int64(13)},
"ProfileId/test": {ProfileId: *GenTestProfileID()},
"DroppedAttributesCount/test": {DroppedAttributesCount: uint32(13)},
"OriginalPayloadFormat/test": {OriginalPayloadFormat: "test_originalpayloadformat"},
"OriginalPayload/test": {OriginalPayload: []byte{1, 2, 3}},
"AttributeIndices/test": {AttributeIndices: []int32{int32(0), int32(13)}},
}
}
================================================
FILE: pdata/internal/generated_proto_profilesdata.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// ProfilesData represents the profiles data that can be stored in persistent storage,
// OR can be embedded by other protocols that transfer OTLP profiles data but do not
// implement the OTLP protocol.
type ProfilesData struct {
ResourceProfiles []*ResourceProfiles
Dictionary ProfilesDictionary
}
var (
protoPoolProfilesData = sync.Pool{
New: func() any {
return &ProfilesData{}
},
}
)
func NewProfilesData() *ProfilesData {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &ProfilesData{}
}
return protoPoolProfilesData.Get().(*ProfilesData)
}
func DeleteProfilesData(orig *ProfilesData, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
for i := range orig.ResourceProfiles {
DeleteResourceProfiles(orig.ResourceProfiles[i], true)
}
DeleteProfilesDictionary(&orig.Dictionary, false)
orig.Reset()
if nullable {
protoPoolProfilesData.Put(orig)
}
}
func CopyProfilesData(dest, src *ProfilesData) *ProfilesData {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewProfilesData()
}
dest.ResourceProfiles = CopyResourceProfilesPtrSlice(dest.ResourceProfiles, src.ResourceProfiles)
CopyProfilesDictionary(&dest.Dictionary, &src.Dictionary)
return dest
}
func CopyProfilesDataSlice(dest, src []ProfilesData) []ProfilesData {
var newDest []ProfilesData
if cap(dest) < len(src) {
newDest = make([]ProfilesData, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteProfilesData(&dest[i], false)
}
}
for i := range src {
CopyProfilesData(&newDest[i], &src[i])
}
return newDest
}
func CopyProfilesDataPtrSlice(dest, src []*ProfilesData) []*ProfilesData {
var newDest []*ProfilesData
if cap(dest) < len(src) {
newDest = make([]*ProfilesData, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewProfilesData()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteProfilesData(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewProfilesData()
}
}
for i := range src {
CopyProfilesData(newDest[i], src[i])
}
return newDest
}
func (orig *ProfilesData) Reset() {
*orig = ProfilesData{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *ProfilesData) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if len(orig.ResourceProfiles) > 0 {
dest.WriteObjectField("resourceProfiles")
dest.WriteArrayStart()
orig.ResourceProfiles[0].MarshalJSON(dest)
for i := 1; i < len(orig.ResourceProfiles); i++ {
dest.WriteMore()
orig.ResourceProfiles[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
dest.WriteObjectField("dictionary")
orig.Dictionary.MarshalJSON(dest)
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *ProfilesData) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "resourceProfiles", "resource_profiles":
for iter.ReadArray() {
orig.ResourceProfiles = append(orig.ResourceProfiles, NewResourceProfiles())
orig.ResourceProfiles[len(orig.ResourceProfiles)-1].UnmarshalJSON(iter)
}
case "dictionary":
orig.Dictionary.UnmarshalJSON(iter)
default:
iter.Skip()
}
}
}
func (orig *ProfilesData) SizeProto() int {
var n int
var l int
_ = l
for i := range orig.ResourceProfiles {
l = orig.ResourceProfiles[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
l = orig.Dictionary.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
return n
}
func (orig *ProfilesData) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
for i := len(orig.ResourceProfiles) - 1; i >= 0; i-- {
l = orig.ResourceProfiles[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
}
l = orig.Dictionary.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
return len(buf) - pos
}
func (orig *ProfilesData) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field ResourceProfiles", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.ResourceProfiles = append(orig.ResourceProfiles, NewResourceProfiles())
err = orig.ResourceProfiles[len(orig.ResourceProfiles)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Dictionary", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.Dictionary.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestProfilesData() *ProfilesData {
orig := NewProfilesData()
orig.ResourceProfiles = []*ResourceProfiles{{}, GenTestResourceProfiles()}
orig.Dictionary = *GenTestProfilesDictionary()
return orig
}
func GenTestProfilesDataPtrSlice() []*ProfilesData {
orig := make([]*ProfilesData, 5)
orig[0] = NewProfilesData()
orig[1] = GenTestProfilesData()
orig[2] = NewProfilesData()
orig[3] = GenTestProfilesData()
orig[4] = NewProfilesData()
return orig
}
func GenTestProfilesDataSlice() []ProfilesData {
orig := make([]ProfilesData, 5)
orig[1] = *GenTestProfilesData()
orig[3] = *GenTestProfilesData()
return orig
}
================================================
FILE: pdata/internal/generated_proto_profilesdata_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpprofiles "go.opentelemetry.io/proto/slim/otlp/profiles/v1development"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyProfilesData(t *testing.T) {
for name, src := range genTestEncodingValuesProfilesData() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewProfilesData()
CopyProfilesData(dest, src)
assert.Equal(t, src, dest)
CopyProfilesData(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyProfilesDataSlice(t *testing.T) {
src := []ProfilesData{}
dest := []ProfilesData{}
// Test CopyTo empty
dest = CopyProfilesDataSlice(dest, src)
assert.Equal(t, []ProfilesData{}, dest)
// Test CopyTo larger slice
src = GenTestProfilesDataSlice()
dest = CopyProfilesDataSlice(dest, src)
assert.Equal(t, GenTestProfilesDataSlice(), dest)
// Test CopyTo same size slice
dest = CopyProfilesDataSlice(dest, src)
assert.Equal(t, GenTestProfilesDataSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyProfilesDataSlice(dest, []ProfilesData{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyProfilesDataSlice(dest, src)
assert.Equal(t, GenTestProfilesDataSlice(), dest)
}
func TestCopyProfilesDataPtrSlice(t *testing.T) {
src := []*ProfilesData{}
dest := []*ProfilesData{}
// Test CopyTo empty
dest = CopyProfilesDataPtrSlice(dest, src)
assert.Equal(t, []*ProfilesData{}, dest)
// Test CopyTo larger slice
src = GenTestProfilesDataPtrSlice()
dest = CopyProfilesDataPtrSlice(dest, src)
assert.Equal(t, GenTestProfilesDataPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyProfilesDataPtrSlice(dest, src)
assert.Equal(t, GenTestProfilesDataPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyProfilesDataPtrSlice(dest, []*ProfilesData{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyProfilesDataPtrSlice(dest, src)
assert.Equal(t, GenTestProfilesDataPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONProfilesDataUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewProfilesData()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewProfilesData(), dest)
}
func TestMarshalAndUnmarshalJSONProfilesData(t *testing.T) {
for name, src := range genTestEncodingValuesProfilesData() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewProfilesData()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteProfilesData(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoProfilesDataFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesProfilesData() {
t.Run(name, func(t *testing.T) {
dest := NewProfilesData()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoProfilesDataUnknown(t *testing.T) {
dest := NewProfilesData()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewProfilesData(), dest)
}
func TestMarshalAndUnmarshalProtoProfilesData(t *testing.T) {
for name, src := range genTestEncodingValuesProfilesData() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewProfilesData()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteProfilesData(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufProfilesData(t *testing.T) {
for name, src := range genTestEncodingValuesProfilesData() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpprofiles.ProfilesData{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewProfilesData()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesProfilesData() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"ResourceProfiles/wrong_wire_type": {0xc},
"ResourceProfiles/missing_value": {0xa},
"Dictionary/wrong_wire_type": {0x14},
"Dictionary/missing_value": {0x12},
}
}
func genTestEncodingValuesProfilesData() map[string]*ProfilesData {
return map[string]*ProfilesData{
"empty": NewProfilesData(),
"ResourceProfiles/test": {ResourceProfiles: []*ResourceProfiles{{}, GenTestResourceProfiles()}},
"Dictionary/test": {Dictionary: *GenTestProfilesDictionary()},
}
}
================================================
FILE: pdata/internal/generated_proto_profilesdictionary.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// ProfilesDictionary is the reference table containing all data shared by profiles across the message being sent.
type ProfilesDictionary struct {
MappingTable []*Mapping
LocationTable []*Location
FunctionTable []*Function
LinkTable []*Link
StringTable []string
AttributeTable []*KeyValueAndUnit
StackTable []*Stack
}
var (
protoPoolProfilesDictionary = sync.Pool{
New: func() any {
return &ProfilesDictionary{}
},
}
)
func NewProfilesDictionary() *ProfilesDictionary {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &ProfilesDictionary{}
}
return protoPoolProfilesDictionary.Get().(*ProfilesDictionary)
}
func DeleteProfilesDictionary(orig *ProfilesDictionary, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
for i := range orig.MappingTable {
DeleteMapping(orig.MappingTable[i], true)
}
for i := range orig.LocationTable {
DeleteLocation(orig.LocationTable[i], true)
}
for i := range orig.FunctionTable {
DeleteFunction(orig.FunctionTable[i], true)
}
for i := range orig.LinkTable {
DeleteLink(orig.LinkTable[i], true)
}
for i := range orig.AttributeTable {
DeleteKeyValueAndUnit(orig.AttributeTable[i], true)
}
for i := range orig.StackTable {
DeleteStack(orig.StackTable[i], true)
}
orig.Reset()
if nullable {
protoPoolProfilesDictionary.Put(orig)
}
}
func CopyProfilesDictionary(dest, src *ProfilesDictionary) *ProfilesDictionary {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewProfilesDictionary()
}
dest.MappingTable = CopyMappingPtrSlice(dest.MappingTable, src.MappingTable)
dest.LocationTable = CopyLocationPtrSlice(dest.LocationTable, src.LocationTable)
dest.FunctionTable = CopyFunctionPtrSlice(dest.FunctionTable, src.FunctionTable)
dest.LinkTable = CopyLinkPtrSlice(dest.LinkTable, src.LinkTable)
dest.StringTable = append(dest.StringTable[:0], src.StringTable...)
dest.AttributeTable = CopyKeyValueAndUnitPtrSlice(dest.AttributeTable, src.AttributeTable)
dest.StackTable = CopyStackPtrSlice(dest.StackTable, src.StackTable)
return dest
}
func CopyProfilesDictionarySlice(dest, src []ProfilesDictionary) []ProfilesDictionary {
var newDest []ProfilesDictionary
if cap(dest) < len(src) {
newDest = make([]ProfilesDictionary, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteProfilesDictionary(&dest[i], false)
}
}
for i := range src {
CopyProfilesDictionary(&newDest[i], &src[i])
}
return newDest
}
func CopyProfilesDictionaryPtrSlice(dest, src []*ProfilesDictionary) []*ProfilesDictionary {
var newDest []*ProfilesDictionary
if cap(dest) < len(src) {
newDest = make([]*ProfilesDictionary, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewProfilesDictionary()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteProfilesDictionary(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewProfilesDictionary()
}
}
for i := range src {
CopyProfilesDictionary(newDest[i], src[i])
}
return newDest
}
func (orig *ProfilesDictionary) Reset() {
*orig = ProfilesDictionary{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *ProfilesDictionary) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if len(orig.MappingTable) > 0 {
dest.WriteObjectField("mappingTable")
dest.WriteArrayStart()
orig.MappingTable[0].MarshalJSON(dest)
for i := 1; i < len(orig.MappingTable); i++ {
dest.WriteMore()
orig.MappingTable[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if len(orig.LocationTable) > 0 {
dest.WriteObjectField("locationTable")
dest.WriteArrayStart()
orig.LocationTable[0].MarshalJSON(dest)
for i := 1; i < len(orig.LocationTable); i++ {
dest.WriteMore()
orig.LocationTable[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if len(orig.FunctionTable) > 0 {
dest.WriteObjectField("functionTable")
dest.WriteArrayStart()
orig.FunctionTable[0].MarshalJSON(dest)
for i := 1; i < len(orig.FunctionTable); i++ {
dest.WriteMore()
orig.FunctionTable[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if len(orig.LinkTable) > 0 {
dest.WriteObjectField("linkTable")
dest.WriteArrayStart()
orig.LinkTable[0].MarshalJSON(dest)
for i := 1; i < len(orig.LinkTable); i++ {
dest.WriteMore()
orig.LinkTable[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if len(orig.StringTable) > 0 {
dest.WriteObjectField("stringTable")
dest.WriteArrayStart()
dest.WriteString(orig.StringTable[0])
for i := 1; i < len(orig.StringTable); i++ {
dest.WriteMore()
dest.WriteString(orig.StringTable[i])
}
dest.WriteArrayEnd()
}
if len(orig.AttributeTable) > 0 {
dest.WriteObjectField("attributeTable")
dest.WriteArrayStart()
orig.AttributeTable[0].MarshalJSON(dest)
for i := 1; i < len(orig.AttributeTable); i++ {
dest.WriteMore()
orig.AttributeTable[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if len(orig.StackTable) > 0 {
dest.WriteObjectField("stackTable")
dest.WriteArrayStart()
orig.StackTable[0].MarshalJSON(dest)
for i := 1; i < len(orig.StackTable); i++ {
dest.WriteMore()
orig.StackTable[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *ProfilesDictionary) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "mappingTable", "mapping_table":
for iter.ReadArray() {
orig.MappingTable = append(orig.MappingTable, NewMapping())
orig.MappingTable[len(orig.MappingTable)-1].UnmarshalJSON(iter)
}
case "locationTable", "location_table":
for iter.ReadArray() {
orig.LocationTable = append(orig.LocationTable, NewLocation())
orig.LocationTable[len(orig.LocationTable)-1].UnmarshalJSON(iter)
}
case "functionTable", "function_table":
for iter.ReadArray() {
orig.FunctionTable = append(orig.FunctionTable, NewFunction())
orig.FunctionTable[len(orig.FunctionTable)-1].UnmarshalJSON(iter)
}
case "linkTable", "link_table":
for iter.ReadArray() {
orig.LinkTable = append(orig.LinkTable, NewLink())
orig.LinkTable[len(orig.LinkTable)-1].UnmarshalJSON(iter)
}
case "stringTable", "string_table":
for iter.ReadArray() {
orig.StringTable = append(orig.StringTable, iter.ReadString())
}
case "attributeTable", "attribute_table":
for iter.ReadArray() {
orig.AttributeTable = append(orig.AttributeTable, NewKeyValueAndUnit())
orig.AttributeTable[len(orig.AttributeTable)-1].UnmarshalJSON(iter)
}
case "stackTable", "stack_table":
for iter.ReadArray() {
orig.StackTable = append(orig.StackTable, NewStack())
orig.StackTable[len(orig.StackTable)-1].UnmarshalJSON(iter)
}
default:
iter.Skip()
}
}
}
func (orig *ProfilesDictionary) SizeProto() int {
var n int
var l int
_ = l
for i := range orig.MappingTable {
l = orig.MappingTable[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
for i := range orig.LocationTable {
l = orig.LocationTable[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
for i := range orig.FunctionTable {
l = orig.FunctionTable[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
for i := range orig.LinkTable {
l = orig.LinkTable[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
for _, s := range orig.StringTable {
l = len(s)
n += 1 + proto.Sov(uint64(l)) + l
}
for i := range orig.AttributeTable {
l = orig.AttributeTable[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
for i := range orig.StackTable {
l = orig.StackTable[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *ProfilesDictionary) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
for i := len(orig.MappingTable) - 1; i >= 0; i-- {
l = orig.MappingTable[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
}
for i := len(orig.LocationTable) - 1; i >= 0; i-- {
l = orig.LocationTable[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
}
for i := len(orig.FunctionTable) - 1; i >= 0; i-- {
l = orig.FunctionTable[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x1a
}
for i := len(orig.LinkTable) - 1; i >= 0; i-- {
l = orig.LinkTable[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x22
}
for i := len(orig.StringTable) - 1; i >= 0; i-- {
l = len(orig.StringTable[i])
pos -= l
copy(buf[pos:], orig.StringTable[i])
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x2a
}
for i := len(orig.AttributeTable) - 1; i >= 0; i-- {
l = orig.AttributeTable[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x32
}
for i := len(orig.StackTable) - 1; i >= 0; i-- {
l = orig.StackTable[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x3a
}
return len(buf) - pos
}
func (orig *ProfilesDictionary) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field MappingTable", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.MappingTable = append(orig.MappingTable, NewMapping())
err = orig.MappingTable[len(orig.MappingTable)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field LocationTable", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.LocationTable = append(orig.LocationTable, NewLocation())
err = orig.LocationTable[len(orig.LocationTable)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 3:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field FunctionTable", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.FunctionTable = append(orig.FunctionTable, NewFunction())
err = orig.FunctionTable[len(orig.FunctionTable)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 4:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field LinkTable", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.LinkTable = append(orig.LinkTable, NewLink())
err = orig.LinkTable[len(orig.LinkTable)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 5:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field StringTable", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.StringTable = append(orig.StringTable, string(buf[startPos:pos]))
case 6:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field AttributeTable", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.AttributeTable = append(orig.AttributeTable, NewKeyValueAndUnit())
err = orig.AttributeTable[len(orig.AttributeTable)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 7:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field StackTable", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.StackTable = append(orig.StackTable, NewStack())
err = orig.StackTable[len(orig.StackTable)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestProfilesDictionary() *ProfilesDictionary {
orig := NewProfilesDictionary()
orig.MappingTable = []*Mapping{{}, GenTestMapping()}
orig.LocationTable = []*Location{{}, GenTestLocation()}
orig.FunctionTable = []*Function{{}, GenTestFunction()}
orig.LinkTable = []*Link{{}, GenTestLink()}
orig.StringTable = []string{"", "test_stringtable"}
orig.AttributeTable = []*KeyValueAndUnit{{}, GenTestKeyValueAndUnit()}
orig.StackTable = []*Stack{{}, GenTestStack()}
return orig
}
func GenTestProfilesDictionaryPtrSlice() []*ProfilesDictionary {
orig := make([]*ProfilesDictionary, 5)
orig[0] = NewProfilesDictionary()
orig[1] = GenTestProfilesDictionary()
orig[2] = NewProfilesDictionary()
orig[3] = GenTestProfilesDictionary()
orig[4] = NewProfilesDictionary()
return orig
}
func GenTestProfilesDictionarySlice() []ProfilesDictionary {
orig := make([]ProfilesDictionary, 5)
orig[1] = *GenTestProfilesDictionary()
orig[3] = *GenTestProfilesDictionary()
return orig
}
================================================
FILE: pdata/internal/generated_proto_profilesdictionary_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpprofiles "go.opentelemetry.io/proto/slim/otlp/profiles/v1development"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyProfilesDictionary(t *testing.T) {
for name, src := range genTestEncodingValuesProfilesDictionary() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewProfilesDictionary()
CopyProfilesDictionary(dest, src)
assert.Equal(t, src, dest)
CopyProfilesDictionary(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyProfilesDictionarySlice(t *testing.T) {
src := []ProfilesDictionary{}
dest := []ProfilesDictionary{}
// Test CopyTo empty
dest = CopyProfilesDictionarySlice(dest, src)
assert.Equal(t, []ProfilesDictionary{}, dest)
// Test CopyTo larger slice
src = GenTestProfilesDictionarySlice()
dest = CopyProfilesDictionarySlice(dest, src)
assert.Equal(t, GenTestProfilesDictionarySlice(), dest)
// Test CopyTo same size slice
dest = CopyProfilesDictionarySlice(dest, src)
assert.Equal(t, GenTestProfilesDictionarySlice(), dest)
// Test CopyTo smaller size slice
dest = CopyProfilesDictionarySlice(dest, []ProfilesDictionary{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyProfilesDictionarySlice(dest, src)
assert.Equal(t, GenTestProfilesDictionarySlice(), dest)
}
func TestCopyProfilesDictionaryPtrSlice(t *testing.T) {
src := []*ProfilesDictionary{}
dest := []*ProfilesDictionary{}
// Test CopyTo empty
dest = CopyProfilesDictionaryPtrSlice(dest, src)
assert.Equal(t, []*ProfilesDictionary{}, dest)
// Test CopyTo larger slice
src = GenTestProfilesDictionaryPtrSlice()
dest = CopyProfilesDictionaryPtrSlice(dest, src)
assert.Equal(t, GenTestProfilesDictionaryPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyProfilesDictionaryPtrSlice(dest, src)
assert.Equal(t, GenTestProfilesDictionaryPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyProfilesDictionaryPtrSlice(dest, []*ProfilesDictionary{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyProfilesDictionaryPtrSlice(dest, src)
assert.Equal(t, GenTestProfilesDictionaryPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONProfilesDictionaryUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewProfilesDictionary()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewProfilesDictionary(), dest)
}
func TestMarshalAndUnmarshalJSONProfilesDictionary(t *testing.T) {
for name, src := range genTestEncodingValuesProfilesDictionary() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewProfilesDictionary()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteProfilesDictionary(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoProfilesDictionaryFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesProfilesDictionary() {
t.Run(name, func(t *testing.T) {
dest := NewProfilesDictionary()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoProfilesDictionaryUnknown(t *testing.T) {
dest := NewProfilesDictionary()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewProfilesDictionary(), dest)
}
func TestMarshalAndUnmarshalProtoProfilesDictionary(t *testing.T) {
for name, src := range genTestEncodingValuesProfilesDictionary() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewProfilesDictionary()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteProfilesDictionary(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufProfilesDictionary(t *testing.T) {
for name, src := range genTestEncodingValuesProfilesDictionary() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpprofiles.ProfilesDictionary{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewProfilesDictionary()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesProfilesDictionary() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"MappingTable/wrong_wire_type": {0xc},
"MappingTable/missing_value": {0xa},
"LocationTable/wrong_wire_type": {0x14},
"LocationTable/missing_value": {0x12},
"FunctionTable/wrong_wire_type": {0x1c},
"FunctionTable/missing_value": {0x1a},
"LinkTable/wrong_wire_type": {0x24},
"LinkTable/missing_value": {0x22},
"StringTable/wrong_wire_type": {0x2c},
"StringTable/missing_value": {0x2a},
"AttributeTable/wrong_wire_type": {0x34},
"AttributeTable/missing_value": {0x32},
"StackTable/wrong_wire_type": {0x3c},
"StackTable/missing_value": {0x3a},
}
}
func genTestEncodingValuesProfilesDictionary() map[string]*ProfilesDictionary {
return map[string]*ProfilesDictionary{
"empty": NewProfilesDictionary(),
"MappingTable/test": {MappingTable: []*Mapping{{}, GenTestMapping()}},
"LocationTable/test": {LocationTable: []*Location{{}, GenTestLocation()}},
"FunctionTable/test": {FunctionTable: []*Function{{}, GenTestFunction()}},
"LinkTable/test": {LinkTable: []*Link{{}, GenTestLink()}},
"StringTable/test": {StringTable: []string{"", "test_stringtable"}},
"AttributeTable/test": {AttributeTable: []*KeyValueAndUnit{{}, GenTestKeyValueAndUnit()}},
"StackTable/test": {StackTable: []*Stack{{}, GenTestStack()}},
}
}
================================================
FILE: pdata/internal/generated_proto_profilesrequest.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"encoding/binary"
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
type ProfilesRequest struct {
RequestContext *RequestContext
ProfilesData ProfilesData
FormatVersion uint32
}
var (
protoPoolProfilesRequest = sync.Pool{
New: func() any {
return &ProfilesRequest{}
},
}
)
func NewProfilesRequest() *ProfilesRequest {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &ProfilesRequest{}
}
return protoPoolProfilesRequest.Get().(*ProfilesRequest)
}
func DeleteProfilesRequest(orig *ProfilesRequest, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
DeleteRequestContext(orig.RequestContext, true)
DeleteProfilesData(&orig.ProfilesData, false)
orig.Reset()
if nullable {
protoPoolProfilesRequest.Put(orig)
}
}
func CopyProfilesRequest(dest, src *ProfilesRequest) *ProfilesRequest {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewProfilesRequest()
}
dest.RequestContext = CopyRequestContext(dest.RequestContext, src.RequestContext)
CopyProfilesData(&dest.ProfilesData, &src.ProfilesData)
dest.FormatVersion = src.FormatVersion
return dest
}
func CopyProfilesRequestSlice(dest, src []ProfilesRequest) []ProfilesRequest {
var newDest []ProfilesRequest
if cap(dest) < len(src) {
newDest = make([]ProfilesRequest, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteProfilesRequest(&dest[i], false)
}
}
for i := range src {
CopyProfilesRequest(&newDest[i], &src[i])
}
return newDest
}
func CopyProfilesRequestPtrSlice(dest, src []*ProfilesRequest) []*ProfilesRequest {
var newDest []*ProfilesRequest
if cap(dest) < len(src) {
newDest = make([]*ProfilesRequest, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewProfilesRequest()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteProfilesRequest(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewProfilesRequest()
}
}
for i := range src {
CopyProfilesRequest(newDest[i], src[i])
}
return newDest
}
func (orig *ProfilesRequest) Reset() {
*orig = ProfilesRequest{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *ProfilesRequest) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if orig.RequestContext != nil {
dest.WriteObjectField("requestContext")
orig.RequestContext.MarshalJSON(dest)
}
dest.WriteObjectField("profilesData")
orig.ProfilesData.MarshalJSON(dest)
if orig.FormatVersion != uint32(0) {
dest.WriteObjectField("formatVersion")
dest.WriteUint32(orig.FormatVersion)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *ProfilesRequest) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "requestContext", "request_context":
orig.RequestContext = NewRequestContext()
orig.RequestContext.UnmarshalJSON(iter)
case "profilesData", "profiles_data":
orig.ProfilesData.UnmarshalJSON(iter)
case "formatVersion", "format_version":
orig.FormatVersion = iter.ReadUint32()
default:
iter.Skip()
}
}
}
func (orig *ProfilesRequest) SizeProto() int {
var n int
var l int
_ = l
if orig.RequestContext != nil {
l = orig.RequestContext.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
l = orig.ProfilesData.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
if orig.FormatVersion != uint32(0) {
n += 5
}
return n
}
func (orig *ProfilesRequest) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
if orig.RequestContext != nil {
l = orig.RequestContext.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
}
l = orig.ProfilesData.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x1a
if orig.FormatVersion != uint32(0) {
pos -= 4
binary.LittleEndian.PutUint32(buf[pos:], uint32(orig.FormatVersion))
pos--
buf[pos] = 0xd
}
return len(buf) - pos
}
func (orig *ProfilesRequest) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field RequestContext", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.RequestContext = NewRequestContext()
err = orig.RequestContext.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 3:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field ProfilesData", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.ProfilesData.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 1:
if wireType != proto.WireTypeI32 {
return fmt.Errorf("proto: wrong wireType = %d for field FormatVersion", wireType)
}
var num uint32
num, pos, err = proto.ConsumeI32(buf, pos)
if err != nil {
return err
}
orig.FormatVersion = uint32(num)
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestProfilesRequest() *ProfilesRequest {
orig := NewProfilesRequest()
orig.RequestContext = GenTestRequestContext()
orig.ProfilesData = *GenTestProfilesData()
orig.FormatVersion = uint32(13)
return orig
}
func GenTestProfilesRequestPtrSlice() []*ProfilesRequest {
orig := make([]*ProfilesRequest, 5)
orig[0] = NewProfilesRequest()
orig[1] = GenTestProfilesRequest()
orig[2] = NewProfilesRequest()
orig[3] = GenTestProfilesRequest()
orig[4] = NewProfilesRequest()
return orig
}
func GenTestProfilesRequestSlice() []ProfilesRequest {
orig := make([]ProfilesRequest, 5)
orig[1] = *GenTestProfilesRequest()
orig[3] = *GenTestProfilesRequest()
return orig
}
================================================
FILE: pdata/internal/generated_proto_profilesrequest_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/emptypb"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyProfilesRequest(t *testing.T) {
for name, src := range genTestEncodingValuesProfilesRequest() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewProfilesRequest()
CopyProfilesRequest(dest, src)
assert.Equal(t, src, dest)
CopyProfilesRequest(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyProfilesRequestSlice(t *testing.T) {
src := []ProfilesRequest{}
dest := []ProfilesRequest{}
// Test CopyTo empty
dest = CopyProfilesRequestSlice(dest, src)
assert.Equal(t, []ProfilesRequest{}, dest)
// Test CopyTo larger slice
src = GenTestProfilesRequestSlice()
dest = CopyProfilesRequestSlice(dest, src)
assert.Equal(t, GenTestProfilesRequestSlice(), dest)
// Test CopyTo same size slice
dest = CopyProfilesRequestSlice(dest, src)
assert.Equal(t, GenTestProfilesRequestSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyProfilesRequestSlice(dest, []ProfilesRequest{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyProfilesRequestSlice(dest, src)
assert.Equal(t, GenTestProfilesRequestSlice(), dest)
}
func TestCopyProfilesRequestPtrSlice(t *testing.T) {
src := []*ProfilesRequest{}
dest := []*ProfilesRequest{}
// Test CopyTo empty
dest = CopyProfilesRequestPtrSlice(dest, src)
assert.Equal(t, []*ProfilesRequest{}, dest)
// Test CopyTo larger slice
src = GenTestProfilesRequestPtrSlice()
dest = CopyProfilesRequestPtrSlice(dest, src)
assert.Equal(t, GenTestProfilesRequestPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyProfilesRequestPtrSlice(dest, src)
assert.Equal(t, GenTestProfilesRequestPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyProfilesRequestPtrSlice(dest, []*ProfilesRequest{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyProfilesRequestPtrSlice(dest, src)
assert.Equal(t, GenTestProfilesRequestPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONProfilesRequestUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewProfilesRequest()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewProfilesRequest(), dest)
}
func TestMarshalAndUnmarshalJSONProfilesRequest(t *testing.T) {
for name, src := range genTestEncodingValuesProfilesRequest() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewProfilesRequest()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteProfilesRequest(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoProfilesRequestFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesProfilesRequest() {
t.Run(name, func(t *testing.T) {
dest := NewProfilesRequest()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoProfilesRequestUnknown(t *testing.T) {
dest := NewProfilesRequest()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewProfilesRequest(), dest)
}
func TestMarshalAndUnmarshalProtoProfilesRequest(t *testing.T) {
for name, src := range genTestEncodingValuesProfilesRequest() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewProfilesRequest()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteProfilesRequest(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufProfilesRequest(t *testing.T) {
for name, src := range genTestEncodingValuesProfilesRequest() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &emptypb.Empty{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewProfilesRequest()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesProfilesRequest() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"RequestContext/wrong_wire_type": {0x14},
"RequestContext/missing_value": {0x12},
"ProfilesData/wrong_wire_type": {0x1c},
"ProfilesData/missing_value": {0x1a},
"FormatVersion/wrong_wire_type": {0xc},
"FormatVersion/missing_value": {0xd},
}
}
func genTestEncodingValuesProfilesRequest() map[string]*ProfilesRequest {
return map[string]*ProfilesRequest{
"empty": NewProfilesRequest(),
"RequestContext/test": {RequestContext: GenTestRequestContext()},
"ProfilesData/test": {ProfilesData: *GenTestProfilesData()},
"FormatVersion/test": {FormatVersion: uint32(13)},
}
}
================================================
FILE: pdata/internal/generated_proto_requestcontext.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
func (m *RequestContext) GetClientAddress() any {
if m != nil {
return m.ClientAddress
}
return nil
}
type RequestContext_IP struct {
IP *IPAddr
}
func (m *RequestContext) GetIP() *IPAddr {
if v, ok := m.GetClientAddress().(*RequestContext_IP); ok {
return v.IP
}
return nil
}
type RequestContext_TCP struct {
TCP *TCPAddr
}
func (m *RequestContext) GetTCP() *TCPAddr {
if v, ok := m.GetClientAddress().(*RequestContext_TCP); ok {
return v.TCP
}
return nil
}
type RequestContext_UDP struct {
UDP *UDPAddr
}
func (m *RequestContext) GetUDP() *UDPAddr {
if v, ok := m.GetClientAddress().(*RequestContext_UDP); ok {
return v.UDP
}
return nil
}
type RequestContext_Unix struct {
Unix *UnixAddr
}
func (m *RequestContext) GetUnix() *UnixAddr {
if v, ok := m.GetClientAddress().(*RequestContext_Unix); ok {
return v.Unix
}
return nil
}
type RequestContext struct {
ClientAddress any
SpanContext *SpanContext
ClientMetadata []KeyValue
}
var (
protoPoolRequestContext = sync.Pool{
New: func() any {
return &RequestContext{}
},
}
ProtoPoolRequestContext_IP = sync.Pool{
New: func() any {
return &RequestContext_IP{}
},
}
ProtoPoolRequestContext_TCP = sync.Pool{
New: func() any {
return &RequestContext_TCP{}
},
}
ProtoPoolRequestContext_UDP = sync.Pool{
New: func() any {
return &RequestContext_UDP{}
},
}
ProtoPoolRequestContext_Unix = sync.Pool{
New: func() any {
return &RequestContext_Unix{}
},
}
)
func NewRequestContext() *RequestContext {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &RequestContext{}
}
return protoPoolRequestContext.Get().(*RequestContext)
}
func DeleteRequestContext(orig *RequestContext, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
DeleteSpanContext(orig.SpanContext, true)
for i := range orig.ClientMetadata {
DeleteKeyValue(&orig.ClientMetadata[i], false)
}
switch ov := orig.ClientAddress.(type) {
case *RequestContext_IP:
DeleteIPAddr(ov.IP, true)
ov.IP = nil
ProtoPoolRequestContext_IP.Put(ov)
case *RequestContext_TCP:
DeleteTCPAddr(ov.TCP, true)
ov.TCP = nil
ProtoPoolRequestContext_TCP.Put(ov)
case *RequestContext_UDP:
DeleteUDPAddr(ov.UDP, true)
ov.UDP = nil
ProtoPoolRequestContext_UDP.Put(ov)
case *RequestContext_Unix:
DeleteUnixAddr(ov.Unix, true)
ov.Unix = nil
ProtoPoolRequestContext_Unix.Put(ov)
}
orig.Reset()
if nullable {
protoPoolRequestContext.Put(orig)
}
}
func CopyRequestContext(dest, src *RequestContext) *RequestContext {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewRequestContext()
}
dest.SpanContext = CopySpanContext(dest.SpanContext, src.SpanContext)
dest.ClientMetadata = CopyKeyValueSlice(dest.ClientMetadata, src.ClientMetadata)
switch t := src.ClientAddress.(type) {
case *RequestContext_IP:
var ov *RequestContext_IP
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &RequestContext_IP{}
} else {
ov = ProtoPoolRequestContext_IP.Get().(*RequestContext_IP)
}
ov.IP = NewIPAddr()
CopyIPAddr(ov.IP, t.IP)
dest.ClientAddress = ov
case *RequestContext_TCP:
var ov *RequestContext_TCP
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &RequestContext_TCP{}
} else {
ov = ProtoPoolRequestContext_TCP.Get().(*RequestContext_TCP)
}
ov.TCP = NewTCPAddr()
CopyTCPAddr(ov.TCP, t.TCP)
dest.ClientAddress = ov
case *RequestContext_UDP:
var ov *RequestContext_UDP
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &RequestContext_UDP{}
} else {
ov = ProtoPoolRequestContext_UDP.Get().(*RequestContext_UDP)
}
ov.UDP = NewUDPAddr()
CopyUDPAddr(ov.UDP, t.UDP)
dest.ClientAddress = ov
case *RequestContext_Unix:
var ov *RequestContext_Unix
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &RequestContext_Unix{}
} else {
ov = ProtoPoolRequestContext_Unix.Get().(*RequestContext_Unix)
}
ov.Unix = NewUnixAddr()
CopyUnixAddr(ov.Unix, t.Unix)
dest.ClientAddress = ov
default:
dest.ClientAddress = nil
}
return dest
}
func CopyRequestContextSlice(dest, src []RequestContext) []RequestContext {
var newDest []RequestContext
if cap(dest) < len(src) {
newDest = make([]RequestContext, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteRequestContext(&dest[i], false)
}
}
for i := range src {
CopyRequestContext(&newDest[i], &src[i])
}
return newDest
}
func CopyRequestContextPtrSlice(dest, src []*RequestContext) []*RequestContext {
var newDest []*RequestContext
if cap(dest) < len(src) {
newDest = make([]*RequestContext, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewRequestContext()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteRequestContext(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewRequestContext()
}
}
for i := range src {
CopyRequestContext(newDest[i], src[i])
}
return newDest
}
func (orig *RequestContext) Reset() {
*orig = RequestContext{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *RequestContext) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if orig.SpanContext != nil {
dest.WriteObjectField("spanContext")
orig.SpanContext.MarshalJSON(dest)
}
if len(orig.ClientMetadata) > 0 {
dest.WriteObjectField("clientMetadata")
dest.WriteArrayStart()
orig.ClientMetadata[0].MarshalJSON(dest)
for i := 1; i < len(orig.ClientMetadata); i++ {
dest.WriteMore()
orig.ClientMetadata[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
switch orig := orig.ClientAddress.(type) {
case *RequestContext_IP:
if orig.IP != nil {
dest.WriteObjectField("iP")
orig.IP.MarshalJSON(dest)
}
case *RequestContext_TCP:
if orig.TCP != nil {
dest.WriteObjectField("tCP")
orig.TCP.MarshalJSON(dest)
}
case *RequestContext_UDP:
if orig.UDP != nil {
dest.WriteObjectField("uDP")
orig.UDP.MarshalJSON(dest)
}
case *RequestContext_Unix:
if orig.Unix != nil {
dest.WriteObjectField("unix")
orig.Unix.MarshalJSON(dest)
}
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *RequestContext) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "spanContext", "span_context":
orig.SpanContext = NewSpanContext()
orig.SpanContext.UnmarshalJSON(iter)
case "clientMetadata", "client_metadata":
for iter.ReadArray() {
orig.ClientMetadata = append(orig.ClientMetadata, KeyValue{})
orig.ClientMetadata[len(orig.ClientMetadata)-1].UnmarshalJSON(iter)
}
case "iP":
{
var ov *RequestContext_IP
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &RequestContext_IP{}
} else {
ov = ProtoPoolRequestContext_IP.Get().(*RequestContext_IP)
}
ov.IP = NewIPAddr()
ov.IP.UnmarshalJSON(iter)
orig.ClientAddress = ov
}
case "tCP":
{
var ov *RequestContext_TCP
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &RequestContext_TCP{}
} else {
ov = ProtoPoolRequestContext_TCP.Get().(*RequestContext_TCP)
}
ov.TCP = NewTCPAddr()
ov.TCP.UnmarshalJSON(iter)
orig.ClientAddress = ov
}
case "uDP":
{
var ov *RequestContext_UDP
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &RequestContext_UDP{}
} else {
ov = ProtoPoolRequestContext_UDP.Get().(*RequestContext_UDP)
}
ov.UDP = NewUDPAddr()
ov.UDP.UnmarshalJSON(iter)
orig.ClientAddress = ov
}
case "unix":
{
var ov *RequestContext_Unix
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &RequestContext_Unix{}
} else {
ov = ProtoPoolRequestContext_Unix.Get().(*RequestContext_Unix)
}
ov.Unix = NewUnixAddr()
ov.Unix.UnmarshalJSON(iter)
orig.ClientAddress = ov
}
default:
iter.Skip()
}
}
}
func (orig *RequestContext) SizeProto() int {
var n int
var l int
_ = l
if orig.SpanContext != nil {
l = orig.SpanContext.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
for i := range orig.ClientMetadata {
l = orig.ClientMetadata[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
switch orig := orig.ClientAddress.(type) {
case nil:
_ = orig
break
case *RequestContext_IP:
if orig.IP != nil {
l = orig.IP.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
case *RequestContext_TCP:
if orig.TCP != nil {
l = orig.TCP.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
case *RequestContext_UDP:
if orig.UDP != nil {
l = orig.UDP.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
case *RequestContext_Unix:
if orig.Unix != nil {
l = orig.Unix.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
}
return n
}
func (orig *RequestContext) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
if orig.SpanContext != nil {
l = orig.SpanContext.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
}
for i := len(orig.ClientMetadata) - 1; i >= 0; i-- {
l = orig.ClientMetadata[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
}
switch orig := orig.ClientAddress.(type) {
case *RequestContext_IP:
if orig.IP != nil {
l = orig.IP.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x1a
}
case *RequestContext_TCP:
if orig.TCP != nil {
l = orig.TCP.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x22
}
case *RequestContext_UDP:
if orig.UDP != nil {
l = orig.UDP.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x2a
}
case *RequestContext_Unix:
if orig.Unix != nil {
l = orig.Unix.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x32
}
}
return len(buf) - pos
}
func (orig *RequestContext) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field SpanContext", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.SpanContext = NewSpanContext()
err = orig.SpanContext.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field ClientMetadata", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.ClientMetadata = append(orig.ClientMetadata, KeyValue{})
err = orig.ClientMetadata[len(orig.ClientMetadata)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 3:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field IP", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
var ov *RequestContext_IP
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &RequestContext_IP{}
} else {
ov = ProtoPoolRequestContext_IP.Get().(*RequestContext_IP)
}
ov.IP = NewIPAddr()
err = ov.IP.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
orig.ClientAddress = ov
case 4:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field TCP", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
var ov *RequestContext_TCP
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &RequestContext_TCP{}
} else {
ov = ProtoPoolRequestContext_TCP.Get().(*RequestContext_TCP)
}
ov.TCP = NewTCPAddr()
err = ov.TCP.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
orig.ClientAddress = ov
case 5:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field UDP", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
var ov *RequestContext_UDP
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &RequestContext_UDP{}
} else {
ov = ProtoPoolRequestContext_UDP.Get().(*RequestContext_UDP)
}
ov.UDP = NewUDPAddr()
err = ov.UDP.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
orig.ClientAddress = ov
case 6:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Unix", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
var ov *RequestContext_Unix
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &RequestContext_Unix{}
} else {
ov = ProtoPoolRequestContext_Unix.Get().(*RequestContext_Unix)
}
ov.Unix = NewUnixAddr()
err = ov.Unix.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
orig.ClientAddress = ov
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestRequestContext() *RequestContext {
orig := NewRequestContext()
orig.SpanContext = GenTestSpanContext()
orig.ClientMetadata = []KeyValue{{}, *GenTestKeyValue()}
orig.ClientAddress = &RequestContext_IP{IP: GenTestIPAddr()}
return orig
}
func GenTestRequestContextPtrSlice() []*RequestContext {
orig := make([]*RequestContext, 5)
orig[0] = NewRequestContext()
orig[1] = GenTestRequestContext()
orig[2] = NewRequestContext()
orig[3] = GenTestRequestContext()
orig[4] = NewRequestContext()
return orig
}
func GenTestRequestContextSlice() []RequestContext {
orig := make([]RequestContext, 5)
orig[1] = *GenTestRequestContext()
orig[3] = *GenTestRequestContext()
return orig
}
================================================
FILE: pdata/internal/generated_proto_requestcontext_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/emptypb"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyRequestContext(t *testing.T) {
for name, src := range genTestEncodingValuesRequestContext() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewRequestContext()
CopyRequestContext(dest, src)
assert.Equal(t, src, dest)
CopyRequestContext(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyRequestContextSlice(t *testing.T) {
src := []RequestContext{}
dest := []RequestContext{}
// Test CopyTo empty
dest = CopyRequestContextSlice(dest, src)
assert.Equal(t, []RequestContext{}, dest)
// Test CopyTo larger slice
src = GenTestRequestContextSlice()
dest = CopyRequestContextSlice(dest, src)
assert.Equal(t, GenTestRequestContextSlice(), dest)
// Test CopyTo same size slice
dest = CopyRequestContextSlice(dest, src)
assert.Equal(t, GenTestRequestContextSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyRequestContextSlice(dest, []RequestContext{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyRequestContextSlice(dest, src)
assert.Equal(t, GenTestRequestContextSlice(), dest)
}
func TestCopyRequestContextPtrSlice(t *testing.T) {
src := []*RequestContext{}
dest := []*RequestContext{}
// Test CopyTo empty
dest = CopyRequestContextPtrSlice(dest, src)
assert.Equal(t, []*RequestContext{}, dest)
// Test CopyTo larger slice
src = GenTestRequestContextPtrSlice()
dest = CopyRequestContextPtrSlice(dest, src)
assert.Equal(t, GenTestRequestContextPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyRequestContextPtrSlice(dest, src)
assert.Equal(t, GenTestRequestContextPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyRequestContextPtrSlice(dest, []*RequestContext{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyRequestContextPtrSlice(dest, src)
assert.Equal(t, GenTestRequestContextPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONRequestContextUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewRequestContext()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewRequestContext(), dest)
}
func TestMarshalAndUnmarshalJSONRequestContext(t *testing.T) {
for name, src := range genTestEncodingValuesRequestContext() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewRequestContext()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteRequestContext(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoRequestContextFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesRequestContext() {
t.Run(name, func(t *testing.T) {
dest := NewRequestContext()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoRequestContextUnknown(t *testing.T) {
dest := NewRequestContext()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewRequestContext(), dest)
}
func TestMarshalAndUnmarshalProtoRequestContext(t *testing.T) {
for name, src := range genTestEncodingValuesRequestContext() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewRequestContext()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteRequestContext(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufRequestContext(t *testing.T) {
for name, src := range genTestEncodingValuesRequestContext() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &emptypb.Empty{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewRequestContext()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesRequestContext() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"SpanContext/wrong_wire_type": {0xc},
"SpanContext/missing_value": {0xa},
"ClientMetadata/wrong_wire_type": {0x14},
"ClientMetadata/missing_value": {0x12},
"IP/wrong_wire_type": {0x1c},
"IP/missing_value": {0x1a},
"TCP/wrong_wire_type": {0x24},
"TCP/missing_value": {0x22},
"UDP/wrong_wire_type": {0x2c},
"UDP/missing_value": {0x2a},
"Unix/wrong_wire_type": {0x34},
"Unix/missing_value": {0x32},
}
}
func genTestEncodingValuesRequestContext() map[string]*RequestContext {
return map[string]*RequestContext{
"empty": NewRequestContext(),
"SpanContext/test": {SpanContext: GenTestSpanContext()},
"ClientMetadata/test": {ClientMetadata: []KeyValue{{}, *GenTestKeyValue()}},
"IP/default": {ClientAddress: &RequestContext_IP{IP: &IPAddr{}}},
"IP/test": {ClientAddress: &RequestContext_IP{IP: GenTestIPAddr()}}, "TCP/default": {ClientAddress: &RequestContext_TCP{TCP: &TCPAddr{}}},
"TCP/test": {ClientAddress: &RequestContext_TCP{TCP: GenTestTCPAddr()}}, "UDP/default": {ClientAddress: &RequestContext_UDP{UDP: &UDPAddr{}}},
"UDP/test": {ClientAddress: &RequestContext_UDP{UDP: GenTestUDPAddr()}}, "Unix/default": {ClientAddress: &RequestContext_Unix{Unix: &UnixAddr{}}},
"Unix/test": {ClientAddress: &RequestContext_Unix{Unix: GenTestUnixAddr()}},
}
}
================================================
FILE: pdata/internal/generated_proto_resource.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// Resource is a message representing the resource information.
type Resource struct {
Attributes []KeyValue
EntityRefs []*EntityRef
DroppedAttributesCount uint32
}
var (
protoPoolResource = sync.Pool{
New: func() any {
return &Resource{}
},
}
)
func NewResource() *Resource {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &Resource{}
}
return protoPoolResource.Get().(*Resource)
}
func DeleteResource(orig *Resource, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
for i := range orig.Attributes {
DeleteKeyValue(&orig.Attributes[i], false)
}
for i := range orig.EntityRefs {
DeleteEntityRef(orig.EntityRefs[i], true)
}
orig.Reset()
if nullable {
protoPoolResource.Put(orig)
}
}
func CopyResource(dest, src *Resource) *Resource {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewResource()
}
dest.Attributes = CopyKeyValueSlice(dest.Attributes, src.Attributes)
dest.DroppedAttributesCount = src.DroppedAttributesCount
dest.EntityRefs = CopyEntityRefPtrSlice(dest.EntityRefs, src.EntityRefs)
return dest
}
func CopyResourceSlice(dest, src []Resource) []Resource {
var newDest []Resource
if cap(dest) < len(src) {
newDest = make([]Resource, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteResource(&dest[i], false)
}
}
for i := range src {
CopyResource(&newDest[i], &src[i])
}
return newDest
}
func CopyResourcePtrSlice(dest, src []*Resource) []*Resource {
var newDest []*Resource
if cap(dest) < len(src) {
newDest = make([]*Resource, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewResource()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteResource(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewResource()
}
}
for i := range src {
CopyResource(newDest[i], src[i])
}
return newDest
}
func (orig *Resource) Reset() {
*orig = Resource{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *Resource) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if len(orig.Attributes) > 0 {
dest.WriteObjectField("attributes")
dest.WriteArrayStart()
orig.Attributes[0].MarshalJSON(dest)
for i := 1; i < len(orig.Attributes); i++ {
dest.WriteMore()
orig.Attributes[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if orig.DroppedAttributesCount != uint32(0) {
dest.WriteObjectField("droppedAttributesCount")
dest.WriteUint32(orig.DroppedAttributesCount)
}
if len(orig.EntityRefs) > 0 {
dest.WriteObjectField("entityRefs")
dest.WriteArrayStart()
orig.EntityRefs[0].MarshalJSON(dest)
for i := 1; i < len(orig.EntityRefs); i++ {
dest.WriteMore()
orig.EntityRefs[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *Resource) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "attributes":
for iter.ReadArray() {
orig.Attributes = append(orig.Attributes, KeyValue{})
orig.Attributes[len(orig.Attributes)-1].UnmarshalJSON(iter)
}
case "droppedAttributesCount", "dropped_attributes_count":
orig.DroppedAttributesCount = iter.ReadUint32()
case "entityRefs", "entity_refs":
for iter.ReadArray() {
orig.EntityRefs = append(orig.EntityRefs, NewEntityRef())
orig.EntityRefs[len(orig.EntityRefs)-1].UnmarshalJSON(iter)
}
default:
iter.Skip()
}
}
}
func (orig *Resource) SizeProto() int {
var n int
var l int
_ = l
for i := range orig.Attributes {
l = orig.Attributes[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
if orig.DroppedAttributesCount != uint32(0) {
n += 1 + proto.Sov(uint64(orig.DroppedAttributesCount))
}
for i := range orig.EntityRefs {
l = orig.EntityRefs[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *Resource) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
for i := len(orig.Attributes) - 1; i >= 0; i-- {
l = orig.Attributes[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
}
if orig.DroppedAttributesCount != uint32(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.DroppedAttributesCount))
pos--
buf[pos] = 0x10
}
for i := len(orig.EntityRefs) - 1; i >= 0; i-- {
l = orig.EntityRefs[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x1a
}
return len(buf) - pos
}
func (orig *Resource) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Attributes", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Attributes = append(orig.Attributes, KeyValue{})
err = orig.Attributes[len(orig.Attributes)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 2:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field DroppedAttributesCount", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.DroppedAttributesCount = uint32(num)
case 3:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field EntityRefs", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.EntityRefs = append(orig.EntityRefs, NewEntityRef())
err = orig.EntityRefs[len(orig.EntityRefs)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestResource() *Resource {
orig := NewResource()
orig.Attributes = []KeyValue{{}, *GenTestKeyValue()}
orig.DroppedAttributesCount = uint32(13)
orig.EntityRefs = []*EntityRef{{}, GenTestEntityRef()}
return orig
}
func GenTestResourcePtrSlice() []*Resource {
orig := make([]*Resource, 5)
orig[0] = NewResource()
orig[1] = GenTestResource()
orig[2] = NewResource()
orig[3] = GenTestResource()
orig[4] = NewResource()
return orig
}
func GenTestResourceSlice() []Resource {
orig := make([]Resource, 5)
orig[1] = *GenTestResource()
orig[3] = *GenTestResource()
return orig
}
================================================
FILE: pdata/internal/generated_proto_resource_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpresource "go.opentelemetry.io/proto/slim/otlp/resource/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyResource(t *testing.T) {
for name, src := range genTestEncodingValuesResource() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewResource()
CopyResource(dest, src)
assert.Equal(t, src, dest)
CopyResource(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyResourceSlice(t *testing.T) {
src := []Resource{}
dest := []Resource{}
// Test CopyTo empty
dest = CopyResourceSlice(dest, src)
assert.Equal(t, []Resource{}, dest)
// Test CopyTo larger slice
src = GenTestResourceSlice()
dest = CopyResourceSlice(dest, src)
assert.Equal(t, GenTestResourceSlice(), dest)
// Test CopyTo same size slice
dest = CopyResourceSlice(dest, src)
assert.Equal(t, GenTestResourceSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyResourceSlice(dest, []Resource{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyResourceSlice(dest, src)
assert.Equal(t, GenTestResourceSlice(), dest)
}
func TestCopyResourcePtrSlice(t *testing.T) {
src := []*Resource{}
dest := []*Resource{}
// Test CopyTo empty
dest = CopyResourcePtrSlice(dest, src)
assert.Equal(t, []*Resource{}, dest)
// Test CopyTo larger slice
src = GenTestResourcePtrSlice()
dest = CopyResourcePtrSlice(dest, src)
assert.Equal(t, GenTestResourcePtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyResourcePtrSlice(dest, src)
assert.Equal(t, GenTestResourcePtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyResourcePtrSlice(dest, []*Resource{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyResourcePtrSlice(dest, src)
assert.Equal(t, GenTestResourcePtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONResourceUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewResource()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewResource(), dest)
}
func TestMarshalAndUnmarshalJSONResource(t *testing.T) {
for name, src := range genTestEncodingValuesResource() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewResource()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteResource(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoResourceFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesResource() {
t.Run(name, func(t *testing.T) {
dest := NewResource()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoResourceUnknown(t *testing.T) {
dest := NewResource()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewResource(), dest)
}
func TestMarshalAndUnmarshalProtoResource(t *testing.T) {
for name, src := range genTestEncodingValuesResource() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewResource()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteResource(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufResource(t *testing.T) {
for name, src := range genTestEncodingValuesResource() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpresource.Resource{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewResource()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesResource() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"Attributes/wrong_wire_type": {0xc},
"Attributes/missing_value": {0xa},
"DroppedAttributesCount/wrong_wire_type": {0x14},
"DroppedAttributesCount/missing_value": {0x10},
"EntityRefs/wrong_wire_type": {0x1c},
"EntityRefs/missing_value": {0x1a},
}
}
func genTestEncodingValuesResource() map[string]*Resource {
return map[string]*Resource{
"empty": NewResource(),
"Attributes/test": {Attributes: []KeyValue{{}, *GenTestKeyValue()}},
"DroppedAttributesCount/test": {DroppedAttributesCount: uint32(13)},
"EntityRefs/test": {EntityRefs: []*EntityRef{{}, GenTestEntityRef()}},
}
}
================================================
FILE: pdata/internal/generated_proto_resourcelogs.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// ResourceLogs is a collection of logs from a Resource.
type ResourceLogs struct {
Resource Resource
ScopeLogs []*ScopeLogs
SchemaUrl string
DeprecatedScopeLogs []*ScopeLogs
}
var (
protoPoolResourceLogs = sync.Pool{
New: func() any {
return &ResourceLogs{}
},
}
)
func NewResourceLogs() *ResourceLogs {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &ResourceLogs{}
}
return protoPoolResourceLogs.Get().(*ResourceLogs)
}
func DeleteResourceLogs(orig *ResourceLogs, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
DeleteResource(&orig.Resource, false)
for i := range orig.ScopeLogs {
DeleteScopeLogs(orig.ScopeLogs[i], true)
}
for i := range orig.DeprecatedScopeLogs {
DeleteScopeLogs(orig.DeprecatedScopeLogs[i], true)
}
orig.Reset()
if nullable {
protoPoolResourceLogs.Put(orig)
}
}
func CopyResourceLogs(dest, src *ResourceLogs) *ResourceLogs {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewResourceLogs()
}
CopyResource(&dest.Resource, &src.Resource)
dest.ScopeLogs = CopyScopeLogsPtrSlice(dest.ScopeLogs, src.ScopeLogs)
dest.SchemaUrl = src.SchemaUrl
dest.DeprecatedScopeLogs = CopyScopeLogsPtrSlice(dest.DeprecatedScopeLogs, src.DeprecatedScopeLogs)
return dest
}
func CopyResourceLogsSlice(dest, src []ResourceLogs) []ResourceLogs {
var newDest []ResourceLogs
if cap(dest) < len(src) {
newDest = make([]ResourceLogs, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteResourceLogs(&dest[i], false)
}
}
for i := range src {
CopyResourceLogs(&newDest[i], &src[i])
}
return newDest
}
func CopyResourceLogsPtrSlice(dest, src []*ResourceLogs) []*ResourceLogs {
var newDest []*ResourceLogs
if cap(dest) < len(src) {
newDest = make([]*ResourceLogs, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewResourceLogs()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteResourceLogs(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewResourceLogs()
}
}
for i := range src {
CopyResourceLogs(newDest[i], src[i])
}
return newDest
}
func (orig *ResourceLogs) Reset() {
*orig = ResourceLogs{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *ResourceLogs) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
dest.WriteObjectField("resource")
orig.Resource.MarshalJSON(dest)
if len(orig.ScopeLogs) > 0 {
dest.WriteObjectField("scopeLogs")
dest.WriteArrayStart()
orig.ScopeLogs[0].MarshalJSON(dest)
for i := 1; i < len(orig.ScopeLogs); i++ {
dest.WriteMore()
orig.ScopeLogs[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if orig.SchemaUrl != "" {
dest.WriteObjectField("schemaUrl")
dest.WriteString(orig.SchemaUrl)
}
if len(orig.DeprecatedScopeLogs) > 0 {
dest.WriteObjectField("deprecatedScopeLogs")
dest.WriteArrayStart()
orig.DeprecatedScopeLogs[0].MarshalJSON(dest)
for i := 1; i < len(orig.DeprecatedScopeLogs); i++ {
dest.WriteMore()
orig.DeprecatedScopeLogs[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *ResourceLogs) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "resource":
orig.Resource.UnmarshalJSON(iter)
case "scopeLogs", "scope_logs":
for iter.ReadArray() {
orig.ScopeLogs = append(orig.ScopeLogs, NewScopeLogs())
orig.ScopeLogs[len(orig.ScopeLogs)-1].UnmarshalJSON(iter)
}
case "schemaUrl", "schema_url":
orig.SchemaUrl = iter.ReadString()
case "deprecatedScopeLogs", "deprecated_scope_logs":
for iter.ReadArray() {
orig.DeprecatedScopeLogs = append(orig.DeprecatedScopeLogs, NewScopeLogs())
orig.DeprecatedScopeLogs[len(orig.DeprecatedScopeLogs)-1].UnmarshalJSON(iter)
}
default:
iter.Skip()
}
}
}
func (orig *ResourceLogs) SizeProto() int {
var n int
var l int
_ = l
l = orig.Resource.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
for i := range orig.ScopeLogs {
l = orig.ScopeLogs[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
l = len(orig.SchemaUrl)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
for i := range orig.DeprecatedScopeLogs {
l = orig.DeprecatedScopeLogs[i].SizeProto()
n += 2 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *ResourceLogs) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
l = orig.Resource.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
for i := len(orig.ScopeLogs) - 1; i >= 0; i-- {
l = orig.ScopeLogs[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
}
l = len(orig.SchemaUrl)
if l > 0 {
pos -= l
copy(buf[pos:], orig.SchemaUrl)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x1a
}
for i := len(orig.DeprecatedScopeLogs) - 1; i >= 0; i-- {
l = orig.DeprecatedScopeLogs[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x3e
pos--
buf[pos] = 0xc2
}
return len(buf) - pos
}
func (orig *ResourceLogs) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Resource", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.Resource.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field ScopeLogs", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.ScopeLogs = append(orig.ScopeLogs, NewScopeLogs())
err = orig.ScopeLogs[len(orig.ScopeLogs)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 3:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field SchemaUrl", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.SchemaUrl = string(buf[startPos:pos])
case 1000:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field DeprecatedScopeLogs", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.DeprecatedScopeLogs = append(orig.DeprecatedScopeLogs, NewScopeLogs())
err = orig.DeprecatedScopeLogs[len(orig.DeprecatedScopeLogs)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestResourceLogs() *ResourceLogs {
orig := NewResourceLogs()
orig.Resource = *GenTestResource()
orig.ScopeLogs = []*ScopeLogs{{}, GenTestScopeLogs()}
orig.SchemaUrl = "test_schemaurl"
orig.DeprecatedScopeLogs = []*ScopeLogs{{}, GenTestScopeLogs()}
return orig
}
func GenTestResourceLogsPtrSlice() []*ResourceLogs {
orig := make([]*ResourceLogs, 5)
orig[0] = NewResourceLogs()
orig[1] = GenTestResourceLogs()
orig[2] = NewResourceLogs()
orig[3] = GenTestResourceLogs()
orig[4] = NewResourceLogs()
return orig
}
func GenTestResourceLogsSlice() []ResourceLogs {
orig := make([]ResourceLogs, 5)
orig[1] = *GenTestResourceLogs()
orig[3] = *GenTestResourceLogs()
return orig
}
================================================
FILE: pdata/internal/generated_proto_resourcelogs_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlplogs "go.opentelemetry.io/proto/slim/otlp/logs/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyResourceLogs(t *testing.T) {
for name, src := range genTestEncodingValuesResourceLogs() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewResourceLogs()
CopyResourceLogs(dest, src)
assert.Equal(t, src, dest)
CopyResourceLogs(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyResourceLogsSlice(t *testing.T) {
src := []ResourceLogs{}
dest := []ResourceLogs{}
// Test CopyTo empty
dest = CopyResourceLogsSlice(dest, src)
assert.Equal(t, []ResourceLogs{}, dest)
// Test CopyTo larger slice
src = GenTestResourceLogsSlice()
dest = CopyResourceLogsSlice(dest, src)
assert.Equal(t, GenTestResourceLogsSlice(), dest)
// Test CopyTo same size slice
dest = CopyResourceLogsSlice(dest, src)
assert.Equal(t, GenTestResourceLogsSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyResourceLogsSlice(dest, []ResourceLogs{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyResourceLogsSlice(dest, src)
assert.Equal(t, GenTestResourceLogsSlice(), dest)
}
func TestCopyResourceLogsPtrSlice(t *testing.T) {
src := []*ResourceLogs{}
dest := []*ResourceLogs{}
// Test CopyTo empty
dest = CopyResourceLogsPtrSlice(dest, src)
assert.Equal(t, []*ResourceLogs{}, dest)
// Test CopyTo larger slice
src = GenTestResourceLogsPtrSlice()
dest = CopyResourceLogsPtrSlice(dest, src)
assert.Equal(t, GenTestResourceLogsPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyResourceLogsPtrSlice(dest, src)
assert.Equal(t, GenTestResourceLogsPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyResourceLogsPtrSlice(dest, []*ResourceLogs{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyResourceLogsPtrSlice(dest, src)
assert.Equal(t, GenTestResourceLogsPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONResourceLogsUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewResourceLogs()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewResourceLogs(), dest)
}
func TestMarshalAndUnmarshalJSONResourceLogs(t *testing.T) {
for name, src := range genTestEncodingValuesResourceLogs() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewResourceLogs()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteResourceLogs(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoResourceLogsFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesResourceLogs() {
t.Run(name, func(t *testing.T) {
dest := NewResourceLogs()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoResourceLogsUnknown(t *testing.T) {
dest := NewResourceLogs()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewResourceLogs(), dest)
}
func TestMarshalAndUnmarshalProtoResourceLogs(t *testing.T) {
for name, src := range genTestEncodingValuesResourceLogs() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewResourceLogs()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteResourceLogs(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufResourceLogs(t *testing.T) {
for name, src := range genTestEncodingValuesResourceLogs() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlplogs.ResourceLogs{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewResourceLogs()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesResourceLogs() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"Resource/wrong_wire_type": {0xc},
"Resource/missing_value": {0xa},
"ScopeLogs/wrong_wire_type": {0x14},
"ScopeLogs/missing_value": {0x12},
"SchemaUrl/wrong_wire_type": {0x1c},
"SchemaUrl/missing_value": {0x1a},
"DeprecatedScopeLogs/wrong_wire_type": {0xc4, 0x3e},
"DeprecatedScopeLogs/missing_value": {0xc2, 0x3e},
}
}
func genTestEncodingValuesResourceLogs() map[string]*ResourceLogs {
return map[string]*ResourceLogs{
"empty": NewResourceLogs(),
"Resource/test": {Resource: *GenTestResource()},
"ScopeLogs/test": {ScopeLogs: []*ScopeLogs{{}, GenTestScopeLogs()}},
"SchemaUrl/test": {SchemaUrl: "test_schemaurl"},
"DeprecatedScopeLogs/test": {DeprecatedScopeLogs: []*ScopeLogs{{}, GenTestScopeLogs()}},
}
}
================================================
FILE: pdata/internal/generated_proto_resourcemetrics.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// ResourceMetrics is a collection of metrics from a Resource.
type ResourceMetrics struct {
Resource Resource
ScopeMetrics []*ScopeMetrics
SchemaUrl string
DeprecatedScopeMetrics []*ScopeMetrics
}
var (
protoPoolResourceMetrics = sync.Pool{
New: func() any {
return &ResourceMetrics{}
},
}
)
func NewResourceMetrics() *ResourceMetrics {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &ResourceMetrics{}
}
return protoPoolResourceMetrics.Get().(*ResourceMetrics)
}
func DeleteResourceMetrics(orig *ResourceMetrics, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
DeleteResource(&orig.Resource, false)
for i := range orig.ScopeMetrics {
DeleteScopeMetrics(orig.ScopeMetrics[i], true)
}
for i := range orig.DeprecatedScopeMetrics {
DeleteScopeMetrics(orig.DeprecatedScopeMetrics[i], true)
}
orig.Reset()
if nullable {
protoPoolResourceMetrics.Put(orig)
}
}
func CopyResourceMetrics(dest, src *ResourceMetrics) *ResourceMetrics {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewResourceMetrics()
}
CopyResource(&dest.Resource, &src.Resource)
dest.ScopeMetrics = CopyScopeMetricsPtrSlice(dest.ScopeMetrics, src.ScopeMetrics)
dest.SchemaUrl = src.SchemaUrl
dest.DeprecatedScopeMetrics = CopyScopeMetricsPtrSlice(dest.DeprecatedScopeMetrics, src.DeprecatedScopeMetrics)
return dest
}
func CopyResourceMetricsSlice(dest, src []ResourceMetrics) []ResourceMetrics {
var newDest []ResourceMetrics
if cap(dest) < len(src) {
newDest = make([]ResourceMetrics, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteResourceMetrics(&dest[i], false)
}
}
for i := range src {
CopyResourceMetrics(&newDest[i], &src[i])
}
return newDest
}
func CopyResourceMetricsPtrSlice(dest, src []*ResourceMetrics) []*ResourceMetrics {
var newDest []*ResourceMetrics
if cap(dest) < len(src) {
newDest = make([]*ResourceMetrics, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewResourceMetrics()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteResourceMetrics(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewResourceMetrics()
}
}
for i := range src {
CopyResourceMetrics(newDest[i], src[i])
}
return newDest
}
func (orig *ResourceMetrics) Reset() {
*orig = ResourceMetrics{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *ResourceMetrics) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
dest.WriteObjectField("resource")
orig.Resource.MarshalJSON(dest)
if len(orig.ScopeMetrics) > 0 {
dest.WriteObjectField("scopeMetrics")
dest.WriteArrayStart()
orig.ScopeMetrics[0].MarshalJSON(dest)
for i := 1; i < len(orig.ScopeMetrics); i++ {
dest.WriteMore()
orig.ScopeMetrics[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if orig.SchemaUrl != "" {
dest.WriteObjectField("schemaUrl")
dest.WriteString(orig.SchemaUrl)
}
if len(orig.DeprecatedScopeMetrics) > 0 {
dest.WriteObjectField("deprecatedScopeMetrics")
dest.WriteArrayStart()
orig.DeprecatedScopeMetrics[0].MarshalJSON(dest)
for i := 1; i < len(orig.DeprecatedScopeMetrics); i++ {
dest.WriteMore()
orig.DeprecatedScopeMetrics[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *ResourceMetrics) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "resource":
orig.Resource.UnmarshalJSON(iter)
case "scopeMetrics", "scope_metrics":
for iter.ReadArray() {
orig.ScopeMetrics = append(orig.ScopeMetrics, NewScopeMetrics())
orig.ScopeMetrics[len(orig.ScopeMetrics)-1].UnmarshalJSON(iter)
}
case "schemaUrl", "schema_url":
orig.SchemaUrl = iter.ReadString()
case "deprecatedScopeMetrics", "deprecated_scope_metrics":
for iter.ReadArray() {
orig.DeprecatedScopeMetrics = append(orig.DeprecatedScopeMetrics, NewScopeMetrics())
orig.DeprecatedScopeMetrics[len(orig.DeprecatedScopeMetrics)-1].UnmarshalJSON(iter)
}
default:
iter.Skip()
}
}
}
func (orig *ResourceMetrics) SizeProto() int {
var n int
var l int
_ = l
l = orig.Resource.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
for i := range orig.ScopeMetrics {
l = orig.ScopeMetrics[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
l = len(orig.SchemaUrl)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
for i := range orig.DeprecatedScopeMetrics {
l = orig.DeprecatedScopeMetrics[i].SizeProto()
n += 2 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *ResourceMetrics) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
l = orig.Resource.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
for i := len(orig.ScopeMetrics) - 1; i >= 0; i-- {
l = orig.ScopeMetrics[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
}
l = len(orig.SchemaUrl)
if l > 0 {
pos -= l
copy(buf[pos:], orig.SchemaUrl)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x1a
}
for i := len(orig.DeprecatedScopeMetrics) - 1; i >= 0; i-- {
l = orig.DeprecatedScopeMetrics[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x3e
pos--
buf[pos] = 0xc2
}
return len(buf) - pos
}
func (orig *ResourceMetrics) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Resource", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.Resource.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field ScopeMetrics", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.ScopeMetrics = append(orig.ScopeMetrics, NewScopeMetrics())
err = orig.ScopeMetrics[len(orig.ScopeMetrics)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 3:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field SchemaUrl", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.SchemaUrl = string(buf[startPos:pos])
case 1000:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field DeprecatedScopeMetrics", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.DeprecatedScopeMetrics = append(orig.DeprecatedScopeMetrics, NewScopeMetrics())
err = orig.DeprecatedScopeMetrics[len(orig.DeprecatedScopeMetrics)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestResourceMetrics() *ResourceMetrics {
orig := NewResourceMetrics()
orig.Resource = *GenTestResource()
orig.ScopeMetrics = []*ScopeMetrics{{}, GenTestScopeMetrics()}
orig.SchemaUrl = "test_schemaurl"
orig.DeprecatedScopeMetrics = []*ScopeMetrics{{}, GenTestScopeMetrics()}
return orig
}
func GenTestResourceMetricsPtrSlice() []*ResourceMetrics {
orig := make([]*ResourceMetrics, 5)
orig[0] = NewResourceMetrics()
orig[1] = GenTestResourceMetrics()
orig[2] = NewResourceMetrics()
orig[3] = GenTestResourceMetrics()
orig[4] = NewResourceMetrics()
return orig
}
func GenTestResourceMetricsSlice() []ResourceMetrics {
orig := make([]ResourceMetrics, 5)
orig[1] = *GenTestResourceMetrics()
orig[3] = *GenTestResourceMetrics()
return orig
}
================================================
FILE: pdata/internal/generated_proto_resourcemetrics_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpmetrics "go.opentelemetry.io/proto/slim/otlp/metrics/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyResourceMetrics(t *testing.T) {
for name, src := range genTestEncodingValuesResourceMetrics() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewResourceMetrics()
CopyResourceMetrics(dest, src)
assert.Equal(t, src, dest)
CopyResourceMetrics(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyResourceMetricsSlice(t *testing.T) {
src := []ResourceMetrics{}
dest := []ResourceMetrics{}
// Test CopyTo empty
dest = CopyResourceMetricsSlice(dest, src)
assert.Equal(t, []ResourceMetrics{}, dest)
// Test CopyTo larger slice
src = GenTestResourceMetricsSlice()
dest = CopyResourceMetricsSlice(dest, src)
assert.Equal(t, GenTestResourceMetricsSlice(), dest)
// Test CopyTo same size slice
dest = CopyResourceMetricsSlice(dest, src)
assert.Equal(t, GenTestResourceMetricsSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyResourceMetricsSlice(dest, []ResourceMetrics{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyResourceMetricsSlice(dest, src)
assert.Equal(t, GenTestResourceMetricsSlice(), dest)
}
func TestCopyResourceMetricsPtrSlice(t *testing.T) {
src := []*ResourceMetrics{}
dest := []*ResourceMetrics{}
// Test CopyTo empty
dest = CopyResourceMetricsPtrSlice(dest, src)
assert.Equal(t, []*ResourceMetrics{}, dest)
// Test CopyTo larger slice
src = GenTestResourceMetricsPtrSlice()
dest = CopyResourceMetricsPtrSlice(dest, src)
assert.Equal(t, GenTestResourceMetricsPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyResourceMetricsPtrSlice(dest, src)
assert.Equal(t, GenTestResourceMetricsPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyResourceMetricsPtrSlice(dest, []*ResourceMetrics{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyResourceMetricsPtrSlice(dest, src)
assert.Equal(t, GenTestResourceMetricsPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONResourceMetricsUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewResourceMetrics()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewResourceMetrics(), dest)
}
func TestMarshalAndUnmarshalJSONResourceMetrics(t *testing.T) {
for name, src := range genTestEncodingValuesResourceMetrics() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewResourceMetrics()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteResourceMetrics(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoResourceMetricsFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesResourceMetrics() {
t.Run(name, func(t *testing.T) {
dest := NewResourceMetrics()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoResourceMetricsUnknown(t *testing.T) {
dest := NewResourceMetrics()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewResourceMetrics(), dest)
}
func TestMarshalAndUnmarshalProtoResourceMetrics(t *testing.T) {
for name, src := range genTestEncodingValuesResourceMetrics() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewResourceMetrics()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteResourceMetrics(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufResourceMetrics(t *testing.T) {
for name, src := range genTestEncodingValuesResourceMetrics() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpmetrics.ResourceMetrics{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewResourceMetrics()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesResourceMetrics() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"Resource/wrong_wire_type": {0xc},
"Resource/missing_value": {0xa},
"ScopeMetrics/wrong_wire_type": {0x14},
"ScopeMetrics/missing_value": {0x12},
"SchemaUrl/wrong_wire_type": {0x1c},
"SchemaUrl/missing_value": {0x1a},
"DeprecatedScopeMetrics/wrong_wire_type": {0xc4, 0x3e},
"DeprecatedScopeMetrics/missing_value": {0xc2, 0x3e},
}
}
func genTestEncodingValuesResourceMetrics() map[string]*ResourceMetrics {
return map[string]*ResourceMetrics{
"empty": NewResourceMetrics(),
"Resource/test": {Resource: *GenTestResource()},
"ScopeMetrics/test": {ScopeMetrics: []*ScopeMetrics{{}, GenTestScopeMetrics()}},
"SchemaUrl/test": {SchemaUrl: "test_schemaurl"},
"DeprecatedScopeMetrics/test": {DeprecatedScopeMetrics: []*ScopeMetrics{{}, GenTestScopeMetrics()}},
}
}
================================================
FILE: pdata/internal/generated_proto_resourceprofiles.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// ResourceProfiles is a collection of profiles from a Resource.
type ResourceProfiles struct {
SchemaUrl string
Resource Resource
ScopeProfiles []*ScopeProfiles
}
var (
protoPoolResourceProfiles = sync.Pool{
New: func() any {
return &ResourceProfiles{}
},
}
)
func NewResourceProfiles() *ResourceProfiles {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &ResourceProfiles{}
}
return protoPoolResourceProfiles.Get().(*ResourceProfiles)
}
func DeleteResourceProfiles(orig *ResourceProfiles, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
DeleteResource(&orig.Resource, false)
for i := range orig.ScopeProfiles {
DeleteScopeProfiles(orig.ScopeProfiles[i], true)
}
orig.Reset()
if nullable {
protoPoolResourceProfiles.Put(orig)
}
}
func CopyResourceProfiles(dest, src *ResourceProfiles) *ResourceProfiles {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewResourceProfiles()
}
CopyResource(&dest.Resource, &src.Resource)
dest.ScopeProfiles = CopyScopeProfilesPtrSlice(dest.ScopeProfiles, src.ScopeProfiles)
dest.SchemaUrl = src.SchemaUrl
return dest
}
func CopyResourceProfilesSlice(dest, src []ResourceProfiles) []ResourceProfiles {
var newDest []ResourceProfiles
if cap(dest) < len(src) {
newDest = make([]ResourceProfiles, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteResourceProfiles(&dest[i], false)
}
}
for i := range src {
CopyResourceProfiles(&newDest[i], &src[i])
}
return newDest
}
func CopyResourceProfilesPtrSlice(dest, src []*ResourceProfiles) []*ResourceProfiles {
var newDest []*ResourceProfiles
if cap(dest) < len(src) {
newDest = make([]*ResourceProfiles, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewResourceProfiles()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteResourceProfiles(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewResourceProfiles()
}
}
for i := range src {
CopyResourceProfiles(newDest[i], src[i])
}
return newDest
}
func (orig *ResourceProfiles) Reset() {
*orig = ResourceProfiles{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *ResourceProfiles) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
dest.WriteObjectField("resource")
orig.Resource.MarshalJSON(dest)
if len(orig.ScopeProfiles) > 0 {
dest.WriteObjectField("scopeProfiles")
dest.WriteArrayStart()
orig.ScopeProfiles[0].MarshalJSON(dest)
for i := 1; i < len(orig.ScopeProfiles); i++ {
dest.WriteMore()
orig.ScopeProfiles[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if orig.SchemaUrl != "" {
dest.WriteObjectField("schemaUrl")
dest.WriteString(orig.SchemaUrl)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *ResourceProfiles) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "resource":
orig.Resource.UnmarshalJSON(iter)
case "scopeProfiles", "scope_profiles":
for iter.ReadArray() {
orig.ScopeProfiles = append(orig.ScopeProfiles, NewScopeProfiles())
orig.ScopeProfiles[len(orig.ScopeProfiles)-1].UnmarshalJSON(iter)
}
case "schemaUrl", "schema_url":
orig.SchemaUrl = iter.ReadString()
default:
iter.Skip()
}
}
}
func (orig *ResourceProfiles) SizeProto() int {
var n int
var l int
_ = l
l = orig.Resource.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
for i := range orig.ScopeProfiles {
l = orig.ScopeProfiles[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
l = len(orig.SchemaUrl)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *ResourceProfiles) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
l = orig.Resource.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
for i := len(orig.ScopeProfiles) - 1; i >= 0; i-- {
l = orig.ScopeProfiles[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
}
l = len(orig.SchemaUrl)
if l > 0 {
pos -= l
copy(buf[pos:], orig.SchemaUrl)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x1a
}
return len(buf) - pos
}
func (orig *ResourceProfiles) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Resource", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.Resource.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field ScopeProfiles", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.ScopeProfiles = append(orig.ScopeProfiles, NewScopeProfiles())
err = orig.ScopeProfiles[len(orig.ScopeProfiles)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 3:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field SchemaUrl", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.SchemaUrl = string(buf[startPos:pos])
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestResourceProfiles() *ResourceProfiles {
orig := NewResourceProfiles()
orig.Resource = *GenTestResource()
orig.ScopeProfiles = []*ScopeProfiles{{}, GenTestScopeProfiles()}
orig.SchemaUrl = "test_schemaurl"
return orig
}
func GenTestResourceProfilesPtrSlice() []*ResourceProfiles {
orig := make([]*ResourceProfiles, 5)
orig[0] = NewResourceProfiles()
orig[1] = GenTestResourceProfiles()
orig[2] = NewResourceProfiles()
orig[3] = GenTestResourceProfiles()
orig[4] = NewResourceProfiles()
return orig
}
func GenTestResourceProfilesSlice() []ResourceProfiles {
orig := make([]ResourceProfiles, 5)
orig[1] = *GenTestResourceProfiles()
orig[3] = *GenTestResourceProfiles()
return orig
}
================================================
FILE: pdata/internal/generated_proto_resourceprofiles_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpprofiles "go.opentelemetry.io/proto/slim/otlp/profiles/v1development"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyResourceProfiles(t *testing.T) {
for name, src := range genTestEncodingValuesResourceProfiles() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewResourceProfiles()
CopyResourceProfiles(dest, src)
assert.Equal(t, src, dest)
CopyResourceProfiles(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyResourceProfilesSlice(t *testing.T) {
src := []ResourceProfiles{}
dest := []ResourceProfiles{}
// Test CopyTo empty
dest = CopyResourceProfilesSlice(dest, src)
assert.Equal(t, []ResourceProfiles{}, dest)
// Test CopyTo larger slice
src = GenTestResourceProfilesSlice()
dest = CopyResourceProfilesSlice(dest, src)
assert.Equal(t, GenTestResourceProfilesSlice(), dest)
// Test CopyTo same size slice
dest = CopyResourceProfilesSlice(dest, src)
assert.Equal(t, GenTestResourceProfilesSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyResourceProfilesSlice(dest, []ResourceProfiles{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyResourceProfilesSlice(dest, src)
assert.Equal(t, GenTestResourceProfilesSlice(), dest)
}
func TestCopyResourceProfilesPtrSlice(t *testing.T) {
src := []*ResourceProfiles{}
dest := []*ResourceProfiles{}
// Test CopyTo empty
dest = CopyResourceProfilesPtrSlice(dest, src)
assert.Equal(t, []*ResourceProfiles{}, dest)
// Test CopyTo larger slice
src = GenTestResourceProfilesPtrSlice()
dest = CopyResourceProfilesPtrSlice(dest, src)
assert.Equal(t, GenTestResourceProfilesPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyResourceProfilesPtrSlice(dest, src)
assert.Equal(t, GenTestResourceProfilesPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyResourceProfilesPtrSlice(dest, []*ResourceProfiles{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyResourceProfilesPtrSlice(dest, src)
assert.Equal(t, GenTestResourceProfilesPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONResourceProfilesUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewResourceProfiles()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewResourceProfiles(), dest)
}
func TestMarshalAndUnmarshalJSONResourceProfiles(t *testing.T) {
for name, src := range genTestEncodingValuesResourceProfiles() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewResourceProfiles()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteResourceProfiles(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoResourceProfilesFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesResourceProfiles() {
t.Run(name, func(t *testing.T) {
dest := NewResourceProfiles()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoResourceProfilesUnknown(t *testing.T) {
dest := NewResourceProfiles()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewResourceProfiles(), dest)
}
func TestMarshalAndUnmarshalProtoResourceProfiles(t *testing.T) {
for name, src := range genTestEncodingValuesResourceProfiles() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewResourceProfiles()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteResourceProfiles(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufResourceProfiles(t *testing.T) {
for name, src := range genTestEncodingValuesResourceProfiles() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpprofiles.ResourceProfiles{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewResourceProfiles()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesResourceProfiles() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"Resource/wrong_wire_type": {0xc},
"Resource/missing_value": {0xa},
"ScopeProfiles/wrong_wire_type": {0x14},
"ScopeProfiles/missing_value": {0x12},
"SchemaUrl/wrong_wire_type": {0x1c},
"SchemaUrl/missing_value": {0x1a},
}
}
func genTestEncodingValuesResourceProfiles() map[string]*ResourceProfiles {
return map[string]*ResourceProfiles{
"empty": NewResourceProfiles(),
"Resource/test": {Resource: *GenTestResource()},
"ScopeProfiles/test": {ScopeProfiles: []*ScopeProfiles{{}, GenTestScopeProfiles()}},
"SchemaUrl/test": {SchemaUrl: "test_schemaurl"},
}
}
================================================
FILE: pdata/internal/generated_proto_resourcespans.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// ResourceSpans is a collection of spans from a Resource.
type ResourceSpans struct {
Resource Resource
ScopeSpans []*ScopeSpans
SchemaUrl string
DeprecatedScopeSpans []*ScopeSpans
}
var (
protoPoolResourceSpans = sync.Pool{
New: func() any {
return &ResourceSpans{}
},
}
)
func NewResourceSpans() *ResourceSpans {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &ResourceSpans{}
}
return protoPoolResourceSpans.Get().(*ResourceSpans)
}
func DeleteResourceSpans(orig *ResourceSpans, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
DeleteResource(&orig.Resource, false)
for i := range orig.ScopeSpans {
DeleteScopeSpans(orig.ScopeSpans[i], true)
}
for i := range orig.DeprecatedScopeSpans {
DeleteScopeSpans(orig.DeprecatedScopeSpans[i], true)
}
orig.Reset()
if nullable {
protoPoolResourceSpans.Put(orig)
}
}
func CopyResourceSpans(dest, src *ResourceSpans) *ResourceSpans {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewResourceSpans()
}
CopyResource(&dest.Resource, &src.Resource)
dest.ScopeSpans = CopyScopeSpansPtrSlice(dest.ScopeSpans, src.ScopeSpans)
dest.SchemaUrl = src.SchemaUrl
dest.DeprecatedScopeSpans = CopyScopeSpansPtrSlice(dest.DeprecatedScopeSpans, src.DeprecatedScopeSpans)
return dest
}
func CopyResourceSpansSlice(dest, src []ResourceSpans) []ResourceSpans {
var newDest []ResourceSpans
if cap(dest) < len(src) {
newDest = make([]ResourceSpans, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteResourceSpans(&dest[i], false)
}
}
for i := range src {
CopyResourceSpans(&newDest[i], &src[i])
}
return newDest
}
func CopyResourceSpansPtrSlice(dest, src []*ResourceSpans) []*ResourceSpans {
var newDest []*ResourceSpans
if cap(dest) < len(src) {
newDest = make([]*ResourceSpans, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewResourceSpans()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteResourceSpans(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewResourceSpans()
}
}
for i := range src {
CopyResourceSpans(newDest[i], src[i])
}
return newDest
}
func (orig *ResourceSpans) Reset() {
*orig = ResourceSpans{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *ResourceSpans) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
dest.WriteObjectField("resource")
orig.Resource.MarshalJSON(dest)
if len(orig.ScopeSpans) > 0 {
dest.WriteObjectField("scopeSpans")
dest.WriteArrayStart()
orig.ScopeSpans[0].MarshalJSON(dest)
for i := 1; i < len(orig.ScopeSpans); i++ {
dest.WriteMore()
orig.ScopeSpans[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if orig.SchemaUrl != "" {
dest.WriteObjectField("schemaUrl")
dest.WriteString(orig.SchemaUrl)
}
if len(orig.DeprecatedScopeSpans) > 0 {
dest.WriteObjectField("deprecatedScopeSpans")
dest.WriteArrayStart()
orig.DeprecatedScopeSpans[0].MarshalJSON(dest)
for i := 1; i < len(orig.DeprecatedScopeSpans); i++ {
dest.WriteMore()
orig.DeprecatedScopeSpans[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *ResourceSpans) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "resource":
orig.Resource.UnmarshalJSON(iter)
case "scopeSpans", "scope_spans":
for iter.ReadArray() {
orig.ScopeSpans = append(orig.ScopeSpans, NewScopeSpans())
orig.ScopeSpans[len(orig.ScopeSpans)-1].UnmarshalJSON(iter)
}
case "schemaUrl", "schema_url":
orig.SchemaUrl = iter.ReadString()
case "deprecatedScopeSpans", "deprecated_scope_spans":
for iter.ReadArray() {
orig.DeprecatedScopeSpans = append(orig.DeprecatedScopeSpans, NewScopeSpans())
orig.DeprecatedScopeSpans[len(orig.DeprecatedScopeSpans)-1].UnmarshalJSON(iter)
}
default:
iter.Skip()
}
}
}
func (orig *ResourceSpans) SizeProto() int {
var n int
var l int
_ = l
l = orig.Resource.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
for i := range orig.ScopeSpans {
l = orig.ScopeSpans[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
l = len(orig.SchemaUrl)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
for i := range orig.DeprecatedScopeSpans {
l = orig.DeprecatedScopeSpans[i].SizeProto()
n += 2 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *ResourceSpans) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
l = orig.Resource.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
for i := len(orig.ScopeSpans) - 1; i >= 0; i-- {
l = orig.ScopeSpans[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
}
l = len(orig.SchemaUrl)
if l > 0 {
pos -= l
copy(buf[pos:], orig.SchemaUrl)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x1a
}
for i := len(orig.DeprecatedScopeSpans) - 1; i >= 0; i-- {
l = orig.DeprecatedScopeSpans[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x3e
pos--
buf[pos] = 0xc2
}
return len(buf) - pos
}
func (orig *ResourceSpans) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Resource", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.Resource.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field ScopeSpans", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.ScopeSpans = append(orig.ScopeSpans, NewScopeSpans())
err = orig.ScopeSpans[len(orig.ScopeSpans)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 3:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field SchemaUrl", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.SchemaUrl = string(buf[startPos:pos])
case 1000:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field DeprecatedScopeSpans", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.DeprecatedScopeSpans = append(orig.DeprecatedScopeSpans, NewScopeSpans())
err = orig.DeprecatedScopeSpans[len(orig.DeprecatedScopeSpans)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestResourceSpans() *ResourceSpans {
orig := NewResourceSpans()
orig.Resource = *GenTestResource()
orig.ScopeSpans = []*ScopeSpans{{}, GenTestScopeSpans()}
orig.SchemaUrl = "test_schemaurl"
orig.DeprecatedScopeSpans = []*ScopeSpans{{}, GenTestScopeSpans()}
return orig
}
func GenTestResourceSpansPtrSlice() []*ResourceSpans {
orig := make([]*ResourceSpans, 5)
orig[0] = NewResourceSpans()
orig[1] = GenTestResourceSpans()
orig[2] = NewResourceSpans()
orig[3] = GenTestResourceSpans()
orig[4] = NewResourceSpans()
return orig
}
func GenTestResourceSpansSlice() []ResourceSpans {
orig := make([]ResourceSpans, 5)
orig[1] = *GenTestResourceSpans()
orig[3] = *GenTestResourceSpans()
return orig
}
================================================
FILE: pdata/internal/generated_proto_resourcespans_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlptrace "go.opentelemetry.io/proto/slim/otlp/trace/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyResourceSpans(t *testing.T) {
for name, src := range genTestEncodingValuesResourceSpans() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewResourceSpans()
CopyResourceSpans(dest, src)
assert.Equal(t, src, dest)
CopyResourceSpans(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyResourceSpansSlice(t *testing.T) {
src := []ResourceSpans{}
dest := []ResourceSpans{}
// Test CopyTo empty
dest = CopyResourceSpansSlice(dest, src)
assert.Equal(t, []ResourceSpans{}, dest)
// Test CopyTo larger slice
src = GenTestResourceSpansSlice()
dest = CopyResourceSpansSlice(dest, src)
assert.Equal(t, GenTestResourceSpansSlice(), dest)
// Test CopyTo same size slice
dest = CopyResourceSpansSlice(dest, src)
assert.Equal(t, GenTestResourceSpansSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyResourceSpansSlice(dest, []ResourceSpans{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyResourceSpansSlice(dest, src)
assert.Equal(t, GenTestResourceSpansSlice(), dest)
}
func TestCopyResourceSpansPtrSlice(t *testing.T) {
src := []*ResourceSpans{}
dest := []*ResourceSpans{}
// Test CopyTo empty
dest = CopyResourceSpansPtrSlice(dest, src)
assert.Equal(t, []*ResourceSpans{}, dest)
// Test CopyTo larger slice
src = GenTestResourceSpansPtrSlice()
dest = CopyResourceSpansPtrSlice(dest, src)
assert.Equal(t, GenTestResourceSpansPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyResourceSpansPtrSlice(dest, src)
assert.Equal(t, GenTestResourceSpansPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyResourceSpansPtrSlice(dest, []*ResourceSpans{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyResourceSpansPtrSlice(dest, src)
assert.Equal(t, GenTestResourceSpansPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONResourceSpansUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewResourceSpans()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewResourceSpans(), dest)
}
func TestMarshalAndUnmarshalJSONResourceSpans(t *testing.T) {
for name, src := range genTestEncodingValuesResourceSpans() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewResourceSpans()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteResourceSpans(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoResourceSpansFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesResourceSpans() {
t.Run(name, func(t *testing.T) {
dest := NewResourceSpans()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoResourceSpansUnknown(t *testing.T) {
dest := NewResourceSpans()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewResourceSpans(), dest)
}
func TestMarshalAndUnmarshalProtoResourceSpans(t *testing.T) {
for name, src := range genTestEncodingValuesResourceSpans() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewResourceSpans()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteResourceSpans(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufResourceSpans(t *testing.T) {
for name, src := range genTestEncodingValuesResourceSpans() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlptrace.ResourceSpans{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewResourceSpans()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesResourceSpans() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"Resource/wrong_wire_type": {0xc},
"Resource/missing_value": {0xa},
"ScopeSpans/wrong_wire_type": {0x14},
"ScopeSpans/missing_value": {0x12},
"SchemaUrl/wrong_wire_type": {0x1c},
"SchemaUrl/missing_value": {0x1a},
"DeprecatedScopeSpans/wrong_wire_type": {0xc4, 0x3e},
"DeprecatedScopeSpans/missing_value": {0xc2, 0x3e},
}
}
func genTestEncodingValuesResourceSpans() map[string]*ResourceSpans {
return map[string]*ResourceSpans{
"empty": NewResourceSpans(),
"Resource/test": {Resource: *GenTestResource()},
"ScopeSpans/test": {ScopeSpans: []*ScopeSpans{{}, GenTestScopeSpans()}},
"SchemaUrl/test": {SchemaUrl: "test_schemaurl"},
"DeprecatedScopeSpans/test": {DeprecatedScopeSpans: []*ScopeSpans{{}, GenTestScopeSpans()}},
}
}
================================================
FILE: pdata/internal/generated_proto_sample.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"encoding/binary"
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// Sample represents each record value encountered within a profiled program.
type Sample struct {
AttributeIndices []int32
Values []int64
TimestampsUnixNano []uint64
StackIndex int32
LinkIndex int32
}
var (
protoPoolSample = sync.Pool{
New: func() any {
return &Sample{}
},
}
)
func NewSample() *Sample {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &Sample{}
}
return protoPoolSample.Get().(*Sample)
}
func DeleteSample(orig *Sample, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
orig.Reset()
if nullable {
protoPoolSample.Put(orig)
}
}
func CopySample(dest, src *Sample) *Sample {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewSample()
}
dest.StackIndex = src.StackIndex
dest.AttributeIndices = append(dest.AttributeIndices[:0], src.AttributeIndices...)
dest.LinkIndex = src.LinkIndex
dest.Values = append(dest.Values[:0], src.Values...)
dest.TimestampsUnixNano = append(dest.TimestampsUnixNano[:0], src.TimestampsUnixNano...)
return dest
}
func CopySampleSlice(dest, src []Sample) []Sample {
var newDest []Sample
if cap(dest) < len(src) {
newDest = make([]Sample, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteSample(&dest[i], false)
}
}
for i := range src {
CopySample(&newDest[i], &src[i])
}
return newDest
}
func CopySamplePtrSlice(dest, src []*Sample) []*Sample {
var newDest []*Sample
if cap(dest) < len(src) {
newDest = make([]*Sample, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewSample()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteSample(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewSample()
}
}
for i := range src {
CopySample(newDest[i], src[i])
}
return newDest
}
func (orig *Sample) Reset() {
*orig = Sample{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *Sample) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if orig.StackIndex != int32(0) {
dest.WriteObjectField("stackIndex")
dest.WriteInt32(orig.StackIndex)
}
if len(orig.AttributeIndices) > 0 {
dest.WriteObjectField("attributeIndices")
dest.WriteArrayStart()
dest.WriteInt32(orig.AttributeIndices[0])
for i := 1; i < len(orig.AttributeIndices); i++ {
dest.WriteMore()
dest.WriteInt32(orig.AttributeIndices[i])
}
dest.WriteArrayEnd()
}
if orig.LinkIndex != int32(0) {
dest.WriteObjectField("linkIndex")
dest.WriteInt32(orig.LinkIndex)
}
if len(orig.Values) > 0 {
dest.WriteObjectField("values")
dest.WriteArrayStart()
dest.WriteInt64(orig.Values[0])
for i := 1; i < len(orig.Values); i++ {
dest.WriteMore()
dest.WriteInt64(orig.Values[i])
}
dest.WriteArrayEnd()
}
if len(orig.TimestampsUnixNano) > 0 {
dest.WriteObjectField("timestampsUnixNano")
dest.WriteArrayStart()
dest.WriteUint64(orig.TimestampsUnixNano[0])
for i := 1; i < len(orig.TimestampsUnixNano); i++ {
dest.WriteMore()
dest.WriteUint64(orig.TimestampsUnixNano[i])
}
dest.WriteArrayEnd()
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *Sample) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "stackIndex", "stack_index":
orig.StackIndex = iter.ReadInt32()
case "attributeIndices", "attribute_indices":
for iter.ReadArray() {
orig.AttributeIndices = append(orig.AttributeIndices, iter.ReadInt32())
}
case "linkIndex", "link_index":
orig.LinkIndex = iter.ReadInt32()
case "values":
for iter.ReadArray() {
orig.Values = append(orig.Values, iter.ReadInt64())
}
case "timestampsUnixNano", "timestamps_unix_nano":
for iter.ReadArray() {
orig.TimestampsUnixNano = append(orig.TimestampsUnixNano, iter.ReadUint64())
}
default:
iter.Skip()
}
}
}
func (orig *Sample) SizeProto() int {
var n int
var l int
_ = l
if orig.StackIndex != int32(0) {
n += 1 + proto.Sov(uint64(orig.StackIndex))
}
if len(orig.AttributeIndices) > 0 {
l = 0
for _, e := range orig.AttributeIndices {
l += proto.Sov(uint64(e))
}
n += 1 + proto.Sov(uint64(l)) + l
}
if orig.LinkIndex != int32(0) {
n += 1 + proto.Sov(uint64(orig.LinkIndex))
}
if len(orig.Values) > 0 {
l = 0
for _, e := range orig.Values {
l += proto.Sov(uint64(e))
}
n += 1 + proto.Sov(uint64(l)) + l
}
l = len(orig.TimestampsUnixNano)
if l > 0 {
l *= 8
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *Sample) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
if orig.StackIndex != int32(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.StackIndex))
pos--
buf[pos] = 0x8
}
l = len(orig.AttributeIndices)
if l > 0 {
endPos := pos
for i := l - 1; i >= 0; i-- {
pos = proto.EncodeVarint(buf, pos, uint64(orig.AttributeIndices[i]))
}
pos = proto.EncodeVarint(buf, pos, uint64(endPos-pos))
pos--
buf[pos] = 0x12
}
if orig.LinkIndex != int32(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.LinkIndex))
pos--
buf[pos] = 0x18
}
l = len(orig.Values)
if l > 0 {
endPos := pos
for i := l - 1; i >= 0; i-- {
pos = proto.EncodeVarint(buf, pos, uint64(orig.Values[i]))
}
pos = proto.EncodeVarint(buf, pos, uint64(endPos-pos))
pos--
buf[pos] = 0x22
}
l = len(orig.TimestampsUnixNano)
if l > 0 {
for i := l - 1; i >= 0; i-- {
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], uint64(orig.TimestampsUnixNano[i]))
}
pos = proto.EncodeVarint(buf, pos, uint64(l*8))
pos--
buf[pos] = 0x2a
}
return len(buf) - pos
}
func (orig *Sample) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field StackIndex", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.StackIndex = int32(num)
case 2:
switch wireType {
case proto.WireTypeLen:
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
var num uint64
for startPos < pos {
num, startPos, err = proto.ConsumeVarint(buf[:pos], startPos)
if err != nil {
return err
}
orig.AttributeIndices = append(orig.AttributeIndices, int32(num))
}
if startPos != pos {
return fmt.Errorf("proto: invalid field len = %d for field AttributeIndices", pos-startPos)
}
case proto.WireTypeVarint:
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.AttributeIndices = append(orig.AttributeIndices, int32(num))
default:
return fmt.Errorf("proto: wrong wireType = %d for field AttributeIndices", wireType)
}
case 3:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field LinkIndex", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.LinkIndex = int32(num)
case 4:
switch wireType {
case proto.WireTypeLen:
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
var num uint64
for startPos < pos {
num, startPos, err = proto.ConsumeVarint(buf[:pos], startPos)
if err != nil {
return err
}
orig.Values = append(orig.Values, int64(num))
}
if startPos != pos {
return fmt.Errorf("proto: invalid field len = %d for field Values", pos-startPos)
}
case proto.WireTypeVarint:
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.Values = append(orig.Values, int64(num))
default:
return fmt.Errorf("proto: wrong wireType = %d for field Values", wireType)
}
case 5:
switch wireType {
case proto.WireTypeLen:
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
size := length / 8
orig.TimestampsUnixNano = make([]uint64, size)
var num uint64
for i := 0; i < size; i++ {
num, startPos, err = proto.ConsumeI64(buf[:pos], startPos)
if err != nil {
return err
}
orig.TimestampsUnixNano[i] = uint64(num)
}
if startPos != pos {
return fmt.Errorf("proto: invalid field len = %d for field TimestampsUnixNano", pos-startPos)
}
case proto.WireTypeI64:
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
orig.TimestampsUnixNano = append(orig.TimestampsUnixNano, uint64(num))
default:
return fmt.Errorf("proto: wrong wireType = %d for field TimestampsUnixNano", wireType)
}
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestSample() *Sample {
orig := NewSample()
orig.StackIndex = int32(13)
orig.AttributeIndices = []int32{int32(0), int32(13)}
orig.LinkIndex = int32(13)
orig.Values = []int64{int64(0), int64(13)}
orig.TimestampsUnixNano = []uint64{uint64(0), uint64(13)}
return orig
}
func GenTestSamplePtrSlice() []*Sample {
orig := make([]*Sample, 5)
orig[0] = NewSample()
orig[1] = GenTestSample()
orig[2] = NewSample()
orig[3] = GenTestSample()
orig[4] = NewSample()
return orig
}
func GenTestSampleSlice() []Sample {
orig := make([]Sample, 5)
orig[1] = *GenTestSample()
orig[3] = *GenTestSample()
return orig
}
================================================
FILE: pdata/internal/generated_proto_sample_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpprofiles "go.opentelemetry.io/proto/slim/otlp/profiles/v1development"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopySample(t *testing.T) {
for name, src := range genTestEncodingValuesSample() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewSample()
CopySample(dest, src)
assert.Equal(t, src, dest)
CopySample(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopySampleSlice(t *testing.T) {
src := []Sample{}
dest := []Sample{}
// Test CopyTo empty
dest = CopySampleSlice(dest, src)
assert.Equal(t, []Sample{}, dest)
// Test CopyTo larger slice
src = GenTestSampleSlice()
dest = CopySampleSlice(dest, src)
assert.Equal(t, GenTestSampleSlice(), dest)
// Test CopyTo same size slice
dest = CopySampleSlice(dest, src)
assert.Equal(t, GenTestSampleSlice(), dest)
// Test CopyTo smaller size slice
dest = CopySampleSlice(dest, []Sample{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopySampleSlice(dest, src)
assert.Equal(t, GenTestSampleSlice(), dest)
}
func TestCopySamplePtrSlice(t *testing.T) {
src := []*Sample{}
dest := []*Sample{}
// Test CopyTo empty
dest = CopySamplePtrSlice(dest, src)
assert.Equal(t, []*Sample{}, dest)
// Test CopyTo larger slice
src = GenTestSamplePtrSlice()
dest = CopySamplePtrSlice(dest, src)
assert.Equal(t, GenTestSamplePtrSlice(), dest)
// Test CopyTo same size slice
dest = CopySamplePtrSlice(dest, src)
assert.Equal(t, GenTestSamplePtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopySamplePtrSlice(dest, []*Sample{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopySamplePtrSlice(dest, src)
assert.Equal(t, GenTestSamplePtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONSampleUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewSample()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewSample(), dest)
}
func TestMarshalAndUnmarshalJSONSample(t *testing.T) {
for name, src := range genTestEncodingValuesSample() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewSample()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteSample(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoSampleFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesSample() {
t.Run(name, func(t *testing.T) {
dest := NewSample()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoSampleUnknown(t *testing.T) {
dest := NewSample()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewSample(), dest)
}
func TestMarshalAndUnmarshalProtoSample(t *testing.T) {
for name, src := range genTestEncodingValuesSample() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewSample()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteSample(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufSample(t *testing.T) {
for name, src := range genTestEncodingValuesSample() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpprofiles.Sample{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewSample()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesSample() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"StackIndex/wrong_wire_type": {0xc},
"StackIndex/missing_value": {0x8},
"AttributeIndices/wrong_wire_type": {0x14},
"AttributeIndices/missing_value": {0x12},
"LinkIndex/wrong_wire_type": {0x1c},
"LinkIndex/missing_value": {0x18},
"Values/wrong_wire_type": {0x24},
"Values/missing_value": {0x22},
"TimestampsUnixNano/wrong_wire_type": {0x2c},
"TimestampsUnixNano/missing_value": {0x2a},
}
}
func genTestEncodingValuesSample() map[string]*Sample {
return map[string]*Sample{
"empty": NewSample(),
"StackIndex/test": {StackIndex: int32(13)},
"AttributeIndices/test": {AttributeIndices: []int32{int32(0), int32(13)}},
"LinkIndex/test": {LinkIndex: int32(13)},
"Values/test": {Values: []int64{int64(0), int64(13)}},
"TimestampsUnixNano/test": {TimestampsUnixNano: []uint64{uint64(0), uint64(13)}},
}
}
================================================
FILE: pdata/internal/generated_proto_scopelogs.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// ScopeLogs is a collection of logs from a LibraryInstrumentation.
type ScopeLogs struct {
SchemaUrl string
LogRecords []*LogRecord
Scope InstrumentationScope
}
var (
protoPoolScopeLogs = sync.Pool{
New: func() any {
return &ScopeLogs{}
},
}
)
func NewScopeLogs() *ScopeLogs {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &ScopeLogs{}
}
return protoPoolScopeLogs.Get().(*ScopeLogs)
}
func DeleteScopeLogs(orig *ScopeLogs, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
DeleteInstrumentationScope(&orig.Scope, false)
for i := range orig.LogRecords {
DeleteLogRecord(orig.LogRecords[i], true)
}
orig.Reset()
if nullable {
protoPoolScopeLogs.Put(orig)
}
}
func CopyScopeLogs(dest, src *ScopeLogs) *ScopeLogs {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewScopeLogs()
}
CopyInstrumentationScope(&dest.Scope, &src.Scope)
dest.LogRecords = CopyLogRecordPtrSlice(dest.LogRecords, src.LogRecords)
dest.SchemaUrl = src.SchemaUrl
return dest
}
func CopyScopeLogsSlice(dest, src []ScopeLogs) []ScopeLogs {
var newDest []ScopeLogs
if cap(dest) < len(src) {
newDest = make([]ScopeLogs, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteScopeLogs(&dest[i], false)
}
}
for i := range src {
CopyScopeLogs(&newDest[i], &src[i])
}
return newDest
}
func CopyScopeLogsPtrSlice(dest, src []*ScopeLogs) []*ScopeLogs {
var newDest []*ScopeLogs
if cap(dest) < len(src) {
newDest = make([]*ScopeLogs, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewScopeLogs()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteScopeLogs(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewScopeLogs()
}
}
for i := range src {
CopyScopeLogs(newDest[i], src[i])
}
return newDest
}
func (orig *ScopeLogs) Reset() {
*orig = ScopeLogs{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *ScopeLogs) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
dest.WriteObjectField("scope")
orig.Scope.MarshalJSON(dest)
if len(orig.LogRecords) > 0 {
dest.WriteObjectField("logRecords")
dest.WriteArrayStart()
orig.LogRecords[0].MarshalJSON(dest)
for i := 1; i < len(orig.LogRecords); i++ {
dest.WriteMore()
orig.LogRecords[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if orig.SchemaUrl != "" {
dest.WriteObjectField("schemaUrl")
dest.WriteString(orig.SchemaUrl)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *ScopeLogs) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "scope":
orig.Scope.UnmarshalJSON(iter)
case "logRecords", "log_records":
for iter.ReadArray() {
orig.LogRecords = append(orig.LogRecords, NewLogRecord())
orig.LogRecords[len(orig.LogRecords)-1].UnmarshalJSON(iter)
}
case "schemaUrl", "schema_url":
orig.SchemaUrl = iter.ReadString()
default:
iter.Skip()
}
}
}
func (orig *ScopeLogs) SizeProto() int {
var n int
var l int
_ = l
l = orig.Scope.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
for i := range orig.LogRecords {
l = orig.LogRecords[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
l = len(orig.SchemaUrl)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *ScopeLogs) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
l = orig.Scope.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
for i := len(orig.LogRecords) - 1; i >= 0; i-- {
l = orig.LogRecords[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
}
l = len(orig.SchemaUrl)
if l > 0 {
pos -= l
copy(buf[pos:], orig.SchemaUrl)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x1a
}
return len(buf) - pos
}
func (orig *ScopeLogs) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Scope", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.Scope.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field LogRecords", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.LogRecords = append(orig.LogRecords, NewLogRecord())
err = orig.LogRecords[len(orig.LogRecords)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 3:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field SchemaUrl", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.SchemaUrl = string(buf[startPos:pos])
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestScopeLogs() *ScopeLogs {
orig := NewScopeLogs()
orig.Scope = *GenTestInstrumentationScope()
orig.LogRecords = []*LogRecord{{}, GenTestLogRecord()}
orig.SchemaUrl = "test_schemaurl"
return orig
}
func GenTestScopeLogsPtrSlice() []*ScopeLogs {
orig := make([]*ScopeLogs, 5)
orig[0] = NewScopeLogs()
orig[1] = GenTestScopeLogs()
orig[2] = NewScopeLogs()
orig[3] = GenTestScopeLogs()
orig[4] = NewScopeLogs()
return orig
}
func GenTestScopeLogsSlice() []ScopeLogs {
orig := make([]ScopeLogs, 5)
orig[1] = *GenTestScopeLogs()
orig[3] = *GenTestScopeLogs()
return orig
}
================================================
FILE: pdata/internal/generated_proto_scopelogs_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlplogs "go.opentelemetry.io/proto/slim/otlp/logs/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyScopeLogs(t *testing.T) {
for name, src := range genTestEncodingValuesScopeLogs() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewScopeLogs()
CopyScopeLogs(dest, src)
assert.Equal(t, src, dest)
CopyScopeLogs(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyScopeLogsSlice(t *testing.T) {
src := []ScopeLogs{}
dest := []ScopeLogs{}
// Test CopyTo empty
dest = CopyScopeLogsSlice(dest, src)
assert.Equal(t, []ScopeLogs{}, dest)
// Test CopyTo larger slice
src = GenTestScopeLogsSlice()
dest = CopyScopeLogsSlice(dest, src)
assert.Equal(t, GenTestScopeLogsSlice(), dest)
// Test CopyTo same size slice
dest = CopyScopeLogsSlice(dest, src)
assert.Equal(t, GenTestScopeLogsSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyScopeLogsSlice(dest, []ScopeLogs{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyScopeLogsSlice(dest, src)
assert.Equal(t, GenTestScopeLogsSlice(), dest)
}
func TestCopyScopeLogsPtrSlice(t *testing.T) {
src := []*ScopeLogs{}
dest := []*ScopeLogs{}
// Test CopyTo empty
dest = CopyScopeLogsPtrSlice(dest, src)
assert.Equal(t, []*ScopeLogs{}, dest)
// Test CopyTo larger slice
src = GenTestScopeLogsPtrSlice()
dest = CopyScopeLogsPtrSlice(dest, src)
assert.Equal(t, GenTestScopeLogsPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyScopeLogsPtrSlice(dest, src)
assert.Equal(t, GenTestScopeLogsPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyScopeLogsPtrSlice(dest, []*ScopeLogs{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyScopeLogsPtrSlice(dest, src)
assert.Equal(t, GenTestScopeLogsPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONScopeLogsUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewScopeLogs()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewScopeLogs(), dest)
}
func TestMarshalAndUnmarshalJSONScopeLogs(t *testing.T) {
for name, src := range genTestEncodingValuesScopeLogs() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewScopeLogs()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteScopeLogs(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoScopeLogsFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesScopeLogs() {
t.Run(name, func(t *testing.T) {
dest := NewScopeLogs()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoScopeLogsUnknown(t *testing.T) {
dest := NewScopeLogs()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewScopeLogs(), dest)
}
func TestMarshalAndUnmarshalProtoScopeLogs(t *testing.T) {
for name, src := range genTestEncodingValuesScopeLogs() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewScopeLogs()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteScopeLogs(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufScopeLogs(t *testing.T) {
for name, src := range genTestEncodingValuesScopeLogs() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlplogs.ScopeLogs{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewScopeLogs()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesScopeLogs() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"Scope/wrong_wire_type": {0xc},
"Scope/missing_value": {0xa},
"LogRecords/wrong_wire_type": {0x14},
"LogRecords/missing_value": {0x12},
"SchemaUrl/wrong_wire_type": {0x1c},
"SchemaUrl/missing_value": {0x1a},
}
}
func genTestEncodingValuesScopeLogs() map[string]*ScopeLogs {
return map[string]*ScopeLogs{
"empty": NewScopeLogs(),
"Scope/test": {Scope: *GenTestInstrumentationScope()},
"LogRecords/test": {LogRecords: []*LogRecord{{}, GenTestLogRecord()}},
"SchemaUrl/test": {SchemaUrl: "test_schemaurl"},
}
}
================================================
FILE: pdata/internal/generated_proto_scopemetrics.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// ScopeMetrics is a collection of metrics from a LibraryInstrumentation.
type ScopeMetrics struct {
SchemaUrl string
Metrics []*Metric
Scope InstrumentationScope
}
var (
protoPoolScopeMetrics = sync.Pool{
New: func() any {
return &ScopeMetrics{}
},
}
)
func NewScopeMetrics() *ScopeMetrics {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &ScopeMetrics{}
}
return protoPoolScopeMetrics.Get().(*ScopeMetrics)
}
func DeleteScopeMetrics(orig *ScopeMetrics, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
DeleteInstrumentationScope(&orig.Scope, false)
for i := range orig.Metrics {
DeleteMetric(orig.Metrics[i], true)
}
orig.Reset()
if nullable {
protoPoolScopeMetrics.Put(orig)
}
}
func CopyScopeMetrics(dest, src *ScopeMetrics) *ScopeMetrics {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewScopeMetrics()
}
CopyInstrumentationScope(&dest.Scope, &src.Scope)
dest.Metrics = CopyMetricPtrSlice(dest.Metrics, src.Metrics)
dest.SchemaUrl = src.SchemaUrl
return dest
}
func CopyScopeMetricsSlice(dest, src []ScopeMetrics) []ScopeMetrics {
var newDest []ScopeMetrics
if cap(dest) < len(src) {
newDest = make([]ScopeMetrics, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteScopeMetrics(&dest[i], false)
}
}
for i := range src {
CopyScopeMetrics(&newDest[i], &src[i])
}
return newDest
}
func CopyScopeMetricsPtrSlice(dest, src []*ScopeMetrics) []*ScopeMetrics {
var newDest []*ScopeMetrics
if cap(dest) < len(src) {
newDest = make([]*ScopeMetrics, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewScopeMetrics()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteScopeMetrics(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewScopeMetrics()
}
}
for i := range src {
CopyScopeMetrics(newDest[i], src[i])
}
return newDest
}
func (orig *ScopeMetrics) Reset() {
*orig = ScopeMetrics{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *ScopeMetrics) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
dest.WriteObjectField("scope")
orig.Scope.MarshalJSON(dest)
if len(orig.Metrics) > 0 {
dest.WriteObjectField("metrics")
dest.WriteArrayStart()
orig.Metrics[0].MarshalJSON(dest)
for i := 1; i < len(orig.Metrics); i++ {
dest.WriteMore()
orig.Metrics[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if orig.SchemaUrl != "" {
dest.WriteObjectField("schemaUrl")
dest.WriteString(orig.SchemaUrl)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *ScopeMetrics) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "scope":
orig.Scope.UnmarshalJSON(iter)
case "metrics":
for iter.ReadArray() {
orig.Metrics = append(orig.Metrics, NewMetric())
orig.Metrics[len(orig.Metrics)-1].UnmarshalJSON(iter)
}
case "schemaUrl", "schema_url":
orig.SchemaUrl = iter.ReadString()
default:
iter.Skip()
}
}
}
func (orig *ScopeMetrics) SizeProto() int {
var n int
var l int
_ = l
l = orig.Scope.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
for i := range orig.Metrics {
l = orig.Metrics[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
l = len(orig.SchemaUrl)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *ScopeMetrics) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
l = orig.Scope.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
for i := len(orig.Metrics) - 1; i >= 0; i-- {
l = orig.Metrics[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
}
l = len(orig.SchemaUrl)
if l > 0 {
pos -= l
copy(buf[pos:], orig.SchemaUrl)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x1a
}
return len(buf) - pos
}
func (orig *ScopeMetrics) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Scope", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.Scope.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Metrics", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Metrics = append(orig.Metrics, NewMetric())
err = orig.Metrics[len(orig.Metrics)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 3:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field SchemaUrl", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.SchemaUrl = string(buf[startPos:pos])
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestScopeMetrics() *ScopeMetrics {
orig := NewScopeMetrics()
orig.Scope = *GenTestInstrumentationScope()
orig.Metrics = []*Metric{{}, GenTestMetric()}
orig.SchemaUrl = "test_schemaurl"
return orig
}
func GenTestScopeMetricsPtrSlice() []*ScopeMetrics {
orig := make([]*ScopeMetrics, 5)
orig[0] = NewScopeMetrics()
orig[1] = GenTestScopeMetrics()
orig[2] = NewScopeMetrics()
orig[3] = GenTestScopeMetrics()
orig[4] = NewScopeMetrics()
return orig
}
func GenTestScopeMetricsSlice() []ScopeMetrics {
orig := make([]ScopeMetrics, 5)
orig[1] = *GenTestScopeMetrics()
orig[3] = *GenTestScopeMetrics()
return orig
}
================================================
FILE: pdata/internal/generated_proto_scopemetrics_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpmetrics "go.opentelemetry.io/proto/slim/otlp/metrics/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyScopeMetrics(t *testing.T) {
for name, src := range genTestEncodingValuesScopeMetrics() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewScopeMetrics()
CopyScopeMetrics(dest, src)
assert.Equal(t, src, dest)
CopyScopeMetrics(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyScopeMetricsSlice(t *testing.T) {
src := []ScopeMetrics{}
dest := []ScopeMetrics{}
// Test CopyTo empty
dest = CopyScopeMetricsSlice(dest, src)
assert.Equal(t, []ScopeMetrics{}, dest)
// Test CopyTo larger slice
src = GenTestScopeMetricsSlice()
dest = CopyScopeMetricsSlice(dest, src)
assert.Equal(t, GenTestScopeMetricsSlice(), dest)
// Test CopyTo same size slice
dest = CopyScopeMetricsSlice(dest, src)
assert.Equal(t, GenTestScopeMetricsSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyScopeMetricsSlice(dest, []ScopeMetrics{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyScopeMetricsSlice(dest, src)
assert.Equal(t, GenTestScopeMetricsSlice(), dest)
}
func TestCopyScopeMetricsPtrSlice(t *testing.T) {
src := []*ScopeMetrics{}
dest := []*ScopeMetrics{}
// Test CopyTo empty
dest = CopyScopeMetricsPtrSlice(dest, src)
assert.Equal(t, []*ScopeMetrics{}, dest)
// Test CopyTo larger slice
src = GenTestScopeMetricsPtrSlice()
dest = CopyScopeMetricsPtrSlice(dest, src)
assert.Equal(t, GenTestScopeMetricsPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyScopeMetricsPtrSlice(dest, src)
assert.Equal(t, GenTestScopeMetricsPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyScopeMetricsPtrSlice(dest, []*ScopeMetrics{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyScopeMetricsPtrSlice(dest, src)
assert.Equal(t, GenTestScopeMetricsPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONScopeMetricsUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewScopeMetrics()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewScopeMetrics(), dest)
}
func TestMarshalAndUnmarshalJSONScopeMetrics(t *testing.T) {
for name, src := range genTestEncodingValuesScopeMetrics() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewScopeMetrics()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteScopeMetrics(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoScopeMetricsFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesScopeMetrics() {
t.Run(name, func(t *testing.T) {
dest := NewScopeMetrics()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoScopeMetricsUnknown(t *testing.T) {
dest := NewScopeMetrics()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewScopeMetrics(), dest)
}
func TestMarshalAndUnmarshalProtoScopeMetrics(t *testing.T) {
for name, src := range genTestEncodingValuesScopeMetrics() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewScopeMetrics()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteScopeMetrics(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufScopeMetrics(t *testing.T) {
for name, src := range genTestEncodingValuesScopeMetrics() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpmetrics.ScopeMetrics{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewScopeMetrics()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesScopeMetrics() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"Scope/wrong_wire_type": {0xc},
"Scope/missing_value": {0xa},
"Metrics/wrong_wire_type": {0x14},
"Metrics/missing_value": {0x12},
"SchemaUrl/wrong_wire_type": {0x1c},
"SchemaUrl/missing_value": {0x1a},
}
}
func genTestEncodingValuesScopeMetrics() map[string]*ScopeMetrics {
return map[string]*ScopeMetrics{
"empty": NewScopeMetrics(),
"Scope/test": {Scope: *GenTestInstrumentationScope()},
"Metrics/test": {Metrics: []*Metric{{}, GenTestMetric()}},
"SchemaUrl/test": {SchemaUrl: "test_schemaurl"},
}
}
================================================
FILE: pdata/internal/generated_proto_scopeprofiles.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// ScopeProfiles is a collection of profiles from a LibraryInstrumentation.
type ScopeProfiles struct {
SchemaUrl string
Profiles []*Profile
Scope InstrumentationScope
}
var (
protoPoolScopeProfiles = sync.Pool{
New: func() any {
return &ScopeProfiles{}
},
}
)
func NewScopeProfiles() *ScopeProfiles {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &ScopeProfiles{}
}
return protoPoolScopeProfiles.Get().(*ScopeProfiles)
}
func DeleteScopeProfiles(orig *ScopeProfiles, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
DeleteInstrumentationScope(&orig.Scope, false)
for i := range orig.Profiles {
DeleteProfile(orig.Profiles[i], true)
}
orig.Reset()
if nullable {
protoPoolScopeProfiles.Put(orig)
}
}
func CopyScopeProfiles(dest, src *ScopeProfiles) *ScopeProfiles {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewScopeProfiles()
}
CopyInstrumentationScope(&dest.Scope, &src.Scope)
dest.Profiles = CopyProfilePtrSlice(dest.Profiles, src.Profiles)
dest.SchemaUrl = src.SchemaUrl
return dest
}
func CopyScopeProfilesSlice(dest, src []ScopeProfiles) []ScopeProfiles {
var newDest []ScopeProfiles
if cap(dest) < len(src) {
newDest = make([]ScopeProfiles, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteScopeProfiles(&dest[i], false)
}
}
for i := range src {
CopyScopeProfiles(&newDest[i], &src[i])
}
return newDest
}
func CopyScopeProfilesPtrSlice(dest, src []*ScopeProfiles) []*ScopeProfiles {
var newDest []*ScopeProfiles
if cap(dest) < len(src) {
newDest = make([]*ScopeProfiles, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewScopeProfiles()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteScopeProfiles(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewScopeProfiles()
}
}
for i := range src {
CopyScopeProfiles(newDest[i], src[i])
}
return newDest
}
func (orig *ScopeProfiles) Reset() {
*orig = ScopeProfiles{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *ScopeProfiles) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
dest.WriteObjectField("scope")
orig.Scope.MarshalJSON(dest)
if len(orig.Profiles) > 0 {
dest.WriteObjectField("profiles")
dest.WriteArrayStart()
orig.Profiles[0].MarshalJSON(dest)
for i := 1; i < len(orig.Profiles); i++ {
dest.WriteMore()
orig.Profiles[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if orig.SchemaUrl != "" {
dest.WriteObjectField("schemaUrl")
dest.WriteString(orig.SchemaUrl)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *ScopeProfiles) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "scope":
orig.Scope.UnmarshalJSON(iter)
case "profiles":
for iter.ReadArray() {
orig.Profiles = append(orig.Profiles, NewProfile())
orig.Profiles[len(orig.Profiles)-1].UnmarshalJSON(iter)
}
case "schemaUrl", "schema_url":
orig.SchemaUrl = iter.ReadString()
default:
iter.Skip()
}
}
}
func (orig *ScopeProfiles) SizeProto() int {
var n int
var l int
_ = l
l = orig.Scope.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
for i := range orig.Profiles {
l = orig.Profiles[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
l = len(orig.SchemaUrl)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *ScopeProfiles) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
l = orig.Scope.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
for i := len(orig.Profiles) - 1; i >= 0; i-- {
l = orig.Profiles[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
}
l = len(orig.SchemaUrl)
if l > 0 {
pos -= l
copy(buf[pos:], orig.SchemaUrl)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x1a
}
return len(buf) - pos
}
func (orig *ScopeProfiles) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Scope", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.Scope.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Profiles", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Profiles = append(orig.Profiles, NewProfile())
err = orig.Profiles[len(orig.Profiles)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 3:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field SchemaUrl", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.SchemaUrl = string(buf[startPos:pos])
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestScopeProfiles() *ScopeProfiles {
orig := NewScopeProfiles()
orig.Scope = *GenTestInstrumentationScope()
orig.Profiles = []*Profile{{}, GenTestProfile()}
orig.SchemaUrl = "test_schemaurl"
return orig
}
func GenTestScopeProfilesPtrSlice() []*ScopeProfiles {
orig := make([]*ScopeProfiles, 5)
orig[0] = NewScopeProfiles()
orig[1] = GenTestScopeProfiles()
orig[2] = NewScopeProfiles()
orig[3] = GenTestScopeProfiles()
orig[4] = NewScopeProfiles()
return orig
}
func GenTestScopeProfilesSlice() []ScopeProfiles {
orig := make([]ScopeProfiles, 5)
orig[1] = *GenTestScopeProfiles()
orig[3] = *GenTestScopeProfiles()
return orig
}
================================================
FILE: pdata/internal/generated_proto_scopeprofiles_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpprofiles "go.opentelemetry.io/proto/slim/otlp/profiles/v1development"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyScopeProfiles(t *testing.T) {
for name, src := range genTestEncodingValuesScopeProfiles() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewScopeProfiles()
CopyScopeProfiles(dest, src)
assert.Equal(t, src, dest)
CopyScopeProfiles(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyScopeProfilesSlice(t *testing.T) {
src := []ScopeProfiles{}
dest := []ScopeProfiles{}
// Test CopyTo empty
dest = CopyScopeProfilesSlice(dest, src)
assert.Equal(t, []ScopeProfiles{}, dest)
// Test CopyTo larger slice
src = GenTestScopeProfilesSlice()
dest = CopyScopeProfilesSlice(dest, src)
assert.Equal(t, GenTestScopeProfilesSlice(), dest)
// Test CopyTo same size slice
dest = CopyScopeProfilesSlice(dest, src)
assert.Equal(t, GenTestScopeProfilesSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyScopeProfilesSlice(dest, []ScopeProfiles{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyScopeProfilesSlice(dest, src)
assert.Equal(t, GenTestScopeProfilesSlice(), dest)
}
func TestCopyScopeProfilesPtrSlice(t *testing.T) {
src := []*ScopeProfiles{}
dest := []*ScopeProfiles{}
// Test CopyTo empty
dest = CopyScopeProfilesPtrSlice(dest, src)
assert.Equal(t, []*ScopeProfiles{}, dest)
// Test CopyTo larger slice
src = GenTestScopeProfilesPtrSlice()
dest = CopyScopeProfilesPtrSlice(dest, src)
assert.Equal(t, GenTestScopeProfilesPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyScopeProfilesPtrSlice(dest, src)
assert.Equal(t, GenTestScopeProfilesPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyScopeProfilesPtrSlice(dest, []*ScopeProfiles{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyScopeProfilesPtrSlice(dest, src)
assert.Equal(t, GenTestScopeProfilesPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONScopeProfilesUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewScopeProfiles()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewScopeProfiles(), dest)
}
func TestMarshalAndUnmarshalJSONScopeProfiles(t *testing.T) {
for name, src := range genTestEncodingValuesScopeProfiles() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewScopeProfiles()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteScopeProfiles(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoScopeProfilesFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesScopeProfiles() {
t.Run(name, func(t *testing.T) {
dest := NewScopeProfiles()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoScopeProfilesUnknown(t *testing.T) {
dest := NewScopeProfiles()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewScopeProfiles(), dest)
}
func TestMarshalAndUnmarshalProtoScopeProfiles(t *testing.T) {
for name, src := range genTestEncodingValuesScopeProfiles() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewScopeProfiles()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteScopeProfiles(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufScopeProfiles(t *testing.T) {
for name, src := range genTestEncodingValuesScopeProfiles() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpprofiles.ScopeProfiles{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewScopeProfiles()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesScopeProfiles() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"Scope/wrong_wire_type": {0xc},
"Scope/missing_value": {0xa},
"Profiles/wrong_wire_type": {0x14},
"Profiles/missing_value": {0x12},
"SchemaUrl/wrong_wire_type": {0x1c},
"SchemaUrl/missing_value": {0x1a},
}
}
func genTestEncodingValuesScopeProfiles() map[string]*ScopeProfiles {
return map[string]*ScopeProfiles{
"empty": NewScopeProfiles(),
"Scope/test": {Scope: *GenTestInstrumentationScope()},
"Profiles/test": {Profiles: []*Profile{{}, GenTestProfile()}},
"SchemaUrl/test": {SchemaUrl: "test_schemaurl"},
}
}
================================================
FILE: pdata/internal/generated_proto_scopespans.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// ScopeSpans is a collection of spans from a LibraryInstrumentation.
type ScopeSpans struct {
SchemaUrl string
Spans []*Span
Scope InstrumentationScope
}
var (
protoPoolScopeSpans = sync.Pool{
New: func() any {
return &ScopeSpans{}
},
}
)
func NewScopeSpans() *ScopeSpans {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &ScopeSpans{}
}
return protoPoolScopeSpans.Get().(*ScopeSpans)
}
func DeleteScopeSpans(orig *ScopeSpans, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
DeleteInstrumentationScope(&orig.Scope, false)
for i := range orig.Spans {
DeleteSpan(orig.Spans[i], true)
}
orig.Reset()
if nullable {
protoPoolScopeSpans.Put(orig)
}
}
func CopyScopeSpans(dest, src *ScopeSpans) *ScopeSpans {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewScopeSpans()
}
CopyInstrumentationScope(&dest.Scope, &src.Scope)
dest.Spans = CopySpanPtrSlice(dest.Spans, src.Spans)
dest.SchemaUrl = src.SchemaUrl
return dest
}
func CopyScopeSpansSlice(dest, src []ScopeSpans) []ScopeSpans {
var newDest []ScopeSpans
if cap(dest) < len(src) {
newDest = make([]ScopeSpans, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteScopeSpans(&dest[i], false)
}
}
for i := range src {
CopyScopeSpans(&newDest[i], &src[i])
}
return newDest
}
func CopyScopeSpansPtrSlice(dest, src []*ScopeSpans) []*ScopeSpans {
var newDest []*ScopeSpans
if cap(dest) < len(src) {
newDest = make([]*ScopeSpans, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewScopeSpans()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteScopeSpans(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewScopeSpans()
}
}
for i := range src {
CopyScopeSpans(newDest[i], src[i])
}
return newDest
}
func (orig *ScopeSpans) Reset() {
*orig = ScopeSpans{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *ScopeSpans) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
dest.WriteObjectField("scope")
orig.Scope.MarshalJSON(dest)
if len(orig.Spans) > 0 {
dest.WriteObjectField("spans")
dest.WriteArrayStart()
orig.Spans[0].MarshalJSON(dest)
for i := 1; i < len(orig.Spans); i++ {
dest.WriteMore()
orig.Spans[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if orig.SchemaUrl != "" {
dest.WriteObjectField("schemaUrl")
dest.WriteString(orig.SchemaUrl)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *ScopeSpans) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "scope":
orig.Scope.UnmarshalJSON(iter)
case "spans":
for iter.ReadArray() {
orig.Spans = append(orig.Spans, NewSpan())
orig.Spans[len(orig.Spans)-1].UnmarshalJSON(iter)
}
case "schemaUrl", "schema_url":
orig.SchemaUrl = iter.ReadString()
default:
iter.Skip()
}
}
}
func (orig *ScopeSpans) SizeProto() int {
var n int
var l int
_ = l
l = orig.Scope.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
for i := range orig.Spans {
l = orig.Spans[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
l = len(orig.SchemaUrl)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *ScopeSpans) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
l = orig.Scope.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
for i := len(orig.Spans) - 1; i >= 0; i-- {
l = orig.Spans[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
}
l = len(orig.SchemaUrl)
if l > 0 {
pos -= l
copy(buf[pos:], orig.SchemaUrl)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x1a
}
return len(buf) - pos
}
func (orig *ScopeSpans) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Scope", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.Scope.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Spans", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Spans = append(orig.Spans, NewSpan())
err = orig.Spans[len(orig.Spans)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 3:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field SchemaUrl", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.SchemaUrl = string(buf[startPos:pos])
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestScopeSpans() *ScopeSpans {
orig := NewScopeSpans()
orig.Scope = *GenTestInstrumentationScope()
orig.Spans = []*Span{{}, GenTestSpan()}
orig.SchemaUrl = "test_schemaurl"
return orig
}
func GenTestScopeSpansPtrSlice() []*ScopeSpans {
orig := make([]*ScopeSpans, 5)
orig[0] = NewScopeSpans()
orig[1] = GenTestScopeSpans()
orig[2] = NewScopeSpans()
orig[3] = GenTestScopeSpans()
orig[4] = NewScopeSpans()
return orig
}
func GenTestScopeSpansSlice() []ScopeSpans {
orig := make([]ScopeSpans, 5)
orig[1] = *GenTestScopeSpans()
orig[3] = *GenTestScopeSpans()
return orig
}
================================================
FILE: pdata/internal/generated_proto_scopespans_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlptrace "go.opentelemetry.io/proto/slim/otlp/trace/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyScopeSpans(t *testing.T) {
for name, src := range genTestEncodingValuesScopeSpans() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewScopeSpans()
CopyScopeSpans(dest, src)
assert.Equal(t, src, dest)
CopyScopeSpans(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyScopeSpansSlice(t *testing.T) {
src := []ScopeSpans{}
dest := []ScopeSpans{}
// Test CopyTo empty
dest = CopyScopeSpansSlice(dest, src)
assert.Equal(t, []ScopeSpans{}, dest)
// Test CopyTo larger slice
src = GenTestScopeSpansSlice()
dest = CopyScopeSpansSlice(dest, src)
assert.Equal(t, GenTestScopeSpansSlice(), dest)
// Test CopyTo same size slice
dest = CopyScopeSpansSlice(dest, src)
assert.Equal(t, GenTestScopeSpansSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyScopeSpansSlice(dest, []ScopeSpans{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyScopeSpansSlice(dest, src)
assert.Equal(t, GenTestScopeSpansSlice(), dest)
}
func TestCopyScopeSpansPtrSlice(t *testing.T) {
src := []*ScopeSpans{}
dest := []*ScopeSpans{}
// Test CopyTo empty
dest = CopyScopeSpansPtrSlice(dest, src)
assert.Equal(t, []*ScopeSpans{}, dest)
// Test CopyTo larger slice
src = GenTestScopeSpansPtrSlice()
dest = CopyScopeSpansPtrSlice(dest, src)
assert.Equal(t, GenTestScopeSpansPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyScopeSpansPtrSlice(dest, src)
assert.Equal(t, GenTestScopeSpansPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyScopeSpansPtrSlice(dest, []*ScopeSpans{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyScopeSpansPtrSlice(dest, src)
assert.Equal(t, GenTestScopeSpansPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONScopeSpansUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewScopeSpans()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewScopeSpans(), dest)
}
func TestMarshalAndUnmarshalJSONScopeSpans(t *testing.T) {
for name, src := range genTestEncodingValuesScopeSpans() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewScopeSpans()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteScopeSpans(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoScopeSpansFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesScopeSpans() {
t.Run(name, func(t *testing.T) {
dest := NewScopeSpans()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoScopeSpansUnknown(t *testing.T) {
dest := NewScopeSpans()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewScopeSpans(), dest)
}
func TestMarshalAndUnmarshalProtoScopeSpans(t *testing.T) {
for name, src := range genTestEncodingValuesScopeSpans() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewScopeSpans()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteScopeSpans(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufScopeSpans(t *testing.T) {
for name, src := range genTestEncodingValuesScopeSpans() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlptrace.ScopeSpans{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewScopeSpans()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesScopeSpans() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"Scope/wrong_wire_type": {0xc},
"Scope/missing_value": {0xa},
"Spans/wrong_wire_type": {0x14},
"Spans/missing_value": {0x12},
"SchemaUrl/wrong_wire_type": {0x1c},
"SchemaUrl/missing_value": {0x1a},
}
}
func genTestEncodingValuesScopeSpans() map[string]*ScopeSpans {
return map[string]*ScopeSpans{
"empty": NewScopeSpans(),
"Scope/test": {Scope: *GenTestInstrumentationScope()},
"Spans/test": {Spans: []*Span{{}, GenTestSpan()}},
"SchemaUrl/test": {SchemaUrl: "test_schemaurl"},
}
}
================================================
FILE: pdata/internal/generated_proto_span.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"encoding/binary"
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// Span represents a single operation within a trace.
// See Span definition in OTLP: https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/trace/v1/trace.proto
type Span struct {
TraceState string
Name string
Attributes []KeyValue
Events []*SpanEvent
Links []*SpanLink
Status Status
StartTimeUnixNano uint64
EndTimeUnixNano uint64
Flags uint32
Kind SpanKind
DroppedAttributesCount uint32
DroppedEventsCount uint32
DroppedLinksCount uint32
TraceId TraceID
SpanId SpanID
ParentSpanId SpanID
}
var (
protoPoolSpan = sync.Pool{
New: func() any {
return &Span{}
},
}
)
func NewSpan() *Span {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &Span{}
}
return protoPoolSpan.Get().(*Span)
}
func DeleteSpan(orig *Span, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
DeleteTraceID(&orig.TraceId, false)
DeleteSpanID(&orig.SpanId, false)
DeleteSpanID(&orig.ParentSpanId, false)
for i := range orig.Attributes {
DeleteKeyValue(&orig.Attributes[i], false)
}
for i := range orig.Events {
DeleteSpanEvent(orig.Events[i], true)
}
for i := range orig.Links {
DeleteSpanLink(orig.Links[i], true)
}
DeleteStatus(&orig.Status, false)
orig.Reset()
if nullable {
protoPoolSpan.Put(orig)
}
}
func CopySpan(dest, src *Span) *Span {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewSpan()
}
CopyTraceID(&dest.TraceId, &src.TraceId)
CopySpanID(&dest.SpanId, &src.SpanId)
dest.TraceState = src.TraceState
CopySpanID(&dest.ParentSpanId, &src.ParentSpanId)
dest.Flags = src.Flags
dest.Name = src.Name
dest.Kind = src.Kind
dest.StartTimeUnixNano = src.StartTimeUnixNano
dest.EndTimeUnixNano = src.EndTimeUnixNano
dest.Attributes = CopyKeyValueSlice(dest.Attributes, src.Attributes)
dest.DroppedAttributesCount = src.DroppedAttributesCount
dest.Events = CopySpanEventPtrSlice(dest.Events, src.Events)
dest.DroppedEventsCount = src.DroppedEventsCount
dest.Links = CopySpanLinkPtrSlice(dest.Links, src.Links)
dest.DroppedLinksCount = src.DroppedLinksCount
CopyStatus(&dest.Status, &src.Status)
return dest
}
func CopySpanSlice(dest, src []Span) []Span {
var newDest []Span
if cap(dest) < len(src) {
newDest = make([]Span, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteSpan(&dest[i], false)
}
}
for i := range src {
CopySpan(&newDest[i], &src[i])
}
return newDest
}
func CopySpanPtrSlice(dest, src []*Span) []*Span {
var newDest []*Span
if cap(dest) < len(src) {
newDest = make([]*Span, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewSpan()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteSpan(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewSpan()
}
}
for i := range src {
CopySpan(newDest[i], src[i])
}
return newDest
}
func (orig *Span) Reset() {
*orig = Span{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *Span) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if !orig.TraceId.IsEmpty() {
dest.WriteObjectField("traceId")
orig.TraceId.MarshalJSON(dest)
}
if !orig.SpanId.IsEmpty() {
dest.WriteObjectField("spanId")
orig.SpanId.MarshalJSON(dest)
}
if orig.TraceState != "" {
dest.WriteObjectField("traceState")
dest.WriteString(orig.TraceState)
}
if !orig.ParentSpanId.IsEmpty() {
dest.WriteObjectField("parentSpanId")
orig.ParentSpanId.MarshalJSON(dest)
}
if orig.Flags != uint32(0) {
dest.WriteObjectField("flags")
dest.WriteUint32(orig.Flags)
}
if orig.Name != "" {
dest.WriteObjectField("name")
dest.WriteString(orig.Name)
}
if int32(orig.Kind) != 0 {
dest.WriteObjectField("kind")
dest.WriteInt32(int32(orig.Kind))
}
if orig.StartTimeUnixNano != uint64(0) {
dest.WriteObjectField("startTimeUnixNano")
dest.WriteUint64(orig.StartTimeUnixNano)
}
if orig.EndTimeUnixNano != uint64(0) {
dest.WriteObjectField("endTimeUnixNano")
dest.WriteUint64(orig.EndTimeUnixNano)
}
if len(orig.Attributes) > 0 {
dest.WriteObjectField("attributes")
dest.WriteArrayStart()
orig.Attributes[0].MarshalJSON(dest)
for i := 1; i < len(orig.Attributes); i++ {
dest.WriteMore()
orig.Attributes[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if orig.DroppedAttributesCount != uint32(0) {
dest.WriteObjectField("droppedAttributesCount")
dest.WriteUint32(orig.DroppedAttributesCount)
}
if len(orig.Events) > 0 {
dest.WriteObjectField("events")
dest.WriteArrayStart()
orig.Events[0].MarshalJSON(dest)
for i := 1; i < len(orig.Events); i++ {
dest.WriteMore()
orig.Events[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if orig.DroppedEventsCount != uint32(0) {
dest.WriteObjectField("droppedEventsCount")
dest.WriteUint32(orig.DroppedEventsCount)
}
if len(orig.Links) > 0 {
dest.WriteObjectField("links")
dest.WriteArrayStart()
orig.Links[0].MarshalJSON(dest)
for i := 1; i < len(orig.Links); i++ {
dest.WriteMore()
orig.Links[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if orig.DroppedLinksCount != uint32(0) {
dest.WriteObjectField("droppedLinksCount")
dest.WriteUint32(orig.DroppedLinksCount)
}
dest.WriteObjectField("status")
orig.Status.MarshalJSON(dest)
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *Span) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "traceId", "trace_id":
orig.TraceId.UnmarshalJSON(iter)
case "spanId", "span_id":
orig.SpanId.UnmarshalJSON(iter)
case "traceState", "trace_state":
orig.TraceState = iter.ReadString()
case "parentSpanId", "parent_span_id":
orig.ParentSpanId.UnmarshalJSON(iter)
case "flags":
orig.Flags = iter.ReadUint32()
case "name":
orig.Name = iter.ReadString()
case "kind":
orig.Kind = SpanKind(iter.ReadEnumValue(SpanKind_value))
case "startTimeUnixNano", "start_time_unix_nano":
orig.StartTimeUnixNano = iter.ReadUint64()
case "endTimeUnixNano", "end_time_unix_nano":
orig.EndTimeUnixNano = iter.ReadUint64()
case "attributes":
for iter.ReadArray() {
orig.Attributes = append(orig.Attributes, KeyValue{})
orig.Attributes[len(orig.Attributes)-1].UnmarshalJSON(iter)
}
case "droppedAttributesCount", "dropped_attributes_count":
orig.DroppedAttributesCount = iter.ReadUint32()
case "events":
for iter.ReadArray() {
orig.Events = append(orig.Events, NewSpanEvent())
orig.Events[len(orig.Events)-1].UnmarshalJSON(iter)
}
case "droppedEventsCount", "dropped_events_count":
orig.DroppedEventsCount = iter.ReadUint32()
case "links":
for iter.ReadArray() {
orig.Links = append(orig.Links, NewSpanLink())
orig.Links[len(orig.Links)-1].UnmarshalJSON(iter)
}
case "droppedLinksCount", "dropped_links_count":
orig.DroppedLinksCount = iter.ReadUint32()
case "status":
orig.Status.UnmarshalJSON(iter)
default:
iter.Skip()
}
}
}
func (orig *Span) SizeProto() int {
var n int
var l int
_ = l
l = orig.TraceId.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
l = orig.SpanId.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
l = len(orig.TraceState)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
l = orig.ParentSpanId.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
if orig.Flags != uint32(0) {
n += 6
}
l = len(orig.Name)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
if orig.Kind != SpanKind(0) {
n += 1 + proto.Sov(uint64(orig.Kind))
}
if orig.StartTimeUnixNano != uint64(0) {
n += 9
}
if orig.EndTimeUnixNano != uint64(0) {
n += 9
}
for i := range orig.Attributes {
l = orig.Attributes[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
if orig.DroppedAttributesCount != uint32(0) {
n += 1 + proto.Sov(uint64(orig.DroppedAttributesCount))
}
for i := range orig.Events {
l = orig.Events[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
if orig.DroppedEventsCount != uint32(0) {
n += 1 + proto.Sov(uint64(orig.DroppedEventsCount))
}
for i := range orig.Links {
l = orig.Links[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
if orig.DroppedLinksCount != uint32(0) {
n += 1 + proto.Sov(uint64(orig.DroppedLinksCount))
}
l = orig.Status.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
return n
}
func (orig *Span) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
l = orig.TraceId.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
l = orig.SpanId.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
l = len(orig.TraceState)
if l > 0 {
pos -= l
copy(buf[pos:], orig.TraceState)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x1a
}
l = orig.ParentSpanId.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x22
if orig.Flags != uint32(0) {
pos -= 4
binary.LittleEndian.PutUint32(buf[pos:], uint32(orig.Flags))
pos--
buf[pos] = 0x1
pos--
buf[pos] = 0x85
}
l = len(orig.Name)
if l > 0 {
pos -= l
copy(buf[pos:], orig.Name)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x2a
}
if orig.Kind != SpanKind(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.Kind))
pos--
buf[pos] = 0x30
}
if orig.StartTimeUnixNano != uint64(0) {
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], uint64(orig.StartTimeUnixNano))
pos--
buf[pos] = 0x39
}
if orig.EndTimeUnixNano != uint64(0) {
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], uint64(orig.EndTimeUnixNano))
pos--
buf[pos] = 0x41
}
for i := len(orig.Attributes) - 1; i >= 0; i-- {
l = orig.Attributes[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x4a
}
if orig.DroppedAttributesCount != uint32(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.DroppedAttributesCount))
pos--
buf[pos] = 0x50
}
for i := len(orig.Events) - 1; i >= 0; i-- {
l = orig.Events[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x5a
}
if orig.DroppedEventsCount != uint32(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.DroppedEventsCount))
pos--
buf[pos] = 0x60
}
for i := len(orig.Links) - 1; i >= 0; i-- {
l = orig.Links[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x6a
}
if orig.DroppedLinksCount != uint32(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.DroppedLinksCount))
pos--
buf[pos] = 0x70
}
l = orig.Status.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x7a
return len(buf) - pos
}
func (orig *Span) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field TraceId", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.TraceId.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field SpanId", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.SpanId.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 3:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field TraceState", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.TraceState = string(buf[startPos:pos])
case 4:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field ParentSpanId", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.ParentSpanId.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 16:
if wireType != proto.WireTypeI32 {
return fmt.Errorf("proto: wrong wireType = %d for field Flags", wireType)
}
var num uint32
num, pos, err = proto.ConsumeI32(buf, pos)
if err != nil {
return err
}
orig.Flags = uint32(num)
case 5:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Name = string(buf[startPos:pos])
case 6:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field Kind", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.Kind = SpanKind(num)
case 7:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field StartTimeUnixNano", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
orig.StartTimeUnixNano = uint64(num)
case 8:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field EndTimeUnixNano", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
orig.EndTimeUnixNano = uint64(num)
case 9:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Attributes", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Attributes = append(orig.Attributes, KeyValue{})
err = orig.Attributes[len(orig.Attributes)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 10:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field DroppedAttributesCount", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.DroppedAttributesCount = uint32(num)
case 11:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Events", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Events = append(orig.Events, NewSpanEvent())
err = orig.Events[len(orig.Events)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 12:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field DroppedEventsCount", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.DroppedEventsCount = uint32(num)
case 13:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Links", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Links = append(orig.Links, NewSpanLink())
err = orig.Links[len(orig.Links)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 14:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field DroppedLinksCount", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.DroppedLinksCount = uint32(num)
case 15:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.Status.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestSpan() *Span {
orig := NewSpan()
orig.TraceId = *GenTestTraceID()
orig.SpanId = *GenTestSpanID()
orig.TraceState = "test_tracestate"
orig.ParentSpanId = *GenTestSpanID()
orig.Flags = uint32(13)
orig.Name = "test_name"
orig.Kind = SpanKind(13)
orig.StartTimeUnixNano = uint64(13)
orig.EndTimeUnixNano = uint64(13)
orig.Attributes = []KeyValue{{}, *GenTestKeyValue()}
orig.DroppedAttributesCount = uint32(13)
orig.Events = []*SpanEvent{{}, GenTestSpanEvent()}
orig.DroppedEventsCount = uint32(13)
orig.Links = []*SpanLink{{}, GenTestSpanLink()}
orig.DroppedLinksCount = uint32(13)
orig.Status = *GenTestStatus()
return orig
}
func GenTestSpanPtrSlice() []*Span {
orig := make([]*Span, 5)
orig[0] = NewSpan()
orig[1] = GenTestSpan()
orig[2] = NewSpan()
orig[3] = GenTestSpan()
orig[4] = NewSpan()
return orig
}
func GenTestSpanSlice() []Span {
orig := make([]Span, 5)
orig[1] = *GenTestSpan()
orig[3] = *GenTestSpan()
return orig
}
================================================
FILE: pdata/internal/generated_proto_span_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlptrace "go.opentelemetry.io/proto/slim/otlp/trace/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopySpan(t *testing.T) {
for name, src := range genTestEncodingValuesSpan() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewSpan()
CopySpan(dest, src)
assert.Equal(t, src, dest)
CopySpan(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopySpanSlice(t *testing.T) {
src := []Span{}
dest := []Span{}
// Test CopyTo empty
dest = CopySpanSlice(dest, src)
assert.Equal(t, []Span{}, dest)
// Test CopyTo larger slice
src = GenTestSpanSlice()
dest = CopySpanSlice(dest, src)
assert.Equal(t, GenTestSpanSlice(), dest)
// Test CopyTo same size slice
dest = CopySpanSlice(dest, src)
assert.Equal(t, GenTestSpanSlice(), dest)
// Test CopyTo smaller size slice
dest = CopySpanSlice(dest, []Span{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopySpanSlice(dest, src)
assert.Equal(t, GenTestSpanSlice(), dest)
}
func TestCopySpanPtrSlice(t *testing.T) {
src := []*Span{}
dest := []*Span{}
// Test CopyTo empty
dest = CopySpanPtrSlice(dest, src)
assert.Equal(t, []*Span{}, dest)
// Test CopyTo larger slice
src = GenTestSpanPtrSlice()
dest = CopySpanPtrSlice(dest, src)
assert.Equal(t, GenTestSpanPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopySpanPtrSlice(dest, src)
assert.Equal(t, GenTestSpanPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopySpanPtrSlice(dest, []*Span{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopySpanPtrSlice(dest, src)
assert.Equal(t, GenTestSpanPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONSpanUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewSpan()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewSpan(), dest)
}
func TestMarshalAndUnmarshalJSONSpan(t *testing.T) {
for name, src := range genTestEncodingValuesSpan() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewSpan()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteSpan(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoSpanFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesSpan() {
t.Run(name, func(t *testing.T) {
dest := NewSpan()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoSpanUnknown(t *testing.T) {
dest := NewSpan()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewSpan(), dest)
}
func TestMarshalAndUnmarshalProtoSpan(t *testing.T) {
for name, src := range genTestEncodingValuesSpan() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewSpan()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteSpan(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufSpan(t *testing.T) {
for name, src := range genTestEncodingValuesSpan() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlptrace.Span{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewSpan()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesSpan() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"TraceId/wrong_wire_type": {0xc},
"TraceId/missing_value": {0xa},
"SpanId/wrong_wire_type": {0x14},
"SpanId/missing_value": {0x12},
"TraceState/wrong_wire_type": {0x1c},
"TraceState/missing_value": {0x1a},
"ParentSpanId/wrong_wire_type": {0x24},
"ParentSpanId/missing_value": {0x22},
"Flags/wrong_wire_type": {0x84, 0x1},
"Flags/missing_value": {0x85, 0x1},
"Name/wrong_wire_type": {0x2c},
"Name/missing_value": {0x2a},
"Kind/wrong_wire_type": {0x34},
"Kind/missing_value": {0x30},
"StartTimeUnixNano/wrong_wire_type": {0x3c},
"StartTimeUnixNano/missing_value": {0x39},
"EndTimeUnixNano/wrong_wire_type": {0x44},
"EndTimeUnixNano/missing_value": {0x41},
"Attributes/wrong_wire_type": {0x4c},
"Attributes/missing_value": {0x4a},
"DroppedAttributesCount/wrong_wire_type": {0x54},
"DroppedAttributesCount/missing_value": {0x50},
"Events/wrong_wire_type": {0x5c},
"Events/missing_value": {0x5a},
"DroppedEventsCount/wrong_wire_type": {0x64},
"DroppedEventsCount/missing_value": {0x60},
"Links/wrong_wire_type": {0x6c},
"Links/missing_value": {0x6a},
"DroppedLinksCount/wrong_wire_type": {0x74},
"DroppedLinksCount/missing_value": {0x70},
"Status/wrong_wire_type": {0x7c},
"Status/missing_value": {0x7a},
}
}
func genTestEncodingValuesSpan() map[string]*Span {
return map[string]*Span{
"empty": NewSpan(),
"TraceId/test": {TraceId: *GenTestTraceID()},
"SpanId/test": {SpanId: *GenTestSpanID()},
"TraceState/test": {TraceState: "test_tracestate"},
"ParentSpanId/test": {ParentSpanId: *GenTestSpanID()},
"Flags/test": {Flags: uint32(13)},
"Name/test": {Name: "test_name"},
"Kind/test": {Kind: SpanKind(13)},
"StartTimeUnixNano/test": {StartTimeUnixNano: uint64(13)},
"EndTimeUnixNano/test": {EndTimeUnixNano: uint64(13)},
"Attributes/test": {Attributes: []KeyValue{{}, *GenTestKeyValue()}},
"DroppedAttributesCount/test": {DroppedAttributesCount: uint32(13)},
"Events/test": {Events: []*SpanEvent{{}, GenTestSpanEvent()}},
"DroppedEventsCount/test": {DroppedEventsCount: uint32(13)},
"Links/test": {Links: []*SpanLink{{}, GenTestSpanLink()}},
"DroppedLinksCount/test": {DroppedLinksCount: uint32(13)},
"Status/test": {Status: *GenTestStatus()},
}
}
================================================
FILE: pdata/internal/generated_proto_spancontext.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"encoding/binary"
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
type SpanContext struct {
TraceState string
TraceFlags uint32
TraceID TraceID
SpanID SpanID
Remote bool
}
var (
protoPoolSpanContext = sync.Pool{
New: func() any {
return &SpanContext{}
},
}
)
func NewSpanContext() *SpanContext {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &SpanContext{}
}
return protoPoolSpanContext.Get().(*SpanContext)
}
func DeleteSpanContext(orig *SpanContext, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
DeleteTraceID(&orig.TraceID, false)
DeleteSpanID(&orig.SpanID, false)
orig.Reset()
if nullable {
protoPoolSpanContext.Put(orig)
}
}
func CopySpanContext(dest, src *SpanContext) *SpanContext {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewSpanContext()
}
CopyTraceID(&dest.TraceID, &src.TraceID)
CopySpanID(&dest.SpanID, &src.SpanID)
dest.TraceFlags = src.TraceFlags
dest.TraceState = src.TraceState
dest.Remote = src.Remote
return dest
}
func CopySpanContextSlice(dest, src []SpanContext) []SpanContext {
var newDest []SpanContext
if cap(dest) < len(src) {
newDest = make([]SpanContext, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteSpanContext(&dest[i], false)
}
}
for i := range src {
CopySpanContext(&newDest[i], &src[i])
}
return newDest
}
func CopySpanContextPtrSlice(dest, src []*SpanContext) []*SpanContext {
var newDest []*SpanContext
if cap(dest) < len(src) {
newDest = make([]*SpanContext, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewSpanContext()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteSpanContext(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewSpanContext()
}
}
for i := range src {
CopySpanContext(newDest[i], src[i])
}
return newDest
}
func (orig *SpanContext) Reset() {
*orig = SpanContext{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *SpanContext) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if !orig.TraceID.IsEmpty() {
dest.WriteObjectField("traceID")
orig.TraceID.MarshalJSON(dest)
}
if !orig.SpanID.IsEmpty() {
dest.WriteObjectField("spanID")
orig.SpanID.MarshalJSON(dest)
}
if orig.TraceFlags != uint32(0) {
dest.WriteObjectField("traceFlags")
dest.WriteUint32(orig.TraceFlags)
}
if orig.TraceState != "" {
dest.WriteObjectField("traceState")
dest.WriteString(orig.TraceState)
}
if orig.Remote != false {
dest.WriteObjectField("remote")
dest.WriteBool(orig.Remote)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *SpanContext) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "traceID", "trace_id":
orig.TraceID.UnmarshalJSON(iter)
case "spanID", "span_id":
orig.SpanID.UnmarshalJSON(iter)
case "traceFlags", "trace_flags":
orig.TraceFlags = iter.ReadUint32()
case "traceState", "trace_state":
orig.TraceState = iter.ReadString()
case "remote":
orig.Remote = iter.ReadBool()
default:
iter.Skip()
}
}
}
func (orig *SpanContext) SizeProto() int {
var n int
var l int
_ = l
l = orig.TraceID.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
l = orig.SpanID.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
if orig.TraceFlags != uint32(0) {
n += 5
}
l = len(orig.TraceState)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
if orig.Remote != false {
n += 2
}
return n
}
func (orig *SpanContext) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
l = orig.TraceID.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
l = orig.SpanID.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
if orig.TraceFlags != uint32(0) {
pos -= 4
binary.LittleEndian.PutUint32(buf[pos:], uint32(orig.TraceFlags))
pos--
buf[pos] = 0x1d
}
l = len(orig.TraceState)
if l > 0 {
pos -= l
copy(buf[pos:], orig.TraceState)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x22
}
if orig.Remote != false {
pos--
if orig.Remote {
buf[pos] = 1
} else {
buf[pos] = 0
}
pos--
buf[pos] = 0x28
}
return len(buf) - pos
}
func (orig *SpanContext) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field TraceID", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.TraceID.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field SpanID", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.SpanID.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 3:
if wireType != proto.WireTypeI32 {
return fmt.Errorf("proto: wrong wireType = %d for field TraceFlags", wireType)
}
var num uint32
num, pos, err = proto.ConsumeI32(buf, pos)
if err != nil {
return err
}
orig.TraceFlags = uint32(num)
case 4:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field TraceState", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.TraceState = string(buf[startPos:pos])
case 5:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field Remote", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.Remote = num != 0
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestSpanContext() *SpanContext {
orig := NewSpanContext()
orig.TraceID = *GenTestTraceID()
orig.SpanID = *GenTestSpanID()
orig.TraceFlags = uint32(13)
orig.TraceState = "test_tracestate"
orig.Remote = true
return orig
}
func GenTestSpanContextPtrSlice() []*SpanContext {
orig := make([]*SpanContext, 5)
orig[0] = NewSpanContext()
orig[1] = GenTestSpanContext()
orig[2] = NewSpanContext()
orig[3] = GenTestSpanContext()
orig[4] = NewSpanContext()
return orig
}
func GenTestSpanContextSlice() []SpanContext {
orig := make([]SpanContext, 5)
orig[1] = *GenTestSpanContext()
orig[3] = *GenTestSpanContext()
return orig
}
================================================
FILE: pdata/internal/generated_proto_spancontext_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/emptypb"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopySpanContext(t *testing.T) {
for name, src := range genTestEncodingValuesSpanContext() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewSpanContext()
CopySpanContext(dest, src)
assert.Equal(t, src, dest)
CopySpanContext(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopySpanContextSlice(t *testing.T) {
src := []SpanContext{}
dest := []SpanContext{}
// Test CopyTo empty
dest = CopySpanContextSlice(dest, src)
assert.Equal(t, []SpanContext{}, dest)
// Test CopyTo larger slice
src = GenTestSpanContextSlice()
dest = CopySpanContextSlice(dest, src)
assert.Equal(t, GenTestSpanContextSlice(), dest)
// Test CopyTo same size slice
dest = CopySpanContextSlice(dest, src)
assert.Equal(t, GenTestSpanContextSlice(), dest)
// Test CopyTo smaller size slice
dest = CopySpanContextSlice(dest, []SpanContext{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopySpanContextSlice(dest, src)
assert.Equal(t, GenTestSpanContextSlice(), dest)
}
func TestCopySpanContextPtrSlice(t *testing.T) {
src := []*SpanContext{}
dest := []*SpanContext{}
// Test CopyTo empty
dest = CopySpanContextPtrSlice(dest, src)
assert.Equal(t, []*SpanContext{}, dest)
// Test CopyTo larger slice
src = GenTestSpanContextPtrSlice()
dest = CopySpanContextPtrSlice(dest, src)
assert.Equal(t, GenTestSpanContextPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopySpanContextPtrSlice(dest, src)
assert.Equal(t, GenTestSpanContextPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopySpanContextPtrSlice(dest, []*SpanContext{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopySpanContextPtrSlice(dest, src)
assert.Equal(t, GenTestSpanContextPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONSpanContextUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewSpanContext()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewSpanContext(), dest)
}
func TestMarshalAndUnmarshalJSONSpanContext(t *testing.T) {
for name, src := range genTestEncodingValuesSpanContext() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewSpanContext()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteSpanContext(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoSpanContextFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesSpanContext() {
t.Run(name, func(t *testing.T) {
dest := NewSpanContext()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoSpanContextUnknown(t *testing.T) {
dest := NewSpanContext()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewSpanContext(), dest)
}
func TestMarshalAndUnmarshalProtoSpanContext(t *testing.T) {
for name, src := range genTestEncodingValuesSpanContext() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewSpanContext()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteSpanContext(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufSpanContext(t *testing.T) {
for name, src := range genTestEncodingValuesSpanContext() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &emptypb.Empty{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewSpanContext()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesSpanContext() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"TraceID/wrong_wire_type": {0xc},
"TraceID/missing_value": {0xa},
"SpanID/wrong_wire_type": {0x14},
"SpanID/missing_value": {0x12},
"TraceFlags/wrong_wire_type": {0x1c},
"TraceFlags/missing_value": {0x1d},
"TraceState/wrong_wire_type": {0x24},
"TraceState/missing_value": {0x22},
"Remote/wrong_wire_type": {0x2c},
"Remote/missing_value": {0x28},
}
}
func genTestEncodingValuesSpanContext() map[string]*SpanContext {
return map[string]*SpanContext{
"empty": NewSpanContext(),
"TraceID/test": {TraceID: *GenTestTraceID()},
"SpanID/test": {SpanID: *GenTestSpanID()},
"TraceFlags/test": {TraceFlags: uint32(13)},
"TraceState/test": {TraceState: "test_tracestate"},
"Remote/test": {Remote: true},
}
}
================================================
FILE: pdata/internal/generated_proto_spanevent.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"encoding/binary"
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// SpanEvent is a time-stamped annotation of the span, consisting of user-supplied
// text description and key-value pairs. See OTLP for event definition.
type SpanEvent struct {
Name string
Attributes []KeyValue
TimeUnixNano uint64
DroppedAttributesCount uint32
}
var (
protoPoolSpanEvent = sync.Pool{
New: func() any {
return &SpanEvent{}
},
}
)
func NewSpanEvent() *SpanEvent {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &SpanEvent{}
}
return protoPoolSpanEvent.Get().(*SpanEvent)
}
func DeleteSpanEvent(orig *SpanEvent, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
for i := range orig.Attributes {
DeleteKeyValue(&orig.Attributes[i], false)
}
orig.Reset()
if nullable {
protoPoolSpanEvent.Put(orig)
}
}
func CopySpanEvent(dest, src *SpanEvent) *SpanEvent {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewSpanEvent()
}
dest.TimeUnixNano = src.TimeUnixNano
dest.Name = src.Name
dest.Attributes = CopyKeyValueSlice(dest.Attributes, src.Attributes)
dest.DroppedAttributesCount = src.DroppedAttributesCount
return dest
}
func CopySpanEventSlice(dest, src []SpanEvent) []SpanEvent {
var newDest []SpanEvent
if cap(dest) < len(src) {
newDest = make([]SpanEvent, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteSpanEvent(&dest[i], false)
}
}
for i := range src {
CopySpanEvent(&newDest[i], &src[i])
}
return newDest
}
func CopySpanEventPtrSlice(dest, src []*SpanEvent) []*SpanEvent {
var newDest []*SpanEvent
if cap(dest) < len(src) {
newDest = make([]*SpanEvent, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewSpanEvent()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteSpanEvent(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewSpanEvent()
}
}
for i := range src {
CopySpanEvent(newDest[i], src[i])
}
return newDest
}
func (orig *SpanEvent) Reset() {
*orig = SpanEvent{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *SpanEvent) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if orig.TimeUnixNano != uint64(0) {
dest.WriteObjectField("timeUnixNano")
dest.WriteUint64(orig.TimeUnixNano)
}
if orig.Name != "" {
dest.WriteObjectField("name")
dest.WriteString(orig.Name)
}
if len(orig.Attributes) > 0 {
dest.WriteObjectField("attributes")
dest.WriteArrayStart()
orig.Attributes[0].MarshalJSON(dest)
for i := 1; i < len(orig.Attributes); i++ {
dest.WriteMore()
orig.Attributes[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if orig.DroppedAttributesCount != uint32(0) {
dest.WriteObjectField("droppedAttributesCount")
dest.WriteUint32(orig.DroppedAttributesCount)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *SpanEvent) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "timeUnixNano", "time_unix_nano":
orig.TimeUnixNano = iter.ReadUint64()
case "name":
orig.Name = iter.ReadString()
case "attributes":
for iter.ReadArray() {
orig.Attributes = append(orig.Attributes, KeyValue{})
orig.Attributes[len(orig.Attributes)-1].UnmarshalJSON(iter)
}
case "droppedAttributesCount", "dropped_attributes_count":
orig.DroppedAttributesCount = iter.ReadUint32()
default:
iter.Skip()
}
}
}
func (orig *SpanEvent) SizeProto() int {
var n int
var l int
_ = l
if orig.TimeUnixNano != uint64(0) {
n += 9
}
l = len(orig.Name)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
for i := range orig.Attributes {
l = orig.Attributes[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
if orig.DroppedAttributesCount != uint32(0) {
n += 1 + proto.Sov(uint64(orig.DroppedAttributesCount))
}
return n
}
func (orig *SpanEvent) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
if orig.TimeUnixNano != uint64(0) {
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], uint64(orig.TimeUnixNano))
pos--
buf[pos] = 0x9
}
l = len(orig.Name)
if l > 0 {
pos -= l
copy(buf[pos:], orig.Name)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
}
for i := len(orig.Attributes) - 1; i >= 0; i-- {
l = orig.Attributes[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x1a
}
if orig.DroppedAttributesCount != uint32(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.DroppedAttributesCount))
pos--
buf[pos] = 0x20
}
return len(buf) - pos
}
func (orig *SpanEvent) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field TimeUnixNano", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
orig.TimeUnixNano = uint64(num)
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Name = string(buf[startPos:pos])
case 3:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Attributes", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Attributes = append(orig.Attributes, KeyValue{})
err = orig.Attributes[len(orig.Attributes)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 4:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field DroppedAttributesCount", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.DroppedAttributesCount = uint32(num)
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestSpanEvent() *SpanEvent {
orig := NewSpanEvent()
orig.TimeUnixNano = uint64(13)
orig.Name = "test_name"
orig.Attributes = []KeyValue{{}, *GenTestKeyValue()}
orig.DroppedAttributesCount = uint32(13)
return orig
}
func GenTestSpanEventPtrSlice() []*SpanEvent {
orig := make([]*SpanEvent, 5)
orig[0] = NewSpanEvent()
orig[1] = GenTestSpanEvent()
orig[2] = NewSpanEvent()
orig[3] = GenTestSpanEvent()
orig[4] = NewSpanEvent()
return orig
}
func GenTestSpanEventSlice() []SpanEvent {
orig := make([]SpanEvent, 5)
orig[1] = *GenTestSpanEvent()
orig[3] = *GenTestSpanEvent()
return orig
}
================================================
FILE: pdata/internal/generated_proto_spanevent_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlptrace "go.opentelemetry.io/proto/slim/otlp/trace/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopySpanEvent(t *testing.T) {
for name, src := range genTestEncodingValuesSpanEvent() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewSpanEvent()
CopySpanEvent(dest, src)
assert.Equal(t, src, dest)
CopySpanEvent(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopySpanEventSlice(t *testing.T) {
src := []SpanEvent{}
dest := []SpanEvent{}
// Test CopyTo empty
dest = CopySpanEventSlice(dest, src)
assert.Equal(t, []SpanEvent{}, dest)
// Test CopyTo larger slice
src = GenTestSpanEventSlice()
dest = CopySpanEventSlice(dest, src)
assert.Equal(t, GenTestSpanEventSlice(), dest)
// Test CopyTo same size slice
dest = CopySpanEventSlice(dest, src)
assert.Equal(t, GenTestSpanEventSlice(), dest)
// Test CopyTo smaller size slice
dest = CopySpanEventSlice(dest, []SpanEvent{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopySpanEventSlice(dest, src)
assert.Equal(t, GenTestSpanEventSlice(), dest)
}
func TestCopySpanEventPtrSlice(t *testing.T) {
src := []*SpanEvent{}
dest := []*SpanEvent{}
// Test CopyTo empty
dest = CopySpanEventPtrSlice(dest, src)
assert.Equal(t, []*SpanEvent{}, dest)
// Test CopyTo larger slice
src = GenTestSpanEventPtrSlice()
dest = CopySpanEventPtrSlice(dest, src)
assert.Equal(t, GenTestSpanEventPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopySpanEventPtrSlice(dest, src)
assert.Equal(t, GenTestSpanEventPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopySpanEventPtrSlice(dest, []*SpanEvent{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopySpanEventPtrSlice(dest, src)
assert.Equal(t, GenTestSpanEventPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONSpanEventUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewSpanEvent()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewSpanEvent(), dest)
}
func TestMarshalAndUnmarshalJSONSpanEvent(t *testing.T) {
for name, src := range genTestEncodingValuesSpanEvent() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewSpanEvent()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteSpanEvent(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoSpanEventFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesSpanEvent() {
t.Run(name, func(t *testing.T) {
dest := NewSpanEvent()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoSpanEventUnknown(t *testing.T) {
dest := NewSpanEvent()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewSpanEvent(), dest)
}
func TestMarshalAndUnmarshalProtoSpanEvent(t *testing.T) {
for name, src := range genTestEncodingValuesSpanEvent() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewSpanEvent()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteSpanEvent(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufSpanEvent(t *testing.T) {
for name, src := range genTestEncodingValuesSpanEvent() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlptrace.Span_Event{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewSpanEvent()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesSpanEvent() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"TimeUnixNano/wrong_wire_type": {0xc},
"TimeUnixNano/missing_value": {0x9},
"Name/wrong_wire_type": {0x14},
"Name/missing_value": {0x12},
"Attributes/wrong_wire_type": {0x1c},
"Attributes/missing_value": {0x1a},
"DroppedAttributesCount/wrong_wire_type": {0x24},
"DroppedAttributesCount/missing_value": {0x20},
}
}
func genTestEncodingValuesSpanEvent() map[string]*SpanEvent {
return map[string]*SpanEvent{
"empty": NewSpanEvent(),
"TimeUnixNano/test": {TimeUnixNano: uint64(13)},
"Name/test": {Name: "test_name"},
"Attributes/test": {Attributes: []KeyValue{{}, *GenTestKeyValue()}},
"DroppedAttributesCount/test": {DroppedAttributesCount: uint32(13)},
}
}
================================================
FILE: pdata/internal/generated_proto_spanlink.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"encoding/binary"
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// SpanLink is a pointer from the current span to another span in the same trace or in a
// different trace.
// See Link definition in OTLP: https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/trace/v1/trace.proto
type SpanLink struct {
TraceState string
Attributes []KeyValue
DroppedAttributesCount uint32
Flags uint32
TraceId TraceID
SpanId SpanID
}
var (
protoPoolSpanLink = sync.Pool{
New: func() any {
return &SpanLink{}
},
}
)
func NewSpanLink() *SpanLink {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &SpanLink{}
}
return protoPoolSpanLink.Get().(*SpanLink)
}
func DeleteSpanLink(orig *SpanLink, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
DeleteTraceID(&orig.TraceId, false)
DeleteSpanID(&orig.SpanId, false)
for i := range orig.Attributes {
DeleteKeyValue(&orig.Attributes[i], false)
}
orig.Reset()
if nullable {
protoPoolSpanLink.Put(orig)
}
}
func CopySpanLink(dest, src *SpanLink) *SpanLink {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewSpanLink()
}
CopyTraceID(&dest.TraceId, &src.TraceId)
CopySpanID(&dest.SpanId, &src.SpanId)
dest.TraceState = src.TraceState
dest.Attributes = CopyKeyValueSlice(dest.Attributes, src.Attributes)
dest.DroppedAttributesCount = src.DroppedAttributesCount
dest.Flags = src.Flags
return dest
}
func CopySpanLinkSlice(dest, src []SpanLink) []SpanLink {
var newDest []SpanLink
if cap(dest) < len(src) {
newDest = make([]SpanLink, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteSpanLink(&dest[i], false)
}
}
for i := range src {
CopySpanLink(&newDest[i], &src[i])
}
return newDest
}
func CopySpanLinkPtrSlice(dest, src []*SpanLink) []*SpanLink {
var newDest []*SpanLink
if cap(dest) < len(src) {
newDest = make([]*SpanLink, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewSpanLink()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteSpanLink(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewSpanLink()
}
}
for i := range src {
CopySpanLink(newDest[i], src[i])
}
return newDest
}
func (orig *SpanLink) Reset() {
*orig = SpanLink{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *SpanLink) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if !orig.TraceId.IsEmpty() {
dest.WriteObjectField("traceId")
orig.TraceId.MarshalJSON(dest)
}
if !orig.SpanId.IsEmpty() {
dest.WriteObjectField("spanId")
orig.SpanId.MarshalJSON(dest)
}
if orig.TraceState != "" {
dest.WriteObjectField("traceState")
dest.WriteString(orig.TraceState)
}
if len(orig.Attributes) > 0 {
dest.WriteObjectField("attributes")
dest.WriteArrayStart()
orig.Attributes[0].MarshalJSON(dest)
for i := 1; i < len(orig.Attributes); i++ {
dest.WriteMore()
orig.Attributes[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if orig.DroppedAttributesCount != uint32(0) {
dest.WriteObjectField("droppedAttributesCount")
dest.WriteUint32(orig.DroppedAttributesCount)
}
if orig.Flags != uint32(0) {
dest.WriteObjectField("flags")
dest.WriteUint32(orig.Flags)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *SpanLink) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "traceId", "trace_id":
orig.TraceId.UnmarshalJSON(iter)
case "spanId", "span_id":
orig.SpanId.UnmarshalJSON(iter)
case "traceState", "trace_state":
orig.TraceState = iter.ReadString()
case "attributes":
for iter.ReadArray() {
orig.Attributes = append(orig.Attributes, KeyValue{})
orig.Attributes[len(orig.Attributes)-1].UnmarshalJSON(iter)
}
case "droppedAttributesCount", "dropped_attributes_count":
orig.DroppedAttributesCount = iter.ReadUint32()
case "flags":
orig.Flags = iter.ReadUint32()
default:
iter.Skip()
}
}
}
func (orig *SpanLink) SizeProto() int {
var n int
var l int
_ = l
l = orig.TraceId.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
l = orig.SpanId.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
l = len(orig.TraceState)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
for i := range orig.Attributes {
l = orig.Attributes[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
if orig.DroppedAttributesCount != uint32(0) {
n += 1 + proto.Sov(uint64(orig.DroppedAttributesCount))
}
if orig.Flags != uint32(0) {
n += 5
}
return n
}
func (orig *SpanLink) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
l = orig.TraceId.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
l = orig.SpanId.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
l = len(orig.TraceState)
if l > 0 {
pos -= l
copy(buf[pos:], orig.TraceState)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x1a
}
for i := len(orig.Attributes) - 1; i >= 0; i-- {
l = orig.Attributes[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x22
}
if orig.DroppedAttributesCount != uint32(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.DroppedAttributesCount))
pos--
buf[pos] = 0x28
}
if orig.Flags != uint32(0) {
pos -= 4
binary.LittleEndian.PutUint32(buf[pos:], uint32(orig.Flags))
pos--
buf[pos] = 0x35
}
return len(buf) - pos
}
func (orig *SpanLink) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field TraceId", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.TraceId.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field SpanId", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.SpanId.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 3:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field TraceState", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.TraceState = string(buf[startPos:pos])
case 4:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Attributes", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Attributes = append(orig.Attributes, KeyValue{})
err = orig.Attributes[len(orig.Attributes)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 5:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field DroppedAttributesCount", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.DroppedAttributesCount = uint32(num)
case 6:
if wireType != proto.WireTypeI32 {
return fmt.Errorf("proto: wrong wireType = %d for field Flags", wireType)
}
var num uint32
num, pos, err = proto.ConsumeI32(buf, pos)
if err != nil {
return err
}
orig.Flags = uint32(num)
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestSpanLink() *SpanLink {
orig := NewSpanLink()
orig.TraceId = *GenTestTraceID()
orig.SpanId = *GenTestSpanID()
orig.TraceState = "test_tracestate"
orig.Attributes = []KeyValue{{}, *GenTestKeyValue()}
orig.DroppedAttributesCount = uint32(13)
orig.Flags = uint32(13)
return orig
}
func GenTestSpanLinkPtrSlice() []*SpanLink {
orig := make([]*SpanLink, 5)
orig[0] = NewSpanLink()
orig[1] = GenTestSpanLink()
orig[2] = NewSpanLink()
orig[3] = GenTestSpanLink()
orig[4] = NewSpanLink()
return orig
}
func GenTestSpanLinkSlice() []SpanLink {
orig := make([]SpanLink, 5)
orig[1] = *GenTestSpanLink()
orig[3] = *GenTestSpanLink()
return orig
}
================================================
FILE: pdata/internal/generated_proto_spanlink_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlptrace "go.opentelemetry.io/proto/slim/otlp/trace/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopySpanLink(t *testing.T) {
for name, src := range genTestEncodingValuesSpanLink() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewSpanLink()
CopySpanLink(dest, src)
assert.Equal(t, src, dest)
CopySpanLink(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopySpanLinkSlice(t *testing.T) {
src := []SpanLink{}
dest := []SpanLink{}
// Test CopyTo empty
dest = CopySpanLinkSlice(dest, src)
assert.Equal(t, []SpanLink{}, dest)
// Test CopyTo larger slice
src = GenTestSpanLinkSlice()
dest = CopySpanLinkSlice(dest, src)
assert.Equal(t, GenTestSpanLinkSlice(), dest)
// Test CopyTo same size slice
dest = CopySpanLinkSlice(dest, src)
assert.Equal(t, GenTestSpanLinkSlice(), dest)
// Test CopyTo smaller size slice
dest = CopySpanLinkSlice(dest, []SpanLink{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopySpanLinkSlice(dest, src)
assert.Equal(t, GenTestSpanLinkSlice(), dest)
}
func TestCopySpanLinkPtrSlice(t *testing.T) {
src := []*SpanLink{}
dest := []*SpanLink{}
// Test CopyTo empty
dest = CopySpanLinkPtrSlice(dest, src)
assert.Equal(t, []*SpanLink{}, dest)
// Test CopyTo larger slice
src = GenTestSpanLinkPtrSlice()
dest = CopySpanLinkPtrSlice(dest, src)
assert.Equal(t, GenTestSpanLinkPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopySpanLinkPtrSlice(dest, src)
assert.Equal(t, GenTestSpanLinkPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopySpanLinkPtrSlice(dest, []*SpanLink{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopySpanLinkPtrSlice(dest, src)
assert.Equal(t, GenTestSpanLinkPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONSpanLinkUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewSpanLink()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewSpanLink(), dest)
}
func TestMarshalAndUnmarshalJSONSpanLink(t *testing.T) {
for name, src := range genTestEncodingValuesSpanLink() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewSpanLink()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteSpanLink(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoSpanLinkFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesSpanLink() {
t.Run(name, func(t *testing.T) {
dest := NewSpanLink()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoSpanLinkUnknown(t *testing.T) {
dest := NewSpanLink()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewSpanLink(), dest)
}
func TestMarshalAndUnmarshalProtoSpanLink(t *testing.T) {
for name, src := range genTestEncodingValuesSpanLink() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewSpanLink()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteSpanLink(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufSpanLink(t *testing.T) {
for name, src := range genTestEncodingValuesSpanLink() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlptrace.Span_Link{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewSpanLink()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesSpanLink() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"TraceId/wrong_wire_type": {0xc},
"TraceId/missing_value": {0xa},
"SpanId/wrong_wire_type": {0x14},
"SpanId/missing_value": {0x12},
"TraceState/wrong_wire_type": {0x1c},
"TraceState/missing_value": {0x1a},
"Attributes/wrong_wire_type": {0x24},
"Attributes/missing_value": {0x22},
"DroppedAttributesCount/wrong_wire_type": {0x2c},
"DroppedAttributesCount/missing_value": {0x28},
"Flags/wrong_wire_type": {0x34},
"Flags/missing_value": {0x35},
}
}
func genTestEncodingValuesSpanLink() map[string]*SpanLink {
return map[string]*SpanLink{
"empty": NewSpanLink(),
"TraceId/test": {TraceId: *GenTestTraceID()},
"SpanId/test": {SpanId: *GenTestSpanID()},
"TraceState/test": {TraceState: "test_tracestate"},
"Attributes/test": {Attributes: []KeyValue{{}, *GenTestKeyValue()}},
"DroppedAttributesCount/test": {DroppedAttributesCount: uint32(13)},
"Flags/test": {Flags: uint32(13)},
}
}
================================================
FILE: pdata/internal/generated_proto_stack.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// Stack represents a stack trace as a list of locations.
type Stack struct {
LocationIndices []int32
}
var (
protoPoolStack = sync.Pool{
New: func() any {
return &Stack{}
},
}
)
func NewStack() *Stack {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &Stack{}
}
return protoPoolStack.Get().(*Stack)
}
func DeleteStack(orig *Stack, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
orig.Reset()
if nullable {
protoPoolStack.Put(orig)
}
}
func CopyStack(dest, src *Stack) *Stack {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewStack()
}
dest.LocationIndices = append(dest.LocationIndices[:0], src.LocationIndices...)
return dest
}
func CopyStackSlice(dest, src []Stack) []Stack {
var newDest []Stack
if cap(dest) < len(src) {
newDest = make([]Stack, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteStack(&dest[i], false)
}
}
for i := range src {
CopyStack(&newDest[i], &src[i])
}
return newDest
}
func CopyStackPtrSlice(dest, src []*Stack) []*Stack {
var newDest []*Stack
if cap(dest) < len(src) {
newDest = make([]*Stack, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewStack()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteStack(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewStack()
}
}
for i := range src {
CopyStack(newDest[i], src[i])
}
return newDest
}
func (orig *Stack) Reset() {
*orig = Stack{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *Stack) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if len(orig.LocationIndices) > 0 {
dest.WriteObjectField("locationIndices")
dest.WriteArrayStart()
dest.WriteInt32(orig.LocationIndices[0])
for i := 1; i < len(orig.LocationIndices); i++ {
dest.WriteMore()
dest.WriteInt32(orig.LocationIndices[i])
}
dest.WriteArrayEnd()
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *Stack) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "locationIndices", "location_indices":
for iter.ReadArray() {
orig.LocationIndices = append(orig.LocationIndices, iter.ReadInt32())
}
default:
iter.Skip()
}
}
}
func (orig *Stack) SizeProto() int {
var n int
var l int
_ = l
if len(orig.LocationIndices) > 0 {
l = 0
for _, e := range orig.LocationIndices {
l += proto.Sov(uint64(e))
}
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *Stack) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
l = len(orig.LocationIndices)
if l > 0 {
endPos := pos
for i := l - 1; i >= 0; i-- {
pos = proto.EncodeVarint(buf, pos, uint64(orig.LocationIndices[i]))
}
pos = proto.EncodeVarint(buf, pos, uint64(endPos-pos))
pos--
buf[pos] = 0xa
}
return len(buf) - pos
}
func (orig *Stack) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
switch wireType {
case proto.WireTypeLen:
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
var num uint64
for startPos < pos {
num, startPos, err = proto.ConsumeVarint(buf[:pos], startPos)
if err != nil {
return err
}
orig.LocationIndices = append(orig.LocationIndices, int32(num))
}
if startPos != pos {
return fmt.Errorf("proto: invalid field len = %d for field LocationIndices", pos-startPos)
}
case proto.WireTypeVarint:
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.LocationIndices = append(orig.LocationIndices, int32(num))
default:
return fmt.Errorf("proto: wrong wireType = %d for field LocationIndices", wireType)
}
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestStack() *Stack {
orig := NewStack()
orig.LocationIndices = []int32{int32(0), int32(13)}
return orig
}
func GenTestStackPtrSlice() []*Stack {
orig := make([]*Stack, 5)
orig[0] = NewStack()
orig[1] = GenTestStack()
orig[2] = NewStack()
orig[3] = GenTestStack()
orig[4] = NewStack()
return orig
}
func GenTestStackSlice() []Stack {
orig := make([]Stack, 5)
orig[1] = *GenTestStack()
orig[3] = *GenTestStack()
return orig
}
================================================
FILE: pdata/internal/generated_proto_stack_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpprofiles "go.opentelemetry.io/proto/slim/otlp/profiles/v1development"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyStack(t *testing.T) {
for name, src := range genTestEncodingValuesStack() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewStack()
CopyStack(dest, src)
assert.Equal(t, src, dest)
CopyStack(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyStackSlice(t *testing.T) {
src := []Stack{}
dest := []Stack{}
// Test CopyTo empty
dest = CopyStackSlice(dest, src)
assert.Equal(t, []Stack{}, dest)
// Test CopyTo larger slice
src = GenTestStackSlice()
dest = CopyStackSlice(dest, src)
assert.Equal(t, GenTestStackSlice(), dest)
// Test CopyTo same size slice
dest = CopyStackSlice(dest, src)
assert.Equal(t, GenTestStackSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyStackSlice(dest, []Stack{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyStackSlice(dest, src)
assert.Equal(t, GenTestStackSlice(), dest)
}
func TestCopyStackPtrSlice(t *testing.T) {
src := []*Stack{}
dest := []*Stack{}
// Test CopyTo empty
dest = CopyStackPtrSlice(dest, src)
assert.Equal(t, []*Stack{}, dest)
// Test CopyTo larger slice
src = GenTestStackPtrSlice()
dest = CopyStackPtrSlice(dest, src)
assert.Equal(t, GenTestStackPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyStackPtrSlice(dest, src)
assert.Equal(t, GenTestStackPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyStackPtrSlice(dest, []*Stack{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyStackPtrSlice(dest, src)
assert.Equal(t, GenTestStackPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONStackUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewStack()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewStack(), dest)
}
func TestMarshalAndUnmarshalJSONStack(t *testing.T) {
for name, src := range genTestEncodingValuesStack() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewStack()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteStack(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoStackFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesStack() {
t.Run(name, func(t *testing.T) {
dest := NewStack()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoStackUnknown(t *testing.T) {
dest := NewStack()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewStack(), dest)
}
func TestMarshalAndUnmarshalProtoStack(t *testing.T) {
for name, src := range genTestEncodingValuesStack() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewStack()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteStack(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufStack(t *testing.T) {
for name, src := range genTestEncodingValuesStack() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpprofiles.Stack{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewStack()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesStack() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"LocationIndices/wrong_wire_type": {0xc},
"LocationIndices/missing_value": {0xa},
}
}
func genTestEncodingValuesStack() map[string]*Stack {
return map[string]*Stack{
"empty": NewStack(),
"LocationIndices/test": {LocationIndices: []int32{int32(0), int32(13)}},
}
}
================================================
FILE: pdata/internal/generated_proto_status.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// Status is an optional final status for this span. Semantically, when Status was not
// set, that means the span ended without errors and to assume Status.Ok (code = 0).
type Status struct {
Message string
Code StatusCode
}
var (
protoPoolStatus = sync.Pool{
New: func() any {
return &Status{}
},
}
)
func NewStatus() *Status {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &Status{}
}
return protoPoolStatus.Get().(*Status)
}
func DeleteStatus(orig *Status, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
orig.Reset()
if nullable {
protoPoolStatus.Put(orig)
}
}
func CopyStatus(dest, src *Status) *Status {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewStatus()
}
dest.Message = src.Message
dest.Code = src.Code
return dest
}
func CopyStatusSlice(dest, src []Status) []Status {
var newDest []Status
if cap(dest) < len(src) {
newDest = make([]Status, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteStatus(&dest[i], false)
}
}
for i := range src {
CopyStatus(&newDest[i], &src[i])
}
return newDest
}
func CopyStatusPtrSlice(dest, src []*Status) []*Status {
var newDest []*Status
if cap(dest) < len(src) {
newDest = make([]*Status, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewStatus()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteStatus(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewStatus()
}
}
for i := range src {
CopyStatus(newDest[i], src[i])
}
return newDest
}
func (orig *Status) Reset() {
*orig = Status{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *Status) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if orig.Message != "" {
dest.WriteObjectField("message")
dest.WriteString(orig.Message)
}
if int32(orig.Code) != 0 {
dest.WriteObjectField("code")
dest.WriteInt32(int32(orig.Code))
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *Status) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "message":
orig.Message = iter.ReadString()
case "code":
orig.Code = StatusCode(iter.ReadEnumValue(StatusCode_value))
default:
iter.Skip()
}
}
}
func (orig *Status) SizeProto() int {
var n int
var l int
_ = l
l = len(orig.Message)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
if orig.Code != StatusCode(0) {
n += 1 + proto.Sov(uint64(orig.Code))
}
return n
}
func (orig *Status) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
l = len(orig.Message)
if l > 0 {
pos -= l
copy(buf[pos:], orig.Message)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
}
if orig.Code != StatusCode(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.Code))
pos--
buf[pos] = 0x18
}
return len(buf) - pos
}
func (orig *Status) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Message = string(buf[startPos:pos])
case 3:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field Code", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.Code = StatusCode(num)
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestStatus() *Status {
orig := NewStatus()
orig.Message = "test_message"
orig.Code = StatusCode(13)
return orig
}
func GenTestStatusPtrSlice() []*Status {
orig := make([]*Status, 5)
orig[0] = NewStatus()
orig[1] = GenTestStatus()
orig[2] = NewStatus()
orig[3] = GenTestStatus()
orig[4] = NewStatus()
return orig
}
func GenTestStatusSlice() []Status {
orig := make([]Status, 5)
orig[1] = *GenTestStatus()
orig[3] = *GenTestStatus()
return orig
}
================================================
FILE: pdata/internal/generated_proto_status_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlptrace "go.opentelemetry.io/proto/slim/otlp/trace/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyStatus(t *testing.T) {
for name, src := range genTestEncodingValuesStatus() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewStatus()
CopyStatus(dest, src)
assert.Equal(t, src, dest)
CopyStatus(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyStatusSlice(t *testing.T) {
src := []Status{}
dest := []Status{}
// Test CopyTo empty
dest = CopyStatusSlice(dest, src)
assert.Equal(t, []Status{}, dest)
// Test CopyTo larger slice
src = GenTestStatusSlice()
dest = CopyStatusSlice(dest, src)
assert.Equal(t, GenTestStatusSlice(), dest)
// Test CopyTo same size slice
dest = CopyStatusSlice(dest, src)
assert.Equal(t, GenTestStatusSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyStatusSlice(dest, []Status{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyStatusSlice(dest, src)
assert.Equal(t, GenTestStatusSlice(), dest)
}
func TestCopyStatusPtrSlice(t *testing.T) {
src := []*Status{}
dest := []*Status{}
// Test CopyTo empty
dest = CopyStatusPtrSlice(dest, src)
assert.Equal(t, []*Status{}, dest)
// Test CopyTo larger slice
src = GenTestStatusPtrSlice()
dest = CopyStatusPtrSlice(dest, src)
assert.Equal(t, GenTestStatusPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyStatusPtrSlice(dest, src)
assert.Equal(t, GenTestStatusPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyStatusPtrSlice(dest, []*Status{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyStatusPtrSlice(dest, src)
assert.Equal(t, GenTestStatusPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONStatusUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewStatus()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewStatus(), dest)
}
func TestMarshalAndUnmarshalJSONStatus(t *testing.T) {
for name, src := range genTestEncodingValuesStatus() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewStatus()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteStatus(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoStatusFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesStatus() {
t.Run(name, func(t *testing.T) {
dest := NewStatus()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoStatusUnknown(t *testing.T) {
dest := NewStatus()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewStatus(), dest)
}
func TestMarshalAndUnmarshalProtoStatus(t *testing.T) {
for name, src := range genTestEncodingValuesStatus() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewStatus()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteStatus(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufStatus(t *testing.T) {
for name, src := range genTestEncodingValuesStatus() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlptrace.Status{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewStatus()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesStatus() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"Message/wrong_wire_type": {0x14},
"Message/missing_value": {0x12},
"Code/wrong_wire_type": {0x1c},
"Code/missing_value": {0x18},
}
}
func genTestEncodingValuesStatus() map[string]*Status {
return map[string]*Status{
"empty": NewStatus(),
"Message/test": {Message: "test_message"},
"Code/test": {Code: StatusCode(13)},
}
}
================================================
FILE: pdata/internal/generated_proto_sum.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// Sum represents the type of a numeric metric that is calculated as a sum of all reported measurements over a time interval.
type Sum struct {
DataPoints []*NumberDataPoint
AggregationTemporality AggregationTemporality
IsMonotonic bool
}
var (
protoPoolSum = sync.Pool{
New: func() any {
return &Sum{}
},
}
)
func NewSum() *Sum {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &Sum{}
}
return protoPoolSum.Get().(*Sum)
}
func DeleteSum(orig *Sum, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
for i := range orig.DataPoints {
DeleteNumberDataPoint(orig.DataPoints[i], true)
}
orig.Reset()
if nullable {
protoPoolSum.Put(orig)
}
}
func CopySum(dest, src *Sum) *Sum {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewSum()
}
dest.DataPoints = CopyNumberDataPointPtrSlice(dest.DataPoints, src.DataPoints)
dest.AggregationTemporality = src.AggregationTemporality
dest.IsMonotonic = src.IsMonotonic
return dest
}
func CopySumSlice(dest, src []Sum) []Sum {
var newDest []Sum
if cap(dest) < len(src) {
newDest = make([]Sum, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteSum(&dest[i], false)
}
}
for i := range src {
CopySum(&newDest[i], &src[i])
}
return newDest
}
func CopySumPtrSlice(dest, src []*Sum) []*Sum {
var newDest []*Sum
if cap(dest) < len(src) {
newDest = make([]*Sum, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewSum()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteSum(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewSum()
}
}
for i := range src {
CopySum(newDest[i], src[i])
}
return newDest
}
func (orig *Sum) Reset() {
*orig = Sum{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *Sum) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if len(orig.DataPoints) > 0 {
dest.WriteObjectField("dataPoints")
dest.WriteArrayStart()
orig.DataPoints[0].MarshalJSON(dest)
for i := 1; i < len(orig.DataPoints); i++ {
dest.WriteMore()
orig.DataPoints[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if int32(orig.AggregationTemporality) != 0 {
dest.WriteObjectField("aggregationTemporality")
dest.WriteInt32(int32(orig.AggregationTemporality))
}
if orig.IsMonotonic != false {
dest.WriteObjectField("isMonotonic")
dest.WriteBool(orig.IsMonotonic)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *Sum) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "dataPoints", "data_points":
for iter.ReadArray() {
orig.DataPoints = append(orig.DataPoints, NewNumberDataPoint())
orig.DataPoints[len(orig.DataPoints)-1].UnmarshalJSON(iter)
}
case "aggregationTemporality", "aggregation_temporality":
orig.AggregationTemporality = AggregationTemporality(iter.ReadEnumValue(AggregationTemporality_value))
case "isMonotonic", "is_monotonic":
orig.IsMonotonic = iter.ReadBool()
default:
iter.Skip()
}
}
}
func (orig *Sum) SizeProto() int {
var n int
var l int
_ = l
for i := range orig.DataPoints {
l = orig.DataPoints[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
if orig.AggregationTemporality != AggregationTemporality(0) {
n += 1 + proto.Sov(uint64(orig.AggregationTemporality))
}
if orig.IsMonotonic != false {
n += 2
}
return n
}
func (orig *Sum) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
for i := len(orig.DataPoints) - 1; i >= 0; i-- {
l = orig.DataPoints[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
}
if orig.AggregationTemporality != AggregationTemporality(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.AggregationTemporality))
pos--
buf[pos] = 0x10
}
if orig.IsMonotonic != false {
pos--
if orig.IsMonotonic {
buf[pos] = 1
} else {
buf[pos] = 0
}
pos--
buf[pos] = 0x18
}
return len(buf) - pos
}
func (orig *Sum) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field DataPoints", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.DataPoints = append(orig.DataPoints, NewNumberDataPoint())
err = orig.DataPoints[len(orig.DataPoints)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 2:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field AggregationTemporality", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.AggregationTemporality = AggregationTemporality(num)
case 3:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field IsMonotonic", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.IsMonotonic = num != 0
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestSum() *Sum {
orig := NewSum()
orig.DataPoints = []*NumberDataPoint{{}, GenTestNumberDataPoint()}
orig.AggregationTemporality = AggregationTemporality(13)
orig.IsMonotonic = true
return orig
}
func GenTestSumPtrSlice() []*Sum {
orig := make([]*Sum, 5)
orig[0] = NewSum()
orig[1] = GenTestSum()
orig[2] = NewSum()
orig[3] = GenTestSum()
orig[4] = NewSum()
return orig
}
func GenTestSumSlice() []Sum {
orig := make([]Sum, 5)
orig[1] = *GenTestSum()
orig[3] = *GenTestSum()
return orig
}
================================================
FILE: pdata/internal/generated_proto_sum_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpmetrics "go.opentelemetry.io/proto/slim/otlp/metrics/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopySum(t *testing.T) {
for name, src := range genTestEncodingValuesSum() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewSum()
CopySum(dest, src)
assert.Equal(t, src, dest)
CopySum(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopySumSlice(t *testing.T) {
src := []Sum{}
dest := []Sum{}
// Test CopyTo empty
dest = CopySumSlice(dest, src)
assert.Equal(t, []Sum{}, dest)
// Test CopyTo larger slice
src = GenTestSumSlice()
dest = CopySumSlice(dest, src)
assert.Equal(t, GenTestSumSlice(), dest)
// Test CopyTo same size slice
dest = CopySumSlice(dest, src)
assert.Equal(t, GenTestSumSlice(), dest)
// Test CopyTo smaller size slice
dest = CopySumSlice(dest, []Sum{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopySumSlice(dest, src)
assert.Equal(t, GenTestSumSlice(), dest)
}
func TestCopySumPtrSlice(t *testing.T) {
src := []*Sum{}
dest := []*Sum{}
// Test CopyTo empty
dest = CopySumPtrSlice(dest, src)
assert.Equal(t, []*Sum{}, dest)
// Test CopyTo larger slice
src = GenTestSumPtrSlice()
dest = CopySumPtrSlice(dest, src)
assert.Equal(t, GenTestSumPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopySumPtrSlice(dest, src)
assert.Equal(t, GenTestSumPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopySumPtrSlice(dest, []*Sum{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopySumPtrSlice(dest, src)
assert.Equal(t, GenTestSumPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONSumUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewSum()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewSum(), dest)
}
func TestMarshalAndUnmarshalJSONSum(t *testing.T) {
for name, src := range genTestEncodingValuesSum() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewSum()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteSum(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoSumFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesSum() {
t.Run(name, func(t *testing.T) {
dest := NewSum()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoSumUnknown(t *testing.T) {
dest := NewSum()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewSum(), dest)
}
func TestMarshalAndUnmarshalProtoSum(t *testing.T) {
for name, src := range genTestEncodingValuesSum() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewSum()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteSum(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufSum(t *testing.T) {
for name, src := range genTestEncodingValuesSum() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpmetrics.Sum{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewSum()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesSum() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"DataPoints/wrong_wire_type": {0xc},
"DataPoints/missing_value": {0xa},
"AggregationTemporality/wrong_wire_type": {0x14},
"AggregationTemporality/missing_value": {0x10},
"IsMonotonic/wrong_wire_type": {0x1c},
"IsMonotonic/missing_value": {0x18},
}
}
func genTestEncodingValuesSum() map[string]*Sum {
return map[string]*Sum{
"empty": NewSum(),
"DataPoints/test": {DataPoints: []*NumberDataPoint{{}, GenTestNumberDataPoint()}},
"AggregationTemporality/test": {AggregationTemporality: AggregationTemporality(13)},
"IsMonotonic/test": {IsMonotonic: true},
}
}
================================================
FILE: pdata/internal/generated_proto_summary.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// Summary represents the type of a metric that is calculated by aggregating as a Summary of all reported double measurements over a time interval.
type Summary struct {
DataPoints []*SummaryDataPoint
}
var (
protoPoolSummary = sync.Pool{
New: func() any {
return &Summary{}
},
}
)
func NewSummary() *Summary {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &Summary{}
}
return protoPoolSummary.Get().(*Summary)
}
func DeleteSummary(orig *Summary, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
for i := range orig.DataPoints {
DeleteSummaryDataPoint(orig.DataPoints[i], true)
}
orig.Reset()
if nullable {
protoPoolSummary.Put(orig)
}
}
func CopySummary(dest, src *Summary) *Summary {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewSummary()
}
dest.DataPoints = CopySummaryDataPointPtrSlice(dest.DataPoints, src.DataPoints)
return dest
}
func CopySummarySlice(dest, src []Summary) []Summary {
var newDest []Summary
if cap(dest) < len(src) {
newDest = make([]Summary, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteSummary(&dest[i], false)
}
}
for i := range src {
CopySummary(&newDest[i], &src[i])
}
return newDest
}
func CopySummaryPtrSlice(dest, src []*Summary) []*Summary {
var newDest []*Summary
if cap(dest) < len(src) {
newDest = make([]*Summary, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewSummary()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteSummary(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewSummary()
}
}
for i := range src {
CopySummary(newDest[i], src[i])
}
return newDest
}
func (orig *Summary) Reset() {
*orig = Summary{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *Summary) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if len(orig.DataPoints) > 0 {
dest.WriteObjectField("dataPoints")
dest.WriteArrayStart()
orig.DataPoints[0].MarshalJSON(dest)
for i := 1; i < len(orig.DataPoints); i++ {
dest.WriteMore()
orig.DataPoints[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *Summary) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "dataPoints", "data_points":
for iter.ReadArray() {
orig.DataPoints = append(orig.DataPoints, NewSummaryDataPoint())
orig.DataPoints[len(orig.DataPoints)-1].UnmarshalJSON(iter)
}
default:
iter.Skip()
}
}
}
func (orig *Summary) SizeProto() int {
var n int
var l int
_ = l
for i := range orig.DataPoints {
l = orig.DataPoints[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *Summary) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
for i := len(orig.DataPoints) - 1; i >= 0; i-- {
l = orig.DataPoints[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
}
return len(buf) - pos
}
func (orig *Summary) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field DataPoints", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.DataPoints = append(orig.DataPoints, NewSummaryDataPoint())
err = orig.DataPoints[len(orig.DataPoints)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestSummary() *Summary {
orig := NewSummary()
orig.DataPoints = []*SummaryDataPoint{{}, GenTestSummaryDataPoint()}
return orig
}
func GenTestSummaryPtrSlice() []*Summary {
orig := make([]*Summary, 5)
orig[0] = NewSummary()
orig[1] = GenTestSummary()
orig[2] = NewSummary()
orig[3] = GenTestSummary()
orig[4] = NewSummary()
return orig
}
func GenTestSummarySlice() []Summary {
orig := make([]Summary, 5)
orig[1] = *GenTestSummary()
orig[3] = *GenTestSummary()
return orig
}
================================================
FILE: pdata/internal/generated_proto_summary_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpmetrics "go.opentelemetry.io/proto/slim/otlp/metrics/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopySummary(t *testing.T) {
for name, src := range genTestEncodingValuesSummary() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewSummary()
CopySummary(dest, src)
assert.Equal(t, src, dest)
CopySummary(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopySummarySlice(t *testing.T) {
src := []Summary{}
dest := []Summary{}
// Test CopyTo empty
dest = CopySummarySlice(dest, src)
assert.Equal(t, []Summary{}, dest)
// Test CopyTo larger slice
src = GenTestSummarySlice()
dest = CopySummarySlice(dest, src)
assert.Equal(t, GenTestSummarySlice(), dest)
// Test CopyTo same size slice
dest = CopySummarySlice(dest, src)
assert.Equal(t, GenTestSummarySlice(), dest)
// Test CopyTo smaller size slice
dest = CopySummarySlice(dest, []Summary{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopySummarySlice(dest, src)
assert.Equal(t, GenTestSummarySlice(), dest)
}
func TestCopySummaryPtrSlice(t *testing.T) {
src := []*Summary{}
dest := []*Summary{}
// Test CopyTo empty
dest = CopySummaryPtrSlice(dest, src)
assert.Equal(t, []*Summary{}, dest)
// Test CopyTo larger slice
src = GenTestSummaryPtrSlice()
dest = CopySummaryPtrSlice(dest, src)
assert.Equal(t, GenTestSummaryPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopySummaryPtrSlice(dest, src)
assert.Equal(t, GenTestSummaryPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopySummaryPtrSlice(dest, []*Summary{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopySummaryPtrSlice(dest, src)
assert.Equal(t, GenTestSummaryPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONSummaryUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewSummary()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewSummary(), dest)
}
func TestMarshalAndUnmarshalJSONSummary(t *testing.T) {
for name, src := range genTestEncodingValuesSummary() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewSummary()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteSummary(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoSummaryFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesSummary() {
t.Run(name, func(t *testing.T) {
dest := NewSummary()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoSummaryUnknown(t *testing.T) {
dest := NewSummary()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewSummary(), dest)
}
func TestMarshalAndUnmarshalProtoSummary(t *testing.T) {
for name, src := range genTestEncodingValuesSummary() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewSummary()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteSummary(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufSummary(t *testing.T) {
for name, src := range genTestEncodingValuesSummary() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpmetrics.Summary{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewSummary()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesSummary() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"DataPoints/wrong_wire_type": {0xc},
"DataPoints/missing_value": {0xa},
}
}
func genTestEncodingValuesSummary() map[string]*Summary {
return map[string]*Summary{
"empty": NewSummary(),
"DataPoints/test": {DataPoints: []*SummaryDataPoint{{}, GenTestSummaryDataPoint()}},
}
}
================================================
FILE: pdata/internal/generated_proto_summarydatapoint.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"encoding/binary"
"fmt"
"math"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// SummaryDataPoint is a single data point in a timeseries that describes the time-varying values of a Summary of double values.
type SummaryDataPoint struct {
Attributes []KeyValue
QuantileValues []*SummaryDataPointValueAtQuantile
StartTimeUnixNano uint64
TimeUnixNano uint64
Count uint64
Sum float64
Flags uint32
}
var (
protoPoolSummaryDataPoint = sync.Pool{
New: func() any {
return &SummaryDataPoint{}
},
}
)
func NewSummaryDataPoint() *SummaryDataPoint {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &SummaryDataPoint{}
}
return protoPoolSummaryDataPoint.Get().(*SummaryDataPoint)
}
func DeleteSummaryDataPoint(orig *SummaryDataPoint, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
for i := range orig.Attributes {
DeleteKeyValue(&orig.Attributes[i], false)
}
for i := range orig.QuantileValues {
DeleteSummaryDataPointValueAtQuantile(orig.QuantileValues[i], true)
}
orig.Reset()
if nullable {
protoPoolSummaryDataPoint.Put(orig)
}
}
func CopySummaryDataPoint(dest, src *SummaryDataPoint) *SummaryDataPoint {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewSummaryDataPoint()
}
dest.Attributes = CopyKeyValueSlice(dest.Attributes, src.Attributes)
dest.StartTimeUnixNano = src.StartTimeUnixNano
dest.TimeUnixNano = src.TimeUnixNano
dest.Count = src.Count
dest.Sum = src.Sum
dest.QuantileValues = CopySummaryDataPointValueAtQuantilePtrSlice(dest.QuantileValues, src.QuantileValues)
dest.Flags = src.Flags
return dest
}
func CopySummaryDataPointSlice(dest, src []SummaryDataPoint) []SummaryDataPoint {
var newDest []SummaryDataPoint
if cap(dest) < len(src) {
newDest = make([]SummaryDataPoint, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteSummaryDataPoint(&dest[i], false)
}
}
for i := range src {
CopySummaryDataPoint(&newDest[i], &src[i])
}
return newDest
}
func CopySummaryDataPointPtrSlice(dest, src []*SummaryDataPoint) []*SummaryDataPoint {
var newDest []*SummaryDataPoint
if cap(dest) < len(src) {
newDest = make([]*SummaryDataPoint, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewSummaryDataPoint()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteSummaryDataPoint(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewSummaryDataPoint()
}
}
for i := range src {
CopySummaryDataPoint(newDest[i], src[i])
}
return newDest
}
func (orig *SummaryDataPoint) Reset() {
*orig = SummaryDataPoint{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *SummaryDataPoint) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if len(orig.Attributes) > 0 {
dest.WriteObjectField("attributes")
dest.WriteArrayStart()
orig.Attributes[0].MarshalJSON(dest)
for i := 1; i < len(orig.Attributes); i++ {
dest.WriteMore()
orig.Attributes[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if orig.StartTimeUnixNano != uint64(0) {
dest.WriteObjectField("startTimeUnixNano")
dest.WriteUint64(orig.StartTimeUnixNano)
}
if orig.TimeUnixNano != uint64(0) {
dest.WriteObjectField("timeUnixNano")
dest.WriteUint64(orig.TimeUnixNano)
}
if orig.Count != uint64(0) {
dest.WriteObjectField("count")
dest.WriteUint64(orig.Count)
}
if orig.Sum != float64(0) {
dest.WriteObjectField("sum")
dest.WriteFloat64(orig.Sum)
}
if len(orig.QuantileValues) > 0 {
dest.WriteObjectField("quantileValues")
dest.WriteArrayStart()
orig.QuantileValues[0].MarshalJSON(dest)
for i := 1; i < len(orig.QuantileValues); i++ {
dest.WriteMore()
orig.QuantileValues[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
if orig.Flags != uint32(0) {
dest.WriteObjectField("flags")
dest.WriteUint32(orig.Flags)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *SummaryDataPoint) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "attributes":
for iter.ReadArray() {
orig.Attributes = append(orig.Attributes, KeyValue{})
orig.Attributes[len(orig.Attributes)-1].UnmarshalJSON(iter)
}
case "startTimeUnixNano", "start_time_unix_nano":
orig.StartTimeUnixNano = iter.ReadUint64()
case "timeUnixNano", "time_unix_nano":
orig.TimeUnixNano = iter.ReadUint64()
case "count":
orig.Count = iter.ReadUint64()
case "sum":
orig.Sum = iter.ReadFloat64()
case "quantileValues", "quantile_values":
for iter.ReadArray() {
orig.QuantileValues = append(orig.QuantileValues, NewSummaryDataPointValueAtQuantile())
orig.QuantileValues[len(orig.QuantileValues)-1].UnmarshalJSON(iter)
}
case "flags":
orig.Flags = iter.ReadUint32()
default:
iter.Skip()
}
}
}
func (orig *SummaryDataPoint) SizeProto() int {
var n int
var l int
_ = l
for i := range orig.Attributes {
l = orig.Attributes[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
if orig.StartTimeUnixNano != uint64(0) {
n += 9
}
if orig.TimeUnixNano != uint64(0) {
n += 9
}
if orig.Count != uint64(0) {
n += 9
}
if orig.Sum != float64(0) {
n += 9
}
for i := range orig.QuantileValues {
l = orig.QuantileValues[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
if orig.Flags != uint32(0) {
n += 1 + proto.Sov(uint64(orig.Flags))
}
return n
}
func (orig *SummaryDataPoint) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
for i := len(orig.Attributes) - 1; i >= 0; i-- {
l = orig.Attributes[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x3a
}
if orig.StartTimeUnixNano != uint64(0) {
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], uint64(orig.StartTimeUnixNano))
pos--
buf[pos] = 0x11
}
if orig.TimeUnixNano != uint64(0) {
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], uint64(orig.TimeUnixNano))
pos--
buf[pos] = 0x19
}
if orig.Count != uint64(0) {
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], uint64(orig.Count))
pos--
buf[pos] = 0x21
}
if orig.Sum != float64(0) {
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], math.Float64bits(orig.Sum))
pos--
buf[pos] = 0x29
}
for i := len(orig.QuantileValues) - 1; i >= 0; i-- {
l = orig.QuantileValues[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x32
}
if orig.Flags != uint32(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.Flags))
pos--
buf[pos] = 0x40
}
return len(buf) - pos
}
func (orig *SummaryDataPoint) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 7:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Attributes", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Attributes = append(orig.Attributes, KeyValue{})
err = orig.Attributes[len(orig.Attributes)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 2:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field StartTimeUnixNano", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
orig.StartTimeUnixNano = uint64(num)
case 3:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field TimeUnixNano", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
orig.TimeUnixNano = uint64(num)
case 4:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field Count", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
orig.Count = uint64(num)
case 5:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field Sum", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
orig.Sum = math.Float64frombits(num)
case 6:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field QuantileValues", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.QuantileValues = append(orig.QuantileValues, NewSummaryDataPointValueAtQuantile())
err = orig.QuantileValues[len(orig.QuantileValues)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 8:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field Flags", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.Flags = uint32(num)
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestSummaryDataPoint() *SummaryDataPoint {
orig := NewSummaryDataPoint()
orig.Attributes = []KeyValue{{}, *GenTestKeyValue()}
orig.StartTimeUnixNano = uint64(13)
orig.TimeUnixNano = uint64(13)
orig.Count = uint64(13)
orig.Sum = float64(3.1415926)
orig.QuantileValues = []*SummaryDataPointValueAtQuantile{{}, GenTestSummaryDataPointValueAtQuantile()}
orig.Flags = uint32(13)
return orig
}
func GenTestSummaryDataPointPtrSlice() []*SummaryDataPoint {
orig := make([]*SummaryDataPoint, 5)
orig[0] = NewSummaryDataPoint()
orig[1] = GenTestSummaryDataPoint()
orig[2] = NewSummaryDataPoint()
orig[3] = GenTestSummaryDataPoint()
orig[4] = NewSummaryDataPoint()
return orig
}
func GenTestSummaryDataPointSlice() []SummaryDataPoint {
orig := make([]SummaryDataPoint, 5)
orig[1] = *GenTestSummaryDataPoint()
orig[3] = *GenTestSummaryDataPoint()
return orig
}
================================================
FILE: pdata/internal/generated_proto_summarydatapoint_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpmetrics "go.opentelemetry.io/proto/slim/otlp/metrics/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopySummaryDataPoint(t *testing.T) {
for name, src := range genTestEncodingValuesSummaryDataPoint() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewSummaryDataPoint()
CopySummaryDataPoint(dest, src)
assert.Equal(t, src, dest)
CopySummaryDataPoint(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopySummaryDataPointSlice(t *testing.T) {
src := []SummaryDataPoint{}
dest := []SummaryDataPoint{}
// Test CopyTo empty
dest = CopySummaryDataPointSlice(dest, src)
assert.Equal(t, []SummaryDataPoint{}, dest)
// Test CopyTo larger slice
src = GenTestSummaryDataPointSlice()
dest = CopySummaryDataPointSlice(dest, src)
assert.Equal(t, GenTestSummaryDataPointSlice(), dest)
// Test CopyTo same size slice
dest = CopySummaryDataPointSlice(dest, src)
assert.Equal(t, GenTestSummaryDataPointSlice(), dest)
// Test CopyTo smaller size slice
dest = CopySummaryDataPointSlice(dest, []SummaryDataPoint{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopySummaryDataPointSlice(dest, src)
assert.Equal(t, GenTestSummaryDataPointSlice(), dest)
}
func TestCopySummaryDataPointPtrSlice(t *testing.T) {
src := []*SummaryDataPoint{}
dest := []*SummaryDataPoint{}
// Test CopyTo empty
dest = CopySummaryDataPointPtrSlice(dest, src)
assert.Equal(t, []*SummaryDataPoint{}, dest)
// Test CopyTo larger slice
src = GenTestSummaryDataPointPtrSlice()
dest = CopySummaryDataPointPtrSlice(dest, src)
assert.Equal(t, GenTestSummaryDataPointPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopySummaryDataPointPtrSlice(dest, src)
assert.Equal(t, GenTestSummaryDataPointPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopySummaryDataPointPtrSlice(dest, []*SummaryDataPoint{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopySummaryDataPointPtrSlice(dest, src)
assert.Equal(t, GenTestSummaryDataPointPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONSummaryDataPointUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewSummaryDataPoint()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewSummaryDataPoint(), dest)
}
func TestMarshalAndUnmarshalJSONSummaryDataPoint(t *testing.T) {
for name, src := range genTestEncodingValuesSummaryDataPoint() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewSummaryDataPoint()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteSummaryDataPoint(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoSummaryDataPointFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesSummaryDataPoint() {
t.Run(name, func(t *testing.T) {
dest := NewSummaryDataPoint()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoSummaryDataPointUnknown(t *testing.T) {
dest := NewSummaryDataPoint()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewSummaryDataPoint(), dest)
}
func TestMarshalAndUnmarshalProtoSummaryDataPoint(t *testing.T) {
for name, src := range genTestEncodingValuesSummaryDataPoint() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewSummaryDataPoint()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteSummaryDataPoint(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufSummaryDataPoint(t *testing.T) {
for name, src := range genTestEncodingValuesSummaryDataPoint() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpmetrics.SummaryDataPoint{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewSummaryDataPoint()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesSummaryDataPoint() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"Attributes/wrong_wire_type": {0x3c},
"Attributes/missing_value": {0x3a},
"StartTimeUnixNano/wrong_wire_type": {0x14},
"StartTimeUnixNano/missing_value": {0x11},
"TimeUnixNano/wrong_wire_type": {0x1c},
"TimeUnixNano/missing_value": {0x19},
"Count/wrong_wire_type": {0x24},
"Count/missing_value": {0x21},
"Sum/wrong_wire_type": {0x2c},
"Sum/missing_value": {0x29},
"QuantileValues/wrong_wire_type": {0x34},
"QuantileValues/missing_value": {0x32},
"Flags/wrong_wire_type": {0x44},
"Flags/missing_value": {0x40},
}
}
func genTestEncodingValuesSummaryDataPoint() map[string]*SummaryDataPoint {
return map[string]*SummaryDataPoint{
"empty": NewSummaryDataPoint(),
"Attributes/test": {Attributes: []KeyValue{{}, *GenTestKeyValue()}},
"StartTimeUnixNano/test": {StartTimeUnixNano: uint64(13)},
"TimeUnixNano/test": {TimeUnixNano: uint64(13)},
"Count/test": {Count: uint64(13)},
"Sum/test": {Sum: float64(3.1415926)},
"QuantileValues/test": {QuantileValues: []*SummaryDataPointValueAtQuantile{{}, GenTestSummaryDataPointValueAtQuantile()}},
"Flags/test": {Flags: uint32(13)},
}
}
================================================
FILE: pdata/internal/generated_proto_summarydatapointvalueatquantile.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"encoding/binary"
"fmt"
"math"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// SummaryDataPointValueAtQuantile is a quantile value within a Summary data point.
type SummaryDataPointValueAtQuantile struct {
Quantile float64
Value float64
}
var (
protoPoolSummaryDataPointValueAtQuantile = sync.Pool{
New: func() any {
return &SummaryDataPointValueAtQuantile{}
},
}
)
func NewSummaryDataPointValueAtQuantile() *SummaryDataPointValueAtQuantile {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &SummaryDataPointValueAtQuantile{}
}
return protoPoolSummaryDataPointValueAtQuantile.Get().(*SummaryDataPointValueAtQuantile)
}
func DeleteSummaryDataPointValueAtQuantile(orig *SummaryDataPointValueAtQuantile, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
orig.Reset()
if nullable {
protoPoolSummaryDataPointValueAtQuantile.Put(orig)
}
}
func CopySummaryDataPointValueAtQuantile(dest, src *SummaryDataPointValueAtQuantile) *SummaryDataPointValueAtQuantile {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewSummaryDataPointValueAtQuantile()
}
dest.Quantile = src.Quantile
dest.Value = src.Value
return dest
}
func CopySummaryDataPointValueAtQuantileSlice(dest, src []SummaryDataPointValueAtQuantile) []SummaryDataPointValueAtQuantile {
var newDest []SummaryDataPointValueAtQuantile
if cap(dest) < len(src) {
newDest = make([]SummaryDataPointValueAtQuantile, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteSummaryDataPointValueAtQuantile(&dest[i], false)
}
}
for i := range src {
CopySummaryDataPointValueAtQuantile(&newDest[i], &src[i])
}
return newDest
}
func CopySummaryDataPointValueAtQuantilePtrSlice(dest, src []*SummaryDataPointValueAtQuantile) []*SummaryDataPointValueAtQuantile {
var newDest []*SummaryDataPointValueAtQuantile
if cap(dest) < len(src) {
newDest = make([]*SummaryDataPointValueAtQuantile, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewSummaryDataPointValueAtQuantile()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteSummaryDataPointValueAtQuantile(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewSummaryDataPointValueAtQuantile()
}
}
for i := range src {
CopySummaryDataPointValueAtQuantile(newDest[i], src[i])
}
return newDest
}
func (orig *SummaryDataPointValueAtQuantile) Reset() {
*orig = SummaryDataPointValueAtQuantile{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *SummaryDataPointValueAtQuantile) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if orig.Quantile != float64(0) {
dest.WriteObjectField("quantile")
dest.WriteFloat64(orig.Quantile)
}
if orig.Value != float64(0) {
dest.WriteObjectField("value")
dest.WriteFloat64(orig.Value)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *SummaryDataPointValueAtQuantile) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "quantile":
orig.Quantile = iter.ReadFloat64()
case "value":
orig.Value = iter.ReadFloat64()
default:
iter.Skip()
}
}
}
func (orig *SummaryDataPointValueAtQuantile) SizeProto() int {
var n int
var l int
_ = l
if orig.Quantile != float64(0) {
n += 9
}
if orig.Value != float64(0) {
n += 9
}
return n
}
func (orig *SummaryDataPointValueAtQuantile) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
if orig.Quantile != float64(0) {
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], math.Float64bits(orig.Quantile))
pos--
buf[pos] = 0x9
}
if orig.Value != float64(0) {
pos -= 8
binary.LittleEndian.PutUint64(buf[pos:], math.Float64bits(orig.Value))
pos--
buf[pos] = 0x11
}
return len(buf) - pos
}
func (orig *SummaryDataPointValueAtQuantile) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field Quantile", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
orig.Quantile = math.Float64frombits(num)
case 2:
if wireType != proto.WireTypeI64 {
return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType)
}
var num uint64
num, pos, err = proto.ConsumeI64(buf, pos)
if err != nil {
return err
}
orig.Value = math.Float64frombits(num)
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestSummaryDataPointValueAtQuantile() *SummaryDataPointValueAtQuantile {
orig := NewSummaryDataPointValueAtQuantile()
orig.Quantile = float64(3.1415926)
orig.Value = float64(3.1415926)
return orig
}
func GenTestSummaryDataPointValueAtQuantilePtrSlice() []*SummaryDataPointValueAtQuantile {
orig := make([]*SummaryDataPointValueAtQuantile, 5)
orig[0] = NewSummaryDataPointValueAtQuantile()
orig[1] = GenTestSummaryDataPointValueAtQuantile()
orig[2] = NewSummaryDataPointValueAtQuantile()
orig[3] = GenTestSummaryDataPointValueAtQuantile()
orig[4] = NewSummaryDataPointValueAtQuantile()
return orig
}
func GenTestSummaryDataPointValueAtQuantileSlice() []SummaryDataPointValueAtQuantile {
orig := make([]SummaryDataPointValueAtQuantile, 5)
orig[1] = *GenTestSummaryDataPointValueAtQuantile()
orig[3] = *GenTestSummaryDataPointValueAtQuantile()
return orig
}
================================================
FILE: pdata/internal/generated_proto_summarydatapointvalueatquantile_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpmetrics "go.opentelemetry.io/proto/slim/otlp/metrics/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopySummaryDataPointValueAtQuantile(t *testing.T) {
for name, src := range genTestEncodingValuesSummaryDataPointValueAtQuantile() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewSummaryDataPointValueAtQuantile()
CopySummaryDataPointValueAtQuantile(dest, src)
assert.Equal(t, src, dest)
CopySummaryDataPointValueAtQuantile(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopySummaryDataPointValueAtQuantileSlice(t *testing.T) {
src := []SummaryDataPointValueAtQuantile{}
dest := []SummaryDataPointValueAtQuantile{}
// Test CopyTo empty
dest = CopySummaryDataPointValueAtQuantileSlice(dest, src)
assert.Equal(t, []SummaryDataPointValueAtQuantile{}, dest)
// Test CopyTo larger slice
src = GenTestSummaryDataPointValueAtQuantileSlice()
dest = CopySummaryDataPointValueAtQuantileSlice(dest, src)
assert.Equal(t, GenTestSummaryDataPointValueAtQuantileSlice(), dest)
// Test CopyTo same size slice
dest = CopySummaryDataPointValueAtQuantileSlice(dest, src)
assert.Equal(t, GenTestSummaryDataPointValueAtQuantileSlice(), dest)
// Test CopyTo smaller size slice
dest = CopySummaryDataPointValueAtQuantileSlice(dest, []SummaryDataPointValueAtQuantile{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopySummaryDataPointValueAtQuantileSlice(dest, src)
assert.Equal(t, GenTestSummaryDataPointValueAtQuantileSlice(), dest)
}
func TestCopySummaryDataPointValueAtQuantilePtrSlice(t *testing.T) {
src := []*SummaryDataPointValueAtQuantile{}
dest := []*SummaryDataPointValueAtQuantile{}
// Test CopyTo empty
dest = CopySummaryDataPointValueAtQuantilePtrSlice(dest, src)
assert.Equal(t, []*SummaryDataPointValueAtQuantile{}, dest)
// Test CopyTo larger slice
src = GenTestSummaryDataPointValueAtQuantilePtrSlice()
dest = CopySummaryDataPointValueAtQuantilePtrSlice(dest, src)
assert.Equal(t, GenTestSummaryDataPointValueAtQuantilePtrSlice(), dest)
// Test CopyTo same size slice
dest = CopySummaryDataPointValueAtQuantilePtrSlice(dest, src)
assert.Equal(t, GenTestSummaryDataPointValueAtQuantilePtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopySummaryDataPointValueAtQuantilePtrSlice(dest, []*SummaryDataPointValueAtQuantile{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopySummaryDataPointValueAtQuantilePtrSlice(dest, src)
assert.Equal(t, GenTestSummaryDataPointValueAtQuantilePtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONSummaryDataPointValueAtQuantileUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewSummaryDataPointValueAtQuantile()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewSummaryDataPointValueAtQuantile(), dest)
}
func TestMarshalAndUnmarshalJSONSummaryDataPointValueAtQuantile(t *testing.T) {
for name, src := range genTestEncodingValuesSummaryDataPointValueAtQuantile() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewSummaryDataPointValueAtQuantile()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteSummaryDataPointValueAtQuantile(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoSummaryDataPointValueAtQuantileFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesSummaryDataPointValueAtQuantile() {
t.Run(name, func(t *testing.T) {
dest := NewSummaryDataPointValueAtQuantile()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoSummaryDataPointValueAtQuantileUnknown(t *testing.T) {
dest := NewSummaryDataPointValueAtQuantile()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewSummaryDataPointValueAtQuantile(), dest)
}
func TestMarshalAndUnmarshalProtoSummaryDataPointValueAtQuantile(t *testing.T) {
for name, src := range genTestEncodingValuesSummaryDataPointValueAtQuantile() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewSummaryDataPointValueAtQuantile()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteSummaryDataPointValueAtQuantile(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufSummaryDataPointValueAtQuantile(t *testing.T) {
for name, src := range genTestEncodingValuesSummaryDataPointValueAtQuantile() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpmetrics.SummaryDataPoint_ValueAtQuantile{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewSummaryDataPointValueAtQuantile()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesSummaryDataPointValueAtQuantile() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"Quantile/wrong_wire_type": {0xc},
"Quantile/missing_value": {0x9},
"Value/wrong_wire_type": {0x14},
"Value/missing_value": {0x11},
}
}
func genTestEncodingValuesSummaryDataPointValueAtQuantile() map[string]*SummaryDataPointValueAtQuantile {
return map[string]*SummaryDataPointValueAtQuantile{
"empty": NewSummaryDataPointValueAtQuantile(),
"Quantile/test": {Quantile: float64(3.1415926)},
"Value/test": {Value: float64(3.1415926)},
}
}
================================================
FILE: pdata/internal/generated_proto_tcpaddr.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
type TCPAddr struct {
Zone string
IP []byte
Port int64
}
var (
protoPoolTCPAddr = sync.Pool{
New: func() any {
return &TCPAddr{}
},
}
)
func NewTCPAddr() *TCPAddr {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &TCPAddr{}
}
return protoPoolTCPAddr.Get().(*TCPAddr)
}
func DeleteTCPAddr(orig *TCPAddr, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
orig.Reset()
if nullable {
protoPoolTCPAddr.Put(orig)
}
}
func CopyTCPAddr(dest, src *TCPAddr) *TCPAddr {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewTCPAddr()
}
dest.IP = src.IP
dest.Port = src.Port
dest.Zone = src.Zone
return dest
}
func CopyTCPAddrSlice(dest, src []TCPAddr) []TCPAddr {
var newDest []TCPAddr
if cap(dest) < len(src) {
newDest = make([]TCPAddr, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteTCPAddr(&dest[i], false)
}
}
for i := range src {
CopyTCPAddr(&newDest[i], &src[i])
}
return newDest
}
func CopyTCPAddrPtrSlice(dest, src []*TCPAddr) []*TCPAddr {
var newDest []*TCPAddr
if cap(dest) < len(src) {
newDest = make([]*TCPAddr, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewTCPAddr()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteTCPAddr(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewTCPAddr()
}
}
for i := range src {
CopyTCPAddr(newDest[i], src[i])
}
return newDest
}
func (orig *TCPAddr) Reset() {
*orig = TCPAddr{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *TCPAddr) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if len(orig.IP) > 0 {
dest.WriteObjectField("iP")
dest.WriteBytes(orig.IP)
}
if orig.Port != int64(0) {
dest.WriteObjectField("port")
dest.WriteInt64(orig.Port)
}
if orig.Zone != "" {
dest.WriteObjectField("zone")
dest.WriteString(orig.Zone)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *TCPAddr) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "iP":
orig.IP = iter.ReadBytes()
case "port":
orig.Port = iter.ReadInt64()
case "zone":
orig.Zone = iter.ReadString()
default:
iter.Skip()
}
}
}
func (orig *TCPAddr) SizeProto() int {
var n int
var l int
_ = l
l = len(orig.IP)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
if orig.Port != int64(0) {
n += 1 + proto.Sov(uint64(orig.Port))
}
l = len(orig.Zone)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *TCPAddr) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
l = len(orig.IP)
if l > 0 {
pos -= l
copy(buf[pos:], orig.IP)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
}
if orig.Port != int64(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.Port))
pos--
buf[pos] = 0x10
}
l = len(orig.Zone)
if l > 0 {
pos -= l
copy(buf[pos:], orig.Zone)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x1a
}
return len(buf) - pos
}
func (orig *TCPAddr) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field IP", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
if length != 0 {
orig.IP = make([]byte, length)
copy(orig.IP, buf[startPos:pos])
}
case 2:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field Port", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.Port = int64(num)
case 3:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Zone", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Zone = string(buf[startPos:pos])
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestTCPAddr() *TCPAddr {
orig := NewTCPAddr()
orig.IP = []byte{1, 2, 3}
orig.Port = int64(13)
orig.Zone = "test_zone"
return orig
}
func GenTestTCPAddrPtrSlice() []*TCPAddr {
orig := make([]*TCPAddr, 5)
orig[0] = NewTCPAddr()
orig[1] = GenTestTCPAddr()
orig[2] = NewTCPAddr()
orig[3] = GenTestTCPAddr()
orig[4] = NewTCPAddr()
return orig
}
func GenTestTCPAddrSlice() []TCPAddr {
orig := make([]TCPAddr, 5)
orig[1] = *GenTestTCPAddr()
orig[3] = *GenTestTCPAddr()
return orig
}
================================================
FILE: pdata/internal/generated_proto_tcpaddr_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/emptypb"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyTCPAddr(t *testing.T) {
for name, src := range genTestEncodingValuesTCPAddr() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewTCPAddr()
CopyTCPAddr(dest, src)
assert.Equal(t, src, dest)
CopyTCPAddr(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyTCPAddrSlice(t *testing.T) {
src := []TCPAddr{}
dest := []TCPAddr{}
// Test CopyTo empty
dest = CopyTCPAddrSlice(dest, src)
assert.Equal(t, []TCPAddr{}, dest)
// Test CopyTo larger slice
src = GenTestTCPAddrSlice()
dest = CopyTCPAddrSlice(dest, src)
assert.Equal(t, GenTestTCPAddrSlice(), dest)
// Test CopyTo same size slice
dest = CopyTCPAddrSlice(dest, src)
assert.Equal(t, GenTestTCPAddrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyTCPAddrSlice(dest, []TCPAddr{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyTCPAddrSlice(dest, src)
assert.Equal(t, GenTestTCPAddrSlice(), dest)
}
func TestCopyTCPAddrPtrSlice(t *testing.T) {
src := []*TCPAddr{}
dest := []*TCPAddr{}
// Test CopyTo empty
dest = CopyTCPAddrPtrSlice(dest, src)
assert.Equal(t, []*TCPAddr{}, dest)
// Test CopyTo larger slice
src = GenTestTCPAddrPtrSlice()
dest = CopyTCPAddrPtrSlice(dest, src)
assert.Equal(t, GenTestTCPAddrPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyTCPAddrPtrSlice(dest, src)
assert.Equal(t, GenTestTCPAddrPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyTCPAddrPtrSlice(dest, []*TCPAddr{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyTCPAddrPtrSlice(dest, src)
assert.Equal(t, GenTestTCPAddrPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONTCPAddrUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewTCPAddr()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewTCPAddr(), dest)
}
func TestMarshalAndUnmarshalJSONTCPAddr(t *testing.T) {
for name, src := range genTestEncodingValuesTCPAddr() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewTCPAddr()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteTCPAddr(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoTCPAddrFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesTCPAddr() {
t.Run(name, func(t *testing.T) {
dest := NewTCPAddr()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoTCPAddrUnknown(t *testing.T) {
dest := NewTCPAddr()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewTCPAddr(), dest)
}
func TestMarshalAndUnmarshalProtoTCPAddr(t *testing.T) {
for name, src := range genTestEncodingValuesTCPAddr() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewTCPAddr()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteTCPAddr(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufTCPAddr(t *testing.T) {
for name, src := range genTestEncodingValuesTCPAddr() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &emptypb.Empty{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewTCPAddr()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesTCPAddr() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"IP/wrong_wire_type": {0xc},
"IP/missing_value": {0xa},
"Port/wrong_wire_type": {0x14},
"Port/missing_value": {0x10},
"Zone/wrong_wire_type": {0x1c},
"Zone/missing_value": {0x1a},
}
}
func genTestEncodingValuesTCPAddr() map[string]*TCPAddr {
return map[string]*TCPAddr{
"empty": NewTCPAddr(),
"IP/test": {IP: []byte{1, 2, 3}},
"Port/test": {Port: int64(13)},
"Zone/test": {Zone: "test_zone"},
}
}
================================================
FILE: pdata/internal/generated_proto_tracesdata.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// TracesData represents the traces data that can be stored in a persistent storage,
// OR can be embedded by other protocols that transfer OTLP traces data but do not
// implement the OTLP protocol.
type TracesData struct {
ResourceSpans []*ResourceSpans
}
var (
protoPoolTracesData = sync.Pool{
New: func() any {
return &TracesData{}
},
}
)
func NewTracesData() *TracesData {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &TracesData{}
}
return protoPoolTracesData.Get().(*TracesData)
}
func DeleteTracesData(orig *TracesData, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
for i := range orig.ResourceSpans {
DeleteResourceSpans(orig.ResourceSpans[i], true)
}
orig.Reset()
if nullable {
protoPoolTracesData.Put(orig)
}
}
func CopyTracesData(dest, src *TracesData) *TracesData {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewTracesData()
}
dest.ResourceSpans = CopyResourceSpansPtrSlice(dest.ResourceSpans, src.ResourceSpans)
return dest
}
func CopyTracesDataSlice(dest, src []TracesData) []TracesData {
var newDest []TracesData
if cap(dest) < len(src) {
newDest = make([]TracesData, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteTracesData(&dest[i], false)
}
}
for i := range src {
CopyTracesData(&newDest[i], &src[i])
}
return newDest
}
func CopyTracesDataPtrSlice(dest, src []*TracesData) []*TracesData {
var newDest []*TracesData
if cap(dest) < len(src) {
newDest = make([]*TracesData, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewTracesData()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteTracesData(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewTracesData()
}
}
for i := range src {
CopyTracesData(newDest[i], src[i])
}
return newDest
}
func (orig *TracesData) Reset() {
*orig = TracesData{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *TracesData) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if len(orig.ResourceSpans) > 0 {
dest.WriteObjectField("resourceSpans")
dest.WriteArrayStart()
orig.ResourceSpans[0].MarshalJSON(dest)
for i := 1; i < len(orig.ResourceSpans); i++ {
dest.WriteMore()
orig.ResourceSpans[i].MarshalJSON(dest)
}
dest.WriteArrayEnd()
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *TracesData) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "resourceSpans", "resource_spans":
for iter.ReadArray() {
orig.ResourceSpans = append(orig.ResourceSpans, NewResourceSpans())
orig.ResourceSpans[len(orig.ResourceSpans)-1].UnmarshalJSON(iter)
}
default:
iter.Skip()
}
}
}
func (orig *TracesData) SizeProto() int {
var n int
var l int
_ = l
for i := range orig.ResourceSpans {
l = orig.ResourceSpans[i].SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *TracesData) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
for i := len(orig.ResourceSpans) - 1; i >= 0; i-- {
l = orig.ResourceSpans[i].MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
}
return len(buf) - pos
}
func (orig *TracesData) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field ResourceSpans", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.ResourceSpans = append(orig.ResourceSpans, NewResourceSpans())
err = orig.ResourceSpans[len(orig.ResourceSpans)-1].UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestTracesData() *TracesData {
orig := NewTracesData()
orig.ResourceSpans = []*ResourceSpans{{}, GenTestResourceSpans()}
return orig
}
func GenTestTracesDataPtrSlice() []*TracesData {
orig := make([]*TracesData, 5)
orig[0] = NewTracesData()
orig[1] = GenTestTracesData()
orig[2] = NewTracesData()
orig[3] = GenTestTracesData()
orig[4] = NewTracesData()
return orig
}
func GenTestTracesDataSlice() []TracesData {
orig := make([]TracesData, 5)
orig[1] = *GenTestTracesData()
orig[3] = *GenTestTracesData()
return orig
}
================================================
FILE: pdata/internal/generated_proto_tracesdata_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlptrace "go.opentelemetry.io/proto/slim/otlp/trace/v1"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyTracesData(t *testing.T) {
for name, src := range genTestEncodingValuesTracesData() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewTracesData()
CopyTracesData(dest, src)
assert.Equal(t, src, dest)
CopyTracesData(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyTracesDataSlice(t *testing.T) {
src := []TracesData{}
dest := []TracesData{}
// Test CopyTo empty
dest = CopyTracesDataSlice(dest, src)
assert.Equal(t, []TracesData{}, dest)
// Test CopyTo larger slice
src = GenTestTracesDataSlice()
dest = CopyTracesDataSlice(dest, src)
assert.Equal(t, GenTestTracesDataSlice(), dest)
// Test CopyTo same size slice
dest = CopyTracesDataSlice(dest, src)
assert.Equal(t, GenTestTracesDataSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyTracesDataSlice(dest, []TracesData{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyTracesDataSlice(dest, src)
assert.Equal(t, GenTestTracesDataSlice(), dest)
}
func TestCopyTracesDataPtrSlice(t *testing.T) {
src := []*TracesData{}
dest := []*TracesData{}
// Test CopyTo empty
dest = CopyTracesDataPtrSlice(dest, src)
assert.Equal(t, []*TracesData{}, dest)
// Test CopyTo larger slice
src = GenTestTracesDataPtrSlice()
dest = CopyTracesDataPtrSlice(dest, src)
assert.Equal(t, GenTestTracesDataPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyTracesDataPtrSlice(dest, src)
assert.Equal(t, GenTestTracesDataPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyTracesDataPtrSlice(dest, []*TracesData{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyTracesDataPtrSlice(dest, src)
assert.Equal(t, GenTestTracesDataPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONTracesDataUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewTracesData()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewTracesData(), dest)
}
func TestMarshalAndUnmarshalJSONTracesData(t *testing.T) {
for name, src := range genTestEncodingValuesTracesData() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewTracesData()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteTracesData(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoTracesDataFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesTracesData() {
t.Run(name, func(t *testing.T) {
dest := NewTracesData()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoTracesDataUnknown(t *testing.T) {
dest := NewTracesData()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewTracesData(), dest)
}
func TestMarshalAndUnmarshalProtoTracesData(t *testing.T) {
for name, src := range genTestEncodingValuesTracesData() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewTracesData()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteTracesData(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufTracesData(t *testing.T) {
for name, src := range genTestEncodingValuesTracesData() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlptrace.TracesData{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewTracesData()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesTracesData() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"ResourceSpans/wrong_wire_type": {0xc},
"ResourceSpans/missing_value": {0xa},
}
}
func genTestEncodingValuesTracesData() map[string]*TracesData {
return map[string]*TracesData{
"empty": NewTracesData(),
"ResourceSpans/test": {ResourceSpans: []*ResourceSpans{{}, GenTestResourceSpans()}},
}
}
================================================
FILE: pdata/internal/generated_proto_tracesrequest.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"encoding/binary"
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
type TracesRequest struct {
RequestContext *RequestContext
TracesData TracesData
FormatVersion uint32
}
var (
protoPoolTracesRequest = sync.Pool{
New: func() any {
return &TracesRequest{}
},
}
)
func NewTracesRequest() *TracesRequest {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &TracesRequest{}
}
return protoPoolTracesRequest.Get().(*TracesRequest)
}
func DeleteTracesRequest(orig *TracesRequest, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
DeleteRequestContext(orig.RequestContext, true)
DeleteTracesData(&orig.TracesData, false)
orig.Reset()
if nullable {
protoPoolTracesRequest.Put(orig)
}
}
func CopyTracesRequest(dest, src *TracesRequest) *TracesRequest {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewTracesRequest()
}
dest.RequestContext = CopyRequestContext(dest.RequestContext, src.RequestContext)
CopyTracesData(&dest.TracesData, &src.TracesData)
dest.FormatVersion = src.FormatVersion
return dest
}
func CopyTracesRequestSlice(dest, src []TracesRequest) []TracesRequest {
var newDest []TracesRequest
if cap(dest) < len(src) {
newDest = make([]TracesRequest, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteTracesRequest(&dest[i], false)
}
}
for i := range src {
CopyTracesRequest(&newDest[i], &src[i])
}
return newDest
}
func CopyTracesRequestPtrSlice(dest, src []*TracesRequest) []*TracesRequest {
var newDest []*TracesRequest
if cap(dest) < len(src) {
newDest = make([]*TracesRequest, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewTracesRequest()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteTracesRequest(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewTracesRequest()
}
}
for i := range src {
CopyTracesRequest(newDest[i], src[i])
}
return newDest
}
func (orig *TracesRequest) Reset() {
*orig = TracesRequest{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *TracesRequest) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if orig.RequestContext != nil {
dest.WriteObjectField("requestContext")
orig.RequestContext.MarshalJSON(dest)
}
dest.WriteObjectField("tracesData")
orig.TracesData.MarshalJSON(dest)
if orig.FormatVersion != uint32(0) {
dest.WriteObjectField("formatVersion")
dest.WriteUint32(orig.FormatVersion)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *TracesRequest) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "requestContext", "request_context":
orig.RequestContext = NewRequestContext()
orig.RequestContext.UnmarshalJSON(iter)
case "tracesData", "traces_data":
orig.TracesData.UnmarshalJSON(iter)
case "formatVersion", "format_version":
orig.FormatVersion = iter.ReadUint32()
default:
iter.Skip()
}
}
}
func (orig *TracesRequest) SizeProto() int {
var n int
var l int
_ = l
if orig.RequestContext != nil {
l = orig.RequestContext.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
}
l = orig.TracesData.SizeProto()
n += 1 + proto.Sov(uint64(l)) + l
if orig.FormatVersion != uint32(0) {
n += 5
}
return n
}
func (orig *TracesRequest) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
if orig.RequestContext != nil {
l = orig.RequestContext.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
}
l = orig.TracesData.MarshalProto(buf[:pos])
pos -= l
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x1a
if orig.FormatVersion != uint32(0) {
pos -= 4
binary.LittleEndian.PutUint32(buf[pos:], uint32(orig.FormatVersion))
pos--
buf[pos] = 0xd
}
return len(buf) - pos
}
func (orig *TracesRequest) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field RequestContext", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.RequestContext = NewRequestContext()
err = orig.RequestContext.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 3:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field TracesData", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
err = orig.TracesData.UnmarshalProto(buf[startPos:pos])
if err != nil {
return err
}
case 1:
if wireType != proto.WireTypeI32 {
return fmt.Errorf("proto: wrong wireType = %d for field FormatVersion", wireType)
}
var num uint32
num, pos, err = proto.ConsumeI32(buf, pos)
if err != nil {
return err
}
orig.FormatVersion = uint32(num)
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestTracesRequest() *TracesRequest {
orig := NewTracesRequest()
orig.RequestContext = GenTestRequestContext()
orig.TracesData = *GenTestTracesData()
orig.FormatVersion = uint32(13)
return orig
}
func GenTestTracesRequestPtrSlice() []*TracesRequest {
orig := make([]*TracesRequest, 5)
orig[0] = NewTracesRequest()
orig[1] = GenTestTracesRequest()
orig[2] = NewTracesRequest()
orig[3] = GenTestTracesRequest()
orig[4] = NewTracesRequest()
return orig
}
func GenTestTracesRequestSlice() []TracesRequest {
orig := make([]TracesRequest, 5)
orig[1] = *GenTestTracesRequest()
orig[3] = *GenTestTracesRequest()
return orig
}
================================================
FILE: pdata/internal/generated_proto_tracesrequest_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/emptypb"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyTracesRequest(t *testing.T) {
for name, src := range genTestEncodingValuesTracesRequest() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewTracesRequest()
CopyTracesRequest(dest, src)
assert.Equal(t, src, dest)
CopyTracesRequest(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyTracesRequestSlice(t *testing.T) {
src := []TracesRequest{}
dest := []TracesRequest{}
// Test CopyTo empty
dest = CopyTracesRequestSlice(dest, src)
assert.Equal(t, []TracesRequest{}, dest)
// Test CopyTo larger slice
src = GenTestTracesRequestSlice()
dest = CopyTracesRequestSlice(dest, src)
assert.Equal(t, GenTestTracesRequestSlice(), dest)
// Test CopyTo same size slice
dest = CopyTracesRequestSlice(dest, src)
assert.Equal(t, GenTestTracesRequestSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyTracesRequestSlice(dest, []TracesRequest{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyTracesRequestSlice(dest, src)
assert.Equal(t, GenTestTracesRequestSlice(), dest)
}
func TestCopyTracesRequestPtrSlice(t *testing.T) {
src := []*TracesRequest{}
dest := []*TracesRequest{}
// Test CopyTo empty
dest = CopyTracesRequestPtrSlice(dest, src)
assert.Equal(t, []*TracesRequest{}, dest)
// Test CopyTo larger slice
src = GenTestTracesRequestPtrSlice()
dest = CopyTracesRequestPtrSlice(dest, src)
assert.Equal(t, GenTestTracesRequestPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyTracesRequestPtrSlice(dest, src)
assert.Equal(t, GenTestTracesRequestPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyTracesRequestPtrSlice(dest, []*TracesRequest{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyTracesRequestPtrSlice(dest, src)
assert.Equal(t, GenTestTracesRequestPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONTracesRequestUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewTracesRequest()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewTracesRequest(), dest)
}
func TestMarshalAndUnmarshalJSONTracesRequest(t *testing.T) {
for name, src := range genTestEncodingValuesTracesRequest() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewTracesRequest()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteTracesRequest(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoTracesRequestFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesTracesRequest() {
t.Run(name, func(t *testing.T) {
dest := NewTracesRequest()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoTracesRequestUnknown(t *testing.T) {
dest := NewTracesRequest()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewTracesRequest(), dest)
}
func TestMarshalAndUnmarshalProtoTracesRequest(t *testing.T) {
for name, src := range genTestEncodingValuesTracesRequest() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewTracesRequest()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteTracesRequest(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufTracesRequest(t *testing.T) {
for name, src := range genTestEncodingValuesTracesRequest() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &emptypb.Empty{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewTracesRequest()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesTracesRequest() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"RequestContext/wrong_wire_type": {0x14},
"RequestContext/missing_value": {0x12},
"TracesData/wrong_wire_type": {0x1c},
"TracesData/missing_value": {0x1a},
"FormatVersion/wrong_wire_type": {0xc},
"FormatVersion/missing_value": {0xd},
}
}
func genTestEncodingValuesTracesRequest() map[string]*TracesRequest {
return map[string]*TracesRequest{
"empty": NewTracesRequest(),
"RequestContext/test": {RequestContext: GenTestRequestContext()},
"TracesData/test": {TracesData: *GenTestTracesData()},
"FormatVersion/test": {FormatVersion: uint32(13)},
}
}
================================================
FILE: pdata/internal/generated_proto_udpaddr.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
type UDPAddr struct {
Zone string
IP []byte
Port int64
}
var (
protoPoolUDPAddr = sync.Pool{
New: func() any {
return &UDPAddr{}
},
}
)
func NewUDPAddr() *UDPAddr {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &UDPAddr{}
}
return protoPoolUDPAddr.Get().(*UDPAddr)
}
func DeleteUDPAddr(orig *UDPAddr, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
orig.Reset()
if nullable {
protoPoolUDPAddr.Put(orig)
}
}
func CopyUDPAddr(dest, src *UDPAddr) *UDPAddr {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewUDPAddr()
}
dest.IP = src.IP
dest.Port = src.Port
dest.Zone = src.Zone
return dest
}
func CopyUDPAddrSlice(dest, src []UDPAddr) []UDPAddr {
var newDest []UDPAddr
if cap(dest) < len(src) {
newDest = make([]UDPAddr, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteUDPAddr(&dest[i], false)
}
}
for i := range src {
CopyUDPAddr(&newDest[i], &src[i])
}
return newDest
}
func CopyUDPAddrPtrSlice(dest, src []*UDPAddr) []*UDPAddr {
var newDest []*UDPAddr
if cap(dest) < len(src) {
newDest = make([]*UDPAddr, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewUDPAddr()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteUDPAddr(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewUDPAddr()
}
}
for i := range src {
CopyUDPAddr(newDest[i], src[i])
}
return newDest
}
func (orig *UDPAddr) Reset() {
*orig = UDPAddr{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *UDPAddr) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if len(orig.IP) > 0 {
dest.WriteObjectField("iP")
dest.WriteBytes(orig.IP)
}
if orig.Port != int64(0) {
dest.WriteObjectField("port")
dest.WriteInt64(orig.Port)
}
if orig.Zone != "" {
dest.WriteObjectField("zone")
dest.WriteString(orig.Zone)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *UDPAddr) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "iP":
orig.IP = iter.ReadBytes()
case "port":
orig.Port = iter.ReadInt64()
case "zone":
orig.Zone = iter.ReadString()
default:
iter.Skip()
}
}
}
func (orig *UDPAddr) SizeProto() int {
var n int
var l int
_ = l
l = len(orig.IP)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
if orig.Port != int64(0) {
n += 1 + proto.Sov(uint64(orig.Port))
}
l = len(orig.Zone)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *UDPAddr) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
l = len(orig.IP)
if l > 0 {
pos -= l
copy(buf[pos:], orig.IP)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
}
if orig.Port != int64(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.Port))
pos--
buf[pos] = 0x10
}
l = len(orig.Zone)
if l > 0 {
pos -= l
copy(buf[pos:], orig.Zone)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x1a
}
return len(buf) - pos
}
func (orig *UDPAddr) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field IP", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
if length != 0 {
orig.IP = make([]byte, length)
copy(orig.IP, buf[startPos:pos])
}
case 2:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field Port", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.Port = int64(num)
case 3:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Zone", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Zone = string(buf[startPos:pos])
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestUDPAddr() *UDPAddr {
orig := NewUDPAddr()
orig.IP = []byte{1, 2, 3}
orig.Port = int64(13)
orig.Zone = "test_zone"
return orig
}
func GenTestUDPAddrPtrSlice() []*UDPAddr {
orig := make([]*UDPAddr, 5)
orig[0] = NewUDPAddr()
orig[1] = GenTestUDPAddr()
orig[2] = NewUDPAddr()
orig[3] = GenTestUDPAddr()
orig[4] = NewUDPAddr()
return orig
}
func GenTestUDPAddrSlice() []UDPAddr {
orig := make([]UDPAddr, 5)
orig[1] = *GenTestUDPAddr()
orig[3] = *GenTestUDPAddr()
return orig
}
================================================
FILE: pdata/internal/generated_proto_udpaddr_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/emptypb"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyUDPAddr(t *testing.T) {
for name, src := range genTestEncodingValuesUDPAddr() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewUDPAddr()
CopyUDPAddr(dest, src)
assert.Equal(t, src, dest)
CopyUDPAddr(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyUDPAddrSlice(t *testing.T) {
src := []UDPAddr{}
dest := []UDPAddr{}
// Test CopyTo empty
dest = CopyUDPAddrSlice(dest, src)
assert.Equal(t, []UDPAddr{}, dest)
// Test CopyTo larger slice
src = GenTestUDPAddrSlice()
dest = CopyUDPAddrSlice(dest, src)
assert.Equal(t, GenTestUDPAddrSlice(), dest)
// Test CopyTo same size slice
dest = CopyUDPAddrSlice(dest, src)
assert.Equal(t, GenTestUDPAddrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyUDPAddrSlice(dest, []UDPAddr{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyUDPAddrSlice(dest, src)
assert.Equal(t, GenTestUDPAddrSlice(), dest)
}
func TestCopyUDPAddrPtrSlice(t *testing.T) {
src := []*UDPAddr{}
dest := []*UDPAddr{}
// Test CopyTo empty
dest = CopyUDPAddrPtrSlice(dest, src)
assert.Equal(t, []*UDPAddr{}, dest)
// Test CopyTo larger slice
src = GenTestUDPAddrPtrSlice()
dest = CopyUDPAddrPtrSlice(dest, src)
assert.Equal(t, GenTestUDPAddrPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyUDPAddrPtrSlice(dest, src)
assert.Equal(t, GenTestUDPAddrPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyUDPAddrPtrSlice(dest, []*UDPAddr{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyUDPAddrPtrSlice(dest, src)
assert.Equal(t, GenTestUDPAddrPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONUDPAddrUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewUDPAddr()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewUDPAddr(), dest)
}
func TestMarshalAndUnmarshalJSONUDPAddr(t *testing.T) {
for name, src := range genTestEncodingValuesUDPAddr() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewUDPAddr()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteUDPAddr(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoUDPAddrFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesUDPAddr() {
t.Run(name, func(t *testing.T) {
dest := NewUDPAddr()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoUDPAddrUnknown(t *testing.T) {
dest := NewUDPAddr()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewUDPAddr(), dest)
}
func TestMarshalAndUnmarshalProtoUDPAddr(t *testing.T) {
for name, src := range genTestEncodingValuesUDPAddr() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewUDPAddr()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteUDPAddr(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufUDPAddr(t *testing.T) {
for name, src := range genTestEncodingValuesUDPAddr() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &emptypb.Empty{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewUDPAddr()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesUDPAddr() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"IP/wrong_wire_type": {0xc},
"IP/missing_value": {0xa},
"Port/wrong_wire_type": {0x14},
"Port/missing_value": {0x10},
"Zone/wrong_wire_type": {0x1c},
"Zone/missing_value": {0x1a},
}
}
func genTestEncodingValuesUDPAddr() map[string]*UDPAddr {
return map[string]*UDPAddr{
"empty": NewUDPAddr(),
"IP/test": {IP: []byte{1, 2, 3}},
"Port/test": {Port: int64(13)},
"Zone/test": {Zone: "test_zone"},
}
}
================================================
FILE: pdata/internal/generated_proto_unixaddr.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
type UnixAddr struct {
Name string
Net string
}
var (
protoPoolUnixAddr = sync.Pool{
New: func() any {
return &UnixAddr{}
},
}
)
func NewUnixAddr() *UnixAddr {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &UnixAddr{}
}
return protoPoolUnixAddr.Get().(*UnixAddr)
}
func DeleteUnixAddr(orig *UnixAddr, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
orig.Reset()
if nullable {
protoPoolUnixAddr.Put(orig)
}
}
func CopyUnixAddr(dest, src *UnixAddr) *UnixAddr {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewUnixAddr()
}
dest.Name = src.Name
dest.Net = src.Net
return dest
}
func CopyUnixAddrSlice(dest, src []UnixAddr) []UnixAddr {
var newDest []UnixAddr
if cap(dest) < len(src) {
newDest = make([]UnixAddr, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteUnixAddr(&dest[i], false)
}
}
for i := range src {
CopyUnixAddr(&newDest[i], &src[i])
}
return newDest
}
func CopyUnixAddrPtrSlice(dest, src []*UnixAddr) []*UnixAddr {
var newDest []*UnixAddr
if cap(dest) < len(src) {
newDest = make([]*UnixAddr, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewUnixAddr()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteUnixAddr(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewUnixAddr()
}
}
for i := range src {
CopyUnixAddr(newDest[i], src[i])
}
return newDest
}
func (orig *UnixAddr) Reset() {
*orig = UnixAddr{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *UnixAddr) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if orig.Name != "" {
dest.WriteObjectField("name")
dest.WriteString(orig.Name)
}
if orig.Net != "" {
dest.WriteObjectField("net")
dest.WriteString(orig.Net)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *UnixAddr) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "name":
orig.Name = iter.ReadString()
case "net":
orig.Net = iter.ReadString()
default:
iter.Skip()
}
}
}
func (orig *UnixAddr) SizeProto() int {
var n int
var l int
_ = l
l = len(orig.Name)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
l = len(orig.Net)
if l > 0 {
n += 1 + proto.Sov(uint64(l)) + l
}
return n
}
func (orig *UnixAddr) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
l = len(orig.Name)
if l > 0 {
pos -= l
copy(buf[pos:], orig.Name)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0xa
}
l = len(orig.Net)
if l > 0 {
pos -= l
copy(buf[pos:], orig.Net)
pos = proto.EncodeVarint(buf, pos, uint64(l))
pos--
buf[pos] = 0x12
}
return len(buf) - pos
}
func (orig *UnixAddr) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Name = string(buf[startPos:pos])
case 2:
if wireType != proto.WireTypeLen {
return fmt.Errorf("proto: wrong wireType = %d for field Net", wireType)
}
var length int
length, pos, err = proto.ConsumeLen(buf, pos)
if err != nil {
return err
}
startPos := pos - length
orig.Net = string(buf[startPos:pos])
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestUnixAddr() *UnixAddr {
orig := NewUnixAddr()
orig.Name = "test_name"
orig.Net = "test_net"
return orig
}
func GenTestUnixAddrPtrSlice() []*UnixAddr {
orig := make([]*UnixAddr, 5)
orig[0] = NewUnixAddr()
orig[1] = GenTestUnixAddr()
orig[2] = NewUnixAddr()
orig[3] = GenTestUnixAddr()
orig[4] = NewUnixAddr()
return orig
}
func GenTestUnixAddrSlice() []UnixAddr {
orig := make([]UnixAddr, 5)
orig[1] = *GenTestUnixAddr()
orig[3] = *GenTestUnixAddr()
return orig
}
================================================
FILE: pdata/internal/generated_proto_unixaddr_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/emptypb"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyUnixAddr(t *testing.T) {
for name, src := range genTestEncodingValuesUnixAddr() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewUnixAddr()
CopyUnixAddr(dest, src)
assert.Equal(t, src, dest)
CopyUnixAddr(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyUnixAddrSlice(t *testing.T) {
src := []UnixAddr{}
dest := []UnixAddr{}
// Test CopyTo empty
dest = CopyUnixAddrSlice(dest, src)
assert.Equal(t, []UnixAddr{}, dest)
// Test CopyTo larger slice
src = GenTestUnixAddrSlice()
dest = CopyUnixAddrSlice(dest, src)
assert.Equal(t, GenTestUnixAddrSlice(), dest)
// Test CopyTo same size slice
dest = CopyUnixAddrSlice(dest, src)
assert.Equal(t, GenTestUnixAddrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyUnixAddrSlice(dest, []UnixAddr{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyUnixAddrSlice(dest, src)
assert.Equal(t, GenTestUnixAddrSlice(), dest)
}
func TestCopyUnixAddrPtrSlice(t *testing.T) {
src := []*UnixAddr{}
dest := []*UnixAddr{}
// Test CopyTo empty
dest = CopyUnixAddrPtrSlice(dest, src)
assert.Equal(t, []*UnixAddr{}, dest)
// Test CopyTo larger slice
src = GenTestUnixAddrPtrSlice()
dest = CopyUnixAddrPtrSlice(dest, src)
assert.Equal(t, GenTestUnixAddrPtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyUnixAddrPtrSlice(dest, src)
assert.Equal(t, GenTestUnixAddrPtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyUnixAddrPtrSlice(dest, []*UnixAddr{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyUnixAddrPtrSlice(dest, src)
assert.Equal(t, GenTestUnixAddrPtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONUnixAddrUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewUnixAddr()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewUnixAddr(), dest)
}
func TestMarshalAndUnmarshalJSONUnixAddr(t *testing.T) {
for name, src := range genTestEncodingValuesUnixAddr() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewUnixAddr()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteUnixAddr(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoUnixAddrFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesUnixAddr() {
t.Run(name, func(t *testing.T) {
dest := NewUnixAddr()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoUnixAddrUnknown(t *testing.T) {
dest := NewUnixAddr()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewUnixAddr(), dest)
}
func TestMarshalAndUnmarshalProtoUnixAddr(t *testing.T) {
for name, src := range genTestEncodingValuesUnixAddr() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewUnixAddr()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteUnixAddr(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufUnixAddr(t *testing.T) {
for name, src := range genTestEncodingValuesUnixAddr() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &emptypb.Empty{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewUnixAddr()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesUnixAddr() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"Name/wrong_wire_type": {0xc},
"Name/missing_value": {0xa},
"Net/wrong_wire_type": {0x14},
"Net/missing_value": {0x12},
}
}
func genTestEncodingValuesUnixAddr() map[string]*UnixAddr {
return map[string]*UnixAddr{
"empty": NewUnixAddr(),
"Name/test": {Name: "test_name"},
"Net/test": {Net: "test_net"},
}
}
================================================
FILE: pdata/internal/generated_proto_valuetype.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"fmt"
"sync"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// ValueType describes the type and units of a value.
type ValueType struct {
TypeStrindex int32
UnitStrindex int32
}
var (
protoPoolValueType = sync.Pool{
New: func() any {
return &ValueType{}
},
}
)
func NewValueType() *ValueType {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &ValueType{}
}
return protoPoolValueType.Get().(*ValueType)
}
func DeleteValueType(orig *ValueType, nullable bool) {
if orig == nil {
return
}
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
orig.Reset()
return
}
orig.Reset()
if nullable {
protoPoolValueType.Put(orig)
}
}
func CopyValueType(dest, src *ValueType) *ValueType {
// If copying to same object, just return.
if src == dest {
return dest
}
if src == nil {
return nil
}
if dest == nil {
dest = NewValueType()
}
dest.TypeStrindex = src.TypeStrindex
dest.UnitStrindex = src.UnitStrindex
return dest
}
func CopyValueTypeSlice(dest, src []ValueType) []ValueType {
var newDest []ValueType
if cap(dest) < len(src) {
newDest = make([]ValueType, len(src))
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteValueType(&dest[i], false)
}
}
for i := range src {
CopyValueType(&newDest[i], &src[i])
}
return newDest
}
func CopyValueTypePtrSlice(dest, src []*ValueType) []*ValueType {
var newDest []*ValueType
if cap(dest) < len(src) {
newDest = make([]*ValueType, len(src))
// Copy old pointers to re-use.
copy(newDest, dest)
// Add new pointers for missing elements from len(dest) to len(srt).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewValueType()
}
} else {
newDest = dest[:len(src)]
// Cleanup the rest of the elements so GC can free the memory.
// This can happen when len(src) < len(dest) < cap(dest).
for i := len(src); i < len(dest); i++ {
DeleteValueType(dest[i], true)
dest[i] = nil
}
// Add new pointers for missing elements.
// This can happen when len(dest) < len(src) < cap(dest).
for i := len(dest); i < len(src); i++ {
newDest[i] = NewValueType()
}
}
for i := range src {
CopyValueType(newDest[i], src[i])
}
return newDest
}
func (orig *ValueType) Reset() {
*orig = ValueType{}
}
// MarshalJSON marshals all properties from the current struct to the destination stream.
func (orig *ValueType) MarshalJSON(dest *json.Stream) {
dest.WriteObjectStart()
if orig.TypeStrindex != int32(0) {
dest.WriteObjectField("typeStrindex")
dest.WriteInt32(orig.TypeStrindex)
}
if orig.UnitStrindex != int32(0) {
dest.WriteObjectField("unitStrindex")
dest.WriteInt32(orig.UnitStrindex)
}
dest.WriteObjectEnd()
}
// UnmarshalJSON unmarshals all properties from the current struct from the source iterator.
func (orig *ValueType) UnmarshalJSON(iter *json.Iterator) {
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
switch f {
case "typeStrindex", "type_strindex":
orig.TypeStrindex = iter.ReadInt32()
case "unitStrindex", "unit_strindex":
orig.UnitStrindex = iter.ReadInt32()
default:
iter.Skip()
}
}
}
func (orig *ValueType) SizeProto() int {
var n int
var l int
_ = l
if orig.TypeStrindex != int32(0) {
n += 1 + proto.Sov(uint64(orig.TypeStrindex))
}
if orig.UnitStrindex != int32(0) {
n += 1 + proto.Sov(uint64(orig.UnitStrindex))
}
return n
}
func (orig *ValueType) MarshalProto(buf []byte) int {
pos := len(buf)
var l int
_ = l
if orig.TypeStrindex != int32(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.TypeStrindex))
pos--
buf[pos] = 0x8
}
if orig.UnitStrindex != int32(0) {
pos = proto.EncodeVarint(buf, pos, uint64(orig.UnitStrindex))
pos--
buf[pos] = 0x10
}
return len(buf) - pos
}
func (orig *ValueType) UnmarshalProto(buf []byte) error {
var err error
var fieldNum int32
var wireType proto.WireType
l := len(buf)
pos := 0
for pos < l {
// If in a group parsing, move to the next tag.
fieldNum, wireType, pos, err = proto.ConsumeTag(buf, pos)
if err != nil {
return err
}
switch fieldNum {
case 1:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field TypeStrindex", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.TypeStrindex = int32(num)
case 2:
if wireType != proto.WireTypeVarint {
return fmt.Errorf("proto: wrong wireType = %d for field UnitStrindex", wireType)
}
var num uint64
num, pos, err = proto.ConsumeVarint(buf, pos)
if err != nil {
return err
}
orig.UnitStrindex = int32(num)
default:
pos, err = proto.ConsumeUnknown(buf, pos, wireType)
if err != nil {
return err
}
}
}
return nil
}
func GenTestValueType() *ValueType {
orig := NewValueType()
orig.TypeStrindex = int32(13)
orig.UnitStrindex = int32(13)
return orig
}
func GenTestValueTypePtrSlice() []*ValueType {
orig := make([]*ValueType, 5)
orig[0] = NewValueType()
orig[1] = GenTestValueType()
orig[2] = NewValueType()
orig[3] = GenTestValueType()
orig[4] = NewValueType()
return orig
}
func GenTestValueTypeSlice() []ValueType {
orig := make([]ValueType, 5)
orig[1] = *GenTestValueType()
orig[3] = *GenTestValueType()
return orig
}
================================================
FILE: pdata/internal/generated_proto_valuetype_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpprofiles "go.opentelemetry.io/proto/slim/otlp/profiles/v1development"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestCopyValueType(t *testing.T) {
for name, src := range genTestEncodingValuesValueType() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
dest := NewValueType()
CopyValueType(dest, src)
assert.Equal(t, src, dest)
CopyValueType(dest, dest)
assert.Equal(t, src, dest)
})
}
}
}
func TestCopyValueTypeSlice(t *testing.T) {
src := []ValueType{}
dest := []ValueType{}
// Test CopyTo empty
dest = CopyValueTypeSlice(dest, src)
assert.Equal(t, []ValueType{}, dest)
// Test CopyTo larger slice
src = GenTestValueTypeSlice()
dest = CopyValueTypeSlice(dest, src)
assert.Equal(t, GenTestValueTypeSlice(), dest)
// Test CopyTo same size slice
dest = CopyValueTypeSlice(dest, src)
assert.Equal(t, GenTestValueTypeSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyValueTypeSlice(dest, []ValueType{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyValueTypeSlice(dest, src)
assert.Equal(t, GenTestValueTypeSlice(), dest)
}
func TestCopyValueTypePtrSlice(t *testing.T) {
src := []*ValueType{}
dest := []*ValueType{}
// Test CopyTo empty
dest = CopyValueTypePtrSlice(dest, src)
assert.Equal(t, []*ValueType{}, dest)
// Test CopyTo larger slice
src = GenTestValueTypePtrSlice()
dest = CopyValueTypePtrSlice(dest, src)
assert.Equal(t, GenTestValueTypePtrSlice(), dest)
// Test CopyTo same size slice
dest = CopyValueTypePtrSlice(dest, src)
assert.Equal(t, GenTestValueTypePtrSlice(), dest)
// Test CopyTo smaller size slice
dest = CopyValueTypePtrSlice(dest, []*ValueType{})
assert.Len(t, dest, 0)
// Test CopyTo larger slice with enough capacity
dest = CopyValueTypePtrSlice(dest, src)
assert.Equal(t, GenTestValueTypePtrSlice(), dest)
}
func TestMarshalAndUnmarshalJSONValueTypeUnknown(t *testing.T) {
iter := json.BorrowIterator([]byte(`{"unknown": "string"}`))
defer json.ReturnIterator(iter)
dest := NewValueType()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, NewValueType(), dest)
}
func TestMarshalAndUnmarshalJSONValueType(t *testing.T) {
for name, src := range genTestEncodingValuesValueType() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := NewValueType()
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
DeleteValueType(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoValueTypeFailing(t *testing.T) {
for name, buf := range genTestFailingUnmarshalProtoValuesValueType() {
t.Run(name, func(t *testing.T) {
dest := NewValueType()
require.Error(t, dest.UnmarshalProto(buf))
})
}
}
func TestMarshalAndUnmarshalProtoValueTypeUnknown(t *testing.T) {
dest := NewValueType()
// message Test { required int64 field = 1313; } encoding { "field": "1234" }
require.NoError(t, dest.UnmarshalProto([]byte{0x88, 0x52, 0xD2, 0x09}))
assert.Equal(t, NewValueType(), dest)
}
func TestMarshalAndUnmarshalProtoValueType(t *testing.T) {
for name, src := range genTestEncodingValuesValueType() {
for _, pooling := range []bool{true, false} {
t.Run(name+"/Pooling="+strconv.FormatBool(pooling), func(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), pooling))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
dest := NewValueType()
require.NoError(t, dest.UnmarshalProto(buf))
assert.Equal(t, src, dest)
DeleteValueType(dest, true)
})
}
}
}
func TestMarshalAndUnmarshalProtoViaProtobufValueType(t *testing.T) {
for name, src := range genTestEncodingValuesValueType() {
t.Run(name, func(t *testing.T) {
buf := make([]byte, src.SizeProto())
gotSize := src.MarshalProto(buf)
assert.Equal(t, len(buf), gotSize)
goDest := &gootlpprofiles.ValueType{}
require.NoError(t, proto.Unmarshal(buf, goDest))
goBuf, err := proto.Marshal(goDest)
require.NoError(t, err)
dest := NewValueType()
require.NoError(t, dest.UnmarshalProto(goBuf))
assert.Equal(t, src, dest)
})
}
}
func genTestFailingUnmarshalProtoValuesValueType() map[string][]byte {
return map[string][]byte{
"invalid_field": {0x02},
"TypeStrindex/wrong_wire_type": {0xc},
"TypeStrindex/missing_value": {0x8},
"UnitStrindex/wrong_wire_type": {0x14},
"UnitStrindex/missing_value": {0x10},
}
}
func genTestEncodingValuesValueType() map[string]*ValueType {
return map[string]*ValueType{
"empty": NewValueType(),
"TypeStrindex/test": {TypeStrindex: int32(13)},
"UnitStrindex/test": {UnitStrindex: int32(13)},
}
}
================================================
FILE: pdata/internal/generated_wrapper_anyvalueslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
type SliceWrapper struct {
orig *[]AnyValue
state *State
}
func GetSliceOrig(ms SliceWrapper) *[]AnyValue {
return ms.orig
}
func GetSliceState(ms SliceWrapper) *State {
return ms.state
}
func NewSliceWrapper(orig *[]AnyValue, state *State) SliceWrapper {
return SliceWrapper{orig: orig, state: state}
}
func GenTestSliceWrapper() SliceWrapper {
orig := GenTestAnyValueSlice()
return NewSliceWrapper(&orig, NewState())
}
================================================
FILE: pdata/internal/generated_wrapper_byteslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
type ByteSliceWrapper struct {
orig *[]byte
state *State
}
func GetByteSliceOrig(ms ByteSliceWrapper) *[]byte {
return ms.orig
}
func GetByteSliceState(ms ByteSliceWrapper) *State {
return ms.state
}
func NewByteSliceWrapper(orig *[]byte, state *State) ByteSliceWrapper {
return ByteSliceWrapper{orig: orig, state: state}
}
func GenTestByteSliceWrapper() ByteSliceWrapper {
orig := []byte{1, 2, 3}
return NewByteSliceWrapper(&orig, NewState())
}
func GenTestByteSlice() []byte {
return []byte{1, 2, 3}
}
================================================
FILE: pdata/internal/generated_wrapper_entityref.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
type EntityRefWrapper struct {
orig *EntityRef
state *State
}
func GetEntityRefOrig(ms EntityRefWrapper) *EntityRef {
return ms.orig
}
func GetEntityRefState(ms EntityRefWrapper) *State {
return ms.state
}
func NewEntityRefWrapper(orig *EntityRef, state *State) EntityRefWrapper {
return EntityRefWrapper{orig: orig, state: state}
}
func GenTestEntityRefWrapper() EntityRefWrapper {
return NewEntityRefWrapper(GenTestEntityRef(), NewState())
}
================================================
FILE: pdata/internal/generated_wrapper_entityrefslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
type EntityRefSliceWrapper struct {
orig *[]*EntityRef
state *State
}
func GetEntityRefSliceOrig(ms EntityRefSliceWrapper) *[]*EntityRef {
return ms.orig
}
func GetEntityRefSliceState(ms EntityRefSliceWrapper) *State {
return ms.state
}
func NewEntityRefSliceWrapper(orig *[]*EntityRef, state *State) EntityRefSliceWrapper {
return EntityRefSliceWrapper{orig: orig, state: state}
}
func GenTestEntityRefSliceWrapper() EntityRefSliceWrapper {
orig := GenTestEntityRefPtrSlice()
return NewEntityRefSliceWrapper(&orig, NewState())
}
================================================
FILE: pdata/internal/generated_wrapper_exportlogsservicerequest.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
type LogsWrapper struct {
orig *ExportLogsServiceRequest
state *State
}
func GetLogsOrig(ms LogsWrapper) *ExportLogsServiceRequest {
return ms.orig
}
func GetLogsState(ms LogsWrapper) *State {
return ms.state
}
func NewLogsWrapper(orig *ExportLogsServiceRequest, state *State) LogsWrapper {
return LogsWrapper{orig: orig, state: state}
}
func GenTestLogsWrapper() LogsWrapper {
return NewLogsWrapper(GenTestExportLogsServiceRequest(), NewState())
}
================================================
FILE: pdata/internal/generated_wrapper_exportmetricsservicerequest.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
type MetricsWrapper struct {
orig *ExportMetricsServiceRequest
state *State
}
func GetMetricsOrig(ms MetricsWrapper) *ExportMetricsServiceRequest {
return ms.orig
}
func GetMetricsState(ms MetricsWrapper) *State {
return ms.state
}
func NewMetricsWrapper(orig *ExportMetricsServiceRequest, state *State) MetricsWrapper {
return MetricsWrapper{orig: orig, state: state}
}
func GenTestMetricsWrapper() MetricsWrapper {
return NewMetricsWrapper(GenTestExportMetricsServiceRequest(), NewState())
}
================================================
FILE: pdata/internal/generated_wrapper_exportprofilesservicerequest.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
type ProfilesWrapper struct {
orig *ExportProfilesServiceRequest
state *State
}
func GetProfilesOrig(ms ProfilesWrapper) *ExportProfilesServiceRequest {
return ms.orig
}
func GetProfilesState(ms ProfilesWrapper) *State {
return ms.state
}
func NewProfilesWrapper(orig *ExportProfilesServiceRequest, state *State) ProfilesWrapper {
return ProfilesWrapper{orig: orig, state: state}
}
func GenTestProfilesWrapper() ProfilesWrapper {
return NewProfilesWrapper(GenTestExportProfilesServiceRequest(), NewState())
}
================================================
FILE: pdata/internal/generated_wrapper_exporttraceservicerequest.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
type TracesWrapper struct {
orig *ExportTraceServiceRequest
state *State
}
func GetTracesOrig(ms TracesWrapper) *ExportTraceServiceRequest {
return ms.orig
}
func GetTracesState(ms TracesWrapper) *State {
return ms.state
}
func NewTracesWrapper(orig *ExportTraceServiceRequest, state *State) TracesWrapper {
return TracesWrapper{orig: orig, state: state}
}
func GenTestTracesWrapper() TracesWrapper {
return NewTracesWrapper(GenTestExportTraceServiceRequest(), NewState())
}
================================================
FILE: pdata/internal/generated_wrapper_float64slice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
type Float64SliceWrapper struct {
orig *[]float64
state *State
}
func GetFloat64SliceOrig(ms Float64SliceWrapper) *[]float64 {
return ms.orig
}
func GetFloat64SliceState(ms Float64SliceWrapper) *State {
return ms.state
}
func NewFloat64SliceWrapper(orig *[]float64, state *State) Float64SliceWrapper {
return Float64SliceWrapper{orig: orig, state: state}
}
func GenTestFloat64SliceWrapper() Float64SliceWrapper {
orig := []float64{1.1, 2.2, 3.3}
return NewFloat64SliceWrapper(&orig, NewState())
}
func GenTestFloat64Slice() []float64 {
return []float64{1.1, 2.2, 3.3}
}
================================================
FILE: pdata/internal/generated_wrapper_instrumentationscope.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
type InstrumentationScopeWrapper struct {
orig *InstrumentationScope
state *State
}
func GetInstrumentationScopeOrig(ms InstrumentationScopeWrapper) *InstrumentationScope {
return ms.orig
}
func GetInstrumentationScopeState(ms InstrumentationScopeWrapper) *State {
return ms.state
}
func NewInstrumentationScopeWrapper(orig *InstrumentationScope, state *State) InstrumentationScopeWrapper {
return InstrumentationScopeWrapper{orig: orig, state: state}
}
func GenTestInstrumentationScopeWrapper() InstrumentationScopeWrapper {
return NewInstrumentationScopeWrapper(GenTestInstrumentationScope(), NewState())
}
================================================
FILE: pdata/internal/generated_wrapper_int32slice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
type Int32SliceWrapper struct {
orig *[]int32
state *State
}
func GetInt32SliceOrig(ms Int32SliceWrapper) *[]int32 {
return ms.orig
}
func GetInt32SliceState(ms Int32SliceWrapper) *State {
return ms.state
}
func NewInt32SliceWrapper(orig *[]int32, state *State) Int32SliceWrapper {
return Int32SliceWrapper{orig: orig, state: state}
}
func GenTestInt32SliceWrapper() Int32SliceWrapper {
orig := []int32{1, 2, 3}
return NewInt32SliceWrapper(&orig, NewState())
}
func GenTestInt32Slice() []int32 {
return []int32{1, 2, 3}
}
================================================
FILE: pdata/internal/generated_wrapper_int64slice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
type Int64SliceWrapper struct {
orig *[]int64
state *State
}
func GetInt64SliceOrig(ms Int64SliceWrapper) *[]int64 {
return ms.orig
}
func GetInt64SliceState(ms Int64SliceWrapper) *State {
return ms.state
}
func NewInt64SliceWrapper(orig *[]int64, state *State) Int64SliceWrapper {
return Int64SliceWrapper{orig: orig, state: state}
}
func GenTestInt64SliceWrapper() Int64SliceWrapper {
orig := []int64{1, 2, 3}
return NewInt64SliceWrapper(&orig, NewState())
}
func GenTestInt64Slice() []int64 {
return []int64{1, 2, 3}
}
================================================
FILE: pdata/internal/generated_wrapper_profilesdata.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
type ProfilesDataWrapper struct {
orig *ProfilesData
state *State
}
func GetProfilesDataOrig(ms ProfilesDataWrapper) *ProfilesData {
return ms.orig
}
func GetProfilesDataState(ms ProfilesDataWrapper) *State {
return ms.state
}
func NewProfilesDataWrapper(orig *ProfilesData, state *State) ProfilesDataWrapper {
return ProfilesDataWrapper{orig: orig, state: state}
}
func GenTestProfilesDataWrapper() ProfilesDataWrapper {
return NewProfilesDataWrapper(GenTestProfilesData(), NewState())
}
================================================
FILE: pdata/internal/generated_wrapper_resource.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
type ResourceWrapper struct {
orig *Resource
state *State
}
func GetResourceOrig(ms ResourceWrapper) *Resource {
return ms.orig
}
func GetResourceState(ms ResourceWrapper) *State {
return ms.state
}
func NewResourceWrapper(orig *Resource, state *State) ResourceWrapper {
return ResourceWrapper{orig: orig, state: state}
}
func GenTestResourceWrapper() ResourceWrapper {
return NewResourceWrapper(GenTestResource(), NewState())
}
================================================
FILE: pdata/internal/generated_wrapper_stringslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
type StringSliceWrapper struct {
orig *[]string
state *State
}
func GetStringSliceOrig(ms StringSliceWrapper) *[]string {
return ms.orig
}
func GetStringSliceState(ms StringSliceWrapper) *State {
return ms.state
}
func NewStringSliceWrapper(orig *[]string, state *State) StringSliceWrapper {
return StringSliceWrapper{orig: orig, state: state}
}
func GenTestStringSliceWrapper() StringSliceWrapper {
orig := []string{"a", "b", "c"}
return NewStringSliceWrapper(&orig, NewState())
}
func GenTestStringSlice() []string {
return []string{"a", "b", "c"}
}
================================================
FILE: pdata/internal/generated_wrapper_uint64slice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package internal
type UInt64SliceWrapper struct {
orig *[]uint64
state *State
}
func GetUInt64SliceOrig(ms UInt64SliceWrapper) *[]uint64 {
return ms.orig
}
func GetUInt64SliceState(ms UInt64SliceWrapper) *State {
return ms.state
}
func NewUInt64SliceWrapper(orig *[]uint64, state *State) UInt64SliceWrapper {
return UInt64SliceWrapper{orig: orig, state: state}
}
func GenTestUInt64SliceWrapper() UInt64SliceWrapper {
orig := []uint64{1, 2, 3}
return NewUInt64SliceWrapper(&orig, NewState())
}
func GenTestUint64Slice() []uint64 {
return []uint64{1, 2, 3}
}
================================================
FILE: pdata/internal/json/iterator.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package json // import "go.opentelemetry.io/collector/pdata/internal/json"
import (
"encoding/base64"
"strconv"
jsoniter "github.com/json-iterator/go"
)
func BorrowIterator(data []byte) *Iterator {
return &Iterator{
delegate: jsoniter.ConfigFastest.BorrowIterator(data),
}
}
func ReturnIterator(s *Iterator) {
jsoniter.ConfigFastest.ReturnIterator(s.delegate)
}
type Iterator struct {
delegate *jsoniter.Iterator
}
// ReadInt32 unmarshalls JSON data into an int32. Accepts both numbers and strings decimal.
// See https://developers.google.com/protocol-buffers/docs/proto3#json.
func (iter *Iterator) ReadInt32() int32 {
switch iter.delegate.WhatIsNext() {
case jsoniter.NumberValue:
return iter.delegate.ReadInt32()
case jsoniter.StringValue:
val, err := strconv.ParseInt(iter.ReadString(), 10, 32)
if err != nil {
iter.ReportError("ReadInt32", err.Error())
return 0
}
return int32(val)
default:
iter.ReportError("ReadInt32", "unsupported value type")
return 0
}
}
// ReadUint32 unmarshalls JSON data into an uint32. Accepts both numbers and strings decimal.
// See https://developers.google.com/protocol-buffers/docs/proto3#json.
func (iter *Iterator) ReadUint32() uint32 {
switch iter.delegate.WhatIsNext() {
case jsoniter.NumberValue:
return iter.delegate.ReadUint32()
case jsoniter.StringValue:
val, err := strconv.ParseUint(iter.ReadString(), 10, 32)
if err != nil {
iter.ReportError("ReadUint32", err.Error())
return 0
}
return uint32(val)
default:
iter.ReportError("ReadUint32", "unsupported value type")
return 0
}
}
// ReadInt64 unmarshalls JSON data into an int64. Accepts both numbers and strings decimal.
// See https://developers.google.com/protocol-buffers/docs/proto3#json.
func (iter *Iterator) ReadInt64() int64 {
switch iter.delegate.WhatIsNext() {
case jsoniter.NumberValue:
return iter.delegate.ReadInt64()
case jsoniter.StringValue:
val, err := strconv.ParseInt(iter.ReadString(), 10, 64)
if err != nil {
iter.ReportError("ReadInt64", err.Error())
return 0
}
return val
default:
iter.ReportError("ReadInt64", "unsupported value type")
return 0
}
}
// ReadUint64 unmarshalls JSON data into an uint64. Accepts both numbers and strings decimal.
// See https://developers.google.com/protocol-buffers/docs/proto3#json.
func (iter *Iterator) ReadUint64() uint64 {
switch iter.delegate.WhatIsNext() {
case jsoniter.NumberValue:
return iter.delegate.ReadUint64()
case jsoniter.StringValue:
val, err := strconv.ParseUint(iter.ReadString(), 10, 64)
if err != nil {
iter.ReportError("ReadUint64", err.Error())
return 0
}
return val
default:
iter.ReportError("ReadUint64", "unsupported value type")
return 0
}
}
func (iter *Iterator) ReadFloat32() float32 {
switch iter.delegate.WhatIsNext() {
case jsoniter.NumberValue:
return iter.delegate.ReadFloat32()
case jsoniter.StringValue:
val, err := strconv.ParseFloat(iter.ReadString(), 32)
if err != nil {
iter.ReportError("ReadUint64", err.Error())
return 0
}
return float32(val)
default:
iter.ReportError("ReadUint64", "unsupported value type")
return 0
}
}
func (iter *Iterator) ReadFloat64() float64 {
switch iter.delegate.WhatIsNext() {
case jsoniter.NumberValue:
return iter.delegate.ReadFloat64()
case jsoniter.StringValue:
val, err := strconv.ParseFloat(iter.ReadString(), 64)
if err != nil {
iter.ReportError("ReadUint64", err.Error())
return 0
}
return val
default:
iter.ReportError("ReadUint64", "unsupported value type")
return 0
}
}
// ReadBool reads a json object as BoolValue
func (iter *Iterator) ReadBool() bool {
return iter.delegate.ReadBool()
}
// ReadString read string from iterator
func (iter *Iterator) ReadString() string {
return iter.delegate.ReadString()
}
// ReadBytes read base64 encoded bytes from iterator.
func (iter *Iterator) ReadBytes() []byte {
buf := iter.ReadStringAsSlice()
if len(buf) == 0 {
return nil
}
orig := make([]byte, base64.StdEncoding.DecodedLen(len(buf)))
n, err := base64.StdEncoding.Decode(orig, buf)
if err != nil {
iter.ReportError("base64.Decode", err.Error())
}
return orig[:n]
}
// ReadStringAsSlice read string from iterator without copying into string form.
// The []byte cannot be kept, as it will change after next iterator call.
func (iter *Iterator) ReadStringAsSlice() []byte {
return iter.delegate.ReadStringAsSlice()
}
// ReportError record a error in iterator instance with current position.
func (iter *Iterator) ReportError(operation, msg string) {
iter.delegate.ReportError(operation, msg)
}
// Error returns any recorded error if any otherwise it returns nil.
func (iter *Iterator) Error() error {
return iter.delegate.Error
}
// Skip skips a json object and positions to relatively the next json object
func (iter *Iterator) Skip() {
iter.delegate.Skip()
}
// ReadArray read array element, returns true if the array has more element to read.
func (iter *Iterator) ReadArray() bool {
return iter.delegate.ReadArray()
}
// ReadObject read one field from object.
// If object ended, returns empty string. Otherwise, returns the field name.
func (iter *Iterator) ReadObject() string {
return iter.delegate.ReadObject()
}
// ReadEnumValue returns the enum integer value representation. Accepts both enum names and enum integer values.
// See https://developers.google.com/protocol-buffers/docs/proto3#json.
func (iter *Iterator) ReadEnumValue(valueMap map[string]int32) int32 {
switch iter.delegate.WhatIsNext() {
case jsoniter.NumberValue:
return iter.ReadInt32()
case jsoniter.StringValue:
val, ok := valueMap[iter.ReadString()]
// Same behavior with official protobuf JSON decoder,
// see https://github.com/open-telemetry/opentelemetry-proto-go/pull/81
if !ok {
iter.ReportError("ReadEnumValue", "unknown string value")
return 0
}
return val
default:
iter.ReportError("ReadEnumValue", "unsupported value type")
return 0
}
}
// ResetBytes reuse iterator instance by specifying another byte array as input
func (iter *Iterator) ResetBytes(input []byte) *Iterator {
iter.delegate.ResetBytes(input)
return iter
}
================================================
FILE: pdata/internal/json/iterator_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package json
import (
"math"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestReadInt32(t *testing.T) {
tests := []struct {
name string
jsonStr string
want int32
wantErr bool
}{
{
name: "number",
jsonStr: `1 `,
want: 1,
},
{
name: "string",
jsonStr: `"1"`,
want: 1,
},
{
name: "negative number",
jsonStr: `-1 `,
want: -1,
},
{
name: "negative string",
jsonStr: `"-1"`,
want: -1,
},
{
name: "wrong string",
jsonStr: `"3.f14"`,
wantErr: true,
},
{
name: "wrong type",
jsonStr: `true`,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
iter := BorrowIterator([]byte(tt.jsonStr))
defer ReturnIterator(iter)
val := iter.ReadInt32()
if tt.wantErr {
require.Error(t, iter.Error())
return
}
require.NoError(t, iter.Error())
assert.Equal(t, tt.want, val)
})
}
}
func TestReadUint32(t *testing.T) {
tests := []struct {
name string
jsonStr string
want uint32
wantErr bool
}{
{
name: "number",
jsonStr: `1 `,
want: 1,
},
{
name: "string",
jsonStr: `"1"`,
want: 1,
},
{
name: "negative number",
jsonStr: `-1 `,
wantErr: true,
},
{
name: "negative string",
jsonStr: `"-1"`,
wantErr: true,
},
{
name: "wrong string",
jsonStr: `"3.f14"`,
wantErr: true,
},
{
name: "wrong type",
jsonStr: `true`,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
iter := BorrowIterator([]byte(tt.jsonStr))
defer ReturnIterator(iter)
val := iter.ReadUint32()
if tt.wantErr {
require.Error(t, iter.Error())
return
}
require.NoError(t, iter.Error())
assert.Equal(t, tt.want, val)
})
}
}
func TestReadInt64(t *testing.T) {
tests := []struct {
name string
jsonStr string
want int64
wantErr bool
}{
{
name: "number",
jsonStr: `1 `,
want: 1,
},
{
name: "string",
jsonStr: `"1"`,
want: 1,
},
{
name: "negative number",
jsonStr: `-1 `,
want: -1,
},
{
name: "negative string",
jsonStr: `"-1"`,
want: -1,
},
{
name: "wrong string",
jsonStr: `"3.f14"`,
wantErr: true,
},
{
name: "wrong type",
jsonStr: `true`,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
iter := BorrowIterator([]byte(tt.jsonStr))
defer ReturnIterator(iter)
val := iter.ReadInt64()
if tt.wantErr {
require.Error(t, iter.Error())
return
}
require.NoError(t, iter.Error())
assert.Equal(t, tt.want, val)
})
}
}
func TestReadUint64(t *testing.T) {
tests := []struct {
name string
jsonStr string
want uint64
wantErr bool
}{
{
name: "number",
jsonStr: `1 `,
want: 1,
},
{
name: "string",
jsonStr: `"1"`,
want: 1,
},
{
name: "negative number",
jsonStr: `-1 `,
wantErr: true,
},
{
name: "negative string",
jsonStr: `"-1"`,
wantErr: true,
},
{
name: "wrong string",
jsonStr: `"3.f14"`,
wantErr: true,
},
{
name: "wrong type",
jsonStr: `true`,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
iter := BorrowIterator([]byte(tt.jsonStr))
defer ReturnIterator(iter)
val := iter.ReadUint64()
if tt.wantErr {
require.Error(t, iter.Error())
return
}
require.NoError(t, iter.Error())
assert.Equal(t, tt.want, val)
})
}
}
func TestReadFloat32(t *testing.T) {
tests := []struct {
name string
jsonStr string
want float32
wantErr bool
}{
{
name: "number",
jsonStr: `3.14 `,
want: 3.14,
},
{
name: "string",
jsonStr: `"3.14"`,
want: 3.14,
},
{
name: "negative number",
jsonStr: `-3.14 `,
want: -3.14,
},
{
name: "negative string",
jsonStr: `"-3.14"`,
want: -3.14,
},
{
name: "wrong string",
jsonStr: `"3.f14"`,
wantErr: true,
},
{
name: "wrong type",
jsonStr: `true`,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
iter := BorrowIterator([]byte(tt.jsonStr))
defer ReturnIterator(iter)
val := iter.ReadFloat32()
if tt.wantErr {
require.Error(t, iter.Error())
return
}
require.NoError(t, iter.Error())
assert.InDelta(t, tt.want, val, 0.01)
})
}
}
func TestReadFloat32MaxValue(t *testing.T) {
iter := BorrowIterator([]byte(`{"value": 3.4028234663852886e+38}`))
defer ReturnIterator(iter)
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
assert.Equal(t, "value", f)
assert.InDelta(t, math.MaxFloat32, iter.ReadFloat64(), 0.01)
}
require.NoError(t, iter.Error())
}
func TestReadFloat64(t *testing.T) {
tests := []struct {
name string
jsonStr string
want float64
wantErr bool
}{
{
name: "number",
jsonStr: `3.14 `,
want: 3.14,
},
{
name: "string",
jsonStr: `"3.14"`,
want: 3.14,
},
{
name: "negative number",
jsonStr: `-3.14 `,
want: -3.14,
},
{
name: "negative string",
jsonStr: `"-3.14"`,
want: -3.14,
},
{
name: "wrong string",
jsonStr: `"3.f14"`,
wantErr: true,
},
{
name: "wrong type",
jsonStr: `true`,
wantErr: true,
},
{
name: "positive infinity",
jsonStr: `"Infinity"`,
want: math.Inf(1),
},
{
name: "negative infinity",
jsonStr: `"-Infinity"`,
want: math.Inf(-1),
},
{
name: "not-a-number",
jsonStr: `"NaN"`,
want: math.NaN(),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
iter := BorrowIterator([]byte(tt.jsonStr))
defer ReturnIterator(iter)
val := iter.ReadFloat64()
if tt.wantErr {
require.Error(t, iter.Error())
return
}
require.NoError(t, iter.Error())
assert.InDelta(t, tt.want, val, 0.01)
})
}
}
func TestReadFloat64MaxValue(t *testing.T) {
iter := BorrowIterator([]byte(`{"value": 1.7976931348623157e+308}`))
defer ReturnIterator(iter)
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
assert.Equal(t, "value", f)
assert.InDelta(t, math.MaxFloat64, iter.ReadFloat64(), 0.01)
}
require.NoError(t, iter.Error())
}
func TestReadEnumValue(t *testing.T) {
valueMap := map[string]int32{
"undefined": 0,
"foo": 1,
"bar": 2,
}
tests := []struct {
name string
jsonStr string
want int32
wantErr bool
}{
{
name: "foo string",
jsonStr: "\"foo\"\n",
want: 1,
},
{
name: "foo number",
jsonStr: "1\n",
want: 1,
},
{
name: "unknown number",
jsonStr: "5\n",
want: 5,
},
{
name: "bar string",
jsonStr: "\"bar\"\n",
want: 2,
},
{
name: "bar number",
jsonStr: "2\n",
want: 2,
},
{
name: "unknown string",
jsonStr: "\"baz\"\n",
wantErr: true,
},
{
name: "wrong type",
jsonStr: "true",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
iter := BorrowIterator([]byte(tt.jsonStr))
defer ReturnIterator(iter)
val := iter.ReadEnumValue(valueMap)
if tt.wantErr {
assert.Error(t, iter.Error())
return
}
require.NoError(t, iter.Error())
assert.Equal(t, tt.want, val)
})
}
}
func TestReadBytes(t *testing.T) {
iter := BorrowIterator([]byte(`{"value": "dGVzdA=="}`))
defer ReturnIterator(iter)
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
assert.Equal(t, "value", f)
assert.Equal(t, "test", string(iter.ReadBytes()))
}
require.NoError(t, iter.Error())
}
================================================
FILE: pdata/internal/json/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package json
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: pdata/internal/json/stream.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package json // import "go.opentelemetry.io/collector/pdata/internal/json"
import (
"encoding/base64"
"errors"
"io"
"math"
"strconv"
jsoniter "github.com/json-iterator/go"
)
func BorrowStream(writer io.Writer) *Stream {
return &Stream{
Stream: jsoniter.ConfigFastest.BorrowStream(writer),
wmTracker: make([]bool, 32),
}
}
func ReturnStream(s *Stream) {
jsoniter.ConfigFastest.ReturnStream(s.Stream)
}
// Stream avoids the need to explicitly call the `Stream.WriteMore` method while marshaling objects by
// checking if a field was previously written inside the current object and automatically appending a ","
// if so before writing the next field.
type Stream struct {
*jsoniter.Stream
// wmTracker acts like a stack which pushes a new value when an object is started and removes the
// top when it is ended. The value added for every object tracks if there is any written field
// already for that object, and if it is then automatically add a "," before any new field.
wmTracker []bool
}
func (ots *Stream) WriteObjectStart() {
ots.Stream.WriteObjectStart()
ots.wmTracker = append(ots.wmTracker, false)
}
func (ots *Stream) WriteObjectField(field string) {
if ots.wmTracker[len(ots.wmTracker)-1] {
ots.WriteMore()
}
ots.Stream.WriteObjectField(field)
ots.wmTracker[len(ots.wmTracker)-1] = true
}
func (ots *Stream) WriteObjectEnd() {
ots.Stream.WriteObjectEnd()
ots.wmTracker = ots.wmTracker[:len(ots.wmTracker)-1]
}
// WriteInt64 writes the values as a decimal string. This is per the protobuf encoding rules for int64, fixed64, uint64.
func (ots *Stream) WriteInt64(val int64) {
ots.WriteString(strconv.FormatInt(val, 10))
}
// WriteUint64 writes the values as a decimal string. This is per the protobuf encoding rules for int64, fixed64, uint64.
func (ots *Stream) WriteUint64(val uint64) {
ots.WriteString(strconv.FormatUint(val, 10))
}
// WriteBytes writes the values as a base64 encoded string. This is per the protobuf encoding rules for bytes.
func (ots *Stream) WriteBytes(val []byte) {
if len(val) == 0 {
ots.WriteString("")
return
}
ots.WriteString(base64.StdEncoding.EncodeToString(val))
}
// WriteFloat64 writes the JSON value that will be a number or one of the special string
// values "NaN", "Infinity", and "-Infinity". Either numbers or strings are accepted.
// Empty strings are invalid. Exponent notation is also accepted.
// See https://protobuf.dev/programming-guides/json/.
func (ots *Stream) WriteFloat64(val float64) {
if math.IsNaN(val) {
ots.WriteString("NaN")
return
}
if math.IsInf(val, 1) {
ots.WriteString("Infinity")
return
}
if math.IsInf(val, -1) {
ots.WriteString("-Infinity")
return
}
ots.Stream.WriteFloat64(val)
}
func (ots *Stream) ReportError(err error) {
ots.Stream.Error = errors.Join(ots.Stream.Error, err)
}
func (ots *Stream) Error() error {
return ots.Stream.Error
}
================================================
FILE: pdata/internal/json/stream_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package json
import (
"math"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNestedObject(t *testing.T) {
s := BorrowStream(nil)
defer ReturnStream(s)
s.WriteObjectStart()
s.WriteObjectField("field1")
s.WriteString("val1")
s.WriteObjectField("field2")
s.WriteObjectStart()
s.WriteObjectField("field3")
s.WriteObjectStart()
s.WriteObjectField("field4")
s.WriteString("val4")
s.WriteObjectField("field5")
s.WriteString("val5")
s.WriteObjectEnd()
s.WriteObjectField("field6")
s.WriteString("val6")
s.WriteObjectField("field7")
s.WriteObjectStart()
s.WriteObjectEnd()
s.WriteObjectEnd()
s.WriteObjectEnd()
expected := `{
"field1": "val1",
"field2": {
"field3": {
"field4": "val4",
"field5": "val5"
},
"field6": "val6",
"field7": {}
}
}`
assert.JSONEq(t, expected, string(s.Buffer()))
}
func TestMarshalFloat(t *testing.T) {
tests := []struct {
name string
inputFloat float64
expected string
}{
{
name: "positive infinity",
inputFloat: math.Inf(1),
expected: `"Infinity"`,
},
{
name: "negative infinity",
inputFloat: math.Inf(-1),
expected: `"-Infinity"`,
},
{
name: "not-a-number",
inputFloat: math.NaN(),
expected: `"NaN"`,
},
{
name: "regular float",
inputFloat: math.MaxFloat64,
expected: "1.7976931348623157e+308",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := BorrowStream(nil)
defer ReturnStream(s)
s.WriteFloat64(tt.inputFloat)
require.Equal(t, tt.expected, string(s.Buffer()))
})
}
}
func TestWriteBytes(t *testing.T) {
s := BorrowStream(nil)
defer ReturnStream(s)
s.WriteBytes([]byte("test"))
require.Equal(t, `"dGVzdA=="`, string(s.Buffer()))
}
================================================
FILE: pdata/internal/metadata/generated_feature_gates.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/featuregate"
)
var PdataUseCustomProtoEncodingFeatureGate = featuregate.GlobalRegistry().MustRegister(
"pdata.useCustomProtoEncoding",
featuregate.StageStable,
featuregate.WithRegisterDescription("When enabled, enable custom proto encoding. This is a required step to enable featuregate pdata.useProtoPooling."),
featuregate.WithRegisterReferenceURL("https://github.com/open-telemetry/opentelemetry-collector/issues/13631"),
featuregate.WithRegisterFromVersion("v0.133.0"),
featuregate.WithRegisterToVersion("v0.137.0"),
)
var PdataUseProtoPoolingFeatureGate = featuregate.GlobalRegistry().MustRegister(
"pdata.useProtoPooling",
featuregate.StageAlpha,
featuregate.WithRegisterDescription("When enabled, enable using local memory pools for underlying data that the pdata messages are pushed to."),
featuregate.WithRegisterReferenceURL("https://github.com/open-telemetry/opentelemetry-collector/issues/13631"),
featuregate.WithRegisterFromVersion("v0.133.0"),
)
================================================
FILE: pdata/internal/otelgrpc/encoding.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelgrpc // import "go.opentelemetry.io/collector/pdata/internal/otelgrpc"
import (
"google.golang.org/grpc/encoding"
"google.golang.org/grpc/mem"
)
var (
defaultBufferPoolSizes = []int{
256,
4 << 10, // 4KB (go page size)
16 << 10, // 16KB (max HTTP/2 frame size used by gRPC)
32 << 10, // 32KB (default buffer size for io.Copy)
512 << 10, // 512KB
1 << 20, // 1MB
4 << 20, // 4MB
16 << 20, // 16MB
}
otelBufferPool = mem.NewTieredBufferPool(defaultBufferPoolSizes...)
)
// DefaultBufferPool returns the current default buffer pool. It is a BufferPool
// created with mem.NewTieredBufferPool that uses a set of default sizes optimized for
// expected telemetry workflows.
func DefaultBufferPool() mem.BufferPool {
return otelBufferPool
}
// Name is the name registered for the proto compressor.
const Name = "proto"
func init() {
encoding.RegisterCodecV2(&codecV2{delegate: encoding.GetCodecV2(Name)})
}
// codecV2 is a custom proto encoding that uses a different tier schema for the TieredBufferPool as well
// as it call into the custom marshal/unmarshal logic that works with memory pooling.
// If not an otlp payload fallback on the default grpc/proto encoding.
type codecV2 struct {
delegate encoding.CodecV2
}
type otelEncoder interface {
SizeProto() int
MarshalProto([]byte) int
UnmarshalProto([]byte) error
}
func (c *codecV2) Marshal(v any) (mem.BufferSlice, error) {
if m, ok := v.(otelEncoder); ok {
size := m.SizeProto()
buf := otelBufferPool.Get(size)
n := m.MarshalProto((*buf)[:size])
*buf = (*buf)[:n]
return []mem.Buffer{mem.NewBuffer(buf, otelBufferPool)}, nil
}
return c.delegate.Marshal(v)
}
func (c *codecV2) Unmarshal(data mem.BufferSlice, v any) (err error) {
if m, ok := v.(otelEncoder); ok {
// TODO: Upgrade custom Unmarshal logic to support reading from mem.BufferSlice.
buf := data.MaterializeToBuffer(otelBufferPool)
defer buf.Free()
return m.UnmarshalProto(buf.ReadOnlyData())
}
return c.delegate.Unmarshal(data, v)
}
func (c *codecV2) Name() string {
return Name
}
================================================
FILE: pdata/internal/otelgrpc/logs_service.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelgrpc // import "go.opentelemetry.io/collector/pdata/internal/otelgrpc"
import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"go.opentelemetry.io/collector/pdata/internal"
)
// 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 {
Export(context.Context, *internal.ExportLogsServiceRequest, ...grpc.CallOption) (*internal.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 *internal.ExportLogsServiceRequest, opts ...grpc.CallOption) (*internal.ExportLogsServiceResponse, error) {
out := new(internal.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 {
Export(context.Context, *internal.ExportLogsServiceRequest) (*internal.ExportLogsServiceResponse, error)
}
// UnimplementedLogsServiceServer can be embedded to have forward compatible implementations.
type UnimplementedLogsServiceServer struct{}
func (*UnimplementedLogsServiceServer) Export(context.Context, *internal.ExportLogsServiceRequest) (*internal.ExportLogsServiceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Export not implemented")
}
func RegisterLogsServiceServer(s *grpc.Server, srv LogsServiceServer) {
s.RegisterService(&logsServiceServiceDesc, srv)
}
// Context cannot be the first parameter of the function because gRPC definition.
//
//nolint:revive
func logsServiceExportHandler(srv any, ctx context.Context, dec func(any) error, interceptor grpc.UnaryServerInterceptor) (any, error) {
in := new(internal.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 any) (any, error) {
return srv.(LogsServiceServer).Export(ctx, req.(*internal.ExportLogsServiceRequest))
}
return interceptor(ctx, in, info, handler)
}
var logsServiceServiceDesc = grpc.ServiceDesc{
ServiceName: "opentelemetry.proto.collector.logs.v1.LogsService",
HandlerType: (*LogsServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Export",
Handler: logsServiceExportHandler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "opentelemetry/proto/collector/logs/v1/logs_service.proto",
}
================================================
FILE: pdata/internal/otelgrpc/metrics_service.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelgrpc // import "go.opentelemetry.io/collector/pdata/internal/otelgrpc"
import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"go.opentelemetry.io/collector/pdata/internal"
)
// 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 {
Export(context.Context, *internal.ExportMetricsServiceRequest, ...grpc.CallOption) (*internal.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 *internal.ExportMetricsServiceRequest, opts ...grpc.CallOption) (*internal.ExportMetricsServiceResponse, error) {
out := new(internal.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 {
Export(context.Context, *internal.ExportMetricsServiceRequest) (*internal.ExportMetricsServiceResponse, error)
}
// UnimplementedMetricsServiceServer can be embedded to have forward compatible implementations.
type UnimplementedMetricsServiceServer struct{}
func (*UnimplementedMetricsServiceServer) Export(context.Context, *internal.ExportMetricsServiceRequest) (*internal.ExportMetricsServiceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Export not implemented")
}
func RegisterMetricsServiceServer(s *grpc.Server, srv MetricsServiceServer) {
s.RegisterService(&metricsServiceServiceDesc, srv)
}
// Context cannot be the first parameter of the function because gRPC definition.
//
//nolint:revive
func metricsServiceExportHandler(srv any, ctx context.Context, dec func(any) error, interceptor grpc.UnaryServerInterceptor) (any, error) {
in := new(internal.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 any) (any, error) {
return srv.(MetricsServiceServer).Export(ctx, req.(*internal.ExportMetricsServiceRequest))
}
return interceptor(ctx, in, info, handler)
}
var metricsServiceServiceDesc = grpc.ServiceDesc{
ServiceName: "opentelemetry.proto.collector.metrics.v1.MetricsService",
HandlerType: (*MetricsServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Export",
Handler: metricsServiceExportHandler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "opentelemetry/proto/collector/metrics/v1/metrics_service.proto",
}
================================================
FILE: pdata/internal/otelgrpc/profiles_service.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelgrpc // import "go.opentelemetry.io/collector/pdata/internal/otelgrpc"
import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"go.opentelemetry.io/collector/pdata/internal"
)
// ProfilesServiceClient is the client API for ProfilesService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type ProfilesServiceClient interface {
Export(context.Context, *internal.ExportProfilesServiceRequest, ...grpc.CallOption) (*internal.ExportProfilesServiceResponse, error)
}
type profilesServiceClient struct {
cc *grpc.ClientConn
}
func NewProfilesServiceClient(cc *grpc.ClientConn) ProfilesServiceClient {
return &profilesServiceClient{cc}
}
func (c *profilesServiceClient) Export(ctx context.Context, in *internal.ExportProfilesServiceRequest, opts ...grpc.CallOption) (*internal.ExportProfilesServiceResponse, error) {
out := new(internal.ExportProfilesServiceResponse)
err := c.cc.Invoke(ctx, "/opentelemetry.proto.collector.profiles.v1development.ProfilesService/Export", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// ProfilesServiceServer is the server API for ProfilesService service.
type ProfilesServiceServer interface {
Export(context.Context, *internal.ExportProfilesServiceRequest) (*internal.ExportProfilesServiceResponse, error)
}
// UnimplementedProfilesServiceServer can be embedded to have forward compatible implementations.
type UnimplementedProfilesServiceServer struct{}
func (*UnimplementedProfilesServiceServer) Export(context.Context, *internal.ExportProfilesServiceRequest) (*internal.ExportProfilesServiceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Export not implemented")
}
func RegisterProfilesServiceServer(s *grpc.Server, srv ProfilesServiceServer) {
s.RegisterService(&profilesServiceServiceDesc, srv)
}
// Context cannot be the first parameter of the function because gRPC definition.
//
//nolint:revive
func profilesServiceExportHandler(srv any, ctx context.Context, dec func(any) error, interceptor grpc.UnaryServerInterceptor) (any, error) {
in := new(internal.ExportProfilesServiceRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ProfilesServiceServer).Export(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/opentelemetry.proto.collector.profiles.v1development.ProfilesService/Export",
}
handler := func(ctx context.Context, req any) (any, error) {
return srv.(ProfilesServiceServer).Export(ctx, req.(*internal.ExportProfilesServiceRequest))
}
return interceptor(ctx, in, info, handler)
}
var profilesServiceServiceDesc = grpc.ServiceDesc{
ServiceName: "opentelemetry.proto.collector.profiles.v1development.ProfilesService",
HandlerType: (*ProfilesServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Export",
Handler: profilesServiceExportHandler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "opentelemetry/proto/collector/profiles/v1development/profiles_service.proto",
}
================================================
FILE: pdata/internal/otelgrpc/trace_service.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelgrpc // import "go.opentelemetry.io/collector/pdata/internal/otelgrpc"
import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"go.opentelemetry.io/collector/pdata/internal"
)
// 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 {
Export(context.Context, *internal.ExportTraceServiceRequest, ...grpc.CallOption) (*internal.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 *internal.ExportTraceServiceRequest, opts ...grpc.CallOption) (*internal.ExportTraceServiceResponse, error) {
out := new(internal.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 {
Export(context.Context, *internal.ExportTraceServiceRequest) (*internal.ExportTraceServiceResponse, error)
}
// UnimplementedTraceServiceServer can be embedded to have forward compatible implementations.
type UnimplementedTraceServiceServer struct{}
func (*UnimplementedTraceServiceServer) Export(context.Context, *internal.ExportTraceServiceRequest) (*internal.ExportTraceServiceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Export not implemented")
}
func RegisterTraceServiceServer(s *grpc.Server, srv TraceServiceServer) {
s.RegisterService(&traceServiceServiceDesc, srv)
}
// Context cannot be the first parameter of the function because gRPC definition.
//
//nolint:revive
func traceServiceExportHandler(srv any, ctx context.Context, dec func(any) error, interceptor grpc.UnaryServerInterceptor) (any, error) {
in := new(internal.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 any) (any, error) {
return srv.(TraceServiceServer).Export(ctx, req.(*internal.ExportTraceServiceRequest))
}
return interceptor(ctx, in, info, handler)
}
var traceServiceServiceDesc = grpc.ServiceDesc{
ServiceName: "opentelemetry.proto.collector.trace.v1.TraceService",
HandlerType: (*TraceServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Export",
Handler: traceServiceExportHandler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "opentelemetry/proto/collector/trace/v1/trace_service.proto",
}
================================================
FILE: pdata/internal/otlp/logs.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlp // import "go.opentelemetry.io/collector/pdata/internal/otlp"
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// MigrateLogs implements any translation needed due to deprecation in OTLP logs protocol.
// Any plog.Unmarshaler implementation from OTLP (proto/json) MUST call this, and the gRPC Server implementation.
func MigrateLogs(rls []*internal.ResourceLogs) {
for _, rl := range rls {
if len(rl.ScopeLogs) == 0 {
rl.ScopeLogs = rl.DeprecatedScopeLogs
}
rl.DeprecatedScopeLogs = nil
}
}
================================================
FILE: pdata/internal/otlp/logs_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlp
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestDeprecatedScopeLogs(t *testing.T) {
sl := new(internal.ScopeLogs)
rls := []*internal.ResourceLogs{
{
ScopeLogs: []*internal.ScopeLogs{sl},
DeprecatedScopeLogs: []*internal.ScopeLogs{sl},
},
{
ScopeLogs: []*internal.ScopeLogs{},
DeprecatedScopeLogs: []*internal.ScopeLogs{sl},
},
}
MigrateLogs(rls)
assert.Same(t, sl, rls[0].ScopeLogs[0])
assert.Same(t, sl, rls[1].ScopeLogs[0])
assert.Nil(t, rls[0].DeprecatedScopeLogs)
assert.Nil(t, rls[0].DeprecatedScopeLogs)
}
================================================
FILE: pdata/internal/otlp/metrics.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlp // import "go.opentelemetry.io/collector/pdata/internal/otlp"
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// MigrateMetrics implements any translation needed due to deprecation in OTLP metrics protocol.
// Any pmetric.Unmarshaler implementation from OTLP (proto/json) MUST call this, and the gRPC Server implementation.
func MigrateMetrics(rms []*internal.ResourceMetrics) {
for _, rm := range rms {
if len(rm.ScopeMetrics) == 0 {
rm.ScopeMetrics = rm.DeprecatedScopeMetrics
}
rm.DeprecatedScopeMetrics = nil
}
}
================================================
FILE: pdata/internal/otlp/metrics_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlp
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestDeprecatedScopeMetrics(t *testing.T) {
sm := new(internal.ScopeMetrics)
rms := []*internal.ResourceMetrics{
{
ScopeMetrics: []*internal.ScopeMetrics{sm},
DeprecatedScopeMetrics: []*internal.ScopeMetrics{sm},
},
{
ScopeMetrics: []*internal.ScopeMetrics{},
DeprecatedScopeMetrics: []*internal.ScopeMetrics{sm},
},
}
MigrateMetrics(rms)
assert.Same(t, sm, rms[0].ScopeMetrics[0])
assert.Same(t, sm, rms[1].ScopeMetrics[0])
assert.Nil(t, rms[0].DeprecatedScopeMetrics)
assert.Nil(t, rms[0].DeprecatedScopeMetrics)
}
================================================
FILE: pdata/internal/otlp/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlp
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: pdata/internal/otlp/profiles.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlp // import "go.opentelemetry.io/collector/pdata/internal/otlp"
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// MigrateProfiles implements any translation needed due to deprecation in OTLP profiles protocol.
// Any pprofile.Unmarshaler implementation from OTLP (proto/json) MUST call this, and the gRPC Server implementation.
func MigrateProfiles(_ []*internal.ResourceProfiles) {}
================================================
FILE: pdata/internal/otlp/profiles_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlp
import (
"testing"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestMigrateProfiles(_ *testing.T) {
rps := []*internal.ResourceProfiles{
{},
}
MigrateProfiles(rps)
}
================================================
FILE: pdata/internal/otlp/traces.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlp // import "go.opentelemetry.io/collector/pdata/internal/otlp"
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// MigrateTraces implements any translation needed due to deprecation in OTLP traces protocol.
// Any ptrace.Unmarshaler implementation from OTLP (proto/json) MUST call this, and the gRPC Server implementation.
func MigrateTraces(rss []*internal.ResourceSpans) {
for _, rs := range rss {
if len(rs.ScopeSpans) == 0 {
rs.ScopeSpans = rs.DeprecatedScopeSpans
}
rs.DeprecatedScopeSpans = nil
}
}
================================================
FILE: pdata/internal/otlp/traces_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlp
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestDeprecatedScopeSpans(t *testing.T) {
ss := new(internal.ScopeSpans)
rss := []*internal.ResourceSpans{
{
ScopeSpans: []*internal.ScopeSpans{ss},
DeprecatedScopeSpans: []*internal.ScopeSpans{ss},
},
{
ScopeSpans: []*internal.ScopeSpans{},
DeprecatedScopeSpans: []*internal.ScopeSpans{ss},
},
}
MigrateTraces(rss)
assert.Same(t, ss, rss[0].ScopeSpans[0])
assert.Same(t, ss, rss[1].ScopeSpans[0])
assert.Nil(t, rss[0].DeprecatedScopeSpans)
assert.Nil(t, rss[0].DeprecatedScopeSpans)
}
================================================
FILE: pdata/internal/profileid.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/pdata/internal"
import (
"encoding/hex"
"errors"
"go.opentelemetry.io/collector/pdata/internal/json"
)
const profileIDSize = 16
var errUnmarshalProfileID = errors.New("unmarshal: invalid ProfileID length")
// ProfileID is a custom data type that is used for all profile_id fields in OTLP
// Protobuf messages.
type ProfileID [profileIDSize]byte
func DeleteProfileID(*ProfileID, bool) {}
func CopyProfileID(dest, src *ProfileID) {
*dest = *src
}
// IsEmpty returns true if id contains at leas one non-zero byte.
func (pid ProfileID) IsEmpty() bool {
return pid == [profileIDSize]byte{}
}
// SizeProto returns the size of the data to serialize in proto format.
func (pid ProfileID) SizeProto() int {
if pid.IsEmpty() {
return 0
}
return profileIDSize
}
// MarshalProto converts profile ID into a binary representation. Called by Protobuf serialization.
func (pid ProfileID) MarshalProto(buf []byte) int {
if pid.IsEmpty() {
return 0
}
return copy(buf[len(buf)-profileIDSize:], pid[:])
}
// UnmarshalProto inflates this profile ID from binary representation. Called by Protobuf serialization.
func (pid *ProfileID) UnmarshalProto(buf []byte) error {
if len(buf) == 0 {
*pid = [profileIDSize]byte{}
return nil
}
if len(buf) != profileIDSize {
return errUnmarshalProfileID
}
copy(pid[:], buf)
return nil
}
// MarshalJSON converts ProfileID into a hex string.
//
//nolint:govet
func (pid ProfileID) MarshalJSON(dest *json.Stream) {
dest.WriteString(hex.EncodeToString(pid[:]))
}
// UnmarshalJSON decodes ProfileID from hex string.
//
//nolint:govet
func (pid *ProfileID) UnmarshalJSON(iter *json.Iterator) {
*pid = [profileIDSize]byte{}
unmarshalJSON(pid[:], iter)
}
func GenTestProfileID() *ProfileID {
pid := ProfileID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1})
return &pid
}
================================================
FILE: pdata/internal/profileid_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/internal/json"
)
func TestProfileID(t *testing.T) {
tid := ProfileID([profileIDSize]byte{})
assert.EqualValues(t, [profileIDSize]byte{}, tid)
assert.Equal(t, 0, tid.SizeProto())
b := [profileIDSize]byte{0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78}
tid = b
assert.EqualValues(t, b, tid)
assert.Equal(t, profileIDSize, tid.SizeProto())
}
func TestProfileIDMarshal(t *testing.T) {
buf := make([]byte, profileIDSize)
tid := ProfileID([profileIDSize]byte{})
n := tid.MarshalProto(buf)
assert.Equal(t, 0, n)
tid = [profileIDSize]byte{0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78}
n = tid.MarshalProto(buf)
assert.Equal(t, profileIDSize, n)
assert.Equal(t, []byte{0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78}, buf[0:profileIDSize])
}
func TestProfileIDUnmarshal(t *testing.T) {
buf := [profileIDSize]byte{0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78}
tid := ProfileID{}
err := tid.UnmarshalProto(buf[:])
require.NoError(t, err)
assert.EqualValues(t, buf, tid)
err = tid.UnmarshalProto(buf[0:0])
require.NoError(t, err)
assert.EqualValues(t, [profileIDSize]byte{}, tid)
err = tid.UnmarshalProto(nil)
require.NoError(t, err)
assert.EqualValues(t, [profileIDSize]byte{}, tid)
}
func TestProfileIDMarshalAndUnmarshalJSON(t *testing.T) {
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src := ProfileID([profileIDSize]byte{0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78})
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := ProfileID{}
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
}
================================================
FILE: pdata/internal/proto/marshal.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package proto // import "go.opentelemetry.io/collector/pdata/internal/proto"
// EncodeVarint encodes the variant at the end of the buffer.
func EncodeVarint(buf []byte, offset int, v uint64) int {
offset -= Sov(v)
base := offset
for v >= 1<<7 {
buf[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
buf[offset] = uint8(v)
return base
}
================================================
FILE: pdata/internal/proto/size.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package proto // import "go.opentelemetry.io/collector/pdata/internal/proto"
import (
"math/bits"
)
func Sov(x uint64) (n int) {
return (bits.Len64(x|1) + 6) / 7
}
func Soz(x uint64) (n int) {
return Sov((x << 1) ^ uint64((int64(x) >> 63)))
}
================================================
FILE: pdata/internal/proto/unmarshal.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package proto // import "go.opentelemetry.io/collector/pdata/internal/proto"
import (
"encoding/binary"
"errors"
"fmt"
"io"
)
// WireType represents the proto wire type.
type WireType int8
const (
WireTypeVarint WireType = 0
WireTypeI64 WireType = 1
WireTypeLen WireType = 2
WireTypeStartGroup WireType = 3
WireTypeEndGroup WireType = 4
WireTypeI32 WireType = 5
)
var (
ErrInvalidLength = errors.New("proto: negative length found during unmarshaling")
ErrIntOverflow = errors.New("proto: integer overflow")
ErrUnexpectedEndOfGroup = errors.New("proto: unexpected end of group")
)
// ConsumeUnknown parses buf starting at pos as a wireType field, reporting the new position.
func ConsumeUnknown(buf []byte, pos int, wireType WireType) (int, error) {
var err error
l := len(buf)
depth := 0
for pos < l {
switch wireType {
case WireTypeVarint:
_, pos, err = ConsumeVarint(buf, pos)
return pos, err
case WireTypeI64:
_, pos, err = ConsumeI64(buf, pos)
return pos, err
case WireTypeLen:
_, pos, err = ConsumeLen(buf, pos)
return pos, err
case WireTypeStartGroup:
depth++
case WireTypeEndGroup:
if depth == 0 {
return 0, ErrUnexpectedEndOfGroup
}
depth--
case WireTypeI32:
_, pos, err = ConsumeI32(buf, pos)
return pos, err
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
// Only when parsing a group can be here, if done return otherwise parse more tags.
if depth == 0 {
return pos, nil
}
// If in a group parsing, move to the next tag.
_, wireType, pos, err = ConsumeTag(buf, pos)
if err != nil {
return 0, err
}
}
return 0, io.ErrUnexpectedEOF
}
// ConsumeI64 parses buf starting at pos as a WireTypeI64 field, reporting the value and the new position.
func ConsumeI64(buf []byte, pos int) (uint64, int, error) {
pos += 8
if pos < 0 || pos > len(buf) {
return 0, 0, io.ErrUnexpectedEOF
}
return binary.LittleEndian.Uint64(buf[pos-8:]), pos, nil
}
// ConsumeLen parses buf starting at pos as a WireTypeLen field, reporting the len and the new position.
func ConsumeLen(buf []byte, pos int) (int, int, error) {
var num uint64
var err error
num, pos, err = ConsumeVarint(buf, pos)
if err != nil {
return 0, 0, err
}
length := int(num)
if length < 0 {
return 0, 0, ErrInvalidLength
}
pos += length
if pos < 0 || pos > len(buf) {
return 0, 0, io.ErrUnexpectedEOF
}
return length, pos, nil
}
// ConsumeI32 parses buf starting at pos as a WireTypeI32 field, reporting the value and the new position.
func ConsumeI32(buf []byte, pos int) (uint32, int, error) {
pos += 4
if pos < 0 || pos > len(buf) {
return 0, 0, io.ErrUnexpectedEOF
}
return binary.LittleEndian.Uint32(buf[pos-4:]), pos, nil
}
// ConsumeTag parses buf starting at pos as a varint-encoded tag, reporting the new position.
func ConsumeTag(buf []byte, pos int) (int32, WireType, int, error) {
tag, pos, err := ConsumeVarint(buf, pos)
if err != nil {
return 0, 0, 0, err
}
fieldNum := int32(tag >> 3)
wireType := int8(tag & 0x7)
if fieldNum <= 0 {
return 0, 0, 0, fmt.Errorf("proto: Link: illegal field=%d (tag=%d, pos=%d)", fieldNum, tag, pos)
}
return fieldNum, WireType(wireType), pos, nil
}
// ConsumeVarint parses buf starting at pos as a varint-encoded uint64, reporting the new position.
func ConsumeVarint(buf []byte, pos int) (uint64, int, error) {
l := len(buf)
var num uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, 0, ErrIntOverflow
}
if pos >= l {
return 0, 0, io.ErrUnexpectedEOF
}
b := buf[pos]
pos++
num |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
return num, pos, nil
}
================================================
FILE: pdata/internal/spanid.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/pdata/internal"
import (
"encoding/hex"
"errors"
"go.opentelemetry.io/collector/pdata/internal/json"
)
const spanIDSize = 8
var errUnmarshalSpanID = errors.New("unmarshal: invalid SpanID length")
// SpanID is a custom data type that is used for all span_id fields in OTLP
// Protobuf messages.
type SpanID [spanIDSize]byte
func DeleteSpanID(*SpanID, bool) {}
func CopySpanID(dest, src *SpanID) {
*dest = *src
}
// IsEmpty returns true if id contains at least one non-zero byte.
func (sid SpanID) IsEmpty() bool {
return sid == [spanIDSize]byte{}
}
// SizeProto returns the size of the data to serialize in proto format.
func (sid SpanID) SizeProto() int {
if sid.IsEmpty() {
return 0
}
return spanIDSize
}
// MarshalProto converts span ID into a binary representation. Called by Protobuf serialization.
func (sid SpanID) MarshalProto(buf []byte) int {
if sid.IsEmpty() {
return 0
}
return copy(buf[len(buf)-spanIDSize:], sid[:])
}
// UnmarshalProto inflates this span ID from binary representation. Called by Protobuf serialization.
func (sid *SpanID) UnmarshalProto(data []byte) error {
if len(data) == 0 {
*sid = [spanIDSize]byte{}
return nil
}
if len(data) != spanIDSize {
return errUnmarshalSpanID
}
copy(sid[:], data)
return nil
}
// MarshalJSON converts SpanID into a hex string.
//
//nolint:govet
func (sid SpanID) MarshalJSON(dest *json.Stream) {
dest.WriteString(hex.EncodeToString(sid[:]))
}
// UnmarshalJSON decodes SpanID from hex string.
//
//nolint:govet
func (sid *SpanID) UnmarshalJSON(iter *json.Iterator) {
*sid = [spanIDSize]byte{}
unmarshalJSON(sid[:], iter)
}
func GenTestSpanID() *SpanID {
sid := SpanID([8]byte{8, 7, 6, 5, 4, 3, 2, 1})
return &sid
}
================================================
FILE: pdata/internal/spanid_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/internal/json"
)
func TestSpanID(t *testing.T) {
sid := SpanID([spanIDSize]byte{})
assert.EqualValues(t, [spanIDSize]byte{}, sid)
assert.Equal(t, 0, sid.SizeProto())
b := [spanIDSize]byte{1, 2, 3, 4, 5, 6, 7, 8}
sid = b
assert.EqualValues(t, b, sid)
assert.Equal(t, 8, sid.SizeProto())
}
func TestSpanIDMarshal(t *testing.T) {
buf := make([]byte, spanIDSize)
sid := SpanID([spanIDSize]byte{})
n := sid.MarshalProto(buf)
assert.Equal(t, 0, n)
sid = [spanIDSize]byte{1, 2, 3, 4, 5, 6, 7, 8}
n = sid.MarshalProto(buf)
assert.Equal(t, spanIDSize, n)
assert.Equal(t, []byte{1, 2, 3, 4, 5, 6, 7, 8}, buf[0:spanIDSize])
}
func TestSpanIDUnmarshal(t *testing.T) {
buf := [spanIDSize]byte{0x12, 0x23, 0xAD, 0x12, 0x23, 0xAD, 0x12, 0x23}
sid := SpanID{}
err := sid.UnmarshalProto(buf[:])
require.NoError(t, err)
assert.EqualValues(t, [spanIDSize]byte{0x12, 0x23, 0xAD, 0x12, 0x23, 0xAD, 0x12, 0x23}, sid)
err = sid.UnmarshalProto(buf[0:0])
require.NoError(t, err)
assert.EqualValues(t, [spanIDSize]byte{}, sid)
err = sid.UnmarshalProto(nil)
require.NoError(t, err)
assert.EqualValues(t, [spanIDSize]byte{}, sid)
err = sid.UnmarshalProto(buf[0:3])
assert.Error(t, err)
}
func TestSpanIDMarshalAndUnmarshalJSON(t *testing.T) {
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src := SpanID([spanIDSize]byte{0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78})
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := SpanID{}
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
}
================================================
FILE: pdata/internal/state.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/pdata/internal"
import (
"sync/atomic"
)
// State defines an ownership state of pmetric.Metrics, plog.Logs, ptrace.Traces or pprofile.Profiles.
type State struct {
refs atomic.Int32
state uint32
}
const (
defaultState uint32 = 0
stateReadOnlyBit = uint32(1 << 0)
statePipelineOwnedBit = uint32(1 << 1)
)
func NewState() *State {
st := &State{
state: defaultState,
}
st.refs.Store(1)
return st
}
func (st *State) MarkReadOnly() {
st.state |= stateReadOnlyBit
}
func (st *State) IsReadOnly() bool {
return st.state&stateReadOnlyBit != 0
}
// AssertMutable panics if the state is not StateMutable.
func (st *State) AssertMutable() {
if st.state&stateReadOnlyBit != 0 {
panic("invalid access to shared data")
}
}
// MarkPipelineOwned marks the data as owned by the pipeline, returns true if the data were
// previously not owned by the pipeline, otherwise false.
func (st *State) MarkPipelineOwned() bool {
if st.state&statePipelineOwnedBit != 0 {
return false
}
st.state |= statePipelineOwnedBit
return true
}
// Ref add one to the count of active references.
func (st *State) Ref() {
st.refs.Add(1)
}
// Unref returns true if reference count got to 0 which means no more active references,
// otherwise it returns false.
func (st *State) Unref() bool {
refs := st.refs.Add(-1)
switch {
case refs > 0:
return false
case refs == 0:
return true
default:
panic("Cannot unref freed data")
}
}
================================================
FILE: pdata/internal/state_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/internal/testutil"
)
func TestAssertMutable(t *testing.T) {
assert.NotPanics(t, func() { NewState().AssertMutable() })
assert.Panics(t, func() {
state := NewState()
state.MarkReadOnly()
state.AssertMutable()
})
}
func BenchmarkAssertMutable(b *testing.B) {
testutil.SkipMemoryBench(b)
b.ReportAllocs()
mutable := NewState()
for b.Loop() {
mutable.AssertMutable()
}
}
================================================
FILE: pdata/internal/traceid.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/pdata/internal"
import (
"encoding/hex"
"errors"
"go.opentelemetry.io/collector/pdata/internal/json"
)
const traceIDSize = 16
var errUnmarshalTraceID = errors.New("unmarshal: invalid TraceID length")
// TraceID is a custom data type that is used for all trace_id fields in OTLP
// Protobuf messages.
type TraceID [traceIDSize]byte
func DeleteTraceID(*TraceID, bool) {}
func CopyTraceID(dest, src *TraceID) {
*dest = *src
}
// IsEmpty returns true if id contains at leas one non-zero byte.
func (tid TraceID) IsEmpty() bool {
return tid == [traceIDSize]byte{}
}
// SizeProto returns the size of the data to serialize in proto format.
func (tid TraceID) SizeProto() int {
if tid.IsEmpty() {
return 0
}
return traceIDSize
}
// MarshalProto converts trace ID into a binary representation. Called by Protobuf serialization.
func (tid TraceID) MarshalProto(buf []byte) int {
if tid.IsEmpty() {
return 0
}
return copy(buf[len(buf)-traceIDSize:], tid[:])
}
// UnmarshalProto inflates this trace ID from binary representation. Called by Protobuf serialization.
func (tid *TraceID) UnmarshalProto(buf []byte) error {
if len(buf) == 0 {
*tid = [traceIDSize]byte{}
return nil
}
if len(buf) != traceIDSize {
return errUnmarshalTraceID
}
copy(tid[:], buf)
return nil
}
// MarshalJSON converts TraceID into a hex string.
//
//nolint:govet
func (tid TraceID) MarshalJSON(dest *json.Stream) {
dest.WriteString(hex.EncodeToString(tid[:]))
}
// UnmarshalJSON decodes TraceID from hex string.
//
//nolint:govet
func (tid *TraceID) UnmarshalJSON(iter *json.Iterator) {
*tid = [profileIDSize]byte{}
unmarshalJSON(tid[:], iter)
}
func GenTestTraceID() *TraceID {
tid := TraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1})
return &tid
}
================================================
FILE: pdata/internal/traceid_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/internal/json"
)
func TestTraceID(t *testing.T) {
tid := TraceID([traceIDSize]byte{})
assert.EqualValues(t, [traceIDSize]byte{}, tid)
assert.Equal(t, 0, tid.SizeProto())
b := [traceIDSize]byte{0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78}
tid = b
assert.EqualValues(t, b, tid)
assert.Equal(t, traceIDSize, tid.SizeProto())
}
func TestTraceIDMarshal(t *testing.T) {
buf := make([]byte, traceIDSize)
tid := TraceID([traceIDSize]byte{})
n := tid.MarshalProto(buf)
assert.Equal(t, 0, n)
tid = [traceIDSize]byte{0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78}
n = tid.MarshalProto(buf)
assert.Equal(t, traceIDSize, n)
assert.Equal(t, []byte{0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78}, buf[0:traceIDSize])
}
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.UnmarshalProto(buf[:])
require.NoError(t, err)
assert.EqualValues(t, buf, tid)
err = tid.UnmarshalProto(buf[0:0])
require.NoError(t, err)
assert.EqualValues(t, [traceIDSize]byte{}, tid)
err = tid.UnmarshalProto(nil)
require.NoError(t, err)
assert.EqualValues(t, [traceIDSize]byte{}, tid)
}
func TestTraceIDMarshalAndUnmarshalJSON(t *testing.T) {
stream := json.BorrowStream(nil)
defer json.ReturnStream(stream)
src := TraceID([traceIDSize]byte{0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78})
src.MarshalJSON(stream)
require.NoError(t, stream.Error())
iter := json.BorrowIterator(stream.Buffer())
defer json.ReturnIterator(iter)
dest := TraceID{}
dest.UnmarshalJSON(iter)
require.NoError(t, iter.Error())
assert.Equal(t, src, dest)
}
================================================
FILE: pdata/internal/unpacked_unmarshal_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal
import (
"encoding/binary"
"math"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/internal/proto"
)
// For each repeated field in the OTLP proto that should be packed, check that we can unmarshal
// payloads where it is encoded as unpacked.
// The Protobuf spec recommends this for backwards compatibility purposes.
// We do not test profiles payloads since their proto definition is currently in development.
func appendTag(buf []byte, fieldNo byte, wireType proto.WireType) []byte {
return append(buf, (fieldNo<<3)|byte(wireType))
}
func appendVarint(buf []byte, v uint64) []byte {
n := proto.Sov(v)
for range n {
buf = append(buf, 0)
}
_ = proto.EncodeVarint(buf, len(buf), v)
return buf
}
func TestUnmarshalUnpackedHistogramDataPoint(t *testing.T) {
var pb []byte
pb = appendTag(pb, 6, proto.WireTypeI64) // bucket_counts
pb = binary.LittleEndian.AppendUint64(pb, 42)
pb = appendTag(pb, 7, proto.WireTypeI64) // explicit_bounds
pb = binary.LittleEndian.AppendUint64(pb, math.Float64bits(42.0))
var hdp HistogramDataPoint
err := hdp.UnmarshalProto(pb)
require.NoError(t, err)
assert.Equal(t, HistogramDataPoint{
BucketCounts: []uint64{42},
ExplicitBounds: []float64{42.0},
}, hdp)
}
func TestUnmarshalUnpackedExponentialHistogramDataPoint_Buckets(t *testing.T) {
var pb []byte
pb = appendTag(pb, 2, proto.WireTypeVarint) // bucket_counts
pb = appendVarint(pb, 42)
var ehdpb ExponentialHistogramDataPointBuckets
err := ehdpb.UnmarshalProto(pb)
require.NoError(t, err)
assert.Equal(t, ExponentialHistogramDataPointBuckets{
BucketCounts: []uint64{42},
}, ehdpb)
}
================================================
FILE: pdata/internal/wrapper_logs.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/pdata/internal"
// LogsToProto internal helper to convert Logs to protobuf representation.
func LogsToProto(l LogsWrapper) LogsData {
return LogsData{
ResourceLogs: l.orig.ResourceLogs,
}
}
// LogsFromProto internal helper to convert protobuf representation to Logs.
// This function set exclusive state assuming that it's called only once per Logs.
func LogsFromProto(orig LogsData) LogsWrapper {
return NewLogsWrapper(&ExportLogsServiceRequest{
ResourceLogs: orig.ResourceLogs,
}, NewState())
}
================================================
FILE: pdata/internal/wrapper_map.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/pdata/internal"
type MapWrapper struct {
orig *[]KeyValue
state *State
}
func GetMapOrig(ms MapWrapper) *[]KeyValue {
return ms.orig
}
func GetMapState(ms MapWrapper) *State {
return ms.state
}
func NewMapWrapper(orig *[]KeyValue, state *State) MapWrapper {
return MapWrapper{orig: orig, state: state}
}
func GenTestMapWrapper() MapWrapper {
orig := GenTestKeyValueSlice()
return NewMapWrapper(&orig, NewState())
}
================================================
FILE: pdata/internal/wrapper_metrics.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/pdata/internal"
// MetricsToProto internal helper to convert Metrics to protobuf representation.
func MetricsToProto(l MetricsWrapper) MetricsData {
return MetricsData{
ResourceMetrics: l.orig.ResourceMetrics,
}
}
// MetricsFromProto internal helper to convert protobuf representation to Metrics.
// This function set exclusive state assuming that it's called only once per Metrics.
func MetricsFromProto(orig MetricsData) MetricsWrapper {
return NewMetricsWrapper(&ExportMetricsServiceRequest{
ResourceMetrics: orig.ResourceMetrics,
}, NewState())
}
================================================
FILE: pdata/internal/wrapper_profiles.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/pdata/internal"
// ProfilesToProto internal helper to convert Profiles to protobuf representation.
func ProfilesToProto(l ProfilesWrapper) ProfilesData {
return ProfilesData{
ResourceProfiles: l.orig.ResourceProfiles,
Dictionary: l.orig.Dictionary,
}
}
// ProfilesFromProto internal helper to convert protobuf representation to Profiles.
// This function set exclusive state assuming that it's called only once per Profiles.
func ProfilesFromProto(orig ProfilesData) ProfilesWrapper {
return NewProfilesWrapper(&ExportProfilesServiceRequest{
ResourceProfiles: orig.ResourceProfiles,
Dictionary: orig.Dictionary,
}, NewState())
}
================================================
FILE: pdata/internal/wrapper_traces.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/pdata/internal"
// TracesToProto internal helper to convert Traces to protobuf representation.
func TracesToProto(l TracesWrapper) TracesData {
return TracesData{
ResourceSpans: l.orig.ResourceSpans,
}
}
// TracesFromProto internal helper to convert protobuf representation to Traces.
// This function set exclusive state assuming that it's called only once per Traces.
func TracesFromProto(orig TracesData) TracesWrapper {
return NewTracesWrapper(&ExportTraceServiceRequest{
ResourceSpans: orig.ResourceSpans,
}, NewState())
}
================================================
FILE: pdata/internal/wrapper_tracestate.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/pdata/internal"
type TraceStateWrapper struct {
orig *string
state *State
}
func GetTraceStateOrig(ms TraceStateWrapper) *string {
return ms.orig
}
func GetTraceStateState(ms TraceStateWrapper) *State {
return ms.state
}
func NewTraceStateWrapper(orig *string, state *State) TraceStateWrapper {
return TraceStateWrapper{orig: orig, state: state}
}
func GenTestTraceStateWrapper() TraceStateWrapper {
return NewTraceStateWrapper(GenTestTraceState(), NewState())
}
func GenTestTraceState() *string {
orig := new(string)
*orig = "rojo=00f067aa0ba902b7"
return orig
}
================================================
FILE: pdata/internal/wrapper_value.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/pdata/internal"
import "go.opentelemetry.io/collector/pdata/internal/metadata"
type ValueWrapper struct {
orig *AnyValue
state *State
}
func GetValueOrig(ms ValueWrapper) *AnyValue {
return ms.orig
}
func GetValueState(ms ValueWrapper) *State {
return ms.state
}
func NewValueWrapper(orig *AnyValue, state *State) ValueWrapper {
return ValueWrapper{orig: orig, state: state}
}
func GenTestValueWrapper() ValueWrapper {
orig := GenTestAnyValue()
return NewValueWrapper(orig, NewState())
}
func NewAnyValueStringValue() *AnyValue_StringValue {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &AnyValue_StringValue{}
}
return ProtoPoolAnyValue_StringValue.Get().(*AnyValue_StringValue)
}
func NewAnyValueIntValue() *AnyValue_IntValue {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &AnyValue_IntValue{}
}
return ProtoPoolAnyValue_IntValue.Get().(*AnyValue_IntValue)
}
func NewAnyValueBoolValue() *AnyValue_BoolValue {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &AnyValue_BoolValue{}
}
return ProtoPoolAnyValue_BoolValue.Get().(*AnyValue_BoolValue)
}
func NewAnyValueDoubleValue() *AnyValue_DoubleValue {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &AnyValue_DoubleValue{}
}
return ProtoPoolAnyValue_DoubleValue.Get().(*AnyValue_DoubleValue)
}
func NewAnyValueBytesValue() *AnyValue_BytesValue {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &AnyValue_BytesValue{}
}
return ProtoPoolAnyValue_BytesValue.Get().(*AnyValue_BytesValue)
}
func NewAnyValueArrayValue() *AnyValue_ArrayValue {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &AnyValue_ArrayValue{}
}
return ProtoPoolAnyValue_ArrayValue.Get().(*AnyValue_ArrayValue)
}
func NewAnyValueKvlistValue() *AnyValue_KvlistValue {
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
return &AnyValue_KvlistValue{}
}
return ProtoPoolAnyValue_KvlistValue.Get().(*AnyValue_KvlistValue)
}
================================================
FILE: pdata/internal/wrapper_value_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal
import (
"testing"
"github.com/stretchr/testify/require"
gootlpcommon "go.opentelemetry.io/proto/slim/otlp/common/v1"
goproto "google.golang.org/protobuf/proto"
)
func TestAnyValueBytes(t *testing.T) {
av := &gootlpcommon.AnyValue{Value: &gootlpcommon.AnyValue_BytesValue{BytesValue: nil}}
buf, err := goproto.Marshal(av)
require.NoError(t, err)
pav := &AnyValue{Value: &AnyValue_BytesValue{BytesValue: nil}}
pbuf := make([]byte, pav.SizeProto())
n := pav.MarshalProto(pbuf)
pbuf = pbuf[:n]
require.Equal(t, buf, pbuf)
}
================================================
FILE: pdata/metadata.yaml
================================================
type: pdata
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
codeowners:
active:
- bogdandrutu
- dmitryax
stability:
stable: [traces, metrics, logs]
feature_gates:
- id: pdata.useCustomProtoEncoding
description: 'When enabled, enable custom proto encoding. This is a required step to enable featuregate pdata.useProtoPooling.'
stage: stable
from_version: 'v0.133.0'
to_version: 'v0.137.0'
reference_url: 'https://github.com/open-telemetry/opentelemetry-collector/issues/13631'
- id: pdata.useProtoPooling
description: 'When enabled, enable using local memory pools for underlying data that the pdata messages are pushed to.'
stage: alpha
from_version: 'v0.133.0'
reference_url: 'https://github.com/open-telemetry/opentelemetry-collector/issues/13631'
================================================
FILE: pdata/pcommon/generated_byteslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pcommon
import (
"iter"
"slices"
"go.opentelemetry.io/collector/pdata/internal"
)
// ByteSlice represents a []byte slice.
// The instance of ByteSlice can be assigned to multiple objects since it's immutable.
//
// Must use NewByteSlice function to create new instances.
// Important: zero-initialized instance is not valid for use.
type ByteSlice internal.ByteSliceWrapper
func (ms ByteSlice) getOrig() *[]byte {
return internal.GetByteSliceOrig(internal.ByteSliceWrapper(ms))
}
func (ms ByteSlice) getState() *internal.State {
return internal.GetByteSliceState(internal.ByteSliceWrapper(ms))
}
// NewByteSlice creates a new empty ByteSlice.
func NewByteSlice() ByteSlice {
orig := []byte(nil)
return ByteSlice(internal.NewByteSliceWrapper(&orig, internal.NewState()))
}
// AsRaw returns a copy of the []byte slice.
func (ms ByteSlice) AsRaw() []byte {
return copyByteSlice(nil, *ms.getOrig())
}
// FromRaw copies raw []byte into the slice ByteSlice.
func (ms ByteSlice) FromRaw(val []byte) {
ms.getState().AssertMutable()
*ms.getOrig() = copyByteSlice(*ms.getOrig(), val)
}
// Len returns length of the []byte slice value.
// Equivalent of len(byteSlice).
func (ms ByteSlice) Len() int {
return len(*ms.getOrig())
}
// At returns an item from particular index.
// Equivalent of byteSlice[i].
func (ms ByteSlice) At(i int) byte {
return (*ms.getOrig())[i]
}
// All returns an iterator over index-value pairs in the slice.
func (ms ByteSlice) All() iter.Seq2[int, byte] {
return func(yield func(int, byte) bool) {
for i := 0; i < ms.Len(); i++ {
if !yield(i, ms.At(i)) {
return
}
}
}
}
// SetAt sets byte item at particular index.
// Equivalent of byteSlice[i] = val
func (ms ByteSlice) SetAt(i int, val byte) {
ms.getState().AssertMutable()
(*ms.getOrig())[i] = val
}
// EnsureCapacity ensures ByteSlice has at least the specified capacity.
// 1. If the newCap <= cap, then is no change in capacity.
// 2. If the newCap > cap, then the slice capacity will be expanded to the provided value which will be equivalent of:
// buf := make([]byte, len(byteSlice), newCap)
// copy(buf, byteSlice)
// byteSlice = buf
func (ms ByteSlice) EnsureCapacity(newCap int) {
ms.getState().AssertMutable()
oldCap := cap(*ms.getOrig())
if newCap <= oldCap {
return
}
newOrig := make([]byte, len(*ms.getOrig()), newCap)
copy(newOrig, *ms.getOrig())
*ms.getOrig() = newOrig
}
// Append appends extra elements to ByteSlice.
// Equivalent of byteSlice = append(byteSlice, elms...)
func (ms ByteSlice) Append(elms ...byte) {
ms.getState().AssertMutable()
*ms.getOrig() = append(*ms.getOrig(), elms...)
}
// MoveTo moves all elements from the current slice overriding the destination and
// resetting the current instance to its zero value.
func (ms ByteSlice) MoveTo(dest ByteSlice) {
ms.getState().AssertMutable()
dest.getState().AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.getOrig() == dest.getOrig() {
return
}
*dest.getOrig() = *ms.getOrig()
*ms.getOrig() = nil
}
// MoveAndAppendTo moves all elements from the current slice and appends them to the dest.
// The current slice will be cleared.
func (ms ByteSlice) MoveAndAppendTo(dest ByteSlice) {
ms.getState().AssertMutable()
dest.getState().AssertMutable()
if *dest.getOrig() == nil {
// We can simply move the entire vector and avoid any allocations.
*dest.getOrig() = *ms.getOrig()
} else {
*dest.getOrig() = append(*dest.getOrig(), *ms.getOrig()...)
}
*ms.getOrig() = nil
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (ms ByteSlice) RemoveIf(f func(byte) bool) {
ms.getState().AssertMutable()
newLen := 0
for i := 0; i < len(*ms.getOrig()); i++ {
if f((*ms.getOrig())[i]) {
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*ms.getOrig())[newLen] = (*ms.getOrig())[i]
var zero byte
(*ms.getOrig())[i] = zero
newLen++
}
*ms.getOrig() = (*ms.getOrig())[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (ms ByteSlice) CopyTo(dest ByteSlice) {
dest.getState().AssertMutable()
if ms.getOrig() == dest.getOrig() {
return
}
*dest.getOrig() = copyByteSlice(*dest.getOrig(), *ms.getOrig())
}
// Equal checks equality with another ByteSlice
func (ms ByteSlice) Equal(val ByteSlice) bool {
return slices.Equal(*ms.getOrig(), *val.getOrig())
}
func copyByteSlice(dst, src []byte) []byte {
return append(dst[:0], src...)
}
================================================
FILE: pdata/pcommon/generated_byteslice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pcommon
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/internal/testutil"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestNewByteSlice(t *testing.T) {
ms := NewByteSlice()
assert.Equal(t, 0, ms.Len())
ms.FromRaw([]byte{1, 2, 3})
assert.Equal(t, 3, ms.Len())
assert.Equal(t, []byte{1, 2, 3}, ms.AsRaw())
ms.SetAt(1, byte(5))
assert.Equal(t, []byte{1, 5, 3}, ms.AsRaw())
ms.FromRaw([]byte{3})
assert.Equal(t, 1, ms.Len())
assert.Equal(t, byte(3), ms.At(0))
cp := NewByteSlice()
ms.CopyTo(cp)
ms.SetAt(0, byte(2))
assert.Equal(t, byte(2), ms.At(0))
assert.Equal(t, byte(3), cp.At(0))
ms.CopyTo(cp)
assert.Equal(t, byte(2), cp.At(0))
mv := NewByteSlice()
ms.MoveTo(mv)
assert.Equal(t, 0, ms.Len())
assert.Equal(t, 1, mv.Len())
assert.Equal(t, byte(2), mv.At(0))
ms.FromRaw([]byte{1, 2, 3})
ms.MoveTo(mv)
assert.Equal(t, 3, mv.Len())
assert.Equal(t, byte(1), mv.At(0))
mv.MoveTo(mv)
assert.Equal(t, 3, mv.Len())
assert.Equal(t, byte(1), mv.At(0))
}
func TestByteSliceReadOnly(t *testing.T) {
raw := []byte{1, 2, 3}
sharedState := internal.NewState()
sharedState.MarkReadOnly()
ms := ByteSlice(internal.NewByteSliceWrapper(&raw, sharedState))
assert.Equal(t, 3, ms.Len())
assert.Equal(t, byte(1), ms.At(0))
assert.Panics(t, func() { ms.Append(1) })
assert.Panics(t, func() { ms.EnsureCapacity(2) })
assert.Equal(t, raw, ms.AsRaw())
assert.Panics(t, func() { ms.FromRaw(raw) })
ms2 := NewByteSlice()
ms.CopyTo(ms2)
assert.Equal(t, ms.AsRaw(), ms2.AsRaw())
assert.Panics(t, func() { ms2.CopyTo(ms) })
assert.Panics(t, func() { ms.MoveTo(ms2) })
assert.Panics(t, func() { ms2.MoveTo(ms) })
}
func TestByteSliceAppend(t *testing.T) {
ms := NewByteSlice()
ms.FromRaw([]byte{1, 2, 3})
ms.Append(5, 5)
assert.Equal(t, 5, ms.Len())
assert.Equal(t, byte(5), ms.At(4))
}
func TestByteSliceEnsureCapacity(t *testing.T) {
ms := NewByteSlice()
ms.EnsureCapacity(4)
assert.Equal(t, 4, cap(*ms.getOrig()))
ms.EnsureCapacity(2)
assert.Equal(t, 4, cap(*ms.getOrig()))
}
func TestByteSliceAll(t *testing.T) {
ms := NewByteSlice()
ms.FromRaw([]byte{1, 2, 3})
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestByteSliceMoveAndAppendTo(t *testing.T) {
// Test moving from an empty slice
ms := NewByteSlice()
ms2 := NewByteSlice()
ms.MoveAndAppendTo(ms2)
assert.Equal(t, NewByteSlice(), ms2)
assert.Equal(t, ms.Len(), 0)
// Test moving to empty slice
ms.FromRaw([]byte{1, 2, 3})
ms.MoveAndAppendTo(ms2)
assert.Equal(t, ms2.Len(), 3)
// Test moving to a non empty slice
ms.FromRaw([]byte{1, 2, 3})
ms.MoveAndAppendTo(ms2)
assert.Equal(t, ms2.Len(), 6)
}
func TestByteSliceRemoveIf(t *testing.T) {
emptySlice := NewByteSlice()
emptySlice.RemoveIf(func(el byte) bool {
t.Fail()
return false
})
ms := NewByteSlice()
ms.FromRaw([]byte{1, 2, 3})
pos := 0
ms.RemoveIf(func(el byte) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, pos/2, ms.Len())
}
func TestByteSliceRemoveIfAll(t *testing.T) {
ms := NewByteSlice()
ms.FromRaw([]byte{1, 2, 3})
ms.RemoveIf(func(el byte) bool {
return true
})
assert.Equal(t, 0, ms.Len())
}
func TestByteSliceEqual(t *testing.T) {
ms := NewByteSlice()
ms2 := NewByteSlice()
assert.True(t, ms.Equal(ms2))
ms.Append(1, 2, 3)
assert.False(t, ms.Equal(ms2))
ms2.Append(1, 2, 3)
assert.True(t, ms.Equal(ms2))
}
func BenchmarkByteSliceEqual(b *testing.B) {
testutil.SkipMemoryBench(b)
ms := NewByteSlice()
ms.Append(1, 2, 3)
cmp := NewByteSlice()
cmp.Append(1, 2, 3)
b.ResetTimer()
b.ReportAllocs()
for n := 0; n < b.N; n++ {
_ = ms.Equal(cmp)
}
}
================================================
FILE: pdata/pcommon/generated_float64slice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pcommon
import (
"iter"
"slices"
"go.opentelemetry.io/collector/pdata/internal"
)
// Float64Slice represents a []float64 slice.
// The instance of Float64Slice can be assigned to multiple objects since it's immutable.
//
// Must use NewFloat64Slice function to create new instances.
// Important: zero-initialized instance is not valid for use.
type Float64Slice internal.Float64SliceWrapper
func (ms Float64Slice) getOrig() *[]float64 {
return internal.GetFloat64SliceOrig(internal.Float64SliceWrapper(ms))
}
func (ms Float64Slice) getState() *internal.State {
return internal.GetFloat64SliceState(internal.Float64SliceWrapper(ms))
}
// NewFloat64Slice creates a new empty Float64Slice.
func NewFloat64Slice() Float64Slice {
orig := []float64(nil)
return Float64Slice(internal.NewFloat64SliceWrapper(&orig, internal.NewState()))
}
// AsRaw returns a copy of the []float64 slice.
func (ms Float64Slice) AsRaw() []float64 {
return copyFloat64Slice(nil, *ms.getOrig())
}
// FromRaw copies raw []float64 into the slice Float64Slice.
func (ms Float64Slice) FromRaw(val []float64) {
ms.getState().AssertMutable()
*ms.getOrig() = copyFloat64Slice(*ms.getOrig(), val)
}
// Len returns length of the []float64 slice value.
// Equivalent of len(float64Slice).
func (ms Float64Slice) Len() int {
return len(*ms.getOrig())
}
// At returns an item from particular index.
// Equivalent of float64Slice[i].
func (ms Float64Slice) At(i int) float64 {
return (*ms.getOrig())[i]
}
// All returns an iterator over index-value pairs in the slice.
func (ms Float64Slice) All() iter.Seq2[int, float64] {
return func(yield func(int, float64) bool) {
for i := 0; i < ms.Len(); i++ {
if !yield(i, ms.At(i)) {
return
}
}
}
}
// SetAt sets float64 item at particular index.
// Equivalent of float64Slice[i] = val
func (ms Float64Slice) SetAt(i int, val float64) {
ms.getState().AssertMutable()
(*ms.getOrig())[i] = val
}
// EnsureCapacity ensures Float64Slice has at least the specified capacity.
// 1. If the newCap <= cap, then is no change in capacity.
// 2. If the newCap > cap, then the slice capacity will be expanded to the provided value which will be equivalent of:
// buf := make([]float64, len(float64Slice), newCap)
// copy(buf, float64Slice)
// float64Slice = buf
func (ms Float64Slice) EnsureCapacity(newCap int) {
ms.getState().AssertMutable()
oldCap := cap(*ms.getOrig())
if newCap <= oldCap {
return
}
newOrig := make([]float64, len(*ms.getOrig()), newCap)
copy(newOrig, *ms.getOrig())
*ms.getOrig() = newOrig
}
// Append appends extra elements to Float64Slice.
// Equivalent of float64Slice = append(float64Slice, elms...)
func (ms Float64Slice) Append(elms ...float64) {
ms.getState().AssertMutable()
*ms.getOrig() = append(*ms.getOrig(), elms...)
}
// MoveTo moves all elements from the current slice overriding the destination and
// resetting the current instance to its zero value.
func (ms Float64Slice) MoveTo(dest Float64Slice) {
ms.getState().AssertMutable()
dest.getState().AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.getOrig() == dest.getOrig() {
return
}
*dest.getOrig() = *ms.getOrig()
*ms.getOrig() = nil
}
// MoveAndAppendTo moves all elements from the current slice and appends them to the dest.
// The current slice will be cleared.
func (ms Float64Slice) MoveAndAppendTo(dest Float64Slice) {
ms.getState().AssertMutable()
dest.getState().AssertMutable()
if *dest.getOrig() == nil {
// We can simply move the entire vector and avoid any allocations.
*dest.getOrig() = *ms.getOrig()
} else {
*dest.getOrig() = append(*dest.getOrig(), *ms.getOrig()...)
}
*ms.getOrig() = nil
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (ms Float64Slice) RemoveIf(f func(float64) bool) {
ms.getState().AssertMutable()
newLen := 0
for i := 0; i < len(*ms.getOrig()); i++ {
if f((*ms.getOrig())[i]) {
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*ms.getOrig())[newLen] = (*ms.getOrig())[i]
var zero float64
(*ms.getOrig())[i] = zero
newLen++
}
*ms.getOrig() = (*ms.getOrig())[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (ms Float64Slice) CopyTo(dest Float64Slice) {
dest.getState().AssertMutable()
if ms.getOrig() == dest.getOrig() {
return
}
*dest.getOrig() = copyFloat64Slice(*dest.getOrig(), *ms.getOrig())
}
// Equal checks equality with another Float64Slice
func (ms Float64Slice) Equal(val Float64Slice) bool {
return slices.Equal(*ms.getOrig(), *val.getOrig())
}
func copyFloat64Slice(dst, src []float64) []float64 {
return append(dst[:0], src...)
}
================================================
FILE: pdata/pcommon/generated_float64slice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pcommon
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/internal/testutil"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestNewFloat64Slice(t *testing.T) {
ms := NewFloat64Slice()
assert.Equal(t, 0, ms.Len())
ms.FromRaw([]float64{1.1, 2.2, 3.3})
assert.Equal(t, 3, ms.Len())
assert.Equal(t, []float64{1.1, 2.2, 3.3}, ms.AsRaw())
ms.SetAt(1, float64(5.5))
assert.Equal(t, []float64{1.1, 5.5, 3.3}, ms.AsRaw())
ms.FromRaw([]float64{3.3})
assert.Equal(t, 1, ms.Len())
assert.InDelta(t, float64(3.3), ms.At(0), 0.01)
cp := NewFloat64Slice()
ms.CopyTo(cp)
ms.SetAt(0, float64(2.2))
assert.InDelta(t, float64(2.2), ms.At(0), 0.01)
assert.InDelta(t, float64(3.3), cp.At(0), 0.01)
ms.CopyTo(cp)
assert.InDelta(t, float64(2.2), cp.At(0), 0.01)
mv := NewFloat64Slice()
ms.MoveTo(mv)
assert.Equal(t, 0, ms.Len())
assert.Equal(t, 1, mv.Len())
assert.InDelta(t, float64(2.2), mv.At(0), 0.01)
ms.FromRaw([]float64{1.1, 2.2, 3.3})
ms.MoveTo(mv)
assert.Equal(t, 3, mv.Len())
assert.InDelta(t, float64(1.1), mv.At(0), 0.01)
mv.MoveTo(mv)
assert.Equal(t, 3, mv.Len())
assert.InDelta(t, float64(1.1), mv.At(0), 0.01)
}
func TestFloat64SliceReadOnly(t *testing.T) {
raw := []float64{1.1, 2.2, 3.3}
sharedState := internal.NewState()
sharedState.MarkReadOnly()
ms := Float64Slice(internal.NewFloat64SliceWrapper(&raw, sharedState))
assert.Equal(t, 3, ms.Len())
assert.InDelta(t, float64(1.1), ms.At(0), 0.01)
assert.Panics(t, func() { ms.Append(1.1) })
assert.Panics(t, func() { ms.EnsureCapacity(2) })
assert.Equal(t, raw, ms.AsRaw())
assert.Panics(t, func() { ms.FromRaw(raw) })
ms2 := NewFloat64Slice()
ms.CopyTo(ms2)
assert.Equal(t, ms.AsRaw(), ms2.AsRaw())
assert.Panics(t, func() { ms2.CopyTo(ms) })
assert.Panics(t, func() { ms.MoveTo(ms2) })
assert.Panics(t, func() { ms2.MoveTo(ms) })
}
func TestFloat64SliceAppend(t *testing.T) {
ms := NewFloat64Slice()
ms.FromRaw([]float64{1.1, 2.2, 3.3})
ms.Append(5.5, 5.5)
assert.Equal(t, 5, ms.Len())
assert.InDelta(t, float64(5.5), ms.At(4), 0.01)
}
func TestFloat64SliceEnsureCapacity(t *testing.T) {
ms := NewFloat64Slice()
ms.EnsureCapacity(4)
assert.Equal(t, 4, cap(*ms.getOrig()))
ms.EnsureCapacity(2)
assert.Equal(t, 4, cap(*ms.getOrig()))
}
func TestFloat64SliceAll(t *testing.T) {
ms := NewFloat64Slice()
ms.FromRaw([]float64{1.1, 2.2, 3.3})
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestFloat64SliceMoveAndAppendTo(t *testing.T) {
// Test moving from an empty slice
ms := NewFloat64Slice()
ms2 := NewFloat64Slice()
ms.MoveAndAppendTo(ms2)
assert.Equal(t, NewFloat64Slice(), ms2)
assert.Equal(t, ms.Len(), 0)
// Test moving to empty slice
ms.FromRaw([]float64{1.1, 2.2, 3.3})
ms.MoveAndAppendTo(ms2)
assert.Equal(t, ms2.Len(), 3)
// Test moving to a non empty slice
ms.FromRaw([]float64{1.1, 2.2, 3.3})
ms.MoveAndAppendTo(ms2)
assert.Equal(t, ms2.Len(), 6)
}
func TestFloat64SliceRemoveIf(t *testing.T) {
emptySlice := NewFloat64Slice()
emptySlice.RemoveIf(func(el float64) bool {
t.Fail()
return false
})
ms := NewFloat64Slice()
ms.FromRaw([]float64{1.1, 2.2, 3.3})
pos := 0
ms.RemoveIf(func(el float64) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, pos/2, ms.Len())
}
func TestFloat64SliceRemoveIfAll(t *testing.T) {
ms := NewFloat64Slice()
ms.FromRaw([]float64{1.1, 2.2, 3.3})
ms.RemoveIf(func(el float64) bool {
return true
})
assert.Equal(t, 0, ms.Len())
}
func TestFloat64SliceEqual(t *testing.T) {
ms := NewFloat64Slice()
ms2 := NewFloat64Slice()
assert.True(t, ms.Equal(ms2))
ms.Append(1.1, 2.2, 3.3)
assert.False(t, ms.Equal(ms2))
ms2.Append(1.1, 2.2, 3.3)
assert.True(t, ms.Equal(ms2))
}
func BenchmarkFloat64SliceEqual(b *testing.B) {
testutil.SkipMemoryBench(b)
ms := NewFloat64Slice()
ms.Append(1.1, 2.2, 3.3)
cmp := NewFloat64Slice()
cmp.Append(1.1, 2.2, 3.3)
b.ResetTimer()
b.ReportAllocs()
for n := 0; n < b.N; n++ {
_ = ms.Equal(cmp)
}
}
================================================
FILE: pdata/pcommon/generated_instrumentationscope.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pcommon
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// InstrumentationScope is a message representing the instrumentation scope information.
//
// This is a reference type, if passed by value and callee modifies it the
// caller will see the modification.
//
// Must use NewInstrumentationScope function to create new instances.
// Important: zero-initialized instance is not valid for use.
type InstrumentationScope internal.InstrumentationScopeWrapper
func newInstrumentationScope(orig *internal.InstrumentationScope, state *internal.State) InstrumentationScope {
return InstrumentationScope(internal.NewInstrumentationScopeWrapper(orig, state))
}
// NewInstrumentationScope creates a new empty InstrumentationScope.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewInstrumentationScope() InstrumentationScope {
return newInstrumentationScope(internal.NewInstrumentationScope(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms InstrumentationScope) MoveTo(dest InstrumentationScope) {
ms.getState().AssertMutable()
dest.getState().AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.getOrig() == dest.getOrig() {
return
}
internal.DeleteInstrumentationScope(dest.getOrig(), false)
*dest.getOrig(), *ms.getOrig() = *ms.getOrig(), *dest.getOrig()
}
// Name returns the name associated with this InstrumentationScope.
func (ms InstrumentationScope) Name() string {
return ms.getOrig().Name
}
// SetName replaces the name associated with this InstrumentationScope.
func (ms InstrumentationScope) SetName(v string) {
ms.getState().AssertMutable()
ms.getOrig().Name = v
}
// Version returns the version associated with this InstrumentationScope.
func (ms InstrumentationScope) Version() string {
return ms.getOrig().Version
}
// SetVersion replaces the version associated with this InstrumentationScope.
func (ms InstrumentationScope) SetVersion(v string) {
ms.getState().AssertMutable()
ms.getOrig().Version = v
}
// Attributes returns the Attributes associated with this InstrumentationScope.
func (ms InstrumentationScope) Attributes() Map {
return Map(internal.NewMapWrapper(&ms.getOrig().Attributes, ms.getState()))
}
// DroppedAttributesCount returns the droppedattributescount associated with this InstrumentationScope.
func (ms InstrumentationScope) DroppedAttributesCount() uint32 {
return ms.getOrig().DroppedAttributesCount
}
// SetDroppedAttributesCount replaces the droppedattributescount associated with this InstrumentationScope.
func (ms InstrumentationScope) SetDroppedAttributesCount(v uint32) {
ms.getState().AssertMutable()
ms.getOrig().DroppedAttributesCount = v
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms InstrumentationScope) CopyTo(dest InstrumentationScope) {
dest.getState().AssertMutable()
internal.CopyInstrumentationScope(dest.getOrig(), ms.getOrig())
}
func (ms InstrumentationScope) getOrig() *internal.InstrumentationScope {
return internal.GetInstrumentationScopeOrig(internal.InstrumentationScopeWrapper(ms))
}
func (ms InstrumentationScope) getState() *internal.State {
return internal.GetInstrumentationScopeState(internal.InstrumentationScopeWrapper(ms))
}
================================================
FILE: pdata/pcommon/generated_instrumentationscope_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pcommon
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestInstrumentationScope_MoveTo(t *testing.T) {
ms := generateTestInstrumentationScope()
dest := NewInstrumentationScope()
ms.MoveTo(dest)
assert.Equal(t, NewInstrumentationScope(), ms)
assert.Equal(t, generateTestInstrumentationScope(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestInstrumentationScope(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newInstrumentationScope(internal.NewInstrumentationScope(), sharedState)) })
assert.Panics(t, func() { newInstrumentationScope(internal.NewInstrumentationScope(), sharedState).MoveTo(dest) })
}
func TestInstrumentationScope_CopyTo(t *testing.T) {
ms := NewInstrumentationScope()
orig := NewInstrumentationScope()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestInstrumentationScope()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newInstrumentationScope(internal.NewInstrumentationScope(), sharedState)) })
}
func TestInstrumentationScope_Name(t *testing.T) {
ms := NewInstrumentationScope()
assert.Empty(t, ms.Name())
ms.SetName("test_name")
assert.Equal(t, "test_name", ms.Name())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newInstrumentationScope(internal.NewInstrumentationScope(), sharedState).SetName("test_name") })
}
func TestInstrumentationScope_Version(t *testing.T) {
ms := NewInstrumentationScope()
assert.Empty(t, ms.Version())
ms.SetVersion("test_version")
assert.Equal(t, "test_version", ms.Version())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() {
newInstrumentationScope(internal.NewInstrumentationScope(), sharedState).SetVersion("test_version")
})
}
func TestInstrumentationScope_Attributes(t *testing.T) {
ms := NewInstrumentationScope()
assert.Equal(t, NewMap(), ms.Attributes())
ms.getOrig().Attributes = internal.GenTestKeyValueSlice()
assert.Equal(t, Map(internal.GenTestMapWrapper()), ms.Attributes())
}
func TestInstrumentationScope_DroppedAttributesCount(t *testing.T) {
ms := NewInstrumentationScope()
assert.Equal(t, uint32(0), ms.DroppedAttributesCount())
ms.SetDroppedAttributesCount(uint32(13))
assert.Equal(t, uint32(13), ms.DroppedAttributesCount())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() {
newInstrumentationScope(internal.NewInstrumentationScope(), sharedState).SetDroppedAttributesCount(uint32(13))
})
}
func generateTestInstrumentationScope() InstrumentationScope {
return newInstrumentationScope(internal.GenTestInstrumentationScope(), internal.NewState())
}
================================================
FILE: pdata/pcommon/generated_int32slice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pcommon
import (
"iter"
"slices"
"go.opentelemetry.io/collector/pdata/internal"
)
// Int32Slice represents a []int32 slice.
// The instance of Int32Slice can be assigned to multiple objects since it's immutable.
//
// Must use NewInt32Slice function to create new instances.
// Important: zero-initialized instance is not valid for use.
type Int32Slice internal.Int32SliceWrapper
func (ms Int32Slice) getOrig() *[]int32 {
return internal.GetInt32SliceOrig(internal.Int32SliceWrapper(ms))
}
func (ms Int32Slice) getState() *internal.State {
return internal.GetInt32SliceState(internal.Int32SliceWrapper(ms))
}
// NewInt32Slice creates a new empty Int32Slice.
func NewInt32Slice() Int32Slice {
orig := []int32(nil)
return Int32Slice(internal.NewInt32SliceWrapper(&orig, internal.NewState()))
}
// AsRaw returns a copy of the []int32 slice.
func (ms Int32Slice) AsRaw() []int32 {
return copyInt32Slice(nil, *ms.getOrig())
}
// FromRaw copies raw []int32 into the slice Int32Slice.
func (ms Int32Slice) FromRaw(val []int32) {
ms.getState().AssertMutable()
*ms.getOrig() = copyInt32Slice(*ms.getOrig(), val)
}
// Len returns length of the []int32 slice value.
// Equivalent of len(int32Slice).
func (ms Int32Slice) Len() int {
return len(*ms.getOrig())
}
// At returns an item from particular index.
// Equivalent of int32Slice[i].
func (ms Int32Slice) At(i int) int32 {
return (*ms.getOrig())[i]
}
// All returns an iterator over index-value pairs in the slice.
func (ms Int32Slice) All() iter.Seq2[int, int32] {
return func(yield func(int, int32) bool) {
for i := 0; i < ms.Len(); i++ {
if !yield(i, ms.At(i)) {
return
}
}
}
}
// SetAt sets int32 item at particular index.
// Equivalent of int32Slice[i] = val
func (ms Int32Slice) SetAt(i int, val int32) {
ms.getState().AssertMutable()
(*ms.getOrig())[i] = val
}
// EnsureCapacity ensures Int32Slice has at least the specified capacity.
// 1. If the newCap <= cap, then is no change in capacity.
// 2. If the newCap > cap, then the slice capacity will be expanded to the provided value which will be equivalent of:
// buf := make([]int32, len(int32Slice), newCap)
// copy(buf, int32Slice)
// int32Slice = buf
func (ms Int32Slice) EnsureCapacity(newCap int) {
ms.getState().AssertMutable()
oldCap := cap(*ms.getOrig())
if newCap <= oldCap {
return
}
newOrig := make([]int32, len(*ms.getOrig()), newCap)
copy(newOrig, *ms.getOrig())
*ms.getOrig() = newOrig
}
// Append appends extra elements to Int32Slice.
// Equivalent of int32Slice = append(int32Slice, elms...)
func (ms Int32Slice) Append(elms ...int32) {
ms.getState().AssertMutable()
*ms.getOrig() = append(*ms.getOrig(), elms...)
}
// MoveTo moves all elements from the current slice overriding the destination and
// resetting the current instance to its zero value.
func (ms Int32Slice) MoveTo(dest Int32Slice) {
ms.getState().AssertMutable()
dest.getState().AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.getOrig() == dest.getOrig() {
return
}
*dest.getOrig() = *ms.getOrig()
*ms.getOrig() = nil
}
// MoveAndAppendTo moves all elements from the current slice and appends them to the dest.
// The current slice will be cleared.
func (ms Int32Slice) MoveAndAppendTo(dest Int32Slice) {
ms.getState().AssertMutable()
dest.getState().AssertMutable()
if *dest.getOrig() == nil {
// We can simply move the entire vector and avoid any allocations.
*dest.getOrig() = *ms.getOrig()
} else {
*dest.getOrig() = append(*dest.getOrig(), *ms.getOrig()...)
}
*ms.getOrig() = nil
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (ms Int32Slice) RemoveIf(f func(int32) bool) {
ms.getState().AssertMutable()
newLen := 0
for i := 0; i < len(*ms.getOrig()); i++ {
if f((*ms.getOrig())[i]) {
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*ms.getOrig())[newLen] = (*ms.getOrig())[i]
var zero int32
(*ms.getOrig())[i] = zero
newLen++
}
*ms.getOrig() = (*ms.getOrig())[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (ms Int32Slice) CopyTo(dest Int32Slice) {
dest.getState().AssertMutable()
if ms.getOrig() == dest.getOrig() {
return
}
*dest.getOrig() = copyInt32Slice(*dest.getOrig(), *ms.getOrig())
}
// Equal checks equality with another Int32Slice
func (ms Int32Slice) Equal(val Int32Slice) bool {
return slices.Equal(*ms.getOrig(), *val.getOrig())
}
func copyInt32Slice(dst, src []int32) []int32 {
return append(dst[:0], src...)
}
================================================
FILE: pdata/pcommon/generated_int32slice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pcommon
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/internal/testutil"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestNewInt32Slice(t *testing.T) {
ms := NewInt32Slice()
assert.Equal(t, 0, ms.Len())
ms.FromRaw([]int32{1, 2, 3})
assert.Equal(t, 3, ms.Len())
assert.Equal(t, []int32{1, 2, 3}, ms.AsRaw())
ms.SetAt(1, int32(5))
assert.Equal(t, []int32{1, 5, 3}, ms.AsRaw())
ms.FromRaw([]int32{3})
assert.Equal(t, 1, ms.Len())
assert.Equal(t, int32(3), ms.At(0))
cp := NewInt32Slice()
ms.CopyTo(cp)
ms.SetAt(0, int32(2))
assert.Equal(t, int32(2), ms.At(0))
assert.Equal(t, int32(3), cp.At(0))
ms.CopyTo(cp)
assert.Equal(t, int32(2), cp.At(0))
mv := NewInt32Slice()
ms.MoveTo(mv)
assert.Equal(t, 0, ms.Len())
assert.Equal(t, 1, mv.Len())
assert.Equal(t, int32(2), mv.At(0))
ms.FromRaw([]int32{1, 2, 3})
ms.MoveTo(mv)
assert.Equal(t, 3, mv.Len())
assert.Equal(t, int32(1), mv.At(0))
mv.MoveTo(mv)
assert.Equal(t, 3, mv.Len())
assert.Equal(t, int32(1), mv.At(0))
}
func TestInt32SliceReadOnly(t *testing.T) {
raw := []int32{1, 2, 3}
sharedState := internal.NewState()
sharedState.MarkReadOnly()
ms := Int32Slice(internal.NewInt32SliceWrapper(&raw, sharedState))
assert.Equal(t, 3, ms.Len())
assert.Equal(t, int32(1), ms.At(0))
assert.Panics(t, func() { ms.Append(1) })
assert.Panics(t, func() { ms.EnsureCapacity(2) })
assert.Equal(t, raw, ms.AsRaw())
assert.Panics(t, func() { ms.FromRaw(raw) })
ms2 := NewInt32Slice()
ms.CopyTo(ms2)
assert.Equal(t, ms.AsRaw(), ms2.AsRaw())
assert.Panics(t, func() { ms2.CopyTo(ms) })
assert.Panics(t, func() { ms.MoveTo(ms2) })
assert.Panics(t, func() { ms2.MoveTo(ms) })
}
func TestInt32SliceAppend(t *testing.T) {
ms := NewInt32Slice()
ms.FromRaw([]int32{1, 2, 3})
ms.Append(5, 5)
assert.Equal(t, 5, ms.Len())
assert.Equal(t, int32(5), ms.At(4))
}
func TestInt32SliceEnsureCapacity(t *testing.T) {
ms := NewInt32Slice()
ms.EnsureCapacity(4)
assert.Equal(t, 4, cap(*ms.getOrig()))
ms.EnsureCapacity(2)
assert.Equal(t, 4, cap(*ms.getOrig()))
}
func TestInt32SliceAll(t *testing.T) {
ms := NewInt32Slice()
ms.FromRaw([]int32{1, 2, 3})
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestInt32SliceMoveAndAppendTo(t *testing.T) {
// Test moving from an empty slice
ms := NewInt32Slice()
ms2 := NewInt32Slice()
ms.MoveAndAppendTo(ms2)
assert.Equal(t, NewInt32Slice(), ms2)
assert.Equal(t, ms.Len(), 0)
// Test moving to empty slice
ms.FromRaw([]int32{1, 2, 3})
ms.MoveAndAppendTo(ms2)
assert.Equal(t, ms2.Len(), 3)
// Test moving to a non empty slice
ms.FromRaw([]int32{1, 2, 3})
ms.MoveAndAppendTo(ms2)
assert.Equal(t, ms2.Len(), 6)
}
func TestInt32SliceRemoveIf(t *testing.T) {
emptySlice := NewInt32Slice()
emptySlice.RemoveIf(func(el int32) bool {
t.Fail()
return false
})
ms := NewInt32Slice()
ms.FromRaw([]int32{1, 2, 3})
pos := 0
ms.RemoveIf(func(el int32) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, pos/2, ms.Len())
}
func TestInt32SliceRemoveIfAll(t *testing.T) {
ms := NewInt32Slice()
ms.FromRaw([]int32{1, 2, 3})
ms.RemoveIf(func(el int32) bool {
return true
})
assert.Equal(t, 0, ms.Len())
}
func TestInt32SliceEqual(t *testing.T) {
ms := NewInt32Slice()
ms2 := NewInt32Slice()
assert.True(t, ms.Equal(ms2))
ms.Append(1, 2, 3)
assert.False(t, ms.Equal(ms2))
ms2.Append(1, 2, 3)
assert.True(t, ms.Equal(ms2))
}
func BenchmarkInt32SliceEqual(b *testing.B) {
testutil.SkipMemoryBench(b)
ms := NewInt32Slice()
ms.Append(1, 2, 3)
cmp := NewInt32Slice()
cmp.Append(1, 2, 3)
b.ResetTimer()
b.ReportAllocs()
for n := 0; n < b.N; n++ {
_ = ms.Equal(cmp)
}
}
================================================
FILE: pdata/pcommon/generated_int64slice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pcommon
import (
"iter"
"slices"
"go.opentelemetry.io/collector/pdata/internal"
)
// Int64Slice represents a []int64 slice.
// The instance of Int64Slice can be assigned to multiple objects since it's immutable.
//
// Must use NewInt64Slice function to create new instances.
// Important: zero-initialized instance is not valid for use.
type Int64Slice internal.Int64SliceWrapper
func (ms Int64Slice) getOrig() *[]int64 {
return internal.GetInt64SliceOrig(internal.Int64SliceWrapper(ms))
}
func (ms Int64Slice) getState() *internal.State {
return internal.GetInt64SliceState(internal.Int64SliceWrapper(ms))
}
// NewInt64Slice creates a new empty Int64Slice.
func NewInt64Slice() Int64Slice {
orig := []int64(nil)
return Int64Slice(internal.NewInt64SliceWrapper(&orig, internal.NewState()))
}
// AsRaw returns a copy of the []int64 slice.
func (ms Int64Slice) AsRaw() []int64 {
return copyInt64Slice(nil, *ms.getOrig())
}
// FromRaw copies raw []int64 into the slice Int64Slice.
func (ms Int64Slice) FromRaw(val []int64) {
ms.getState().AssertMutable()
*ms.getOrig() = copyInt64Slice(*ms.getOrig(), val)
}
// Len returns length of the []int64 slice value.
// Equivalent of len(int64Slice).
func (ms Int64Slice) Len() int {
return len(*ms.getOrig())
}
// At returns an item from particular index.
// Equivalent of int64Slice[i].
func (ms Int64Slice) At(i int) int64 {
return (*ms.getOrig())[i]
}
// All returns an iterator over index-value pairs in the slice.
func (ms Int64Slice) All() iter.Seq2[int, int64] {
return func(yield func(int, int64) bool) {
for i := 0; i < ms.Len(); i++ {
if !yield(i, ms.At(i)) {
return
}
}
}
}
// SetAt sets int64 item at particular index.
// Equivalent of int64Slice[i] = val
func (ms Int64Slice) SetAt(i int, val int64) {
ms.getState().AssertMutable()
(*ms.getOrig())[i] = val
}
// EnsureCapacity ensures Int64Slice has at least the specified capacity.
// 1. If the newCap <= cap, then is no change in capacity.
// 2. If the newCap > cap, then the slice capacity will be expanded to the provided value which will be equivalent of:
// buf := make([]int64, len(int64Slice), newCap)
// copy(buf, int64Slice)
// int64Slice = buf
func (ms Int64Slice) EnsureCapacity(newCap int) {
ms.getState().AssertMutable()
oldCap := cap(*ms.getOrig())
if newCap <= oldCap {
return
}
newOrig := make([]int64, len(*ms.getOrig()), newCap)
copy(newOrig, *ms.getOrig())
*ms.getOrig() = newOrig
}
// Append appends extra elements to Int64Slice.
// Equivalent of int64Slice = append(int64Slice, elms...)
func (ms Int64Slice) Append(elms ...int64) {
ms.getState().AssertMutable()
*ms.getOrig() = append(*ms.getOrig(), elms...)
}
// MoveTo moves all elements from the current slice overriding the destination and
// resetting the current instance to its zero value.
func (ms Int64Slice) MoveTo(dest Int64Slice) {
ms.getState().AssertMutable()
dest.getState().AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.getOrig() == dest.getOrig() {
return
}
*dest.getOrig() = *ms.getOrig()
*ms.getOrig() = nil
}
// MoveAndAppendTo moves all elements from the current slice and appends them to the dest.
// The current slice will be cleared.
func (ms Int64Slice) MoveAndAppendTo(dest Int64Slice) {
ms.getState().AssertMutable()
dest.getState().AssertMutable()
if *dest.getOrig() == nil {
// We can simply move the entire vector and avoid any allocations.
*dest.getOrig() = *ms.getOrig()
} else {
*dest.getOrig() = append(*dest.getOrig(), *ms.getOrig()...)
}
*ms.getOrig() = nil
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (ms Int64Slice) RemoveIf(f func(int64) bool) {
ms.getState().AssertMutable()
newLen := 0
for i := 0; i < len(*ms.getOrig()); i++ {
if f((*ms.getOrig())[i]) {
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*ms.getOrig())[newLen] = (*ms.getOrig())[i]
var zero int64
(*ms.getOrig())[i] = zero
newLen++
}
*ms.getOrig() = (*ms.getOrig())[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (ms Int64Slice) CopyTo(dest Int64Slice) {
dest.getState().AssertMutable()
if ms.getOrig() == dest.getOrig() {
return
}
*dest.getOrig() = copyInt64Slice(*dest.getOrig(), *ms.getOrig())
}
// Equal checks equality with another Int64Slice
func (ms Int64Slice) Equal(val Int64Slice) bool {
return slices.Equal(*ms.getOrig(), *val.getOrig())
}
func copyInt64Slice(dst, src []int64) []int64 {
return append(dst[:0], src...)
}
================================================
FILE: pdata/pcommon/generated_int64slice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pcommon
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/internal/testutil"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestNewInt64Slice(t *testing.T) {
ms := NewInt64Slice()
assert.Equal(t, 0, ms.Len())
ms.FromRaw([]int64{1, 2, 3})
assert.Equal(t, 3, ms.Len())
assert.Equal(t, []int64{1, 2, 3}, ms.AsRaw())
ms.SetAt(1, int64(5))
assert.Equal(t, []int64{1, 5, 3}, ms.AsRaw())
ms.FromRaw([]int64{3})
assert.Equal(t, 1, ms.Len())
assert.Equal(t, int64(3), ms.At(0))
cp := NewInt64Slice()
ms.CopyTo(cp)
ms.SetAt(0, int64(2))
assert.Equal(t, int64(2), ms.At(0))
assert.Equal(t, int64(3), cp.At(0))
ms.CopyTo(cp)
assert.Equal(t, int64(2), cp.At(0))
mv := NewInt64Slice()
ms.MoveTo(mv)
assert.Equal(t, 0, ms.Len())
assert.Equal(t, 1, mv.Len())
assert.Equal(t, int64(2), mv.At(0))
ms.FromRaw([]int64{1, 2, 3})
ms.MoveTo(mv)
assert.Equal(t, 3, mv.Len())
assert.Equal(t, int64(1), mv.At(0))
mv.MoveTo(mv)
assert.Equal(t, 3, mv.Len())
assert.Equal(t, int64(1), mv.At(0))
}
func TestInt64SliceReadOnly(t *testing.T) {
raw := []int64{1, 2, 3}
sharedState := internal.NewState()
sharedState.MarkReadOnly()
ms := Int64Slice(internal.NewInt64SliceWrapper(&raw, sharedState))
assert.Equal(t, 3, ms.Len())
assert.Equal(t, int64(1), ms.At(0))
assert.Panics(t, func() { ms.Append(1) })
assert.Panics(t, func() { ms.EnsureCapacity(2) })
assert.Equal(t, raw, ms.AsRaw())
assert.Panics(t, func() { ms.FromRaw(raw) })
ms2 := NewInt64Slice()
ms.CopyTo(ms2)
assert.Equal(t, ms.AsRaw(), ms2.AsRaw())
assert.Panics(t, func() { ms2.CopyTo(ms) })
assert.Panics(t, func() { ms.MoveTo(ms2) })
assert.Panics(t, func() { ms2.MoveTo(ms) })
}
func TestInt64SliceAppend(t *testing.T) {
ms := NewInt64Slice()
ms.FromRaw([]int64{1, 2, 3})
ms.Append(5, 5)
assert.Equal(t, 5, ms.Len())
assert.Equal(t, int64(5), ms.At(4))
}
func TestInt64SliceEnsureCapacity(t *testing.T) {
ms := NewInt64Slice()
ms.EnsureCapacity(4)
assert.Equal(t, 4, cap(*ms.getOrig()))
ms.EnsureCapacity(2)
assert.Equal(t, 4, cap(*ms.getOrig()))
}
func TestInt64SliceAll(t *testing.T) {
ms := NewInt64Slice()
ms.FromRaw([]int64{1, 2, 3})
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestInt64SliceMoveAndAppendTo(t *testing.T) {
// Test moving from an empty slice
ms := NewInt64Slice()
ms2 := NewInt64Slice()
ms.MoveAndAppendTo(ms2)
assert.Equal(t, NewInt64Slice(), ms2)
assert.Equal(t, ms.Len(), 0)
// Test moving to empty slice
ms.FromRaw([]int64{1, 2, 3})
ms.MoveAndAppendTo(ms2)
assert.Equal(t, ms2.Len(), 3)
// Test moving to a non empty slice
ms.FromRaw([]int64{1, 2, 3})
ms.MoveAndAppendTo(ms2)
assert.Equal(t, ms2.Len(), 6)
}
func TestInt64SliceRemoveIf(t *testing.T) {
emptySlice := NewInt64Slice()
emptySlice.RemoveIf(func(el int64) bool {
t.Fail()
return false
})
ms := NewInt64Slice()
ms.FromRaw([]int64{1, 2, 3})
pos := 0
ms.RemoveIf(func(el int64) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, pos/2, ms.Len())
}
func TestInt64SliceRemoveIfAll(t *testing.T) {
ms := NewInt64Slice()
ms.FromRaw([]int64{1, 2, 3})
ms.RemoveIf(func(el int64) bool {
return true
})
assert.Equal(t, 0, ms.Len())
}
func TestInt64SliceEqual(t *testing.T) {
ms := NewInt64Slice()
ms2 := NewInt64Slice()
assert.True(t, ms.Equal(ms2))
ms.Append(1, 2, 3)
assert.False(t, ms.Equal(ms2))
ms2.Append(1, 2, 3)
assert.True(t, ms.Equal(ms2))
}
func BenchmarkInt64SliceEqual(b *testing.B) {
testutil.SkipMemoryBench(b)
ms := NewInt64Slice()
ms.Append(1, 2, 3)
cmp := NewInt64Slice()
cmp.Append(1, 2, 3)
b.ResetTimer()
b.ReportAllocs()
for n := 0; n < b.N; n++ {
_ = ms.Equal(cmp)
}
}
================================================
FILE: pdata/pcommon/generated_resource.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pcommon
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// Resource is a message representing the 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 internal.ResourceWrapper
func newResource(orig *internal.Resource, state *internal.State) Resource {
return Resource(internal.NewResourceWrapper(orig, state))
}
// NewResource creates a new empty Resource.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewResource() Resource {
return newResource(internal.NewResource(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms Resource) MoveTo(dest Resource) {
ms.getState().AssertMutable()
dest.getState().AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.getOrig() == dest.getOrig() {
return
}
internal.DeleteResource(dest.getOrig(), false)
*dest.getOrig(), *ms.getOrig() = *ms.getOrig(), *dest.getOrig()
}
// Attributes returns the Attributes associated with this Resource.
func (ms Resource) Attributes() Map {
return Map(internal.NewMapWrapper(&ms.getOrig().Attributes, ms.getState()))
}
// DroppedAttributesCount returns the droppedattributescount associated with this Resource.
func (ms Resource) DroppedAttributesCount() uint32 {
return ms.getOrig().DroppedAttributesCount
}
// SetDroppedAttributesCount replaces the droppedattributescount associated with this Resource.
func (ms Resource) SetDroppedAttributesCount(v uint32) {
ms.getState().AssertMutable()
ms.getOrig().DroppedAttributesCount = v
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms Resource) CopyTo(dest Resource) {
dest.getState().AssertMutable()
internal.CopyResource(dest.getOrig(), ms.getOrig())
}
func (ms Resource) getOrig() *internal.Resource {
return internal.GetResourceOrig(internal.ResourceWrapper(ms))
}
func (ms Resource) getState() *internal.State {
return internal.GetResourceState(internal.ResourceWrapper(ms))
}
================================================
FILE: pdata/pcommon/generated_resource_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pcommon
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestResource_MoveTo(t *testing.T) {
ms := generateTestResource()
dest := NewResource()
ms.MoveTo(dest)
assert.Equal(t, NewResource(), ms)
assert.Equal(t, generateTestResource(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestResource(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newResource(internal.NewResource(), sharedState)) })
assert.Panics(t, func() { newResource(internal.NewResource(), sharedState).MoveTo(dest) })
}
func TestResource_CopyTo(t *testing.T) {
ms := NewResource()
orig := NewResource()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestResource()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newResource(internal.NewResource(), sharedState)) })
}
func TestResource_Attributes(t *testing.T) {
ms := NewResource()
assert.Equal(t, NewMap(), ms.Attributes())
ms.getOrig().Attributes = internal.GenTestKeyValueSlice()
assert.Equal(t, Map(internal.GenTestMapWrapper()), ms.Attributes())
}
func TestResource_DroppedAttributesCount(t *testing.T) {
ms := NewResource()
assert.Equal(t, uint32(0), ms.DroppedAttributesCount())
ms.SetDroppedAttributesCount(uint32(13))
assert.Equal(t, uint32(13), ms.DroppedAttributesCount())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newResource(internal.NewResource(), sharedState).SetDroppedAttributesCount(uint32(13)) })
}
func generateTestResource() Resource {
return newResource(internal.GenTestResource(), internal.NewState())
}
================================================
FILE: pdata/pcommon/generated_slice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pcommon
import (
"iter"
"go.opentelemetry.io/collector/pdata/internal"
)
// Slice logically represents a slice of Value.
//
// This is a reference type. If passed by value and callee modifies it, the
// caller will see the modification.
//
// Must use NewSlice function to create new instances.
// Important: zero-initialized instance is not valid for use.
type Slice internal.SliceWrapper
func newSlice(orig *[]internal.AnyValue, state *internal.State) Slice {
return Slice(internal.NewSliceWrapper(orig, state))
}
// NewSlice creates a SliceWrapper with 0 elements.
// Can use "EnsureCapacity" to initialize with a given capacity.
func NewSlice() Slice {
orig := []internal.AnyValue(nil)
return newSlice(&orig, internal.NewState())
}
// Len returns the number of elements in the slice.
//
// Returns "0" for a newly instance created with "NewSlice()".
func (es Slice) Len() int {
return len(*es.getOrig())
}
// 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 Slice) At(i int) Value {
return newValue(&(*es.getOrig())[i], es.getState())
}
// All returns an iterator over index-value pairs in the slice.
//
// for i, v := range es.All() {
// ... // Do something with index-value pair
// }
func (es Slice) All() iter.Seq2[int, Value] {
return func(yield func(int, Value) bool) {
for i := 0; i < es.Len(); i++ {
if !yield(i, es.At(i)) {
return
}
}
}
}
// EnsureCapacity is an operation that ensures the slice has at least the specified capacity.
// 1. If the newCap <= cap then no change in capacity.
// 2. If the newCap > cap then the slice capacity will be expanded to equal newCap.
//
// Here is how a new Slice can be initialized:
//
// es := NewSlice()
// es.EnsureCapacity(4)
// for i := 0; i < 4; i++ {
// e := es.AppendEmpty()
// // Here should set all the values for e.
// }
func (es Slice) EnsureCapacity(newCap int) {
es.getState().AssertMutable()
oldCap := cap(*es.getOrig())
if newCap <= oldCap {
return
}
newOrig := make([]internal.AnyValue, len(*es.getOrig()), newCap)
copy(newOrig, *es.getOrig())
*es.getOrig() = newOrig
}
// AppendEmpty will append to the end of the slice an empty Value.
// It returns the newly added Value.
func (es Slice) AppendEmpty() Value {
es.getState().AssertMutable()
*es.getOrig() = append(*es.getOrig(), internal.AnyValue{})
return es.At(es.Len() - 1)
}
// MoveAndAppendTo moves all elements from the current slice and appends them to the dest.
// The current slice will be cleared.
func (es Slice) MoveAndAppendTo(dest Slice) {
es.getState().AssertMutable()
dest.getState().AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if es.getOrig() == dest.getOrig() {
return
}
if *dest.getOrig() == nil {
// We can simply move the entire vector and avoid any allocations.
*dest.getOrig() = *es.getOrig()
} else {
*dest.getOrig() = append(*dest.getOrig(), *es.getOrig()...)
}
*es.getOrig() = nil
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (es Slice) RemoveIf(f func(Value) bool) {
es.getState().AssertMutable()
newLen := 0
for i := 0; i < len(*es.getOrig()); i++ {
if f(es.At(i)) {
internal.DeleteAnyValue(&(*es.getOrig())[i], false)
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*es.getOrig())[newLen] = (*es.getOrig())[i]
(*es.getOrig())[i].Reset()
newLen++
}
*es.getOrig() = (*es.getOrig())[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (es Slice) CopyTo(dest Slice) {
dest.getState().AssertMutable()
if es.getOrig() == dest.getOrig() {
return
}
*dest.getOrig() = internal.CopyAnyValueSlice(*dest.getOrig(), *es.getOrig())
}
func (ms Slice) getOrig() *[]internal.AnyValue {
return internal.GetSliceOrig(internal.SliceWrapper(ms))
}
func (ms Slice) getState() *internal.State {
return internal.GetSliceState(internal.SliceWrapper(ms))
}
================================================
FILE: pdata/pcommon/generated_slice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pcommon
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestSlice(t *testing.T) {
es := NewSlice()
assert.Equal(t, 0, es.Len())
es = newSlice(&[]internal.AnyValue{}, internal.NewState())
assert.Equal(t, 0, es.Len())
emptyVal := NewValueEmpty()
testVal := Value(internal.GenTestValueWrapper())
for i := 0; i < 7; i++ {
es.AppendEmpty()
assert.Equal(t, emptyVal, es.At(i))
(*es.getOrig())[i] = *internal.GenTestAnyValue()
assert.Equal(t, testVal, es.At(i))
}
assert.Equal(t, 7, es.Len())
}
func TestSliceReadOnly(t *testing.T) {
sharedState := internal.NewState()
sharedState.MarkReadOnly()
es := newSlice(&[]internal.AnyValue{}, sharedState)
assert.Equal(t, 0, es.Len())
assert.Panics(t, func() { es.AppendEmpty() })
assert.Panics(t, func() { es.EnsureCapacity(2) })
es2 := NewSlice()
es.CopyTo(es2)
assert.Panics(t, func() { es2.CopyTo(es) })
assert.Panics(t, func() { es.MoveAndAppendTo(es2) })
assert.Panics(t, func() { es2.MoveAndAppendTo(es) })
}
func TestSlice_CopyTo(t *testing.T) {
dest := NewSlice()
src := generateTestSlice()
src.CopyTo(dest)
assert.Equal(t, generateTestSlice(), dest)
dest.CopyTo(dest)
assert.Equal(t, generateTestSlice(), dest)
}
func TestSlice_EnsureCapacity(t *testing.T) {
es := generateTestSlice()
// Test ensure smaller capacity.
const ensureSmallLen = 4
es.EnsureCapacity(ensureSmallLen)
assert.Less(t, ensureSmallLen, es.Len())
assert.Equal(t, es.Len(), cap(*es.getOrig()))
assert.Equal(t, generateTestSlice(), es)
// Test ensure larger capacity
const ensureLargeLen = 9
es.EnsureCapacity(ensureLargeLen)
assert.Less(t, generateTestSlice().Len(), ensureLargeLen)
assert.Equal(t, ensureLargeLen, cap(*es.getOrig()))
assert.Equal(t, generateTestSlice(), es)
}
func TestSlice_MoveAndAppendTo(t *testing.T) {
// Test MoveAndAppendTo to empty
expectedSlice := generateTestSlice()
dest := NewSlice()
src := generateTestSlice()
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo empty slice
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo not empty slice
generateTestSlice().MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
dest.MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
}
func TestSlice_RemoveIf(t *testing.T) {
// Test RemoveIf on empty slice
emptySlice := NewSlice()
emptySlice.RemoveIf(func(el Value) bool {
t.Fail()
return false
})
// Test RemoveIf
filtered := generateTestSlice()
pos := 0
filtered.RemoveIf(func(el Value) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, 2, filtered.Len())
}
func TestSlice_RemoveIfAll(t *testing.T) {
got := generateTestSlice()
got.RemoveIf(func(el Value) bool {
return true
})
assert.Equal(t, 0, got.Len())
}
func TestSliceAll(t *testing.T) {
ms := generateTestSlice()
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func generateTestSlice() Slice {
ms := NewSlice()
*ms.getOrig() = internal.GenTestAnyValueSlice()
return ms
}
================================================
FILE: pdata/pcommon/generated_stringslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pcommon
import (
"iter"
"slices"
"go.opentelemetry.io/collector/pdata/internal"
)
// StringSlice represents a []string slice.
// The instance of StringSlice can be assigned to multiple objects since it's immutable.
//
// Must use NewStringSlice function to create new instances.
// Important: zero-initialized instance is not valid for use.
type StringSlice internal.StringSliceWrapper
func (ms StringSlice) getOrig() *[]string {
return internal.GetStringSliceOrig(internal.StringSliceWrapper(ms))
}
func (ms StringSlice) getState() *internal.State {
return internal.GetStringSliceState(internal.StringSliceWrapper(ms))
}
// NewStringSlice creates a new empty StringSlice.
func NewStringSlice() StringSlice {
orig := []string(nil)
return StringSlice(internal.NewStringSliceWrapper(&orig, internal.NewState()))
}
// AsRaw returns a copy of the []string slice.
func (ms StringSlice) AsRaw() []string {
return copyStringSlice(nil, *ms.getOrig())
}
// FromRaw copies raw []string into the slice StringSlice.
func (ms StringSlice) FromRaw(val []string) {
ms.getState().AssertMutable()
*ms.getOrig() = copyStringSlice(*ms.getOrig(), val)
}
// Len returns length of the []string slice value.
// Equivalent of len(stringSlice).
func (ms StringSlice) Len() int {
return len(*ms.getOrig())
}
// At returns an item from particular index.
// Equivalent of stringSlice[i].
func (ms StringSlice) At(i int) string {
return (*ms.getOrig())[i]
}
// All returns an iterator over index-value pairs in the slice.
func (ms StringSlice) All() iter.Seq2[int, string] {
return func(yield func(int, string) bool) {
for i := 0; i < ms.Len(); i++ {
if !yield(i, ms.At(i)) {
return
}
}
}
}
// SetAt sets string item at particular index.
// Equivalent of stringSlice[i] = val
func (ms StringSlice) SetAt(i int, val string) {
ms.getState().AssertMutable()
(*ms.getOrig())[i] = val
}
// EnsureCapacity ensures StringSlice has at least the specified capacity.
// 1. If the newCap <= cap, then is no change in capacity.
// 2. If the newCap > cap, then the slice capacity will be expanded to the provided value which will be equivalent of:
// buf := make([]string, len(stringSlice), newCap)
// copy(buf, stringSlice)
// stringSlice = buf
func (ms StringSlice) EnsureCapacity(newCap int) {
ms.getState().AssertMutable()
oldCap := cap(*ms.getOrig())
if newCap <= oldCap {
return
}
newOrig := make([]string, len(*ms.getOrig()), newCap)
copy(newOrig, *ms.getOrig())
*ms.getOrig() = newOrig
}
// Append appends extra elements to StringSlice.
// Equivalent of stringSlice = append(stringSlice, elms...)
func (ms StringSlice) Append(elms ...string) {
ms.getState().AssertMutable()
*ms.getOrig() = append(*ms.getOrig(), elms...)
}
// MoveTo moves all elements from the current slice overriding the destination and
// resetting the current instance to its zero value.
func (ms StringSlice) MoveTo(dest StringSlice) {
ms.getState().AssertMutable()
dest.getState().AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.getOrig() == dest.getOrig() {
return
}
*dest.getOrig() = *ms.getOrig()
*ms.getOrig() = nil
}
// MoveAndAppendTo moves all elements from the current slice and appends them to the dest.
// The current slice will be cleared.
func (ms StringSlice) MoveAndAppendTo(dest StringSlice) {
ms.getState().AssertMutable()
dest.getState().AssertMutable()
if *dest.getOrig() == nil {
// We can simply move the entire vector and avoid any allocations.
*dest.getOrig() = *ms.getOrig()
} else {
*dest.getOrig() = append(*dest.getOrig(), *ms.getOrig()...)
}
*ms.getOrig() = nil
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (ms StringSlice) RemoveIf(f func(string) bool) {
ms.getState().AssertMutable()
newLen := 0
for i := 0; i < len(*ms.getOrig()); i++ {
if f((*ms.getOrig())[i]) {
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*ms.getOrig())[newLen] = (*ms.getOrig())[i]
var zero string
(*ms.getOrig())[i] = zero
newLen++
}
*ms.getOrig() = (*ms.getOrig())[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (ms StringSlice) CopyTo(dest StringSlice) {
dest.getState().AssertMutable()
if ms.getOrig() == dest.getOrig() {
return
}
*dest.getOrig() = copyStringSlice(*dest.getOrig(), *ms.getOrig())
}
// Equal checks equality with another StringSlice
func (ms StringSlice) Equal(val StringSlice) bool {
return slices.Equal(*ms.getOrig(), *val.getOrig())
}
func copyStringSlice(dst, src []string) []string {
return append(dst[:0], src...)
}
================================================
FILE: pdata/pcommon/generated_stringslice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pcommon
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/internal/testutil"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestNewStringSlice(t *testing.T) {
ms := NewStringSlice()
assert.Equal(t, 0, ms.Len())
ms.FromRaw([]string{"a", "b", "c"})
assert.Equal(t, 3, ms.Len())
assert.Equal(t, []string{"a", "b", "c"}, ms.AsRaw())
ms.SetAt(1, string("d"))
assert.Equal(t, []string{"a", "d", "c"}, ms.AsRaw())
ms.FromRaw([]string{"c"})
assert.Equal(t, 1, ms.Len())
assert.Equal(t, string("c"), ms.At(0))
cp := NewStringSlice()
ms.CopyTo(cp)
ms.SetAt(0, string("b"))
assert.Equal(t, string("b"), ms.At(0))
assert.Equal(t, string("c"), cp.At(0))
ms.CopyTo(cp)
assert.Equal(t, string("b"), cp.At(0))
mv := NewStringSlice()
ms.MoveTo(mv)
assert.Equal(t, 0, ms.Len())
assert.Equal(t, 1, mv.Len())
assert.Equal(t, string("b"), mv.At(0))
ms.FromRaw([]string{"a", "b", "c"})
ms.MoveTo(mv)
assert.Equal(t, 3, mv.Len())
assert.Equal(t, string("a"), mv.At(0))
mv.MoveTo(mv)
assert.Equal(t, 3, mv.Len())
assert.Equal(t, string("a"), mv.At(0))
}
func TestStringSliceReadOnly(t *testing.T) {
raw := []string{"a", "b", "c"}
sharedState := internal.NewState()
sharedState.MarkReadOnly()
ms := StringSlice(internal.NewStringSliceWrapper(&raw, sharedState))
assert.Equal(t, 3, ms.Len())
assert.Equal(t, string("a"), ms.At(0))
assert.Panics(t, func() { ms.Append("a") })
assert.Panics(t, func() { ms.EnsureCapacity(2) })
assert.Equal(t, raw, ms.AsRaw())
assert.Panics(t, func() { ms.FromRaw(raw) })
ms2 := NewStringSlice()
ms.CopyTo(ms2)
assert.Equal(t, ms.AsRaw(), ms2.AsRaw())
assert.Panics(t, func() { ms2.CopyTo(ms) })
assert.Panics(t, func() { ms.MoveTo(ms2) })
assert.Panics(t, func() { ms2.MoveTo(ms) })
}
func TestStringSliceAppend(t *testing.T) {
ms := NewStringSlice()
ms.FromRaw([]string{"a", "b", "c"})
ms.Append("d", "d")
assert.Equal(t, 5, ms.Len())
assert.Equal(t, string("d"), ms.At(4))
}
func TestStringSliceEnsureCapacity(t *testing.T) {
ms := NewStringSlice()
ms.EnsureCapacity(4)
assert.Equal(t, 4, cap(*ms.getOrig()))
ms.EnsureCapacity(2)
assert.Equal(t, 4, cap(*ms.getOrig()))
}
func TestStringSliceAll(t *testing.T) {
ms := NewStringSlice()
ms.FromRaw([]string{"a", "b", "c"})
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestStringSliceMoveAndAppendTo(t *testing.T) {
// Test moving from an empty slice
ms := NewStringSlice()
ms2 := NewStringSlice()
ms.MoveAndAppendTo(ms2)
assert.Equal(t, NewStringSlice(), ms2)
assert.Equal(t, ms.Len(), 0)
// Test moving to empty slice
ms.FromRaw([]string{"a", "b", "c"})
ms.MoveAndAppendTo(ms2)
assert.Equal(t, ms2.Len(), 3)
// Test moving to a non empty slice
ms.FromRaw([]string{"a", "b", "c"})
ms.MoveAndAppendTo(ms2)
assert.Equal(t, ms2.Len(), 6)
}
func TestStringSliceRemoveIf(t *testing.T) {
emptySlice := NewStringSlice()
emptySlice.RemoveIf(func(el string) bool {
t.Fail()
return false
})
ms := NewStringSlice()
ms.FromRaw([]string{"a", "b", "c"})
pos := 0
ms.RemoveIf(func(el string) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, pos/2, ms.Len())
}
func TestStringSliceRemoveIfAll(t *testing.T) {
ms := NewStringSlice()
ms.FromRaw([]string{"a", "b", "c"})
ms.RemoveIf(func(el string) bool {
return true
})
assert.Equal(t, 0, ms.Len())
}
func TestStringSliceEqual(t *testing.T) {
ms := NewStringSlice()
ms2 := NewStringSlice()
assert.True(t, ms.Equal(ms2))
ms.Append("a", "b", "c")
assert.False(t, ms.Equal(ms2))
ms2.Append("a", "b", "c")
assert.True(t, ms.Equal(ms2))
}
func BenchmarkStringSliceEqual(b *testing.B) {
testutil.SkipMemoryBench(b)
ms := NewStringSlice()
ms.Append("a", "b", "c")
cmp := NewStringSlice()
cmp.Append("a", "b", "c")
b.ResetTimer()
b.ReportAllocs()
for n := 0; n < b.N; n++ {
_ = ms.Equal(cmp)
}
}
================================================
FILE: pdata/pcommon/generated_uint64slice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pcommon
import (
"iter"
"slices"
"go.opentelemetry.io/collector/pdata/internal"
)
// UInt64Slice represents a []uint64 slice.
// The instance of UInt64Slice can be assigned to multiple objects since it's immutable.
//
// Must use NewUInt64Slice function to create new instances.
// Important: zero-initialized instance is not valid for use.
type UInt64Slice internal.UInt64SliceWrapper
func (ms UInt64Slice) getOrig() *[]uint64 {
return internal.GetUInt64SliceOrig(internal.UInt64SliceWrapper(ms))
}
func (ms UInt64Slice) getState() *internal.State {
return internal.GetUInt64SliceState(internal.UInt64SliceWrapper(ms))
}
// NewUInt64Slice creates a new empty UInt64Slice.
func NewUInt64Slice() UInt64Slice {
orig := []uint64(nil)
return UInt64Slice(internal.NewUInt64SliceWrapper(&orig, internal.NewState()))
}
// AsRaw returns a copy of the []uint64 slice.
func (ms UInt64Slice) AsRaw() []uint64 {
return copyUint64Slice(nil, *ms.getOrig())
}
// FromRaw copies raw []uint64 into the slice UInt64Slice.
func (ms UInt64Slice) FromRaw(val []uint64) {
ms.getState().AssertMutable()
*ms.getOrig() = copyUint64Slice(*ms.getOrig(), val)
}
// Len returns length of the []uint64 slice value.
// Equivalent of len(uInt64Slice).
func (ms UInt64Slice) Len() int {
return len(*ms.getOrig())
}
// At returns an item from particular index.
// Equivalent of uInt64Slice[i].
func (ms UInt64Slice) At(i int) uint64 {
return (*ms.getOrig())[i]
}
// All returns an iterator over index-value pairs in the slice.
func (ms UInt64Slice) All() iter.Seq2[int, uint64] {
return func(yield func(int, uint64) bool) {
for i := 0; i < ms.Len(); i++ {
if !yield(i, ms.At(i)) {
return
}
}
}
}
// SetAt sets uint64 item at particular index.
// Equivalent of uInt64Slice[i] = val
func (ms UInt64Slice) SetAt(i int, val uint64) {
ms.getState().AssertMutable()
(*ms.getOrig())[i] = val
}
// EnsureCapacity ensures UInt64Slice has at least the specified capacity.
// 1. If the newCap <= cap, then is no change in capacity.
// 2. If the newCap > cap, then the slice capacity will be expanded to the provided value which will be equivalent of:
// buf := make([]uint64, len(uInt64Slice), newCap)
// copy(buf, uInt64Slice)
// uInt64Slice = buf
func (ms UInt64Slice) EnsureCapacity(newCap int) {
ms.getState().AssertMutable()
oldCap := cap(*ms.getOrig())
if newCap <= oldCap {
return
}
newOrig := make([]uint64, len(*ms.getOrig()), newCap)
copy(newOrig, *ms.getOrig())
*ms.getOrig() = newOrig
}
// Append appends extra elements to UInt64Slice.
// Equivalent of uInt64Slice = append(uInt64Slice, elms...)
func (ms UInt64Slice) Append(elms ...uint64) {
ms.getState().AssertMutable()
*ms.getOrig() = append(*ms.getOrig(), elms...)
}
// MoveTo moves all elements from the current slice overriding the destination and
// resetting the current instance to its zero value.
func (ms UInt64Slice) MoveTo(dest UInt64Slice) {
ms.getState().AssertMutable()
dest.getState().AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.getOrig() == dest.getOrig() {
return
}
*dest.getOrig() = *ms.getOrig()
*ms.getOrig() = nil
}
// MoveAndAppendTo moves all elements from the current slice and appends them to the dest.
// The current slice will be cleared.
func (ms UInt64Slice) MoveAndAppendTo(dest UInt64Slice) {
ms.getState().AssertMutable()
dest.getState().AssertMutable()
if *dest.getOrig() == nil {
// We can simply move the entire vector and avoid any allocations.
*dest.getOrig() = *ms.getOrig()
} else {
*dest.getOrig() = append(*dest.getOrig(), *ms.getOrig()...)
}
*ms.getOrig() = nil
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (ms UInt64Slice) RemoveIf(f func(uint64) bool) {
ms.getState().AssertMutable()
newLen := 0
for i := 0; i < len(*ms.getOrig()); i++ {
if f((*ms.getOrig())[i]) {
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*ms.getOrig())[newLen] = (*ms.getOrig())[i]
var zero uint64
(*ms.getOrig())[i] = zero
newLen++
}
*ms.getOrig() = (*ms.getOrig())[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (ms UInt64Slice) CopyTo(dest UInt64Slice) {
dest.getState().AssertMutable()
if ms.getOrig() == dest.getOrig() {
return
}
*dest.getOrig() = copyUint64Slice(*dest.getOrig(), *ms.getOrig())
}
// Equal checks equality with another UInt64Slice
func (ms UInt64Slice) Equal(val UInt64Slice) bool {
return slices.Equal(*ms.getOrig(), *val.getOrig())
}
func copyUint64Slice(dst, src []uint64) []uint64 {
return append(dst[:0], src...)
}
================================================
FILE: pdata/pcommon/generated_uint64slice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pcommon
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/internal/testutil"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestNewUInt64Slice(t *testing.T) {
ms := NewUInt64Slice()
assert.Equal(t, 0, ms.Len())
ms.FromRaw([]uint64{1, 2, 3})
assert.Equal(t, 3, ms.Len())
assert.Equal(t, []uint64{1, 2, 3}, ms.AsRaw())
ms.SetAt(1, uint64(5))
assert.Equal(t, []uint64{1, 5, 3}, ms.AsRaw())
ms.FromRaw([]uint64{3})
assert.Equal(t, 1, ms.Len())
assert.Equal(t, uint64(3), ms.At(0))
cp := NewUInt64Slice()
ms.CopyTo(cp)
ms.SetAt(0, uint64(2))
assert.Equal(t, uint64(2), ms.At(0))
assert.Equal(t, uint64(3), cp.At(0))
ms.CopyTo(cp)
assert.Equal(t, uint64(2), cp.At(0))
mv := NewUInt64Slice()
ms.MoveTo(mv)
assert.Equal(t, 0, ms.Len())
assert.Equal(t, 1, mv.Len())
assert.Equal(t, uint64(2), mv.At(0))
ms.FromRaw([]uint64{1, 2, 3})
ms.MoveTo(mv)
assert.Equal(t, 3, mv.Len())
assert.Equal(t, uint64(1), mv.At(0))
mv.MoveTo(mv)
assert.Equal(t, 3, mv.Len())
assert.Equal(t, uint64(1), mv.At(0))
}
func TestUInt64SliceReadOnly(t *testing.T) {
raw := []uint64{1, 2, 3}
sharedState := internal.NewState()
sharedState.MarkReadOnly()
ms := UInt64Slice(internal.NewUInt64SliceWrapper(&raw, sharedState))
assert.Equal(t, 3, ms.Len())
assert.Equal(t, uint64(1), ms.At(0))
assert.Panics(t, func() { ms.Append(1) })
assert.Panics(t, func() { ms.EnsureCapacity(2) })
assert.Equal(t, raw, ms.AsRaw())
assert.Panics(t, func() { ms.FromRaw(raw) })
ms2 := NewUInt64Slice()
ms.CopyTo(ms2)
assert.Equal(t, ms.AsRaw(), ms2.AsRaw())
assert.Panics(t, func() { ms2.CopyTo(ms) })
assert.Panics(t, func() { ms.MoveTo(ms2) })
assert.Panics(t, func() { ms2.MoveTo(ms) })
}
func TestUInt64SliceAppend(t *testing.T) {
ms := NewUInt64Slice()
ms.FromRaw([]uint64{1, 2, 3})
ms.Append(5, 5)
assert.Equal(t, 5, ms.Len())
assert.Equal(t, uint64(5), ms.At(4))
}
func TestUInt64SliceEnsureCapacity(t *testing.T) {
ms := NewUInt64Slice()
ms.EnsureCapacity(4)
assert.Equal(t, 4, cap(*ms.getOrig()))
ms.EnsureCapacity(2)
assert.Equal(t, 4, cap(*ms.getOrig()))
}
func TestUInt64SliceAll(t *testing.T) {
ms := NewUInt64Slice()
ms.FromRaw([]uint64{1, 2, 3})
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestUInt64SliceMoveAndAppendTo(t *testing.T) {
// Test moving from an empty slice
ms := NewUInt64Slice()
ms2 := NewUInt64Slice()
ms.MoveAndAppendTo(ms2)
assert.Equal(t, NewUInt64Slice(), ms2)
assert.Equal(t, ms.Len(), 0)
// Test moving to empty slice
ms.FromRaw([]uint64{1, 2, 3})
ms.MoveAndAppendTo(ms2)
assert.Equal(t, ms2.Len(), 3)
// Test moving to a non empty slice
ms.FromRaw([]uint64{1, 2, 3})
ms.MoveAndAppendTo(ms2)
assert.Equal(t, ms2.Len(), 6)
}
func TestUInt64SliceRemoveIf(t *testing.T) {
emptySlice := NewUInt64Slice()
emptySlice.RemoveIf(func(el uint64) bool {
t.Fail()
return false
})
ms := NewUInt64Slice()
ms.FromRaw([]uint64{1, 2, 3})
pos := 0
ms.RemoveIf(func(el uint64) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, pos/2, ms.Len())
}
func TestUInt64SliceRemoveIfAll(t *testing.T) {
ms := NewUInt64Slice()
ms.FromRaw([]uint64{1, 2, 3})
ms.RemoveIf(func(el uint64) bool {
return true
})
assert.Equal(t, 0, ms.Len())
}
func TestUInt64SliceEqual(t *testing.T) {
ms := NewUInt64Slice()
ms2 := NewUInt64Slice()
assert.True(t, ms.Equal(ms2))
ms.Append(1, 2, 3)
assert.False(t, ms.Equal(ms2))
ms2.Append(1, 2, 3)
assert.True(t, ms.Equal(ms2))
}
func BenchmarkUInt64SliceEqual(b *testing.B) {
testutil.SkipMemoryBench(b)
ms := NewUInt64Slice()
ms.Append(1, 2, 3)
cmp := NewUInt64Slice()
cmp.Append(1, 2, 3)
b.ResetTimer()
b.ReportAllocs()
for n := 0; n < b.N; n++ {
_ = ms.Equal(cmp)
}
}
================================================
FILE: pdata/pcommon/map.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pcommon // import "go.opentelemetry.io/collector/pdata/pcommon"
import (
"iter"
"go.uber.org/multierr"
"go.opentelemetry.io/collector/pdata/internal"
)
// Map stores a map of string keys to elements of Value type.
//
// Must use NewMap function to create new instances.
// Important: zero-initialized instance is not valid for use.
type Map internal.MapWrapper
// NewMap creates a Map with 0 elements.
func NewMap() Map {
orig := []internal.KeyValue(nil)
return Map(internal.NewMapWrapper(&orig, internal.NewState()))
}
func (m Map) getOrig() *[]internal.KeyValue {
return internal.GetMapOrig(internal.MapWrapper(m))
}
func (m Map) getState() *internal.State {
return internal.GetMapState(internal.MapWrapper(m))
}
func newMap(orig *[]internal.KeyValue, state *internal.State) Map {
return Map(internal.NewMapWrapper(orig, state))
}
// Clear erases any existing entries in this Map instance.
func (m Map) Clear() {
m.getState().AssertMutable()
*m.getOrig() = nil
}
// EnsureCapacity increases the capacity of this Map instance, if necessary,
// to ensure that it can hold at least the number of elements specified by the capacity argument.
func (m Map) EnsureCapacity(capacity int) {
m.getState().AssertMutable()
oldOrig := *m.getOrig()
if capacity <= cap(oldOrig) {
return
}
*m.getOrig() = make([]internal.KeyValue, len(oldOrig), capacity)
copy(*m.getOrig(), oldOrig)
}
// Get returns the Value associated with the key and true. The returned
// Value is not a copy, it is a reference to the value stored in this map.
// It is allowed to modify the returned value using Value.Set* functions.
// Such modification will be applied to the value stored in this map.
// Accessing the returned value after modifying the underlying map
// (removing or adding new values) is an undefined behavior.
//
// If the key does not exist, returns a zero-initialized KeyValue and false.
// Calling any functions on the returned invalid instance may cause a panic.
func (m Map) Get(key string) (Value, bool) {
for i := range *m.getOrig() {
akv := &(*m.getOrig())[i]
if akv.Key == key {
return newValue(&akv.Value, m.getState()), true
}
}
return newValue(nil, m.getState()), false
}
// Remove removes the entry associated with the key and returns true if the key
// was present in the map, otherwise returns false.
func (m Map) Remove(key string) bool {
m.getState().AssertMutable()
for i := range *m.getOrig() {
akv := &(*m.getOrig())[i]
if akv.Key == key {
*akv = (*m.getOrig())[len(*m.getOrig())-1]
*m.getOrig() = (*m.getOrig())[:len(*m.getOrig())-1]
return true
}
}
return false
}
// RemoveIf removes the entries for which the function in question returns true
func (m Map) RemoveIf(f func(string, Value) bool) {
m.getState().AssertMutable()
newLen := 0
for i := 0; i < len(*m.getOrig()); i++ {
if f((*m.getOrig())[i].Key, newValue(&(*m.getOrig())[i].Value, m.getState())) {
(*m.getOrig())[i] = internal.KeyValue{}
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*m.getOrig())[newLen] = (*m.getOrig())[i]
(*m.getOrig())[i] = internal.KeyValue{}
newLen++
}
*m.getOrig() = (*m.getOrig())[:newLen]
}
// PutEmpty inserts or updates an empty value to the map under given key
// and return the updated/inserted value.
func (m Map) PutEmpty(k string) Value {
m.getState().AssertMutable()
if av, existing := m.Get(k); existing {
av.getOrig().Value = nil
return newValue(av.getOrig(), m.getState())
}
*m.getOrig() = append(*m.getOrig(), internal.KeyValue{Key: k})
return newValue(&(*m.getOrig())[len(*m.getOrig())-1].Value, m.getState())
}
// GetOrPutEmpty returns the Value associated with the key and true (loaded) if the key exists in the map,
// otherwise inserts an empty value to the map under the given key and returns the inserted value
// and false (loaded).
func (m Map) GetOrPutEmpty(k string) (Value, bool) {
m.getState().AssertMutable()
if av, existing := m.Get(k); existing {
return av, true
}
*m.getOrig() = append(*m.getOrig(), internal.KeyValue{Key: k})
return newValue(&(*m.getOrig())[len(*m.getOrig())-1].Value, m.getState()), false
}
// PutStr performs the Insert or Update action. The Value is
// inserted to the map that did not originally have the key. The key/value is
// updated to the map where the key already existed.
func (m Map) PutStr(k, v string) {
m.getState().AssertMutable()
if av, existing := m.Get(k); existing {
av.SetStr(v)
return
}
ov := internal.NewAnyValueStringValue()
ov.StringValue = v
*m.getOrig() = append(*m.getOrig(), internal.KeyValue{Key: k, Value: internal.AnyValue{Value: ov}})
}
// PutInt performs the Insert or Update action. The int Value is
// inserted to the map that did not originally have the key. The key/value is
// updated to the map where the key already existed.
func (m Map) PutInt(k string, v int64) {
m.getState().AssertMutable()
if av, existing := m.Get(k); existing {
av.SetInt(v)
return
}
ov := internal.NewAnyValueIntValue()
ov.IntValue = v
*m.getOrig() = append(*m.getOrig(), internal.KeyValue{Key: k, Value: internal.AnyValue{Value: ov}})
}
// PutDouble performs the Insert or Update action. The double Value is
// inserted to the map that did not originally have the key. The key/value is
// updated to the map where the key already existed.
func (m Map) PutDouble(k string, v float64) {
m.getState().AssertMutable()
if av, existing := m.Get(k); existing {
av.SetDouble(v)
return
}
ov := internal.NewAnyValueDoubleValue()
ov.DoubleValue = v
*m.getOrig() = append(*m.getOrig(), internal.KeyValue{Key: k, Value: internal.AnyValue{Value: ov}})
}
// PutBool performs the Insert or Update action. The bool Value is
// inserted to the map that did not originally have the key. The key/value is
// updated to the map where the key already existed.
func (m Map) PutBool(k string, v bool) {
m.getState().AssertMutable()
if av, existing := m.Get(k); existing {
av.SetBool(v)
return
}
ov := internal.NewAnyValueBoolValue()
ov.BoolValue = v
*m.getOrig() = append(*m.getOrig(), internal.KeyValue{Key: k, Value: internal.AnyValue{Value: ov}})
}
// PutEmptyBytes inserts or updates an empty byte slice under given key and returns it.
func (m Map) PutEmptyBytes(k string) ByteSlice {
m.getState().AssertMutable()
if av, existing := m.Get(k); existing {
return av.SetEmptyBytes()
}
ov := internal.NewAnyValueBytesValue()
*m.getOrig() = append(*m.getOrig(), internal.KeyValue{Key: k, Value: internal.AnyValue{Value: ov}})
return ByteSlice(internal.NewByteSliceWrapper(&ov.BytesValue, m.getState()))
}
// PutEmptyMap inserts or updates an empty map under given key and returns it.
func (m Map) PutEmptyMap(k string) Map {
m.getState().AssertMutable()
if av, existing := m.Get(k); existing {
return av.SetEmptyMap()
}
ov := internal.NewAnyValueKvlistValue()
ov.KvlistValue = internal.NewKeyValueList()
*m.getOrig() = append(*m.getOrig(), internal.KeyValue{Key: k, Value: internal.AnyValue{Value: ov}})
return Map(internal.NewMapWrapper(&ov.KvlistValue.Values, m.getState()))
}
// PutEmptySlice inserts or updates an empty slice under given key and returns it.
func (m Map) PutEmptySlice(k string) Slice {
m.getState().AssertMutable()
if av, existing := m.Get(k); existing {
return av.SetEmptySlice()
}
ov := internal.NewAnyValueArrayValue()
ov.ArrayValue = internal.NewArrayValue()
*m.getOrig() = append(*m.getOrig(), internal.KeyValue{Key: k, Value: internal.AnyValue{Value: ov}})
return Slice(internal.NewSliceWrapper(&ov.ArrayValue.Values, m.getState()))
}
// Len returns the length of this map.
//
// Because the Map is represented internally by a slice of pointers, and the data are comping from the wire,
// it is possible that when iterating using "Range" to get access to fewer elements because nil elements are skipped.
func (m Map) Len() int {
return len(*m.getOrig())
}
// Range calls f sequentially for each key and value present in the map. If f returns false, range stops the iteration.
//
// Example:
//
// sm.Range(func(k string, v Value) bool {
// ...
// })
func (m Map) Range(f func(k string, v Value) bool) {
for i := range *m.getOrig() {
kv := &(*m.getOrig())[i]
if !f(kv.Key, Value(internal.NewValueWrapper(&kv.Value, m.getState()))) {
break
}
}
}
// All returns an iterator over key-value pairs in the Map.
//
// for k, v := range es.All() {
// ... // Do something with key-value pair
// }
func (m Map) All() iter.Seq2[string, Value] {
return func(yield func(string, Value) bool) {
for i := range *m.getOrig() {
kv := &(*m.getOrig())[i]
if !yield(kv.Key, Value(internal.NewValueWrapper(&kv.Value, m.getState()))) {
return
}
}
}
}
// MoveTo moves all key/values from the current map overriding the destination and
// resetting the current instance to its zero value
func (m Map) MoveTo(dest Map) {
m.getState().AssertMutable()
dest.getState().AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if m.getOrig() == dest.getOrig() {
return
}
*dest.getOrig() = *m.getOrig()
*m.getOrig() = nil
}
// CopyTo copies all elements from the current map overriding the destination.
func (m Map) CopyTo(dest Map) {
dest.getState().AssertMutable()
if m.getOrig() == dest.getOrig() {
return
}
*dest.getOrig() = internal.CopyKeyValueSlice(*dest.getOrig(), *m.getOrig())
}
// AsRaw returns a standard go map representation of this Map.
func (m Map) AsRaw() map[string]any {
rawMap := make(map[string]any, m.Len())
m.Range(func(k string, v Value) bool {
rawMap[k] = v.AsRaw()
return true
})
return rawMap
}
// FromRaw overrides this Map instance from a standard go map.
func (m Map) FromRaw(rawMap map[string]any) error {
m.getState().AssertMutable()
if len(rawMap) == 0 {
*m.getOrig() = nil
return nil
}
var errs error
origs := make([]internal.KeyValue, len(rawMap))
ix := 0
for k, iv := range rawMap {
origs[ix].Key = k
errs = multierr.Append(errs, newValue(&origs[ix].Value, m.getState()).FromRaw(iv))
ix++
}
*m.getOrig() = origs
return errs
}
// Equal checks equality with another Map
func (m Map) Equal(val Map) bool {
if m.Len() != val.Len() {
return false
}
fullEqual := true
m.Range(func(k string, v Value) bool {
vv, ok := val.Get(k)
if !ok {
fullEqual = false
return fullEqual
}
if !v.Equal(vv) {
fullEqual = false
}
return fullEqual
})
return fullEqual
}
================================================
FILE: pdata/pcommon/map_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pcommon
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/internal/testutil"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestMap(t *testing.T) {
assert.Equal(t, 0, NewMap().Len())
val, exist := NewMap().Get("test_key")
assert.False(t, exist)
assert.Equal(t, newValue(nil, internal.NewState()), val)
putString := NewMap()
putString.PutStr("k", "v")
assert.Equal(t, generateTestStringMap(t), putString)
putInt := NewMap()
putInt.PutInt("k", 123)
assert.Equal(t, generateTestIntMap(t), putInt)
putDouble := NewMap()
putDouble.PutDouble("k", 12.3)
assert.Equal(t, generateTestDoubleMap(t), putDouble)
putBool := NewMap()
putBool.PutBool("k", true)
assert.Equal(t, generateTestBoolMap(t), putBool)
putBytes := NewMap()
putBytes.PutEmptyBytes("k").FromRaw([]byte{1, 2, 3, 4, 5})
assert.Equal(t, generateTestBytesMap(t), putBytes)
putMap := NewMap()
putMap.PutEmptyMap("k")
assert.Equal(t, generateTestEmptyMap(t), putMap)
putSlice := NewMap()
putSlice.PutEmptySlice("k")
assert.Equal(t, generateTestEmptySlice(t), putSlice)
removeMap := NewMap()
assert.False(t, removeMap.Remove("k"))
assert.Equal(t, NewMap(), removeMap)
}
func TestMapReadOnly(t *testing.T) {
state := internal.NewState()
state.MarkReadOnly()
m := newMap(&[]internal.KeyValue{
{Key: "k1", Value: internal.AnyValue{Value: &internal.AnyValue_StringValue{StringValue: "v1"}}},
}, state)
assert.Equal(t, 1, m.Len())
v, ok := m.Get("k1")
assert.True(t, ok)
assert.Equal(t, "v1", v.Str())
m.Range(func(k string, v Value) bool {
assert.Equal(t, "k1", k)
assert.Equal(t, "v1", v.Str())
return true
})
assert.Panics(t, func() { m.PutStr("k2", "v2") })
assert.Panics(t, func() { m.PutInt("k2", 123) })
assert.Panics(t, func() { m.PutDouble("k2", 1.23) })
assert.Panics(t, func() { m.PutBool("k2", true) })
assert.Panics(t, func() { m.PutEmpty("foo") })
assert.Panics(t, func() { m.GetOrPutEmpty("foo") })
assert.Panics(t, func() { m.PutEmptyBytes("k2") })
assert.Panics(t, func() { m.PutEmptyMap("k2") })
assert.Panics(t, func() { m.PutEmptySlice("k2") })
assert.Panics(t, func() { m.Remove("k1") })
assert.Panics(t, func() { m.RemoveIf(func(string, Value) bool { return true }) })
assert.Panics(t, func() { m.EnsureCapacity(2) })
m2 := NewMap()
m.CopyTo(m2)
assert.Equal(t, m2.AsRaw(), m.AsRaw())
assert.Panics(t, func() { NewMap().CopyTo(m) })
assert.Equal(t, map[string]any{"k1": "v1"}, m.AsRaw())
assert.Panics(t, func() { _ = m.FromRaw(map[string]any{"k1": "v1"}) })
}
func TestMapPutEmpty(t *testing.T) {
m := NewMap()
v := m.PutEmpty("k1")
assert.Equal(t, map[string]any{
"k1": nil,
}, m.AsRaw())
v.SetBool(true)
assert.Equal(t, map[string]any{
"k1": true,
}, m.AsRaw())
v = m.PutEmpty("k1")
v.SetInt(1)
v2, ok := m.Get("k1")
assert.True(t, ok)
assert.Equal(t, int64(1), v2.Int())
}
func TestMapGetOrPutEmpty(t *testing.T) {
m := NewMap()
v := m.PutEmpty("k1")
v.SetStr("test")
assert.Equal(t, map[string]any{
"k1": "test",
}, m.AsRaw())
v, found := m.GetOrPutEmpty("k1")
assert.True(t, found)
require.Equal(t, ValueTypeStr, v.Type())
assert.Equal(t, "test", v.Str())
v, found = m.GetOrPutEmpty("k2")
assert.False(t, found)
require.Equal(t, ValueTypeEmpty, v.Type())
}
func TestMapPutEmptyMap(t *testing.T) {
m := NewMap()
childMap := m.PutEmptyMap("k1")
assert.Equal(t, map[string]any{
"k1": map[string]any{},
}, m.AsRaw())
childMap.PutEmptySlice("k2").AppendEmpty().SetStr("val")
assert.Equal(t, map[string]any{
"k1": map[string]any{
"k2": []any{"val"},
},
}, m.AsRaw())
childMap.PutEmptyMap("k2").PutInt("k3", 1)
assert.Equal(t, map[string]any{
"k1": map[string]any{
"k2": map[string]any{"k3": int64(1)},
},
}, m.AsRaw())
}
func TestMapPutEmptySlice(t *testing.T) {
m := NewMap()
childSlice := m.PutEmptySlice("k")
assert.Equal(t, map[string]any{
"k": []any{},
}, m.AsRaw())
childSlice.AppendEmpty().SetDouble(1.1)
assert.Equal(t, map[string]any{
"k": []any{1.1},
}, m.AsRaw())
m.PutEmptySlice("k")
assert.Equal(t, map[string]any{
"k": []any{},
}, m.AsRaw())
childSliceVal, ok := m.Get("k")
assert.True(t, ok)
childSliceVal.Slice().AppendEmpty().SetEmptySlice().AppendEmpty().SetStr("val")
assert.Equal(t, map[string]any{
"k": []any{[]any{"val"}},
}, m.AsRaw())
}
func TestMapPutEmptyBytes(t *testing.T) {
m := NewMap()
b := m.PutEmptyBytes("k")
bv, ok := m.Get("k")
assert.True(t, ok)
assert.Equal(t, []byte(nil), bv.Bytes().AsRaw())
b.FromRaw([]byte{1, 2, 3})
bv, ok = m.Get("k")
assert.True(t, ok)
assert.Equal(t, []byte{1, 2, 3}, bv.Bytes().AsRaw())
m.PutEmptyBytes("k")
bv, ok = m.Get("k")
assert.True(t, ok)
assert.Equal(t, []byte(nil), bv.Bytes().AsRaw())
bv.Bytes().FromRaw([]byte{3, 2, 1})
bv, ok = m.Get("k")
assert.True(t, ok)
assert.Equal(t, []byte{3, 2, 1}, bv.Bytes().AsRaw())
}
func TestMapWithEmpty(t *testing.T) {
origWithNil := []internal.KeyValue{
{},
{
Key: "test_key",
Value: internal.AnyValue{Value: &internal.AnyValue_StringValue{StringValue: "test_value"}},
},
{
Key: "test_key2",
Value: internal.AnyValue{Value: nil},
},
}
sm := newMap(&origWithNil, internal.NewState())
val, exist := sm.Get("test_key")
assert.True(t, exist)
assert.Equal(t, ValueTypeStr, val.Type())
assert.Equal(t, "test_value", val.Str())
val, exist = sm.Get("test_key2")
assert.True(t, exist)
assert.Equal(t, ValueTypeEmpty, val.Type())
assert.Empty(t, val.Str())
sm.PutStr("other_key_string", "other_value")
val, exist = sm.Get("other_key_string")
assert.True(t, exist)
assert.Equal(t, ValueTypeStr, val.Type())
assert.Equal(t, "other_value", val.Str())
sm.PutInt("other_key_int", 123)
val, exist = sm.Get("other_key_int")
assert.True(t, exist)
assert.Equal(t, ValueTypeInt, val.Type())
assert.EqualValues(t, 123, val.Int())
sm.PutDouble("other_key_double", 1.23)
val, exist = sm.Get("other_key_double")
assert.True(t, exist)
assert.Equal(t, ValueTypeDouble, val.Type())
assert.InDelta(t, 1.23, val.Double(), 0.01)
sm.PutBool("other_key_bool", true)
val, exist = sm.Get("other_key_bool")
assert.True(t, exist)
assert.Equal(t, ValueTypeBool, val.Type())
assert.True(t, val.Bool())
sm.PutEmptyBytes("other_key_bytes").FromRaw([]byte{7, 8, 9})
val, exist = sm.Get("other_key_bytes")
assert.True(t, exist)
assert.Equal(t, ValueTypeBytes, val.Type())
assert.Equal(t, []byte{7, 8, 9}, val.Bytes().AsRaw())
sm.PutStr("another_key_string", "another_value")
val, exist = sm.Get("another_key_string")
assert.True(t, exist)
assert.Equal(t, ValueTypeStr, val.Type())
assert.Equal(t, "another_value", val.Str())
sm.PutInt("another_key_int", 456)
val, exist = sm.Get("another_key_int")
assert.True(t, exist)
assert.Equal(t, ValueTypeInt, val.Type())
assert.EqualValues(t, 456, val.Int())
sm.PutDouble("another_key_double", 4.56)
val, exist = sm.Get("another_key_double")
assert.True(t, exist)
assert.Equal(t, ValueTypeDouble, val.Type())
assert.InDelta(t, 4.56, val.Double(), 0.01)
sm.PutBool("another_key_bool", false)
val, exist = sm.Get("another_key_bool")
assert.True(t, exist)
assert.Equal(t, ValueTypeBool, val.Type())
assert.False(t, val.Bool())
sm.PutEmptyBytes("another_key_bytes").FromRaw([]byte{1})
val, exist = sm.Get("another_key_bytes")
assert.True(t, exist)
assert.Equal(t, ValueTypeBytes, val.Type())
assert.Equal(t, []byte{1}, val.Bytes().AsRaw())
assert.True(t, sm.Remove("other_key_string"))
assert.True(t, sm.Remove("other_key_int"))
assert.True(t, sm.Remove("other_key_double"))
assert.True(t, sm.Remove("other_key_bool"))
assert.True(t, sm.Remove("other_key_bytes"))
assert.True(t, sm.Remove("another_key_string"))
assert.True(t, sm.Remove("another_key_int"))
assert.True(t, sm.Remove("another_key_double"))
assert.True(t, sm.Remove("another_key_bool"))
assert.True(t, sm.Remove("another_key_bytes"))
assert.False(t, sm.Remove("other_key_string"))
assert.False(t, sm.Remove("another_key_string"))
// Test that the initial key is still there.
val, exist = sm.Get("test_key")
assert.True(t, exist)
assert.Equal(t, ValueTypeStr, val.Type())
assert.Equal(t, "test_value", val.Str())
val, exist = sm.Get("test_key2")
assert.True(t, exist)
assert.Equal(t, ValueTypeEmpty, val.Type())
assert.Empty(t, val.Str())
_, exist = sm.Get("test_key3")
assert.False(t, exist)
}
func TestMapIterationNil(t *testing.T) {
NewMap().Range(func(string, Value) bool {
// Fail if any element is returned
t.Fail()
return true
})
}
func TestMap_Range(t *testing.T) {
rawMap := map[string]any{
"k_string": "123",
"k_int": int64(123),
"k_double": float64(1.23),
"k_bool": true,
"k_empty": nil,
}
am := NewMap()
require.NoError(t, am.FromRaw(rawMap))
assert.Equal(t, 5, am.Len())
calls := 0
am.Range(func(string, Value) bool {
calls++
return false
})
assert.Equal(t, 1, calls)
am.Range(func(k string, v Value) bool {
assert.Equal(t, rawMap[k], v.AsRaw())
delete(rawMap, k)
return true
})
assert.Empty(t, rawMap)
}
func TestMap_All(t *testing.T) {
rawMap := map[string]any{
"k_string": "123",
"k_int": int64(123),
"k_double": float64(1.23),
"k_bool": true,
"k_empty": nil,
}
am := NewMap()
require.NoError(t, am.FromRaw(rawMap))
assert.Equal(t, 5, am.Len())
calls := 0
for range am.All() {
calls++
}
assert.Equal(t, am.Len(), calls)
for k, v := range am.All() {
assert.Equal(t, rawMap[k], v.AsRaw())
delete(rawMap, k)
}
assert.Empty(t, rawMap)
}
func TestMap_FromRaw(t *testing.T) {
am := NewMap()
require.NoError(t, am.FromRaw(map[string]any{}))
assert.Equal(t, 0, am.Len())
am.PutEmpty("k")
assert.Equal(t, 1, am.Len())
require.NoError(t, am.FromRaw(nil))
assert.Equal(t, 0, am.Len())
am.PutEmpty("k")
assert.Equal(t, 1, am.Len())
require.NoError(t, am.FromRaw(map[string]any{
"k_string": "123",
"k_int": 123,
"k_double": 1.23,
"k_bool": true,
"k_null": nil,
"k_bytes": []byte{1, 2, 3},
"k_slice": []any{1, 2.1, "val"},
"k_map": map[string]any{
"k_int": 1,
"k_string": "val",
},
}))
assert.Equal(t, 8, am.Len())
v, ok := am.Get("k_string")
assert.True(t, ok)
assert.Equal(t, "123", v.Str())
v, ok = am.Get("k_int")
assert.True(t, ok)
assert.Equal(t, int64(123), v.Int())
v, ok = am.Get("k_double")
assert.True(t, ok)
assert.InDelta(t, 1.23, v.Double(), 0.01)
v, ok = am.Get("k_null")
assert.True(t, ok)
assert.Equal(t, ValueTypeEmpty, v.Type())
v, ok = am.Get("k_bytes")
assert.True(t, ok)
assert.Equal(t, []byte{1, 2, 3}, v.Bytes().AsRaw())
v, ok = am.Get("k_slice")
assert.True(t, ok)
assert.Equal(t, []any{int64(1), 2.1, "val"}, v.Slice().AsRaw())
v, ok = am.Get("k_map")
assert.True(t, ok)
assert.Equal(t, map[string]any{
"k_int": int64(1),
"k_string": "val",
}, v.Map().AsRaw())
}
func TestMap_MoveTo(t *testing.T) {
dest := NewMap()
// Test MoveTo to empty
NewMap().MoveTo(dest)
assert.Equal(t, 0, dest.Len())
// Test MoveTo larger slice
src := Map(internal.GenTestMapWrapper())
src.MoveTo(dest)
assert.Equal(t, Map(internal.GenTestMapWrapper()), dest)
assert.Equal(t, 0, src.Len())
// Test MoveTo from empty to non-empty
NewMap().MoveTo(dest)
assert.Equal(t, 0, dest.Len())
dest.PutStr("k", "v")
dest.MoveTo(dest)
assert.Equal(t, 1, dest.Len())
assert.Equal(t, map[string]any{"k": "v"}, dest.AsRaw())
}
func TestMap_CopyTo(t *testing.T) {
dest := NewMap()
// Test CopyTo to empty
NewMap().CopyTo(dest)
assert.Equal(t, 0, dest.Len())
// Test CopyTo larger slice
Map(internal.GenTestMapWrapper()).CopyTo(dest)
assert.Equal(t, Map(internal.GenTestMapWrapper()), dest)
// Test CopyTo same size slice
Map(internal.GenTestMapWrapper()).CopyTo(dest)
assert.Equal(t, Map(internal.GenTestMapWrapper()), dest)
// Test CopyTo with an empty Value in the destination
(*dest.getOrig())[0].Value = internal.AnyValue{}
Map(internal.GenTestMapWrapper()).CopyTo(dest)
assert.Equal(t, Map(internal.GenTestMapWrapper()), dest)
// Test CopyTo same size slice
dest.CopyTo(dest)
assert.Equal(t, Map(internal.GenTestMapWrapper()), dest)
}
func TestMap_CopyToAndEnsureCapacity(t *testing.T) {
dest := NewMap()
src := Map(internal.GenTestMapWrapper())
dest.EnsureCapacity(src.Len())
src.CopyTo(dest)
assert.Equal(t, Map(internal.GenTestMapWrapper()), dest)
}
func TestMap_EnsureCapacity_Zero(t *testing.T) {
am := NewMap()
am.EnsureCapacity(0)
assert.Equal(t, 0, am.Len())
assert.Equal(t, 0, cap(*am.getOrig()))
}
func TestMap_EnsureCapacity(t *testing.T) {
am := NewMap()
am.EnsureCapacity(5)
assert.Equal(t, 0, am.Len())
assert.Equal(t, 5, cap(*am.getOrig()))
am.EnsureCapacity(3)
assert.Equal(t, 0, am.Len())
assert.Equal(t, 5, cap(*am.getOrig()))
am.EnsureCapacity(8)
assert.Equal(t, 0, am.Len())
assert.Equal(t, 8, cap(*am.getOrig()))
}
func TestMap_EnsureCapacity_Existing(t *testing.T) {
am := NewMap()
am.PutStr("foo", "bar")
assert.Equal(t, 1, am.Len())
// Add more capacity.
am.EnsureCapacity(5)
// Ensure previously existing element is still there.
assert.Equal(t, 1, am.Len())
v, ok := am.Get("foo")
assert.Equal(t, "bar", v.Str())
assert.True(t, ok)
assert.Equal(t, 5, cap(*am.getOrig()))
// Add one more element.
am.PutStr("abc", "xyz")
// Verify that both elements are there.
assert.Equal(t, 2, am.Len())
v, ok = am.Get("foo")
assert.Equal(t, "bar", v.Str())
assert.True(t, ok)
v, ok = am.Get("abc")
assert.Equal(t, "xyz", v.Str())
assert.True(t, ok)
}
func TestMap_Clear(t *testing.T) {
am := NewMap()
assert.Nil(t, *am.getOrig())
am.Clear()
assert.Nil(t, *am.getOrig())
am.EnsureCapacity(5)
assert.NotNil(t, *am.getOrig())
am.Clear()
assert.Nil(t, *am.getOrig())
}
func TestMap_RemoveIf(t *testing.T) {
am := NewMap()
am.PutStr("k_string", "123")
am.PutInt("k_int", int64(123))
am.PutDouble("k_double", float64(1.23))
am.PutBool("k_bool", true)
am.PutEmpty("k_empty")
assert.Equal(t, 5, am.Len())
am.RemoveIf(func(key string, val Value) bool {
return key == "k_int" || val.Type() == ValueTypeBool
})
assert.Equal(t, 3, am.Len())
_, exists := am.Get("k_string")
assert.True(t, exists)
_, exists = am.Get("k_int")
assert.False(t, exists)
_, exists = am.Get("k_double")
assert.True(t, exists)
_, exists = am.Get("k_bool")
assert.False(t, exists)
_, exists = am.Get("k_empty")
assert.True(t, exists)
}
func TestMap_RemoveIfAll(t *testing.T) {
am := Map(internal.GenTestMapWrapper())
assert.Equal(t, 5, am.Len())
am.RemoveIf(func(string, Value) bool {
return true
})
assert.Equal(t, 0, am.Len())
}
func generateTestEmptyMap(t *testing.T) Map {
m := NewMap()
assert.NoError(t, m.FromRaw(map[string]any{"k": map[string]any(nil)}))
return m
}
func generateTestEmptySlice(t *testing.T) Map {
m := NewMap()
assert.NoError(t, m.FromRaw(map[string]any{"k": []any(nil)}))
return m
}
func generateTestStringMap(t *testing.T) Map {
m := NewMap()
assert.NoError(t, m.FromRaw(map[string]any{"k": "v"}))
return m
}
func generateTestIntMap(t *testing.T) Map {
m := NewMap()
assert.NoError(t, m.FromRaw(map[string]any{"k": 123}))
return m
}
func generateTestDoubleMap(t *testing.T) Map {
m := NewMap()
assert.NoError(t, m.FromRaw(map[string]any{"k": 12.3}))
return m
}
func generateTestBoolMap(t *testing.T) Map {
m := NewMap()
assert.NoError(t, m.FromRaw(map[string]any{"k": true}))
return m
}
func generateTestBytesMap(t *testing.T) Map {
m := NewMap()
assert.NoError(t, m.FromRaw(map[string]any{"k": []byte{1, 2, 3, 4, 5}}))
return m
}
func TestInvalidMap(t *testing.T) {
v := Map{}
testFunc := func(string, Value) bool {
return true
}
assert.Panics(t, func() { v.Clear() })
assert.Panics(t, func() { v.EnsureCapacity(1) })
assert.Panics(t, func() { v.Get("foo") })
assert.Panics(t, func() { v.Remove("foo") })
assert.Panics(t, func() { v.RemoveIf(testFunc) })
assert.Panics(t, func() { v.PutEmpty("foo") })
assert.Panics(t, func() { v.GetOrPutEmpty("foo") })
assert.Panics(t, func() { v.PutStr("foo", "bar") })
assert.Panics(t, func() { v.PutInt("foo", 1) })
assert.Panics(t, func() { v.PutDouble("foo", 1.1) })
assert.Panics(t, func() { v.PutBool("foo", true) })
assert.Panics(t, func() { v.PutEmptyBytes("foo") })
assert.Panics(t, func() { v.PutEmptyMap("foo") })
assert.Panics(t, func() { v.PutEmptySlice("foo") })
assert.Panics(t, func() { v.Len() })
assert.Panics(t, func() { v.Range(testFunc) })
assert.Panics(t, func() { v.CopyTo(NewMap()) })
assert.Panics(t, func() { v.AsRaw() })
assert.Panics(t, func() { _ = v.FromRaw(map[string]any{"foo": "bar"}) })
}
func TestMapEqual(t *testing.T) {
for _, tt := range []struct {
name string
val Map
comparison Map
expected bool
}{
{
name: "with two empty maps",
val: NewMap(),
comparison: NewMap(),
expected: true,
},
{
name: "with two equal values",
val: func() Map {
m := NewMap()
m.PutStr("hello", "world")
return m
}(),
comparison: func() Map {
m := NewMap()
m.PutStr("hello", "world")
return m
}(),
expected: true,
},
{
name: "with multiple equal values",
val: func() Map {
m := NewMap()
m.PutStr("hello", "world")
m.PutStr("bonjour", "monde")
return m
}(),
comparison: func() Map {
m := NewMap()
m.PutStr("hello", "world")
m.PutStr("bonjour", "monde")
return m
}(),
expected: true,
},
{
name: "with two different values",
val: func() Map {
m := NewMap()
m.PutStr("hello", "world")
return m
}(),
comparison: func() Map {
m := NewMap()
m.PutStr("bonjour", "monde")
return m
}(),
expected: false,
},
{
name: "with the same key and different values",
val: func() Map {
m := NewMap()
m.PutStr("hello", "world")
return m
}(),
comparison: func() Map {
m := NewMap()
m.PutStr("hello", "monde")
return m
}(),
expected: false,
},
{
name: "with multiple different values",
val: func() Map {
m := NewMap()
m.PutStr("hello", "world")
m.PutStr("bonjour", "monde")
return m
}(),
comparison: func() Map {
m := NewMap()
m.PutStr("question", "unknown")
m.PutStr("answer", "42")
return m
}(),
expected: false,
},
} {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, tt.val.Equal(tt.comparison))
})
}
}
func BenchmarkMapEqual(b *testing.B) {
testutil.SkipMemoryBench(b)
m := NewMap()
m.PutStr("hello", "world")
cmp := NewMap()
cmp.PutStr("hello", "world")
b.ReportAllocs()
for b.Loop() {
_ = m.Equal(cmp)
}
}
================================================
FILE: pdata/pcommon/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pcommon
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: pdata/pcommon/slice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pcommon // import "go.opentelemetry.io/collector/pdata/pcommon"
import (
"go.uber.org/multierr"
"go.opentelemetry.io/collector/pdata/internal"
)
// AsRaw return []any copy of the Slice.
func (es Slice) AsRaw() []any {
rawSlice := make([]any, 0, es.Len())
for i := 0; i < es.Len(); i++ {
rawSlice = append(rawSlice, es.At(i).AsRaw())
}
return rawSlice
}
// FromRaw copies []any into the Slice.
func (es Slice) FromRaw(rawSlice []any) error {
es.getState().AssertMutable()
if len(rawSlice) == 0 {
*es.getOrig() = nil
return nil
}
var errs error
origs := make([]internal.AnyValue, len(rawSlice))
for ix, iv := range rawSlice {
errs = multierr.Append(errs, newValue(&origs[ix], es.getState()).FromRaw(iv))
}
*es.getOrig() = origs
return errs
}
// Equal checks equality with another Slice
func (es Slice) Equal(val Slice) bool {
if es.Len() != val.Len() {
return false
}
for i := 0; i < es.Len(); i++ {
if !es.At(i).Equal(val.At(i)) {
return false
}
}
return true
}
================================================
FILE: pdata/pcommon/slice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pcommon
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/internal/testutil"
)
func TestSlice_AsFromRaw(t *testing.T) {
es := NewSlice()
assert.Equal(t, 0, es.Len())
raw := []any{int64(1), float64(2.3), true, "test", []any{"other"}, map[string]any{"key": "value", "int": int64(2)}}
require.NoError(t, es.FromRaw(raw))
assert.Equal(t, 6, es.Len())
assert.Equal(t, raw, es.AsRaw())
}
func TestInvalidSlice(t *testing.T) {
es := Slice{}
assert.Panics(t, func() { es.Len() })
assert.Panics(t, func() { es.At(0) })
assert.Panics(t, func() { es.CopyTo(Slice{}) })
assert.Panics(t, func() { es.EnsureCapacity(1) })
assert.Panics(t, func() { es.AppendEmpty() })
assert.Panics(t, func() { es.MoveAndAppendTo(Slice{}) })
assert.Panics(t, func() { es.RemoveIf(func(Value) bool { return false }) })
assert.Panics(t, func() { es.AsRaw() })
assert.Panics(t, func() { _ = es.FromRaw([]any{3}) })
}
func TestSliceEqual(t *testing.T) {
es := NewSlice()
es2 := NewSlice()
assert.True(t, es.Equal(es2))
v := es.AppendEmpty()
v.SetStr("test")
assert.False(t, es.Equal(es2))
v = es2.AppendEmpty()
v.SetStr("test")
assert.True(t, es.Equal(es2))
}
func BenchmarkSliceEqual(b *testing.B) {
testutil.SkipMemoryBench(b)
es := NewSlice()
v := es.AppendEmpty()
v.SetStr("test")
cmp := NewSlice()
v = cmp.AppendEmpty()
v.SetStr("test")
b.ReportAllocs()
for b.Loop() {
_ = es.Equal(cmp)
}
}
================================================
FILE: pdata/pcommon/spanid.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pcommon // import "go.opentelemetry.io/collector/pdata/pcommon"
import (
"encoding/hex"
"go.opentelemetry.io/collector/pdata/internal"
)
var emptySpanID = SpanID([8]byte{})
// SpanID is span identifier.
type SpanID [8]byte
// NewSpanIDEmpty returns a new empty (all zero bytes) SpanID.
func NewSpanIDEmpty() SpanID {
return emptySpanID
}
// String returns string representation of the SpanID.
//
// Important: Don't rely on this method to get a string identifier of SpanID,
// Use hex.EncodeToString explicitly instead.
// This method meant to implement Stringer interface for display purposes only.
func (ms SpanID) String() string {
if ms.IsEmpty() {
return ""
}
return hex.EncodeToString(ms[:])
}
// IsEmpty returns true if id doesn't contain at least one non-zero byte.
func (ms SpanID) IsEmpty() bool {
return internal.SpanID(ms).IsEmpty()
}
================================================
FILE: pdata/pcommon/spanid_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pcommon
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSpanID(t *testing.T) {
sid := SpanID([8]byte{1, 2, 3, 4, 4, 3, 2, 1})
assert.Equal(t, [8]byte{1, 2, 3, 4, 4, 3, 2, 1}, [8]byte(sid))
assert.False(t, sid.IsEmpty())
}
func TestNewSpanIDEmpty(t *testing.T) {
sid := NewSpanIDEmpty()
assert.Equal(t, [8]byte{}, [8]byte(sid))
assert.True(t, sid.IsEmpty())
}
func TestSpanIDString(t *testing.T) {
sid := SpanID([8]byte{})
assert.Empty(t, sid.String())
sid = SpanID([8]byte{0x12, 0x23, 0xAD, 0x12, 0x23, 0xAD, 0x12, 0x23})
assert.Equal(t, "1223ad1223ad1223", sid.String())
}
func TestSpanIDImmutable(t *testing.T) {
initialBytes := [8]byte{0x12, 0x23, 0xAD, 0x12, 0x23, 0xAD, 0x12, 0x23}
sid := SpanID(initialBytes)
assert.Equal(t, SpanID(initialBytes), sid)
// Get the bytes and try to mutate.
sid[4] = 0x89
// Does not change the already created SpanID.
assert.NotEqual(t, SpanID(initialBytes), sid)
}
================================================
FILE: pdata/pcommon/timestamp.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pcommon // import "go.opentelemetry.io/collector/pdata/pcommon"
import (
"time"
)
// Timestamp is a time specified as UNIX Epoch time in nanoseconds since
// 1970-01-01 00:00:00 +0000 UTC.
type Timestamp uint64
// NewTimestampFromTime constructs a new Timestamp from the provided time.Time.
func NewTimestampFromTime(t time.Time) Timestamp {
return Timestamp(uint64(t.UnixNano()))
}
// AsTime converts this to a time.Time.
func (ts Timestamp) AsTime() time.Time {
return time.Unix(0, int64(ts)).UTC()
}
// String returns the string representation of this in UTC.
func (ts Timestamp) String() string {
return ts.AsTime().String()
}
================================================
FILE: pdata/pcommon/timestamp_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pcommon
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestUnixNanosConverters(t *testing.T) {
t1 := time.Date(2020, 3, 24, 1, 13, 23, 789, time.UTC)
tun := Timestamp(t1.UnixNano())
assert.EqualValues(t, uint64(1585012403000000789), tun)
assert.Equal(t, tun, NewTimestampFromTime(t1))
assert.Equal(t, t1, NewTimestampFromTime(t1).AsTime())
assert.Equal(t, "2020-03-24 01:13:23.000000789 +0000 UTC", t1.String())
}
func TestZeroTimestamp(t *testing.T) {
assert.Equal(t, time.Unix(0, 0).UTC(), Timestamp(0).AsTime())
assert.Zero(t, NewTimestampFromTime(time.Unix(0, 0).UTC()))
assert.Equal(t, "1970-01-01 00:00:00 +0000 UTC", Timestamp(0).String())
}
================================================
FILE: pdata/pcommon/trace_state.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pcommon // import "go.opentelemetry.io/collector/pdata/pcommon"
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// TraceState represents the trace state from the w3c-trace-context.
//
// Must use NewTraceState function to create new instances.
// Important: zero-initialized instance is not valid for use.
type TraceState internal.TraceStateWrapper
func NewTraceState() TraceState {
return TraceState(internal.NewTraceStateWrapper(new(string), internal.NewState()))
}
func (ms TraceState) getOrig() *string {
return internal.GetTraceStateOrig(internal.TraceStateWrapper(ms))
}
func (ms TraceState) getState() *internal.State {
return internal.GetTraceStateState(internal.TraceStateWrapper(ms))
}
// AsRaw returns the string representation of the tracestate in w3c-trace-context format: https://www.w3.org/TR/trace-context/#tracestate-header
func (ms TraceState) AsRaw() string {
return *ms.getOrig()
}
// FromRaw copies the string representation in w3c-trace-context format of the tracestate into this TraceState.
func (ms TraceState) FromRaw(v string) {
ms.getState().AssertMutable()
*ms.getOrig() = v
}
// MoveTo moves the TraceState instance overriding the destination
// and resetting the current instance to its zero value.
func (ms TraceState) MoveTo(dest TraceState) {
ms.getState().AssertMutable()
dest.getState().AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.getOrig() == dest.getOrig() {
return
}
*dest.getOrig() = *ms.getOrig()
*ms.getOrig() = ""
}
// CopyTo copies the TraceState instance overriding the destination.
func (ms TraceState) CopyTo(dest TraceState) {
dest.getState().AssertMutable()
*dest.getOrig() = *ms.getOrig()
}
================================================
FILE: pdata/pcommon/trace_state_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pcommon
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestTraceState_MoveTo(t *testing.T) {
ms := TraceState(internal.GenTestTraceStateWrapper())
dest := NewTraceState()
ms.MoveTo(dest)
assert.Equal(t, NewTraceState(), ms)
assert.Equal(t, TraceState(internal.GenTestTraceStateWrapper()), dest)
dest.MoveTo(dest)
assert.Equal(t, TraceState(internal.GenTestTraceStateWrapper()), dest)
}
func TestTraceState_CopyTo(t *testing.T) {
ms := NewTraceState()
orig := NewTraceState()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = TraceState(internal.GenTestTraceStateWrapper())
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
}
func TestTraceState_FromRaw_AsRaw(t *testing.T) {
ms := NewTraceState()
assert.Empty(t, ms.AsRaw())
ms.FromRaw("congo=t61rcWkgMzE")
assert.Equal(t, "congo=t61rcWkgMzE", ms.AsRaw())
}
func TestInvalidTraceState(t *testing.T) {
v := TraceState{}
assert.Panics(t, func() { v.AsRaw() })
assert.Panics(t, func() { v.FromRaw("") })
assert.Panics(t, func() { v.MoveTo(TraceState{}) })
assert.Panics(t, func() { v.CopyTo(TraceState{}) })
}
================================================
FILE: pdata/pcommon/traceid.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pcommon // import "go.opentelemetry.io/collector/pdata/pcommon"
import (
"encoding/hex"
"go.opentelemetry.io/collector/pdata/internal"
)
var emptyTraceID = TraceID([16]byte{})
// TraceID is a trace identifier.
type TraceID [16]byte
// NewTraceIDEmpty returns a new empty (all zero bytes) TraceID.
func NewTraceIDEmpty() TraceID {
return emptyTraceID
}
// String returns string representation of the TraceID.
//
// Important: Don't rely on this method to get a string identifier of TraceID.
// Use hex.EncodeToString explicitly instead.
// This method meant to implement Stringer interface for display purposes only.
func (ms TraceID) String() string {
if ms.IsEmpty() {
return ""
}
return hex.EncodeToString(ms[:])
}
// IsEmpty returns true if id doesn't contain at least one non-zero byte.
func (ms TraceID) IsEmpty() bool {
return internal.TraceID(ms).IsEmpty()
}
================================================
FILE: pdata/pcommon/traceid_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pcommon
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestTraceID(t *testing.T) {
tid := TraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1})
assert.Equal(t, [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}, [16]byte(tid))
assert.False(t, tid.IsEmpty())
}
func TestNewTraceIDEmpty(t *testing.T) {
tid := NewTraceIDEmpty()
assert.Equal(t, [16]byte{}, [16]byte(tid))
assert.True(t, tid.IsEmpty())
}
func TestTraceIDString(t *testing.T) {
tid := TraceID([16]byte{})
assert.Empty(t, tid.String())
tid = [16]byte{0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78}
assert.Equal(t, "12345678123456781234567812345678", tid.String())
}
func TestTraceIDImmutable(t *testing.T) {
initialBytes := [16]byte{0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78}
tid := TraceID(initialBytes)
assert.Equal(t, TraceID(initialBytes), tid)
// Get the bytes and try to mutate.
tid[4] = 0x23
// Does not change the already created TraceID.
assert.NotEqual(t, TraceID(initialBytes), tid)
}
================================================
FILE: pdata/pcommon/value.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pcommon // import "go.opentelemetry.io/collector/pdata/pcommon"
import (
"encoding/base64"
"encoding/json"
"fmt"
"math"
"strconv"
"go.opentelemetry.io/collector/pdata/internal"
)
// ValueType specifies the type of Value.
type ValueType int32
const (
ValueTypeEmpty ValueType = iota
ValueTypeStr
ValueTypeInt
ValueTypeDouble
ValueTypeBool
ValueTypeMap
ValueTypeSlice
ValueTypeBytes
)
// String returns the string representation of the ValueType.
func (avt ValueType) String() string {
switch avt {
case ValueTypeEmpty:
return "Empty"
case ValueTypeStr:
return "Str"
case ValueTypeBool:
return "Bool"
case ValueTypeInt:
return "Int"
case ValueTypeDouble:
return "Double"
case ValueTypeMap:
return "Map"
case ValueTypeSlice:
return "Slice"
case ValueTypeBytes:
return "Bytes"
}
return ""
}
// Value is a mutable cell containing any value. Typically used as an element of Map or Slice.
// Must use one of NewValue+ 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.:
//
// func f1(val Value) { val.SetInt(234) }
// func f2() {
// v := NewValueStr("a string")
// f1(v)
// _ := v.Type() // this will return ValueTypeInt
// }
//
// Important: zero-initialized instance is not valid for use. All Value functions below must
// be called only on instances that are created via NewValue+ functions.
type Value internal.ValueWrapper
// NewValueEmpty creates a new Value with an empty value.
func NewValueEmpty() Value {
return newValue(&internal.AnyValue{}, internal.NewState())
}
// NewValueStr creates a new Value with the given string value.
func NewValueStr(v string) Value {
ov := internal.NewAnyValueStringValue()
ov.StringValue = v
orig := internal.NewAnyValue()
orig.Value = ov
return newValue(orig, internal.NewState())
}
// NewValueInt creates a new Value with the given int64 value.
func NewValueInt(v int64) Value {
ov := internal.NewAnyValueIntValue()
ov.IntValue = v
orig := internal.NewAnyValue()
orig.Value = ov
return newValue(orig, internal.NewState())
}
// NewValueDouble creates a new Value with the given float64 value.
func NewValueDouble(v float64) Value {
ov := internal.NewAnyValueDoubleValue()
ov.DoubleValue = v
orig := internal.NewAnyValue()
orig.Value = ov
return newValue(orig, internal.NewState())
}
// NewValueBool creates a new Value with the given bool value.
func NewValueBool(v bool) Value {
ov := internal.NewAnyValueBoolValue()
ov.BoolValue = v
orig := internal.NewAnyValue()
orig.Value = ov
return newValue(orig, internal.NewState())
}
// NewValueMap creates a new Value of map type.
func NewValueMap() Value {
ov := internal.NewAnyValueKvlistValue()
ov.KvlistValue = internal.NewKeyValueList()
orig := internal.NewAnyValue()
orig.Value = ov
return newValue(orig, internal.NewState())
}
// NewValueSlice creates a new Value of array type.
func NewValueSlice() Value {
ov := internal.NewAnyValueArrayValue()
ov.ArrayValue = internal.NewArrayValue()
orig := internal.NewAnyValue()
orig.Value = ov
return newValue(orig, internal.NewState())
}
// NewValueBytes creates a new empty Value of byte type.
func NewValueBytes() Value {
ov := internal.NewAnyValueBytesValue()
orig := internal.NewAnyValue()
orig.Value = ov
return newValue(orig, internal.NewState())
}
func newValue(orig *internal.AnyValue, state *internal.State) Value {
return Value(internal.NewValueWrapper(orig, state))
}
func (v Value) getOrig() *internal.AnyValue {
return internal.GetValueOrig(internal.ValueWrapper(v))
}
func (v Value) getState() *internal.State {
return internal.GetValueState(internal.ValueWrapper(v))
}
// FromRaw sets the value from the given raw value.
// Calling this function on zero-initialized Value will cause a panic.
func (v Value) FromRaw(iv any) error {
switch tv := iv.(type) {
case nil:
v.getOrig().Value = nil
case string:
v.SetStr(tv)
case int:
v.SetInt(int64(tv))
case int8:
v.SetInt(int64(tv))
case int16:
v.SetInt(int64(tv))
case int32:
v.SetInt(int64(tv))
case int64:
v.SetInt(tv)
case uint:
v.SetInt(int64(tv))
case uint8:
v.SetInt(int64(tv))
case uint16:
v.SetInt(int64(tv))
case uint32:
v.SetInt(int64(tv))
case uint64:
v.SetInt(int64(tv))
case float32:
v.SetDouble(float64(tv))
case float64:
v.SetDouble(tv)
case bool:
v.SetBool(tv)
case []byte:
v.SetEmptyBytes().FromRaw(tv)
case map[string]any:
return v.SetEmptyMap().FromRaw(tv)
case []any:
return v.SetEmptySlice().FromRaw(tv)
default:
return fmt.Errorf("", tv)
}
return nil
}
// Type returns the type of the value for this Value.
// Calling this function on zero-initialized Value will cause a panic.
func (v Value) Type() ValueType {
switch v.getOrig().Value.(type) {
case *internal.AnyValue_StringValue:
return ValueTypeStr
case *internal.AnyValue_BoolValue:
return ValueTypeBool
case *internal.AnyValue_IntValue:
return ValueTypeInt
case *internal.AnyValue_DoubleValue:
return ValueTypeDouble
case *internal.AnyValue_KvlistValue:
return ValueTypeMap
case *internal.AnyValue_ArrayValue:
return ValueTypeSlice
case *internal.AnyValue_BytesValue:
return ValueTypeBytes
}
return ValueTypeEmpty
}
// Str returns the string value associated with this Value.
// The shorter name is used instead of String to avoid implementing fmt.Stringer interface.
// If the Type() is not ValueTypeStr then returns empty string.
func (v Value) Str() string {
return v.getOrig().GetStringValue()
}
// Int returns the int64 value associated with this Value.
// If the Type() is not ValueTypeInt then returns int64(0).
func (v Value) Int() int64 {
return v.getOrig().GetIntValue()
}
// Double returns the float64 value associated with this Value.
// If the Type() is not ValueTypeDouble then returns float64(0).
func (v Value) Double() float64 {
return v.getOrig().GetDoubleValue()
}
// Bool returns the bool value associated with this Value.
// If the Type() is not ValueTypeBool then returns false.
func (v Value) Bool() bool {
return v.getOrig().GetBoolValue()
}
// Map returns the map value associated with this Value.
// If the function is called on zero-initialized Value or if the Type() is not ValueTypeMap
// then it returns an invalid map. Note that using such map can cause panic.
func (v Value) Map() Map {
kvlist := v.getOrig().GetKvlistValue()
if kvlist == nil {
return Map{}
}
return newMap(&kvlist.Values, internal.GetValueState(internal.ValueWrapper(v)))
}
// Slice returns the slice value associated with this Value.
// If the function is called on zero-initialized Value or if the Type() is not ValueTypeSlice
// then returns an invalid slice. Note that using such slice can cause panic.
func (v Value) Slice() Slice {
arr := v.getOrig().GetArrayValue()
if arr == nil {
return Slice{}
}
return newSlice(&arr.Values, internal.GetValueState(internal.ValueWrapper(v)))
}
// Bytes returns the ByteSlice value associated with this Value.
// If the function is called on zero-initialized Value or if the Type() is not ValueTypeBytes
// then returns an invalid ByteSlice object. Note that using such slice can cause panic.
func (v Value) Bytes() ByteSlice {
bv, ok := v.getOrig().GetValue().(*internal.AnyValue_BytesValue)
if !ok {
return ByteSlice{}
}
return ByteSlice(internal.NewByteSliceWrapper(&bv.BytesValue, internal.GetValueState(internal.ValueWrapper(v))))
}
// SetStr replaces the string value associated with this Value,
// it also changes the type to be ValueTypeStr.
// The shorter name is used instead of SetString to avoid implementing
// fmt.Stringer interface by the corresponding getter method.
// Calling this function on zero-initialized Value will cause a panic.
func (v Value) SetStr(sv string) {
v.getState().AssertMutable()
// Delete everything but the AnyValue object itself.
internal.DeleteAnyValue(v.getOrig(), false)
ov := internal.NewAnyValueStringValue()
ov.StringValue = sv
v.getOrig().Value = ov
}
// SetInt replaces the int64 value associated with this Value,
// it also changes the type to be ValueTypeInt.
// Calling this function on zero-initialized Value will cause a panic.
func (v Value) SetInt(iv int64) {
v.getState().AssertMutable()
// Delete everything but the AnyValue object itself.
internal.DeleteAnyValue(v.getOrig(), false)
ov := internal.NewAnyValueIntValue()
ov.IntValue = iv
v.getOrig().Value = ov
}
// SetDouble replaces the float64 value associated with this Value,
// it also changes the type to be ValueTypeDouble.
// Calling this function on zero-initialized Value will cause a panic.
func (v Value) SetDouble(dv float64) {
v.getState().AssertMutable()
// Delete everything but the AnyValue object itself.
internal.DeleteAnyValue(v.getOrig(), false)
ov := internal.NewAnyValueDoubleValue()
ov.DoubleValue = dv
v.getOrig().Value = ov
}
// SetBool replaces the bool value associated with this Value,
// it also changes the type to be ValueTypeBool.
// Calling this function on zero-initialized Value will cause a panic.
func (v Value) SetBool(bv bool) {
v.getState().AssertMutable()
// Delete everything but the AnyValue object itself.
internal.DeleteAnyValue(v.getOrig(), false)
ov := internal.NewAnyValueBoolValue()
ov.BoolValue = bv
v.getOrig().Value = ov
}
// SetEmptyBytes sets value to an empty byte slice and returns it.
// Calling this function on zero-initialized Value will cause a panic.
func (v Value) SetEmptyBytes() ByteSlice {
v.getState().AssertMutable()
// Delete everything but the AnyValue object itself.
internal.DeleteAnyValue(v.getOrig(), false)
bv := internal.NewAnyValueBytesValue()
v.getOrig().Value = bv
return ByteSlice(internal.NewByteSliceWrapper(&bv.BytesValue, v.getState()))
}
// SetEmptyMap sets value to an empty map and returns it.
// Calling this function on zero-initialized Value will cause a panic.
func (v Value) SetEmptyMap() Map {
v.getState().AssertMutable()
// Delete everything but the AnyValue object itself.
internal.DeleteAnyValue(v.getOrig(), false)
ov := internal.NewAnyValueKvlistValue()
ov.KvlistValue = internal.NewKeyValueList()
v.getOrig().Value = ov
return newMap(&ov.KvlistValue.Values, v.getState())
}
// SetEmptySlice sets value to an empty slice and returns it.
// Calling this function on zero-initialized Value will cause a panic.
func (v Value) SetEmptySlice() Slice {
v.getState().AssertMutable()
// Delete everything but the AnyValue object itself.
internal.DeleteAnyValue(v.getOrig(), false)
ov := internal.NewAnyValueArrayValue()
ov.ArrayValue = internal.NewArrayValue()
v.getOrig().Value = ov
return newSlice(&ov.ArrayValue.Values, v.getState())
}
// MoveTo moves the Value from current overriding the destination and
// resetting the current instance to empty value.
// Calling this function on zero-initialized Value will cause a panic.
func (v Value) MoveTo(dest Value) {
v.getState().AssertMutable()
dest.getState().AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if v.getOrig() == dest.getOrig() {
return
}
*dest.getOrig() = *v.getOrig()
v.getOrig().Value = nil
}
// CopyTo copies the Value instance overriding the destination.
// Calling this function on zero-initialized Value will cause a panic.
func (v Value) CopyTo(dest Value) {
dest.getState().AssertMutable()
internal.CopyAnyValue(dest.getOrig(), v.getOrig())
}
// AsString converts an OTLP Value object of any type to its equivalent string
// representation. This differs from Str which only returns a non-empty value
// if the ValueType is ValueTypeStr.
// Calling this function on zero-initialized Value will cause a panic.
func (v Value) AsString() string {
switch v.Type() {
case ValueTypeEmpty:
return ""
case ValueTypeStr:
return v.Str()
case ValueTypeBool:
return strconv.FormatBool(v.Bool())
case ValueTypeDouble:
return float64AsString(v.Double())
case ValueTypeInt:
return strconv.FormatInt(v.Int(), 10)
case ValueTypeMap:
jsonStr, _ := json.Marshal(v.Map().AsRaw())
return string(jsonStr)
case ValueTypeBytes:
return base64.StdEncoding.EncodeToString(*v.Bytes().getOrig())
case ValueTypeSlice:
jsonStr, _ := json.Marshal(v.Slice().AsRaw())
return string(jsonStr)
default:
return fmt.Sprintf("", v.Type())
}
}
// See https://cs.opensource.google/go/go/+/refs/tags/go1.17.7:src/encoding/json/encode.go;l=585.
// This allows us to avoid using reflection.
func float64AsString(f float64) string {
if math.IsInf(f, 0) || math.IsNaN(f) {
return "json: unsupported value: " + strconv.FormatFloat(f, 'g', -1, 64)
}
// Convert as if by ES6 number to string conversion.
// This matches most other JSON generators.
// See golang.org/issue/6384 and golang.org/issue/14135.
// Like fmt %g, but the exponent cutoffs are different
// and exponents themselves are not padded to two digits.
scratch := [64]byte{}
b := scratch[:0]
abs := math.Abs(f)
fmt := byte('f')
if abs != 0 && (abs < 1e-6 || abs >= 1e21) {
fmt = 'e'
}
b = strconv.AppendFloat(b, f, fmt, -1, 64)
if fmt == 'e' {
// clean up e-09 to e-9
n := len(b)
if n >= 4 && b[n-4] == 'e' && b[n-3] == '-' && b[n-2] == '0' {
b[n-2] = b[n-1]
b = b[:n-1]
}
}
return string(b)
}
func (v Value) AsRaw() any {
switch v.Type() {
case ValueTypeEmpty:
return nil
case ValueTypeStr:
return v.Str()
case ValueTypeBool:
return v.Bool()
case ValueTypeDouble:
return v.Double()
case ValueTypeInt:
return v.Int()
case ValueTypeBytes:
return v.Bytes().AsRaw()
case ValueTypeMap:
return v.Map().AsRaw()
case ValueTypeSlice:
return v.Slice().AsRaw()
}
return fmt.Sprintf("", v.Type())
}
func (v Value) Equal(c Value) bool {
if v.Type() != c.Type() {
return false
}
switch v.Type() {
case ValueTypeEmpty:
return true
case ValueTypeStr:
return v.Str() == c.Str()
case ValueTypeBool:
return v.Bool() == c.Bool()
case ValueTypeDouble:
return v.Double() == c.Double()
case ValueTypeInt:
return v.Int() == c.Int()
case ValueTypeBytes:
return v.Bytes().Equal(c.Bytes())
case ValueTypeMap:
return v.Map().Equal(c.Map())
case ValueTypeSlice:
return v.Slice().Equal(c.Slice())
}
return false
}
================================================
FILE: pdata/pcommon/value_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pcommon
import (
"encoding/base64"
"math"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/internal/testutil"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestValue(t *testing.T) {
v := NewValueStr("abc")
assert.Equal(t, ValueTypeStr, v.Type())
assert.Equal(t, "abc", v.Str())
v = NewValueInt(123)
assert.Equal(t, ValueTypeInt, v.Type())
assert.EqualValues(t, 123, v.Int())
v = NewValueDouble(3.4)
assert.Equal(t, ValueTypeDouble, v.Type())
assert.InDelta(t, 3.4, v.Double(), 0.01)
v = NewValueBool(true)
assert.Equal(t, ValueTypeBool, v.Type())
assert.True(t, v.Bool())
v = NewValueBytes()
assert.Equal(t, ValueTypeBytes, v.Type())
v = NewValueEmpty()
assert.Equal(t, ValueTypeEmpty, v.Type())
v = NewValueMap()
assert.Equal(t, ValueTypeMap, v.Type())
v = NewValueSlice()
assert.Equal(t, ValueTypeSlice, v.Type())
}
func TestValueReadOnly(t *testing.T) {
state := internal.NewState()
state.MarkReadOnly()
v := newValue(&internal.AnyValue{Value: &internal.AnyValue_StringValue{StringValue: "v"}}, state)
assert.Equal(t, ValueTypeStr, v.Type())
assert.Equal(t, "v", v.Str())
assert.EqualValues(t, 0, v.Int())
assert.InDelta(t, 0, v.Double(), 0.01)
assert.False(t, v.Bool())
assert.Equal(t, ByteSlice{}, v.Bytes())
assert.Equal(t, Map{}, v.Map())
assert.Equal(t, Slice{}, v.Slice())
assert.Equal(t, "v", v.AsString())
assert.Panics(t, func() { v.SetStr("abc") })
assert.Panics(t, func() { v.SetInt(123) })
assert.Panics(t, func() { v.SetDouble(3.4) })
assert.Panics(t, func() { v.SetBool(true) })
assert.Panics(t, func() { v.SetEmptyBytes() })
assert.Panics(t, func() { v.SetEmptyMap() })
assert.Panics(t, func() { v.SetEmptySlice() })
v2 := NewValueEmpty()
v.CopyTo(v2)
assert.Equal(t, v.AsRaw(), v2.AsRaw())
assert.Panics(t, func() { v2.CopyTo(v) })
}
func TestValueType(t *testing.T) {
assert.Equal(t, "Empty", ValueTypeEmpty.String())
assert.Equal(t, "Str", ValueTypeStr.String())
assert.Equal(t, "Bool", ValueTypeBool.String())
assert.Equal(t, "Int", ValueTypeInt.String())
assert.Equal(t, "Double", ValueTypeDouble.String())
assert.Equal(t, "Map", ValueTypeMap.String())
assert.Equal(t, "Slice", ValueTypeSlice.String())
assert.Equal(t, "Bytes", ValueTypeBytes.String())
assert.Empty(t, ValueType(100).String())
}
func TestValueMap(t *testing.T) {
m1 := NewValueMap()
assert.Equal(t, ValueTypeMap, m1.Type())
assert.Equal(t, NewMap(), m1.Map())
assert.Equal(t, 0, m1.Map().Len())
m1.Map().PutDouble("double_key", 123)
assert.Equal(t, 1, m1.Map().Len())
got, exists := m1.Map().Get("double_key")
assert.True(t, exists)
assert.Equal(t, NewValueDouble(123), got)
// Create a second map.
m2 := m1.Map().PutEmptyMap("child_map")
assert.Equal(t, 0, m2.Len())
// Modify the source map that was inserted.
m2.PutStr("key_in_child", "somestr")
assert.Equal(t, 1, m2.Len())
got, exists = m2.Get("key_in_child")
assert.True(t, exists)
assert.Equal(t, NewValueStr("somestr"), got)
// Insert the second map as a child. This should perform a deep copy.
assert.Equal(t, 2, m1.Map().Len())
got, exists = m1.Map().Get("double_key")
assert.True(t, exists)
assert.Equal(t, NewValueDouble(123), got)
got, exists = m1.Map().Get("child_map")
assert.True(t, exists)
assert.Equal(t, m2, got.Map())
// Modify the source map m2 that was inserted into m1.
m2.PutStr("key_in_child", "somestr2")
assert.Equal(t, 1, m2.Len())
got, exists = m2.Get("key_in_child")
assert.True(t, exists)
assert.Equal(t, NewValueStr("somestr2"), got)
// The child map inside m1 should be modified.
childMap, childMapExists := m1.Map().Get("child_map")
require.True(t, childMapExists)
got, exists = childMap.Map().Get("key_in_child")
require.True(t, exists)
assert.Equal(t, NewValueStr("somestr2"), got)
// Now modify the inserted map (not the source)
childMap.Map().PutStr("key_in_child", "somestr3")
assert.Equal(t, 1, childMap.Map().Len())
got, exists = childMap.Map().Get("key_in_child")
require.True(t, exists)
assert.Equal(t, NewValueStr("somestr3"), got)
// The source child map should be modified.
got, exists = m2.Get("key_in_child")
require.True(t, exists)
assert.Equal(t, NewValueStr("somestr3"), got)
removed := m1.Map().Remove("double_key")
assert.True(t, removed)
assert.Equal(t, 1, m1.Map().Len())
_, exists = m1.Map().Get("double_key")
assert.False(t, exists)
removed = m1.Map().Remove("child_map")
assert.True(t, removed)
assert.Equal(t, 0, m1.Map().Len())
_, exists = m1.Map().Get("child_map")
assert.False(t, exists)
// Test nil KvlistValue case for MapWrapper() func.
orig := &internal.AnyValue{Value: &internal.AnyValue_KvlistValue{KvlistValue: nil}}
m1 = newValue(orig, internal.NewState())
assert.Equal(t, Map{}, m1.Map())
}
func TestValueSlice(t *testing.T) {
a1 := NewValueSlice()
assert.Equal(t, ValueTypeSlice, a1.Type())
assert.Equal(t, NewSlice(), a1.Slice())
assert.Equal(t, 0, a1.Slice().Len())
a1.Slice().AppendEmpty().SetDouble(123)
assert.Equal(t, 1, a1.Slice().Len())
assert.Equal(t, NewValueDouble(123), a1.Slice().At(0))
// Create a second array.
a2 := NewValueSlice()
assert.Equal(t, 0, a2.Slice().Len())
a2.Slice().AppendEmpty().SetStr("somestr")
assert.Equal(t, 1, a2.Slice().Len())
assert.Equal(t, NewValueStr("somestr"), a2.Slice().At(0))
// Insert the second array as a child.
a2.CopyTo(a1.Slice().AppendEmpty())
assert.Equal(t, 2, a1.Slice().Len())
assert.Equal(t, NewValueDouble(123), a1.Slice().At(0))
assert.Equal(t, a2, a1.Slice().At(1))
// Check that the array was correctly inserted.
childArray := a1.Slice().At(1)
assert.Equal(t, ValueTypeSlice, childArray.Type())
assert.Equal(t, 1, childArray.Slice().Len())
v := childArray.Slice().At(0)
assert.Equal(t, ValueTypeStr, v.Type())
assert.Equal(t, "somestr", v.Str())
// Test nil values case for Slice() func.
a1 = newValue(&internal.AnyValue{Value: &internal.AnyValue_ArrayValue{ArrayValue: nil}}, internal.NewState())
assert.Equal(t, newSlice(nil, nil), a1.Slice())
}
func TestNilOrigSetValue(t *testing.T) {
av := NewValueEmpty()
av.SetStr("abc")
assert.Equal(t, "abc", av.Str())
av = NewValueEmpty()
av.SetInt(123)
assert.EqualValues(t, 123, av.Int())
av = NewValueEmpty()
av.SetBool(true)
assert.True(t, av.Bool())
av = NewValueEmpty()
av.SetDouble(1.23)
assert.InDelta(t, 1.23, av.Double(), 0.01)
av = NewValueEmpty()
av.SetEmptyBytes().FromRaw([]byte{1, 2, 3})
assert.Equal(t, []byte{1, 2, 3}, av.Bytes().AsRaw())
av = NewValueEmpty()
require.NoError(t, av.SetEmptyMap().FromRaw(map[string]any{"k": "v"}))
assert.Equal(t, map[string]any{"k": "v"}, av.Map().AsRaw())
av = NewValueEmpty()
require.NoError(t, av.SetEmptySlice().FromRaw([]any{int64(1), "val"}))
assert.Equal(t, []any{int64(1), "val"}, av.Slice().AsRaw())
}
func TestValue_MoveTo(t *testing.T) {
src := NewValueMap()
src.Map().PutStr("key", "value")
dest := NewValueEmpty()
assert.True(t, dest.Equal(NewValueEmpty()))
src.MoveTo(dest)
assert.True(t, src.Equal(NewValueEmpty()))
expected := NewValueMap()
expected.Map().PutStr("key", "value")
assert.True(t, dest.Equal(expected))
dest.MoveTo(dest)
assert.True(t, dest.Equal(expected))
}
func TestValue_CopyTo(t *testing.T) {
dest := NewValueEmpty()
orig := internal.GenTestAnyValue()
newValue(orig, internal.NewState()).CopyTo(dest)
assert.Equal(t, internal.GenTestAnyValue(), dest.getOrig())
}
func TestSliceWithNilValues(t *testing.T) {
origWithNil := []internal.AnyValue{
{},
{Value: &internal.AnyValue_StringValue{StringValue: "test_value"}},
}
sm := newSlice(&origWithNil, internal.NewState())
val := sm.At(0)
assert.Equal(t, ValueTypeEmpty, val.Type())
assert.Empty(t, val.Str())
val = sm.At(1)
assert.Equal(t, ValueTypeStr, val.Type())
assert.Equal(t, "test_value", val.Str())
sm.AppendEmpty().SetStr("other_value")
val = sm.At(2)
assert.Equal(t, ValueTypeStr, val.Type())
assert.Equal(t, "other_value", val.Str())
}
func TestValueAsString(t *testing.T) {
tests := []struct {
name string
input Value
expected string
}{
{
name: "string",
input: NewValueStr("string value"),
expected: "string value",
},
{
name: "int64",
input: NewValueInt(42),
expected: "42",
},
{
name: "float64",
input: NewValueDouble(1.61803399),
expected: "1.61803399",
},
{
name: "small float64",
input: NewValueDouble(.000000009),
expected: "9e-9",
},
{
name: "bad float64",
input: NewValueDouble(math.Inf(1)),
expected: "json: unsupported value: +Inf",
},
{
name: "boolean",
input: NewValueBool(true),
expected: "true",
},
{
name: "empty_map",
input: NewValueMap(),
expected: "{}",
},
{
name: "simple_map",
input: generateTestValueMap(),
expected: "{\"arrKey\":[\"strOne\",\"strTwo\"],\"boolKey\":false,\"floatKey\":18.6,\"intKey\":7,\"mapKey\":{\"keyOne\":\"valOne\",\"keyTwo\":\"valTwo\"},\"nullKey\":null,\"strKey\":\"strVal\"}",
},
{
name: "empty_array",
input: NewValueSlice(),
expected: "[]",
},
{
name: "simple_array",
input: generateTestValueSlice(),
expected: "[\"strVal\",7,18.6,false,null]",
},
{
name: "empty",
input: NewValueEmpty(),
expected: "",
},
{
name: "bytes",
input: generateTestValueBytes(),
expected: base64.StdEncoding.EncodeToString([]byte("String bytes")),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actual := tt.input.AsString()
assert.Equal(t, tt.expected, actual)
})
}
}
func TestValueAsRaw(t *testing.T) {
tests := []struct {
name string
input Value
expected any
}{
{
name: "string",
input: NewValueStr("value"),
expected: "value",
},
{
name: "int",
input: NewValueInt(11),
expected: int64(11),
},
{
name: "double",
input: NewValueDouble(1.2),
expected: 1.2,
},
{
name: "bytes",
input: generateTestValueBytes(),
expected: []byte("String bytes"),
},
{
name: "empty",
input: NewValueEmpty(),
expected: nil,
},
{
name: "slice",
input: generateTestValueSlice(),
expected: []any{"strVal", int64(7), 18.6, false, nil},
},
{
name: "map",
input: generateTestValueMap(),
expected: map[string]any{
"mapKey": map[string]any{"keyOne": "valOne", "keyTwo": "valTwo"},
"nullKey": nil,
"strKey": "strVal",
"arrKey": []any{"strOne", "strTwo"},
"boolKey": false,
"floatKey": 18.6,
"intKey": int64(7),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actual := tt.input.AsRaw()
assert.Equal(t, tt.expected, actual)
})
}
}
func TestNewValueFromRaw(t *testing.T) {
tests := []struct {
name string
input any
expected Value
}{
{
name: "nil",
input: nil,
expected: NewValueEmpty(),
},
{
name: "string",
input: "text",
expected: NewValueStr("text"),
},
{
name: "int",
input: 123,
expected: NewValueInt(int64(123)),
},
{
name: "int8",
input: int8(12),
expected: NewValueInt(int64(12)),
},
{
name: "int16",
input: int16(23),
expected: NewValueInt(int64(23)),
},
{
name: "int32",
input: int32(34),
expected: NewValueInt(int64(34)),
},
{
name: "int64",
input: int64(45),
expected: NewValueInt(45),
},
{
name: "uint",
input: uint(56),
expected: NewValueInt(int64(56)),
},
{
name: "uint8",
input: uint8(67),
expected: NewValueInt(int64(67)),
},
{
name: "uint16",
input: uint16(78),
expected: NewValueInt(int64(78)),
},
{
name: "uint32",
input: uint32(89),
expected: NewValueInt(int64(89)),
},
{
name: "uint64",
input: uint64(90),
expected: NewValueInt(int64(90)),
},
{
name: "float32",
input: float32(1.234),
expected: NewValueDouble(float64(float32(1.234))),
},
{
name: "float64",
input: float64(2.345),
expected: NewValueDouble(float64(2.345)),
},
{
name: "bool",
input: true,
expected: NewValueBool(true),
},
{
name: "bytes",
input: []byte{1, 2, 3},
expected: func() Value {
m := NewValueBytes()
m.Bytes().FromRaw([]byte{1, 2, 3})
return m
}(),
},
{
name: "map",
input: map[string]any{
"k": "v",
},
expected: func() Value {
m := NewValueMap()
assert.NoError(t, m.Map().FromRaw(map[string]any{"k": "v"}))
return m
}(),
},
{
name: "empty map",
input: map[string]any{},
expected: func() Value {
m := NewValueMap()
assert.NoError(t, m.Map().FromRaw(map[string]any{}))
return m
}(),
},
{
name: "slice",
input: []any{"v1", "v2"},
expected: (func() Value {
s := NewValueSlice()
assert.NoError(t, s.Slice().FromRaw([]any{"v1", "v2"}))
return s
})(),
},
{
name: "empty slice",
input: []any{},
expected: (func() Value {
s := NewValueSlice()
assert.NoError(t, s.Slice().FromRaw([]any{}))
return s
})(),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actual := NewValueEmpty()
require.NoError(t, actual.FromRaw(tt.input))
assert.Equal(t, tt.expected, actual)
})
}
}
func TestNewValueFromRawInvalid(t *testing.T) {
actual := NewValueEmpty()
assert.EqualError(t, actual.FromRaw(ValueTypeDouble), "")
}
func TestInvalidValue(t *testing.T) {
v := Value{}
assert.False(t, v.Bool())
assert.Equal(t, int64(0), v.Int())
assert.InDelta(t, float64(0), v.Double(), 0.01)
assert.Empty(t, v.Str())
assert.Equal(t, ByteSlice{}, v.Bytes())
assert.Equal(t, Map{}, v.Map())
assert.Equal(t, Slice{}, v.Slice())
assert.Panics(t, func() { v.AsString() })
assert.Panics(t, func() { v.AsRaw() })
assert.Panics(t, func() { _ = v.FromRaw(1) })
assert.Panics(t, func() { v.Type() })
assert.Panics(t, func() { v.SetStr("") })
assert.Panics(t, func() { v.SetInt(0) })
assert.Panics(t, func() { v.SetDouble(0) })
assert.Panics(t, func() { v.SetBool(false) })
assert.Panics(t, func() { v.SetEmptyBytes() })
assert.Panics(t, func() { v.SetEmptyMap() })
assert.Panics(t, func() { v.SetEmptySlice() })
nv := NewValueEmpty()
v.CopyTo(nv)
assert.Nil(t, nv.getOrig().Value)
}
func TestValueEqual(t *testing.T) {
for _, tt := range []struct {
name string
value Value
comparison Value
expected bool
}{
{
name: "different types",
value: NewValueEmpty(),
comparison: NewValueStr("test"),
expected: false,
},
{
name: "same empty",
value: NewValueEmpty(),
comparison: NewValueEmpty(),
expected: true,
},
{
name: "same strings",
value: NewValueStr("test"),
comparison: NewValueStr("test"),
expected: true,
},
{
name: "different strings",
value: NewValueStr("test"),
comparison: NewValueStr("non-test"),
expected: false,
},
{
name: "same booleans",
value: NewValueBool(true),
comparison: NewValueBool(true),
expected: true,
},
{
name: "different booleans",
value: NewValueBool(true),
comparison: NewValueBool(false),
expected: false,
},
{
name: "same int",
value: NewValueInt(42),
comparison: NewValueInt(42),
expected: true,
},
{
name: "different ints",
value: NewValueInt(42),
comparison: NewValueInt(1701),
expected: false,
},
{
name: "same double",
value: NewValueDouble(13.37),
comparison: NewValueDouble(13.37),
expected: true,
},
{
name: "different doubles",
value: NewValueDouble(13.37),
comparison: NewValueDouble(17.01),
expected: false,
},
{
name: "same byte slice",
value: func() Value {
m := NewValueBytes()
m.Bytes().FromRaw([]byte{1, 3, 3, 7})
return m
}(),
comparison: func() Value {
m := NewValueBytes()
m.Bytes().FromRaw([]byte{1, 3, 3, 7})
return m
}(),
expected: true,
},
{
name: "different byte slice",
value: func() Value {
m := NewValueBytes()
m.Bytes().FromRaw([]byte{1, 3, 3, 7})
return m
}(),
comparison: func() Value {
m := NewValueBytes()
m.Bytes().FromRaw([]byte{1, 7, 0, 1})
return m
}(),
expected: false,
},
{
name: "same slice",
value: func() Value {
m := NewValueSlice()
require.NoError(t, m.Slice().FromRaw([]any{1337}))
return m
}(),
comparison: func() Value {
m := NewValueSlice()
require.NoError(t, m.Slice().FromRaw([]any{1337}))
return m
}(),
expected: true,
},
{
name: "different slice",
value: func() Value {
m := NewValueSlice()
require.NoError(t, m.Slice().FromRaw([]any{1337}))
return m
}(),
comparison: func() Value {
m := NewValueSlice()
require.NoError(t, m.Slice().FromRaw([]any{1701}))
return m
}(),
expected: false,
},
{
name: "same map",
value: func() Value {
m := NewValueMap()
m.Map().PutStr("hello", "world")
return m
}(),
comparison: func() Value {
m := NewValueMap()
m.Map().PutStr("hello", "world")
return m
}(),
expected: true,
},
{
name: "different maps",
value: func() Value {
m := NewValueMap()
m.Map().PutStr("hello", "world")
return m
}(),
comparison: func() Value {
m := NewValueMap()
m.Map().PutStr("bonjour", "monde")
return m
}(),
expected: false,
},
} {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, tt.value.Equal(tt.comparison))
})
}
}
func BenchmarkValueEqual(b *testing.B) {
testutil.SkipMemoryBench(b)
for _, bb := range []struct {
name string
value Value
comparison Value
}{
{
name: "nil",
value: NewValueEmpty(),
comparison: NewValueEmpty(),
},
{
name: "strings",
value: NewValueStr("test"),
comparison: NewValueStr("test"),
},
{
name: "booleans",
value: NewValueBool(true),
comparison: NewValueBool(true),
},
{
name: "ints",
value: NewValueInt(42),
comparison: NewValueInt(42),
},
{
name: "doubles",
value: NewValueDouble(13.37),
comparison: NewValueDouble(13.37),
},
{
name: "byte slices",
value: func() Value {
m := NewValueBytes()
m.Bytes().FromRaw([]byte{1, 3, 3, 7})
return m
}(),
comparison: func() Value {
m := NewValueBytes()
m.Bytes().FromRaw([]byte{1, 3, 3, 7})
return m
}(),
},
{
name: "slices",
value: func() Value {
m := NewValueSlice()
require.NoError(b, m.Slice().FromRaw([]any{1337}))
return m
}(),
comparison: func() Value {
m := NewValueSlice()
require.NoError(b, m.Slice().FromRaw([]any{1337}))
return m
}(),
},
{
name: "maps",
value: func() Value {
m := NewValueMap()
m.Map().PutStr("hello", "world")
return m
}(),
comparison: func() Value {
m := NewValueMap()
m.Map().PutStr("hello", "world")
return m
}(),
},
} {
b.Run(bb.name, func(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
_ = bb.value.Equal(bb.comparison)
}
})
}
}
func generateTestValueMap() Value {
ret := NewValueMap()
attrMap := ret.Map()
attrMap.PutStr("strKey", "strVal")
attrMap.PutInt("intKey", 7)
attrMap.PutDouble("floatKey", 18.6)
attrMap.PutBool("boolKey", false)
attrMap.PutEmpty("nullKey")
m := attrMap.PutEmptyMap("mapKey")
m.PutStr("keyOne", "valOne")
m.PutStr("keyTwo", "valTwo")
s := attrMap.PutEmptySlice("arrKey")
s.AppendEmpty().SetStr("strOne")
s.AppendEmpty().SetStr("strTwo")
return ret
}
func generateTestValueSlice() Value {
ret := NewValueSlice()
attrArr := ret.Slice()
attrArr.AppendEmpty().SetStr("strVal")
attrArr.AppendEmpty().SetInt(7)
attrArr.AppendEmpty().SetDouble(18.6)
attrArr.AppendEmpty().SetBool(false)
attrArr.AppendEmpty()
return ret
}
func generateTestValueBytes() Value {
v := NewValueBytes()
v.Bytes().FromRaw([]byte("String bytes"))
return v
}
================================================
FILE: pdata/plog/config.schema.yaml
================================================
$defs:
log_record_flags:
description: LogRecordFlags defines flags for the LogRecord. The 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.
type: integer
x-customType: uint32
logs:
description: 'Logs is the top-level struct that is propagated through the logs pipeline. Use NewLogs to create new instance, zero-initialized instance is not valid for use. This is a reference type, if passed by value and callee modifies it the caller will see the modification. Must use NewLogs function to create new instances. Important: zero-initialized instance is not valid for use.'
$ref: go.opentelemetry.io/collector/pdata/internal.logs_wrapper
severity_number:
description: SeverityNumber represents severity number of a log record.
type: integer
x-customType: int32
================================================
FILE: pdata/plog/doc_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package plog_test
import (
"fmt"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
)
func ExampleNewLogs() {
logs := plog.NewLogs()
resourceLogs := logs.ResourceLogs().AppendEmpty()
resourceLogs.Resource().Attributes().PutStr("service.name", "my-service")
resourceLogs.Resource().Attributes().PutStr("service.version", "1.0.0")
resourceLogs.Resource().Attributes().PutStr("host.name", "server-01")
scopeLogs := resourceLogs.ScopeLogs().AppendEmpty()
scopeLogs.Scope().SetName("my-logger")
scopeLogs.Scope().SetVersion("1.0.0")
logRecord := scopeLogs.LogRecords().AppendEmpty()
logRecord.SetTimestamp(pcommon.Timestamp(1640995200000000000))
logRecord.SetSeverityNumber(plog.SeverityNumberInfo)
logRecord.SetSeverityText("INFO")
logRecord.Body().SetStr("User login successful")
logRecord.Attributes().PutStr("user.id", "user123")
logRecord.Attributes().PutStr("session.id", "session456")
logRecord.Attributes().PutStr("action", "login")
fmt.Printf("Resource logs count: %d\n", logs.ResourceLogs().Len())
fmt.Printf("Log records count: %d\n", scopeLogs.LogRecords().Len())
fmt.Printf("Log message: %s\n", logRecord.Body().Str())
fmt.Printf("Severity: %s\n", logRecord.SeverityText())
// Output:
// Resource logs count: 1
// Log records count: 1
// Log message: User login successful
// Severity: INFO
}
func ExampleLogRecord_SetSeverityNumber() {
logs := plog.NewLogs()
resourceLogs := logs.ResourceLogs().AppendEmpty()
scopeLogs := resourceLogs.ScopeLogs().AppendEmpty()
severities := []struct {
level plog.SeverityNumber
text string
msg string
}{
{plog.SeverityNumberDebug, "DEBUG", "Debug information"},
{plog.SeverityNumberInfo, "INFO", "Application started"},
{plog.SeverityNumberWarn, "WARN", "Configuration file not found, using defaults"},
{plog.SeverityNumberError, "ERROR", "Failed to connect to database"},
{plog.SeverityNumberFatal, "FATAL", "Critical system failure"},
}
for _, s := range severities {
logRecord := scopeLogs.LogRecords().AppendEmpty()
logRecord.SetSeverityNumber(s.level)
logRecord.SetSeverityText(s.text)
logRecord.Body().SetStr(s.msg)
logRecord.SetTimestamp(pcommon.Timestamp(1640995200000000000))
}
fmt.Printf("Total log records: %d\n", scopeLogs.LogRecords().Len())
first := scopeLogs.LogRecords().At(0)
last := scopeLogs.LogRecords().At(scopeLogs.LogRecords().Len() - 1)
fmt.Printf("First log: %s - %s\n", first.SeverityText(), first.Body().Str())
fmt.Printf("Last log: %s - %s\n", last.SeverityText(), last.Body().Str())
// Output:
// Total log records: 5
// First log: DEBUG - Debug information
// Last log: FATAL - Critical system failure
}
func ExampleLogRecord_Body() {
logs := plog.NewLogs()
resourceLogs := logs.ResourceLogs().AppendEmpty()
scopeLogs := resourceLogs.ScopeLogs().AppendEmpty()
logRecord1 := scopeLogs.LogRecords().AppendEmpty()
logRecord1.Body().SetStr("Simple string message")
logRecord1.SetSeverityNumber(plog.SeverityNumberInfo)
logRecord2 := scopeLogs.LogRecords().AppendEmpty()
body := logRecord2.Body().SetEmptyMap()
body.PutStr("event", "user_action")
body.PutStr("user_id", "user123")
body.PutInt("timestamp", 1640995200)
body.PutBool("success", true)
logRecord2.SetSeverityNumber(plog.SeverityNumberInfo)
logRecord3 := scopeLogs.LogRecords().AppendEmpty()
bodySlice := logRecord3.Body().SetEmptySlice()
bodySlice.AppendEmpty().SetStr("Step 1: Initialize connection")
bodySlice.AppendEmpty().SetStr("Step 2: Authenticate user")
bodySlice.AppendEmpty().SetStr("Step 3: Load configuration")
logRecord3.SetSeverityNumber(plog.SeverityNumberDebug)
fmt.Printf("Log 1 body type: %s\n", logRecord1.Body().Type())
fmt.Printf("Log 2 body type: %s\n", logRecord2.Body().Type())
fmt.Printf("Log 3 body type: %s\n", logRecord3.Body().Type())
fmt.Printf("Log 3 steps count: %d\n", logRecord3.Body().Slice().Len())
// Output:
// Log 1 body type: Str
// Log 2 body type: Map
// Log 3 body type: Slice
// Log 3 steps count: 3
}
func ExampleLogRecord_TraceID() {
logs := plog.NewLogs()
resourceLogs := logs.ResourceLogs().AppendEmpty()
scopeLogs := resourceLogs.ScopeLogs().AppendEmpty()
logRecord := scopeLogs.LogRecords().AppendEmpty()
logRecord.Body().SetStr("Processing request")
logRecord.SetSeverityNumber(plog.SeverityNumberInfo)
traceID := pcommon.TraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})
spanID := pcommon.SpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8})
logRecord.SetTraceID(traceID)
logRecord.SetSpanID(spanID)
logRecord.SetFlags(plog.DefaultLogRecordFlags.WithIsSampled(true))
fmt.Printf("Log message: %s\n", logRecord.Body().Str())
fmt.Printf("TraceID: %s\n", logRecord.TraceID())
fmt.Printf("SpanID: %s\n", logRecord.SpanID())
fmt.Printf("Is sampled: %t\n", logRecord.Flags().IsSampled())
// Output:
// Log message: Processing request
// TraceID: 0102030405060708090a0b0c0d0e0f10
// SpanID: 0102030405060708
// Is sampled: true
}
func ExampleLogRecord_ObservedTimestamp() {
logs := plog.NewLogs()
resourceLogs := logs.ResourceLogs().AppendEmpty()
scopeLogs := resourceLogs.ScopeLogs().AppendEmpty()
logRecord := scopeLogs.LogRecords().AppendEmpty()
logRecord.Body().SetStr("Log entry with observation time")
logRecord.SetSeverityNumber(plog.SeverityNumberInfo)
// Set both original timestamp and observed timestamp
originalTime := pcommon.Timestamp(1640995200000000000) // 2022-01-01 00:00:00 UTC
observedTime := pcommon.Timestamp(1640995200500000000) // 2022-01-01 00:00:00.5 UTC
logRecord.SetTimestamp(originalTime)
logRecord.SetObservedTimestamp(observedTime)
fmt.Printf("Original timestamp: %d\n", logRecord.Timestamp())
fmt.Printf("Observed timestamp: %d\n", logRecord.ObservedTimestamp())
fmt.Printf("Delay (ns): %d\n", logRecord.ObservedTimestamp()-logRecord.Timestamp())
// Output:
// Original timestamp: 1640995200000000000
// Observed timestamp: 1640995200500000000
// Delay (ns): 500000000
}
func ExampleLogRecord_EventName() {
logs := plog.NewLogs()
resourceLogs := logs.ResourceLogs().AppendEmpty()
scopeLogs := resourceLogs.ScopeLogs().AppendEmpty()
logRecord := scopeLogs.LogRecords().AppendEmpty()
logRecord.SetEventName("user.login")
logRecord.Body().SetStr("User authentication event")
logRecord.SetSeverityNumber(plog.SeverityNumberInfo)
logRecord.Attributes().PutStr("user.id", "user123")
logRecord.Attributes().PutStr("session.id", "session456")
logRecord.Attributes().PutBool("success", true)
fmt.Printf("Event name: %s\n", logRecord.EventName())
fmt.Printf("Log body: %s\n", logRecord.Body().Str())
// Output:
// Event name: user.login
// Log body: User authentication event
}
func ExampleLogRecordFlags() {
logs := plog.NewLogs()
resourceLogs := logs.ResourceLogs().AppendEmpty()
scopeLogs := resourceLogs.ScopeLogs().AppendEmpty()
logRecord := scopeLogs.LogRecords().AppendEmpty()
logRecord.Body().SetStr("Log with flags")
logRecord.SetSeverityNumber(plog.SeverityNumberInfo)
// Test default flags
defaultFlags := plog.DefaultLogRecordFlags
logRecord.SetFlags(defaultFlags)
fmt.Printf("Default flags IsSampled: %t\n", logRecord.Flags().IsSampled())
// Test with sampled flag
flagsWithSampled := defaultFlags.WithIsSampled(true)
logRecord.SetFlags(flagsWithSampled)
fmt.Printf("With sampled flag: %t\n", logRecord.Flags().IsSampled())
// Test removing sampled flag
flagsWithoutSampled := flagsWithSampled.WithIsSampled(false)
logRecord.SetFlags(flagsWithoutSampled)
fmt.Printf("Without sampled flag: %t\n", logRecord.Flags().IsSampled())
// Output:
// Default flags IsSampled: false
// With sampled flag: true
// Without sampled flag: false
}
func ExampleSeverityNumber() {
logs := plog.NewLogs()
resourceLogs := logs.ResourceLogs().AppendEmpty()
scopeLogs := resourceLogs.ScopeLogs().AppendEmpty()
// Test all severity levels
severityLevels := []struct {
level plog.SeverityNumber
name string
}{
{plog.SeverityNumberUnspecified, "Unspecified"},
{plog.SeverityNumberTrace, "Trace"},
{plog.SeverityNumberTrace2, "Trace2"},
{plog.SeverityNumberTrace3, "Trace3"},
{plog.SeverityNumberTrace4, "Trace4"},
{plog.SeverityNumberDebug, "Debug"},
{plog.SeverityNumberDebug2, "Debug2"},
{plog.SeverityNumberDebug3, "Debug3"},
{plog.SeverityNumberDebug4, "Debug4"},
{plog.SeverityNumberInfo, "Info"},
{plog.SeverityNumberInfo2, "Info2"},
{plog.SeverityNumberInfo3, "Info3"},
{plog.SeverityNumberInfo4, "Info4"},
{plog.SeverityNumberWarn, "Warn"},
{plog.SeverityNumberWarn2, "Warn2"},
{plog.SeverityNumberWarn3, "Warn3"},
{plog.SeverityNumberWarn4, "Warn4"},
{plog.SeverityNumberError, "Error"},
{plog.SeverityNumberError2, "Error2"},
{plog.SeverityNumberError3, "Error3"},
{plog.SeverityNumberError4, "Error4"},
{plog.SeverityNumberFatal, "Fatal"},
{plog.SeverityNumberFatal2, "Fatal2"},
{plog.SeverityNumberFatal3, "Fatal3"},
{plog.SeverityNumberFatal4, "Fatal4"},
}
for i, s := range severityLevels {
if i < 5 { // Only create first 5 to keep output manageable
logRecord := scopeLogs.LogRecords().AppendEmpty()
logRecord.SetSeverityNumber(s.level)
logRecord.SetSeverityText(s.name)
logRecord.Body().SetStr("Log at " + s.name + " level")
}
}
fmt.Printf("Total severity levels tested: %d\n", len(severityLevels))
fmt.Printf("Created log records: %d\n", scopeLogs.LogRecords().Len())
fmt.Printf("First severity: %s\n", scopeLogs.LogRecords().At(0).SeverityText())
fmt.Printf("Last severity: %s\n", scopeLogs.LogRecords().At(4).SeverityText())
// Output:
// Total severity levels tested: 25
// Created log records: 5
// First severity: Unspecified
// Last severity: Trace4
}
func ExampleLogRecord_DroppedAttributesCount() {
logs := plog.NewLogs()
resourceLogs := logs.ResourceLogs().AppendEmpty()
scopeLogs := resourceLogs.ScopeLogs().AppendEmpty()
logRecord := scopeLogs.LogRecords().AppendEmpty()
logRecord.Body().SetStr("Log with some attributes dropped")
logRecord.SetSeverityNumber(plog.SeverityNumberWarn)
// Add some attributes
logRecord.Attributes().PutStr("included.attr1", "value1")
logRecord.Attributes().PutStr("included.attr2", "value2")
logRecord.Attributes().PutInt("included.count", 42)
// Set dropped attributes count
logRecord.SetDroppedAttributesCount(7)
fmt.Printf("Current attributes: %d\n", logRecord.Attributes().Len())
fmt.Printf("Dropped attributes: %d\n", logRecord.DroppedAttributesCount())
fmt.Printf("Total original attributes: %d\n", logRecord.Attributes().Len()+int(logRecord.DroppedAttributesCount()))
// Output:
// Current attributes: 3
// Dropped attributes: 7
// Total original attributes: 10
}
================================================
FILE: pdata/plog/encoding.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package plog // import "go.opentelemetry.io/collector/pdata/plog"
// MarshalSizer is the interface that groups the basic Marshal and Size methods
type MarshalSizer interface {
Marshaler
Sizer
}
// Marshaler marshals Logs into bytes.
type Marshaler interface {
// MarshalLogs the given Logs into bytes.
// If the error is not nil, the returned bytes slice cannot be used.
MarshalLogs(ld Logs) ([]byte, error)
}
// Unmarshaler unmarshalls bytes into Logs.
type Unmarshaler interface {
// UnmarshalLogs the given bytes into Logs.
// If the error is not nil, the returned Logs cannot be used.
UnmarshalLogs(buf []byte) (Logs, error)
}
// Sizer is an optional interface implemented by the Marshaler,
// that calculates the size of a marshaled Logs.
type Sizer interface {
// LogsSize returns the size in bytes of a marshaled Logs.
LogsSize(ld Logs) int
}
================================================
FILE: pdata/plog/fuzz_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package plog // import "go.opentelemetry.io/collector/pdata/plog"
import (
"bytes"
"testing"
"github.com/stretchr/testify/require"
)
var unexpectedBytes = "expected the same bytes from unmarshaling and marshaling."
func FuzzUnmarshalJSONLogs(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
u1 := &JSONUnmarshaler{}
ld1, err := u1.UnmarshalLogs(data)
if err != nil {
return
}
m1 := &JSONMarshaler{}
b1, err := m1.MarshalLogs(ld1)
require.NoError(t, err, "failed to marshal valid struct")
u2 := &JSONUnmarshaler{}
ld2, err := u2.UnmarshalLogs(b1)
require.NoError(t, err, "failed to unmarshal valid bytes")
m2 := &JSONMarshaler{}
b2, err := m2.MarshalLogs(ld2)
require.NoError(t, err, "failed to marshal valid struct")
require.True(t, bytes.Equal(b1, b2), "%s. \nexpected %d but got %d\n", unexpectedBytes, b1, b2)
})
}
func FuzzUnmarshalPBLogs(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
u1 := &ProtoUnmarshaler{}
ld1, err := u1.UnmarshalLogs(data)
if err != nil {
return
}
m1 := &ProtoMarshaler{}
b1, err := m1.MarshalLogs(ld1)
require.NoError(t, err, "failed to marshal valid struct")
u2 := &ProtoUnmarshaler{}
ld2, err := u2.UnmarshalLogs(b1)
require.NoError(t, err, "failed to unmarshal valid bytes")
m2 := &ProtoMarshaler{}
b2, err := m2.MarshalLogs(ld2)
require.NoError(t, err, "failed to marshal valid struct")
require.True(t, bytes.Equal(b1, b2), "%s. \nexpected %d but got %d\n", unexpectedBytes, b1, b2)
})
}
================================================
FILE: pdata/plog/generated_logrecord.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package plog
import (
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// 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 *internal.LogRecord
state *internal.State
}
func newLogRecord(orig *internal.LogRecord, state *internal.State) LogRecord {
return LogRecord{orig: orig, state: state}
}
// NewLogRecord creates a new empty LogRecord.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewLogRecord() LogRecord {
return newLogRecord(internal.NewLogRecord(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms LogRecord) MoveTo(dest LogRecord) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteLogRecord(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// Timestamp returns the timestamp associated with this LogRecord.
func (ms LogRecord) Timestamp() pcommon.Timestamp {
return pcommon.Timestamp(ms.orig.TimeUnixNano)
}
// SetTimestamp replaces the timestamp associated with this LogRecord.
func (ms LogRecord) SetTimestamp(v pcommon.Timestamp) {
ms.state.AssertMutable()
ms.orig.TimeUnixNano = uint64(v)
}
// ObservedTimestamp returns the observedtimestamp associated with this LogRecord.
func (ms LogRecord) ObservedTimestamp() pcommon.Timestamp {
return pcommon.Timestamp(ms.orig.ObservedTimeUnixNano)
}
// SetObservedTimestamp replaces the observedtimestamp associated with this LogRecord.
func (ms LogRecord) SetObservedTimestamp(v pcommon.Timestamp) {
ms.state.AssertMutable()
ms.orig.ObservedTimeUnixNano = uint64(v)
}
// SeverityNumber returns the severitynumber associated with this LogRecord.
func (ms LogRecord) SeverityNumber() SeverityNumber {
return SeverityNumber(ms.orig.SeverityNumber)
}
// SetSeverityNumber replaces the severitynumber associated with this LogRecord.
func (ms LogRecord) SetSeverityNumber(v SeverityNumber) {
ms.state.AssertMutable()
ms.orig.SeverityNumber = internal.SeverityNumber(v)
}
// SeverityText returns the severitytext associated with this LogRecord.
func (ms LogRecord) SeverityText() string {
return ms.orig.SeverityText
}
// SetSeverityText replaces the severitytext associated with this LogRecord.
func (ms LogRecord) SetSeverityText(v string) {
ms.state.AssertMutable()
ms.orig.SeverityText = v
}
// Body returns the body associated with this LogRecord.
func (ms LogRecord) Body() pcommon.Value {
return pcommon.Value(internal.NewValueWrapper(&ms.orig.Body, ms.state))
}
// Attributes returns the Attributes associated with this LogRecord.
func (ms LogRecord) Attributes() pcommon.Map {
return pcommon.Map(internal.NewMapWrapper(&ms.orig.Attributes, ms.state))
}
// DroppedAttributesCount returns the droppedattributescount associated with this LogRecord.
func (ms LogRecord) DroppedAttributesCount() uint32 {
return ms.orig.DroppedAttributesCount
}
// SetDroppedAttributesCount replaces the droppedattributescount associated with this LogRecord.
func (ms LogRecord) SetDroppedAttributesCount(v uint32) {
ms.state.AssertMutable()
ms.orig.DroppedAttributesCount = v
}
// Flags returns the flags associated with this LogRecord.
func (ms LogRecord) Flags() LogRecordFlags {
return LogRecordFlags(ms.orig.Flags)
}
// SetFlags replaces the flags associated with this LogRecord.
func (ms LogRecord) SetFlags(v LogRecordFlags) {
ms.state.AssertMutable()
ms.orig.Flags = uint32(v)
}
// TraceID returns the traceid associated with this LogRecord.
func (ms LogRecord) TraceID() pcommon.TraceID {
return pcommon.TraceID(ms.orig.TraceId)
}
// SetTraceID replaces the traceid associated with this LogRecord.
func (ms LogRecord) SetTraceID(v pcommon.TraceID) {
ms.state.AssertMutable()
ms.orig.TraceId = internal.TraceID(v)
}
// SpanID returns the spanid associated with this LogRecord.
func (ms LogRecord) SpanID() pcommon.SpanID {
return pcommon.SpanID(ms.orig.SpanId)
}
// SetSpanID replaces the spanid associated with this LogRecord.
func (ms LogRecord) SetSpanID(v pcommon.SpanID) {
ms.state.AssertMutable()
ms.orig.SpanId = internal.SpanID(v)
}
// EventName returns the eventname associated with this LogRecord.
func (ms LogRecord) EventName() string {
return ms.orig.EventName
}
// SetEventName replaces the eventname associated with this LogRecord.
func (ms LogRecord) SetEventName(v string) {
ms.state.AssertMutable()
ms.orig.EventName = v
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms LogRecord) CopyTo(dest LogRecord) {
dest.state.AssertMutable()
internal.CopyLogRecord(dest.orig, ms.orig)
}
================================================
FILE: pdata/plog/generated_logrecord_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package plog
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestLogRecord_MoveTo(t *testing.T) {
ms := generateTestLogRecord()
dest := NewLogRecord()
ms.MoveTo(dest)
assert.Equal(t, NewLogRecord(), ms)
assert.Equal(t, generateTestLogRecord(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestLogRecord(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newLogRecord(internal.NewLogRecord(), sharedState)) })
assert.Panics(t, func() { newLogRecord(internal.NewLogRecord(), sharedState).MoveTo(dest) })
}
func TestLogRecord_CopyTo(t *testing.T) {
ms := NewLogRecord()
orig := NewLogRecord()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestLogRecord()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newLogRecord(internal.NewLogRecord(), sharedState)) })
}
func TestLogRecord_Timestamp(t *testing.T) {
ms := NewLogRecord()
assert.Equal(t, pcommon.Timestamp(0), ms.Timestamp())
testValTimestamp := pcommon.Timestamp(1234567890)
ms.SetTimestamp(testValTimestamp)
assert.Equal(t, testValTimestamp, ms.Timestamp())
}
func TestLogRecord_ObservedTimestamp(t *testing.T) {
ms := NewLogRecord()
assert.Equal(t, pcommon.Timestamp(0), ms.ObservedTimestamp())
testValObservedTimestamp := pcommon.Timestamp(1234567890)
ms.SetObservedTimestamp(testValObservedTimestamp)
assert.Equal(t, testValObservedTimestamp, ms.ObservedTimestamp())
}
func TestLogRecord_SeverityNumber(t *testing.T) {
ms := NewLogRecord()
assert.Equal(t, SeverityNumber(internal.SeverityNumber_SEVERITY_NUMBER_UNSPECIFIED), ms.SeverityNumber())
testValSeverityNumber := SeverityNumber(internal.SeverityNumber_SEVERITY_NUMBER_DEBUG)
ms.SetSeverityNumber(testValSeverityNumber)
assert.Equal(t, testValSeverityNumber, ms.SeverityNumber())
}
func TestLogRecord_SeverityText(t *testing.T) {
ms := NewLogRecord()
assert.Empty(t, ms.SeverityText())
ms.SetSeverityText("test_severitytext")
assert.Equal(t, "test_severitytext", ms.SeverityText())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newLogRecord(internal.NewLogRecord(), sharedState).SetSeverityText("test_severitytext") })
}
func TestLogRecord_Body(t *testing.T) {
ms := NewLogRecord()
assert.Equal(t, pcommon.NewValueEmpty(), ms.Body())
ms.orig.Body = *internal.GenTestAnyValue()
assert.Equal(t, pcommon.Value(internal.GenTestValueWrapper()), ms.Body())
}
func TestLogRecord_Attributes(t *testing.T) {
ms := NewLogRecord()
assert.Equal(t, pcommon.NewMap(), ms.Attributes())
ms.orig.Attributes = internal.GenTestKeyValueSlice()
assert.Equal(t, pcommon.Map(internal.GenTestMapWrapper()), ms.Attributes())
}
func TestLogRecord_DroppedAttributesCount(t *testing.T) {
ms := NewLogRecord()
assert.Equal(t, uint32(0), ms.DroppedAttributesCount())
ms.SetDroppedAttributesCount(uint32(13))
assert.Equal(t, uint32(13), ms.DroppedAttributesCount())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newLogRecord(internal.NewLogRecord(), sharedState).SetDroppedAttributesCount(uint32(13)) })
}
func TestLogRecord_Flags(t *testing.T) {
ms := NewLogRecord()
assert.Equal(t, LogRecordFlags(0), ms.Flags())
testValFlags := LogRecordFlags(1)
ms.SetFlags(testValFlags)
assert.Equal(t, testValFlags, ms.Flags())
}
func TestLogRecord_TraceID(t *testing.T) {
ms := NewLogRecord()
assert.Equal(t, pcommon.TraceID(internal.TraceID([16]byte{})), ms.TraceID())
testValTraceID := pcommon.TraceID(internal.TraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}))
ms.SetTraceID(testValTraceID)
assert.Equal(t, testValTraceID, ms.TraceID())
}
func TestLogRecord_SpanID(t *testing.T) {
ms := NewLogRecord()
assert.Equal(t, pcommon.SpanID(internal.SpanID([8]byte{})), ms.SpanID())
testValSpanID := pcommon.SpanID(internal.SpanID([8]byte{8, 7, 6, 5, 4, 3, 2, 1}))
ms.SetSpanID(testValSpanID)
assert.Equal(t, testValSpanID, ms.SpanID())
}
func TestLogRecord_EventName(t *testing.T) {
ms := NewLogRecord()
assert.Empty(t, ms.EventName())
ms.SetEventName("test_eventname")
assert.Equal(t, "test_eventname", ms.EventName())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newLogRecord(internal.NewLogRecord(), sharedState).SetEventName("test_eventname") })
}
func generateTestLogRecord() LogRecord {
return newLogRecord(internal.GenTestLogRecord(), internal.NewState())
}
================================================
FILE: pdata/plog/generated_logrecordslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package plog
import (
"iter"
"sort"
"go.opentelemetry.io/collector/pdata/internal"
)
// LogRecordSlice 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 NewLogRecordSlice function to create new instances.
// Important: zero-initialized instance is not valid for use.
type LogRecordSlice struct {
orig *[]*internal.LogRecord
state *internal.State
}
func newLogRecordSlice(orig *[]*internal.LogRecord, state *internal.State) LogRecordSlice {
return LogRecordSlice{orig: orig, state: state}
}
// NewLogRecordSlice creates a LogRecordSliceWrapper with 0 elements.
// Can use "EnsureCapacity" to initialize with a given capacity.
func NewLogRecordSlice() LogRecordSlice {
orig := []*internal.LogRecord(nil)
return newLogRecordSlice(&orig, internal.NewState())
}
// Len returns the number of elements in the slice.
//
// Returns "0" for a newly instance created with "NewLogRecordSlice()".
func (es LogRecordSlice) 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 LogRecordSlice) At(i int) LogRecord {
return newLogRecord((*es.orig)[i], es.state)
}
// All returns an iterator over index-value pairs in the slice.
//
// for i, v := range es.All() {
// ... // Do something with index-value pair
// }
func (es LogRecordSlice) All() iter.Seq2[int, LogRecord] {
return func(yield func(int, LogRecord) bool) {
for i := 0; i < es.Len(); i++ {
if !yield(i, es.At(i)) {
return
}
}
}
}
// EnsureCapacity is an operation that ensures the slice has at least the specified capacity.
// 1. If the newCap <= cap then no change in capacity.
// 2. If the newCap > cap then the slice capacity will be expanded to equal newCap.
//
// Here is how a new LogRecordSlice can be initialized:
//
// es := NewLogRecordSlice()
// es.EnsureCapacity(4)
// for i := 0; i < 4; i++ {
// e := es.AppendEmpty()
// // Here should set all the values for e.
// }
func (es LogRecordSlice) EnsureCapacity(newCap int) {
es.state.AssertMutable()
oldCap := cap(*es.orig)
if newCap <= oldCap {
return
}
newOrig := make([]*internal.LogRecord, len(*es.orig), newCap)
copy(newOrig, *es.orig)
*es.orig = newOrig
}
// AppendEmpty will append to the end of the slice an empty LogRecord.
// It returns the newly added LogRecord.
func (es LogRecordSlice) AppendEmpty() LogRecord {
es.state.AssertMutable()
*es.orig = append(*es.orig, internal.NewLogRecord())
return es.At(es.Len() - 1)
}
// MoveAndAppendTo moves all elements from the current slice and appends them to the dest.
// The current slice will be cleared.
func (es LogRecordSlice) MoveAndAppendTo(dest LogRecordSlice) {
es.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if es.orig == dest.orig {
return
}
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
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (es LogRecordSlice) RemoveIf(f func(LogRecord) bool) {
es.state.AssertMutable()
newLen := 0
for i := 0; i < len(*es.orig); i++ {
if f(es.At(i)) {
internal.DeleteLogRecord((*es.orig)[i], true)
(*es.orig)[i] = nil
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*es.orig)[newLen] = (*es.orig)[i]
// Cannot delete here since we just move the data(or pointer to data) to a different position in the slice.
(*es.orig)[i] = nil
newLen++
}
*es.orig = (*es.orig)[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (es LogRecordSlice) CopyTo(dest LogRecordSlice) {
dest.state.AssertMutable()
if es.orig == dest.orig {
return
}
*dest.orig = internal.CopyLogRecordPtrSlice(*dest.orig, *es.orig)
}
// Sort sorts the LogRecord elements within LogRecordSlice given the
// provided less function so that two instances of LogRecordSlice
// can be compared.
func (es LogRecordSlice) Sort(less func(a, b LogRecord) bool) {
es.state.AssertMutable()
sort.SliceStable(*es.orig, func(i, j int) bool { return less(es.At(i), es.At(j)) })
}
================================================
FILE: pdata/plog/generated_logrecordslice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package plog
import (
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestLogRecordSlice(t *testing.T) {
es := NewLogRecordSlice()
assert.Equal(t, 0, es.Len())
es = newLogRecordSlice(&[]*internal.LogRecord{}, internal.NewState())
assert.Equal(t, 0, es.Len())
emptyVal := NewLogRecord()
testVal := generateTestLogRecord()
for i := 0; i < 7; i++ {
es.AppendEmpty()
assert.Equal(t, emptyVal, es.At(i))
(*es.orig)[i] = internal.GenTestLogRecord()
assert.Equal(t, testVal, es.At(i))
}
assert.Equal(t, 7, es.Len())
}
func TestLogRecordSliceReadOnly(t *testing.T) {
sharedState := internal.NewState()
sharedState.MarkReadOnly()
es := newLogRecordSlice(&[]*internal.LogRecord{}, sharedState)
assert.Equal(t, 0, es.Len())
assert.Panics(t, func() { es.AppendEmpty() })
assert.Panics(t, func() { es.EnsureCapacity(2) })
es2 := NewLogRecordSlice()
es.CopyTo(es2)
assert.Panics(t, func() { es2.CopyTo(es) })
assert.Panics(t, func() { es.MoveAndAppendTo(es2) })
assert.Panics(t, func() { es2.MoveAndAppendTo(es) })
}
func TestLogRecordSlice_CopyTo(t *testing.T) {
dest := NewLogRecordSlice()
src := generateTestLogRecordSlice()
src.CopyTo(dest)
assert.Equal(t, generateTestLogRecordSlice(), dest)
dest.CopyTo(dest)
assert.Equal(t, generateTestLogRecordSlice(), dest)
}
func TestLogRecordSlice_EnsureCapacity(t *testing.T) {
es := generateTestLogRecordSlice()
// Test ensure smaller capacity.
const ensureSmallLen = 4
es.EnsureCapacity(ensureSmallLen)
assert.Less(t, ensureSmallLen, es.Len())
assert.Equal(t, es.Len(), cap(*es.orig))
assert.Equal(t, generateTestLogRecordSlice(), es)
// Test ensure larger capacity
const ensureLargeLen = 9
es.EnsureCapacity(ensureLargeLen)
assert.Less(t, generateTestLogRecordSlice().Len(), ensureLargeLen)
assert.Equal(t, ensureLargeLen, cap(*es.orig))
assert.Equal(t, generateTestLogRecordSlice(), es)
}
func TestLogRecordSlice_MoveAndAppendTo(t *testing.T) {
// Test MoveAndAppendTo to empty
expectedSlice := generateTestLogRecordSlice()
dest := NewLogRecordSlice()
src := generateTestLogRecordSlice()
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestLogRecordSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo empty slice
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestLogRecordSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo not empty slice
generateTestLogRecordSlice().MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
dest.MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
}
func TestLogRecordSlice_RemoveIf(t *testing.T) {
// Test RemoveIf on empty slice
emptySlice := NewLogRecordSlice()
emptySlice.RemoveIf(func(el LogRecord) bool {
t.Fail()
return false
})
// Test RemoveIf
filtered := generateTestLogRecordSlice()
pos := 0
filtered.RemoveIf(func(el LogRecord) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, 2, filtered.Len())
}
func TestLogRecordSlice_RemoveIfAll(t *testing.T) {
got := generateTestLogRecordSlice()
got.RemoveIf(func(el LogRecord) bool {
return true
})
assert.Equal(t, 0, got.Len())
}
func TestLogRecordSliceAll(t *testing.T) {
ms := generateTestLogRecordSlice()
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestLogRecordSlice_Sort(t *testing.T) {
es := generateTestLogRecordSlice()
es.Sort(func(a, b LogRecord) bool {
return uintptr(unsafe.Pointer(a.orig)) < uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Less(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
es.Sort(func(a, b LogRecord) bool {
return uintptr(unsafe.Pointer(a.orig)) > uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Greater(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
}
func generateTestLogRecordSlice() LogRecordSlice {
ms := NewLogRecordSlice()
*ms.orig = internal.GenTestLogRecordPtrSlice()
return ms
}
================================================
FILE: pdata/plog/generated_logs.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package plog
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// Logs is the top-level struct that is propagated through the logs pipeline.
// Use NewLogs to create new instance, zero-initialized instance is not valid for use.
//
// This is a reference type, if passed by value and callee modifies it the
// caller will see the modification.
//
// Must use NewLogs function to create new instances.
// Important: zero-initialized instance is not valid for use.
type Logs internal.LogsWrapper
func newLogs(orig *internal.ExportLogsServiceRequest, state *internal.State) Logs {
return Logs(internal.NewLogsWrapper(orig, state))
}
// NewLogs creates a new empty Logs.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewLogs() Logs {
return newLogs(internal.NewExportLogsServiceRequest(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms Logs) MoveTo(dest Logs) {
ms.getState().AssertMutable()
dest.getState().AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.getOrig() == dest.getOrig() {
return
}
internal.DeleteExportLogsServiceRequest(dest.getOrig(), false)
*dest.getOrig(), *ms.getOrig() = *ms.getOrig(), *dest.getOrig()
}
// ResourceLogs returns the ResourceLogs associated with this Logs.
func (ms Logs) ResourceLogs() ResourceLogsSlice {
return newResourceLogsSlice(&ms.getOrig().ResourceLogs, ms.getState())
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms Logs) CopyTo(dest Logs) {
dest.getState().AssertMutable()
internal.CopyExportLogsServiceRequest(dest.getOrig(), ms.getOrig())
}
func (ms Logs) getOrig() *internal.ExportLogsServiceRequest {
return internal.GetLogsOrig(internal.LogsWrapper(ms))
}
func (ms Logs) getState() *internal.State {
return internal.GetLogsState(internal.LogsWrapper(ms))
}
================================================
FILE: pdata/plog/generated_logs_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package plog
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestLogs_MoveTo(t *testing.T) {
ms := generateTestLogs()
dest := NewLogs()
ms.MoveTo(dest)
assert.Equal(t, NewLogs(), ms)
assert.Equal(t, generateTestLogs(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestLogs(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newLogs(internal.NewExportLogsServiceRequest(), sharedState)) })
assert.Panics(t, func() { newLogs(internal.NewExportLogsServiceRequest(), sharedState).MoveTo(dest) })
}
func TestLogs_CopyTo(t *testing.T) {
ms := NewLogs()
orig := NewLogs()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestLogs()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newLogs(internal.NewExportLogsServiceRequest(), sharedState)) })
}
func TestLogs_ResourceLogs(t *testing.T) {
ms := NewLogs()
assert.Equal(t, NewResourceLogsSlice(), ms.ResourceLogs())
ms.getOrig().ResourceLogs = internal.GenTestResourceLogsPtrSlice()
assert.Equal(t, generateTestResourceLogsSlice(), ms.ResourceLogs())
}
func generateTestLogs() Logs {
return newLogs(internal.GenTestExportLogsServiceRequest(), internal.NewState())
}
================================================
FILE: pdata/plog/generated_resourcelogs.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package plog
import (
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// 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 *internal.ResourceLogs
state *internal.State
}
func newResourceLogs(orig *internal.ResourceLogs, state *internal.State) ResourceLogs {
return ResourceLogs{orig: orig, state: state}
}
// NewResourceLogs creates a new empty ResourceLogs.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewResourceLogs() ResourceLogs {
return newResourceLogs(internal.NewResourceLogs(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms ResourceLogs) MoveTo(dest ResourceLogs) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteResourceLogs(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// Resource returns the resource associated with this ResourceLogs.
func (ms ResourceLogs) Resource() pcommon.Resource {
return pcommon.Resource(internal.NewResourceWrapper(&ms.orig.Resource, ms.state))
}
// ScopeLogs returns the ScopeLogs associated with this ResourceLogs.
func (ms ResourceLogs) ScopeLogs() ScopeLogsSlice {
return newScopeLogsSlice(&ms.orig.ScopeLogs, ms.state)
}
// SchemaUrl returns the schemaurl associated with this ResourceLogs.
func (ms ResourceLogs) SchemaUrl() string {
return ms.orig.SchemaUrl
}
// SetSchemaUrl replaces the schemaurl associated with this ResourceLogs.
func (ms ResourceLogs) SetSchemaUrl(v string) {
ms.state.AssertMutable()
ms.orig.SchemaUrl = v
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms ResourceLogs) CopyTo(dest ResourceLogs) {
dest.state.AssertMutable()
internal.CopyResourceLogs(dest.orig, ms.orig)
}
================================================
FILE: pdata/plog/generated_resourcelogs_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package plog
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestResourceLogs_MoveTo(t *testing.T) {
ms := generateTestResourceLogs()
dest := NewResourceLogs()
ms.MoveTo(dest)
assert.Equal(t, NewResourceLogs(), ms)
assert.Equal(t, generateTestResourceLogs(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestResourceLogs(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newResourceLogs(internal.NewResourceLogs(), sharedState)) })
assert.Panics(t, func() { newResourceLogs(internal.NewResourceLogs(), sharedState).MoveTo(dest) })
}
func TestResourceLogs_CopyTo(t *testing.T) {
ms := NewResourceLogs()
orig := NewResourceLogs()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestResourceLogs()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newResourceLogs(internal.NewResourceLogs(), sharedState)) })
}
func TestResourceLogs_Resource(t *testing.T) {
ms := NewResourceLogs()
assert.Equal(t, pcommon.NewResource(), ms.Resource())
ms.orig.Resource = *internal.GenTestResource()
assert.Equal(t, pcommon.Resource(internal.GenTestResourceWrapper()), ms.Resource())
}
func TestResourceLogs_ScopeLogs(t *testing.T) {
ms := NewResourceLogs()
assert.Equal(t, NewScopeLogsSlice(), ms.ScopeLogs())
ms.orig.ScopeLogs = internal.GenTestScopeLogsPtrSlice()
assert.Equal(t, generateTestScopeLogsSlice(), ms.ScopeLogs())
}
func TestResourceLogs_SchemaUrl(t *testing.T) {
ms := NewResourceLogs()
assert.Empty(t, ms.SchemaUrl())
ms.SetSchemaUrl("test_schemaurl")
assert.Equal(t, "test_schemaurl", ms.SchemaUrl())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newResourceLogs(internal.NewResourceLogs(), sharedState).SetSchemaUrl("test_schemaurl") })
}
func generateTestResourceLogs() ResourceLogs {
return newResourceLogs(internal.GenTestResourceLogs(), internal.NewState())
}
================================================
FILE: pdata/plog/generated_resourcelogsslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package plog
import (
"iter"
"sort"
"go.opentelemetry.io/collector/pdata/internal"
)
// 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 *[]*internal.ResourceLogs
state *internal.State
}
func newResourceLogsSlice(orig *[]*internal.ResourceLogs, state *internal.State) ResourceLogsSlice {
return ResourceLogsSlice{orig: orig, state: state}
}
// NewResourceLogsSlice creates a ResourceLogsSliceWrapper with 0 elements.
// Can use "EnsureCapacity" to initialize with a given capacity.
func NewResourceLogsSlice() ResourceLogsSlice {
orig := []*internal.ResourceLogs(nil)
return newResourceLogsSlice(&orig, internal.NewState())
}
// 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(i int) ResourceLogs {
return newResourceLogs((*es.orig)[i], es.state)
}
// All returns an iterator over index-value pairs in the slice.
//
// for i, v := range es.All() {
// ... // Do something with index-value pair
// }
func (es ResourceLogsSlice) All() iter.Seq2[int, ResourceLogs] {
return func(yield func(int, ResourceLogs) bool) {
for i := 0; i < es.Len(); i++ {
if !yield(i, es.At(i)) {
return
}
}
}
}
// EnsureCapacity is an operation that ensures the slice has at least the specified capacity.
// 1. If the newCap <= cap then no change in capacity.
// 2. If the newCap > cap then the slice capacity will be expanded to equal newCap.
//
// Here is how a new ResourceLogsSlice can be initialized:
//
// es := NewResourceLogsSlice()
// es.EnsureCapacity(4)
// for i := 0; i < 4; i++ {
// e := es.AppendEmpty()
// // Here should set all the values for e.
// }
func (es ResourceLogsSlice) EnsureCapacity(newCap int) {
es.state.AssertMutable()
oldCap := cap(*es.orig)
if newCap <= oldCap {
return
}
newOrig := make([]*internal.ResourceLogs, len(*es.orig), newCap)
copy(newOrig, *es.orig)
*es.orig = newOrig
}
// AppendEmpty will append to the end of the slice an empty ResourceLogs.
// It returns the newly added ResourceLogs.
func (es ResourceLogsSlice) AppendEmpty() ResourceLogs {
es.state.AssertMutable()
*es.orig = append(*es.orig, internal.NewResourceLogs())
return es.At(es.Len() - 1)
}
// 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) {
es.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if es.orig == dest.orig {
return
}
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
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (es ResourceLogsSlice) RemoveIf(f func(ResourceLogs) bool) {
es.state.AssertMutable()
newLen := 0
for i := 0; i < len(*es.orig); i++ {
if f(es.At(i)) {
internal.DeleteResourceLogs((*es.orig)[i], true)
(*es.orig)[i] = nil
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*es.orig)[newLen] = (*es.orig)[i]
// Cannot delete here since we just move the data(or pointer to data) to a different position in the slice.
(*es.orig)[i] = nil
newLen++
}
*es.orig = (*es.orig)[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (es ResourceLogsSlice) CopyTo(dest ResourceLogsSlice) {
dest.state.AssertMutable()
if es.orig == dest.orig {
return
}
*dest.orig = internal.CopyResourceLogsPtrSlice(*dest.orig, *es.orig)
}
// Sort sorts the ResourceLogs elements within ResourceLogsSlice given the
// provided less function so that two instances of ResourceLogsSlice
// can be compared.
func (es ResourceLogsSlice) Sort(less func(a, b ResourceLogs) bool) {
es.state.AssertMutable()
sort.SliceStable(*es.orig, func(i, j int) bool { return less(es.At(i), es.At(j)) })
}
================================================
FILE: pdata/plog/generated_resourcelogsslice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package plog
import (
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestResourceLogsSlice(t *testing.T) {
es := NewResourceLogsSlice()
assert.Equal(t, 0, es.Len())
es = newResourceLogsSlice(&[]*internal.ResourceLogs{}, internal.NewState())
assert.Equal(t, 0, es.Len())
emptyVal := NewResourceLogs()
testVal := generateTestResourceLogs()
for i := 0; i < 7; i++ {
es.AppendEmpty()
assert.Equal(t, emptyVal, es.At(i))
(*es.orig)[i] = internal.GenTestResourceLogs()
assert.Equal(t, testVal, es.At(i))
}
assert.Equal(t, 7, es.Len())
}
func TestResourceLogsSliceReadOnly(t *testing.T) {
sharedState := internal.NewState()
sharedState.MarkReadOnly()
es := newResourceLogsSlice(&[]*internal.ResourceLogs{}, sharedState)
assert.Equal(t, 0, es.Len())
assert.Panics(t, func() { es.AppendEmpty() })
assert.Panics(t, func() { es.EnsureCapacity(2) })
es2 := NewResourceLogsSlice()
es.CopyTo(es2)
assert.Panics(t, func() { es2.CopyTo(es) })
assert.Panics(t, func() { es.MoveAndAppendTo(es2) })
assert.Panics(t, func() { es2.MoveAndAppendTo(es) })
}
func TestResourceLogsSlice_CopyTo(t *testing.T) {
dest := NewResourceLogsSlice()
src := generateTestResourceLogsSlice()
src.CopyTo(dest)
assert.Equal(t, generateTestResourceLogsSlice(), dest)
dest.CopyTo(dest)
assert.Equal(t, generateTestResourceLogsSlice(), dest)
}
func TestResourceLogsSlice_EnsureCapacity(t *testing.T) {
es := generateTestResourceLogsSlice()
// Test ensure smaller capacity.
const ensureSmallLen = 4
es.EnsureCapacity(ensureSmallLen)
assert.Less(t, ensureSmallLen, es.Len())
assert.Equal(t, es.Len(), cap(*es.orig))
assert.Equal(t, generateTestResourceLogsSlice(), es)
// Test ensure larger capacity
const ensureLargeLen = 9
es.EnsureCapacity(ensureLargeLen)
assert.Less(t, generateTestResourceLogsSlice().Len(), ensureLargeLen)
assert.Equal(t, ensureLargeLen, cap(*es.orig))
assert.Equal(t, generateTestResourceLogsSlice(), es)
}
func TestResourceLogsSlice_MoveAndAppendTo(t *testing.T) {
// Test MoveAndAppendTo to empty
expectedSlice := generateTestResourceLogsSlice()
dest := NewResourceLogsSlice()
src := generateTestResourceLogsSlice()
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestResourceLogsSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo empty slice
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestResourceLogsSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo not empty slice
generateTestResourceLogsSlice().MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
dest.MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
}
func TestResourceLogsSlice_RemoveIf(t *testing.T) {
// Test RemoveIf on empty slice
emptySlice := NewResourceLogsSlice()
emptySlice.RemoveIf(func(el ResourceLogs) bool {
t.Fail()
return false
})
// Test RemoveIf
filtered := generateTestResourceLogsSlice()
pos := 0
filtered.RemoveIf(func(el ResourceLogs) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, 2, filtered.Len())
}
func TestResourceLogsSlice_RemoveIfAll(t *testing.T) {
got := generateTestResourceLogsSlice()
got.RemoveIf(func(el ResourceLogs) bool {
return true
})
assert.Equal(t, 0, got.Len())
}
func TestResourceLogsSliceAll(t *testing.T) {
ms := generateTestResourceLogsSlice()
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestResourceLogsSlice_Sort(t *testing.T) {
es := generateTestResourceLogsSlice()
es.Sort(func(a, b ResourceLogs) bool {
return uintptr(unsafe.Pointer(a.orig)) < uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Less(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
es.Sort(func(a, b ResourceLogs) bool {
return uintptr(unsafe.Pointer(a.orig)) > uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Greater(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
}
func generateTestResourceLogsSlice() ResourceLogsSlice {
ms := NewResourceLogsSlice()
*ms.orig = internal.GenTestResourceLogsPtrSlice()
return ms
}
================================================
FILE: pdata/plog/generated_scopelogs.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package plog
import (
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// ScopeLogs 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 NewScopeLogs function to create new instances.
// Important: zero-initialized instance is not valid for use.
type ScopeLogs struct {
orig *internal.ScopeLogs
state *internal.State
}
func newScopeLogs(orig *internal.ScopeLogs, state *internal.State) ScopeLogs {
return ScopeLogs{orig: orig, state: state}
}
// NewScopeLogs creates a new empty ScopeLogs.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewScopeLogs() ScopeLogs {
return newScopeLogs(internal.NewScopeLogs(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms ScopeLogs) MoveTo(dest ScopeLogs) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteScopeLogs(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// Scope returns the scope associated with this ScopeLogs.
func (ms ScopeLogs) Scope() pcommon.InstrumentationScope {
return pcommon.InstrumentationScope(internal.NewInstrumentationScopeWrapper(&ms.orig.Scope, ms.state))
}
// LogRecords returns the LogRecords associated with this ScopeLogs.
func (ms ScopeLogs) LogRecords() LogRecordSlice {
return newLogRecordSlice(&ms.orig.LogRecords, ms.state)
}
// SchemaUrl returns the schemaurl associated with this ScopeLogs.
func (ms ScopeLogs) SchemaUrl() string {
return ms.orig.SchemaUrl
}
// SetSchemaUrl replaces the schemaurl associated with this ScopeLogs.
func (ms ScopeLogs) SetSchemaUrl(v string) {
ms.state.AssertMutable()
ms.orig.SchemaUrl = v
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms ScopeLogs) CopyTo(dest ScopeLogs) {
dest.state.AssertMutable()
internal.CopyScopeLogs(dest.orig, ms.orig)
}
================================================
FILE: pdata/plog/generated_scopelogs_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package plog
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestScopeLogs_MoveTo(t *testing.T) {
ms := generateTestScopeLogs()
dest := NewScopeLogs()
ms.MoveTo(dest)
assert.Equal(t, NewScopeLogs(), ms)
assert.Equal(t, generateTestScopeLogs(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestScopeLogs(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newScopeLogs(internal.NewScopeLogs(), sharedState)) })
assert.Panics(t, func() { newScopeLogs(internal.NewScopeLogs(), sharedState).MoveTo(dest) })
}
func TestScopeLogs_CopyTo(t *testing.T) {
ms := NewScopeLogs()
orig := NewScopeLogs()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestScopeLogs()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newScopeLogs(internal.NewScopeLogs(), sharedState)) })
}
func TestScopeLogs_Scope(t *testing.T) {
ms := NewScopeLogs()
assert.Equal(t, pcommon.NewInstrumentationScope(), ms.Scope())
ms.orig.Scope = *internal.GenTestInstrumentationScope()
assert.Equal(t, pcommon.InstrumentationScope(internal.GenTestInstrumentationScopeWrapper()), ms.Scope())
}
func TestScopeLogs_LogRecords(t *testing.T) {
ms := NewScopeLogs()
assert.Equal(t, NewLogRecordSlice(), ms.LogRecords())
ms.orig.LogRecords = internal.GenTestLogRecordPtrSlice()
assert.Equal(t, generateTestLogRecordSlice(), ms.LogRecords())
}
func TestScopeLogs_SchemaUrl(t *testing.T) {
ms := NewScopeLogs()
assert.Empty(t, ms.SchemaUrl())
ms.SetSchemaUrl("test_schemaurl")
assert.Equal(t, "test_schemaurl", ms.SchemaUrl())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newScopeLogs(internal.NewScopeLogs(), sharedState).SetSchemaUrl("test_schemaurl") })
}
func generateTestScopeLogs() ScopeLogs {
return newScopeLogs(internal.GenTestScopeLogs(), internal.NewState())
}
================================================
FILE: pdata/plog/generated_scopelogsslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package plog
import (
"iter"
"sort"
"go.opentelemetry.io/collector/pdata/internal"
)
// ScopeLogsSlice logically represents a slice of ScopeLogs.
//
// This is a reference type. If passed by value and callee modifies it, the
// caller will see the modification.
//
// Must use NewScopeLogsSlice function to create new instances.
// Important: zero-initialized instance is not valid for use.
type ScopeLogsSlice struct {
orig *[]*internal.ScopeLogs
state *internal.State
}
func newScopeLogsSlice(orig *[]*internal.ScopeLogs, state *internal.State) ScopeLogsSlice {
return ScopeLogsSlice{orig: orig, state: state}
}
// NewScopeLogsSlice creates a ScopeLogsSliceWrapper with 0 elements.
// Can use "EnsureCapacity" to initialize with a given capacity.
func NewScopeLogsSlice() ScopeLogsSlice {
orig := []*internal.ScopeLogs(nil)
return newScopeLogsSlice(&orig, internal.NewState())
}
// Len returns the number of elements in the slice.
//
// Returns "0" for a newly instance created with "NewScopeLogsSlice()".
func (es ScopeLogsSlice) 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 ScopeLogsSlice) At(i int) ScopeLogs {
return newScopeLogs((*es.orig)[i], es.state)
}
// All returns an iterator over index-value pairs in the slice.
//
// for i, v := range es.All() {
// ... // Do something with index-value pair
// }
func (es ScopeLogsSlice) All() iter.Seq2[int, ScopeLogs] {
return func(yield func(int, ScopeLogs) bool) {
for i := 0; i < es.Len(); i++ {
if !yield(i, es.At(i)) {
return
}
}
}
}
// EnsureCapacity is an operation that ensures the slice has at least the specified capacity.
// 1. If the newCap <= cap then no change in capacity.
// 2. If the newCap > cap then the slice capacity will be expanded to equal newCap.
//
// Here is how a new ScopeLogsSlice can be initialized:
//
// es := NewScopeLogsSlice()
// es.EnsureCapacity(4)
// for i := 0; i < 4; i++ {
// e := es.AppendEmpty()
// // Here should set all the values for e.
// }
func (es ScopeLogsSlice) EnsureCapacity(newCap int) {
es.state.AssertMutable()
oldCap := cap(*es.orig)
if newCap <= oldCap {
return
}
newOrig := make([]*internal.ScopeLogs, len(*es.orig), newCap)
copy(newOrig, *es.orig)
*es.orig = newOrig
}
// AppendEmpty will append to the end of the slice an empty ScopeLogs.
// It returns the newly added ScopeLogs.
func (es ScopeLogsSlice) AppendEmpty() ScopeLogs {
es.state.AssertMutable()
*es.orig = append(*es.orig, internal.NewScopeLogs())
return es.At(es.Len() - 1)
}
// MoveAndAppendTo moves all elements from the current slice and appends them to the dest.
// The current slice will be cleared.
func (es ScopeLogsSlice) MoveAndAppendTo(dest ScopeLogsSlice) {
es.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if es.orig == dest.orig {
return
}
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
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (es ScopeLogsSlice) RemoveIf(f func(ScopeLogs) bool) {
es.state.AssertMutable()
newLen := 0
for i := 0; i < len(*es.orig); i++ {
if f(es.At(i)) {
internal.DeleteScopeLogs((*es.orig)[i], true)
(*es.orig)[i] = nil
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*es.orig)[newLen] = (*es.orig)[i]
// Cannot delete here since we just move the data(or pointer to data) to a different position in the slice.
(*es.orig)[i] = nil
newLen++
}
*es.orig = (*es.orig)[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (es ScopeLogsSlice) CopyTo(dest ScopeLogsSlice) {
dest.state.AssertMutable()
if es.orig == dest.orig {
return
}
*dest.orig = internal.CopyScopeLogsPtrSlice(*dest.orig, *es.orig)
}
// Sort sorts the ScopeLogs elements within ScopeLogsSlice given the
// provided less function so that two instances of ScopeLogsSlice
// can be compared.
func (es ScopeLogsSlice) Sort(less func(a, b ScopeLogs) bool) {
es.state.AssertMutable()
sort.SliceStable(*es.orig, func(i, j int) bool { return less(es.At(i), es.At(j)) })
}
================================================
FILE: pdata/plog/generated_scopelogsslice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package plog
import (
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestScopeLogsSlice(t *testing.T) {
es := NewScopeLogsSlice()
assert.Equal(t, 0, es.Len())
es = newScopeLogsSlice(&[]*internal.ScopeLogs{}, internal.NewState())
assert.Equal(t, 0, es.Len())
emptyVal := NewScopeLogs()
testVal := generateTestScopeLogs()
for i := 0; i < 7; i++ {
es.AppendEmpty()
assert.Equal(t, emptyVal, es.At(i))
(*es.orig)[i] = internal.GenTestScopeLogs()
assert.Equal(t, testVal, es.At(i))
}
assert.Equal(t, 7, es.Len())
}
func TestScopeLogsSliceReadOnly(t *testing.T) {
sharedState := internal.NewState()
sharedState.MarkReadOnly()
es := newScopeLogsSlice(&[]*internal.ScopeLogs{}, sharedState)
assert.Equal(t, 0, es.Len())
assert.Panics(t, func() { es.AppendEmpty() })
assert.Panics(t, func() { es.EnsureCapacity(2) })
es2 := NewScopeLogsSlice()
es.CopyTo(es2)
assert.Panics(t, func() { es2.CopyTo(es) })
assert.Panics(t, func() { es.MoveAndAppendTo(es2) })
assert.Panics(t, func() { es2.MoveAndAppendTo(es) })
}
func TestScopeLogsSlice_CopyTo(t *testing.T) {
dest := NewScopeLogsSlice()
src := generateTestScopeLogsSlice()
src.CopyTo(dest)
assert.Equal(t, generateTestScopeLogsSlice(), dest)
dest.CopyTo(dest)
assert.Equal(t, generateTestScopeLogsSlice(), dest)
}
func TestScopeLogsSlice_EnsureCapacity(t *testing.T) {
es := generateTestScopeLogsSlice()
// Test ensure smaller capacity.
const ensureSmallLen = 4
es.EnsureCapacity(ensureSmallLen)
assert.Less(t, ensureSmallLen, es.Len())
assert.Equal(t, es.Len(), cap(*es.orig))
assert.Equal(t, generateTestScopeLogsSlice(), es)
// Test ensure larger capacity
const ensureLargeLen = 9
es.EnsureCapacity(ensureLargeLen)
assert.Less(t, generateTestScopeLogsSlice().Len(), ensureLargeLen)
assert.Equal(t, ensureLargeLen, cap(*es.orig))
assert.Equal(t, generateTestScopeLogsSlice(), es)
}
func TestScopeLogsSlice_MoveAndAppendTo(t *testing.T) {
// Test MoveAndAppendTo to empty
expectedSlice := generateTestScopeLogsSlice()
dest := NewScopeLogsSlice()
src := generateTestScopeLogsSlice()
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestScopeLogsSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo empty slice
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestScopeLogsSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo not empty slice
generateTestScopeLogsSlice().MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
dest.MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
}
func TestScopeLogsSlice_RemoveIf(t *testing.T) {
// Test RemoveIf on empty slice
emptySlice := NewScopeLogsSlice()
emptySlice.RemoveIf(func(el ScopeLogs) bool {
t.Fail()
return false
})
// Test RemoveIf
filtered := generateTestScopeLogsSlice()
pos := 0
filtered.RemoveIf(func(el ScopeLogs) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, 2, filtered.Len())
}
func TestScopeLogsSlice_RemoveIfAll(t *testing.T) {
got := generateTestScopeLogsSlice()
got.RemoveIf(func(el ScopeLogs) bool {
return true
})
assert.Equal(t, 0, got.Len())
}
func TestScopeLogsSliceAll(t *testing.T) {
ms := generateTestScopeLogsSlice()
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestScopeLogsSlice_Sort(t *testing.T) {
es := generateTestScopeLogsSlice()
es.Sort(func(a, b ScopeLogs) bool {
return uintptr(unsafe.Pointer(a.orig)) < uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Less(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
es.Sort(func(a, b ScopeLogs) bool {
return uintptr(unsafe.Pointer(a.orig)) > uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Greater(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
}
func generateTestScopeLogsSlice() ScopeLogsSlice {
ms := NewScopeLogsSlice()
*ms.orig = internal.GenTestScopeLogsPtrSlice()
return ms
}
================================================
FILE: pdata/plog/json.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package plog // import "go.opentelemetry.io/collector/pdata/plog"
import (
"slices"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/otlp"
)
// JSONMarshaler marshals Logs to JSON bytes using the OTLP/JSON format.
type JSONMarshaler struct{}
// MarshalLogs to the OTLP/JSON format.
func (*JSONMarshaler) MarshalLogs(ld Logs) ([]byte, error) {
dest := json.BorrowStream(nil)
defer json.ReturnStream(dest)
ld.getOrig().MarshalJSON(dest)
if dest.Error() != nil {
return nil, dest.Error()
}
return slices.Clone(dest.Buffer()), nil
}
var _ Unmarshaler = (*JSONUnmarshaler)(nil)
// JSONUnmarshaler unmarshals OTLP/JSON formatted-bytes to Logs.
type JSONUnmarshaler struct{}
// UnmarshalLogs from OTLP/JSON format into Logs.
func (*JSONUnmarshaler) UnmarshalLogs(buf []byte) (Logs, error) {
iter := json.BorrowIterator(buf)
defer json.ReturnIterator(iter)
ld := NewLogs()
ld.getOrig().UnmarshalJSON(iter)
if iter.Error() != nil {
return Logs{}, iter.Error()
}
otlp.MigrateLogs(ld.getOrig().ResourceLogs)
return ld, nil
}
================================================
FILE: pdata/plog/log_record_flags.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package plog // import "go.opentelemetry.io/collector/pdata/plog"
const isSampledMask = uint32(1)
var DefaultLogRecordFlags = LogRecordFlags(0)
// LogRecordFlags defines flags for the LogRecord. The 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.
type LogRecordFlags uint32
// IsSampled returns true if the LogRecordFlags contains the IsSampled flag.
func (ms LogRecordFlags) IsSampled() bool {
return uint32(ms)&isSampledMask != 0
}
// WithIsSampled returns a new LogRecordFlags, with the IsSampled flag set to the given value.
func (ms LogRecordFlags) WithIsSampled(b bool) LogRecordFlags {
orig := uint32(ms)
if b {
orig |= isSampledMask
} else {
orig &^= isSampledMask
}
return LogRecordFlags(orig)
}
================================================
FILE: pdata/plog/log_record_flags_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package plog
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestLogRecordFlags(t *testing.T) {
flags := LogRecordFlags(1)
assert.True(t, flags.IsSampled())
assert.EqualValues(t, uint32(1), flags)
flags = flags.WithIsSampled(false)
assert.False(t, flags.IsSampled())
assert.EqualValues(t, uint32(0), flags)
flags = flags.WithIsSampled(true)
assert.True(t, flags.IsSampled())
assert.EqualValues(t, uint32(1), flags)
}
func TestDefaultLogRecordFlags(t *testing.T) {
flags := DefaultLogRecordFlags
assert.False(t, flags.IsSampled())
assert.EqualValues(t, uint32(0), flags)
}
================================================
FILE: pdata/plog/logs.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package plog // import "go.opentelemetry.io/collector/pdata/plog"
// MarkReadOnly marks the Logs as shared so that no further modifications can be done on it.
func (ms Logs) MarkReadOnly() {
ms.getState().MarkReadOnly()
}
// IsReadOnly returns true if this Logs instance is read-only.
func (ms Logs) IsReadOnly() bool {
return ms.getState().IsReadOnly()
}
// LogRecordCount calculates the total number of log records.
func (ms Logs) LogRecordCount() int {
logCount := 0
rss := ms.ResourceLogs()
for i := 0; i < rss.Len(); i++ {
rs := rss.At(i)
ill := rs.ScopeLogs()
for i := 0; i < ill.Len(); i++ {
logs := ill.At(i)
logCount += logs.LogRecords().Len()
}
}
return logCount
}
================================================
FILE: pdata/plog/logs_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package plog
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestLogRecordCount(t *testing.T) {
logs := NewLogs()
assert.Equal(t, 0, logs.LogRecordCount())
rl := logs.ResourceLogs().AppendEmpty()
assert.Equal(t, 0, logs.LogRecordCount())
ill := rl.ScopeLogs().AppendEmpty()
assert.Equal(t, 0, logs.LogRecordCount())
ill.LogRecords().AppendEmpty()
assert.Equal(t, 1, logs.LogRecordCount())
rms := logs.ResourceLogs()
rms.EnsureCapacity(3)
rms.AppendEmpty().ScopeLogs().AppendEmpty()
illl := rms.AppendEmpty().ScopeLogs().AppendEmpty().LogRecords()
for range 5 {
illl.AppendEmpty()
}
// 5 + 1 (from rms.At(0) initialized first)
assert.Equal(t, 6, logs.LogRecordCount())
}
func TestLogRecordCountWithEmpty(t *testing.T) {
assert.Zero(t, NewLogs().LogRecordCount())
assert.Zero(t, newLogs(&internal.ExportLogsServiceRequest{
ResourceLogs: []*internal.ResourceLogs{{}},
}, new(internal.State)).LogRecordCount())
assert.Zero(t, newLogs(&internal.ExportLogsServiceRequest{
ResourceLogs: []*internal.ResourceLogs{
{
ScopeLogs: []*internal.ScopeLogs{{}},
},
},
}, new(internal.State)).LogRecordCount())
assert.Equal(t, 1, newLogs(&internal.ExportLogsServiceRequest{
ResourceLogs: []*internal.ResourceLogs{
{
ScopeLogs: []*internal.ScopeLogs{
{
LogRecords: []*internal.LogRecord{{}},
},
},
},
},
}, new(internal.State)).LogRecordCount())
}
func TestReadOnlyLogsInvalidUsage(t *testing.T) {
ld := NewLogs()
assert.False(t, ld.IsReadOnly())
res := ld.ResourceLogs().AppendEmpty().Resource()
res.Attributes().PutStr("k1", "v1")
ld.MarkReadOnly()
assert.True(t, ld.IsReadOnly())
assert.Panics(t, func() { res.Attributes().PutStr("k2", "v2") })
}
func BenchmarkLogsUsage(b *testing.B) {
ld := generateTestLogs()
ts := pcommon.NewTimestampFromTime(time.Now())
b.ReportAllocs()
for b.Loop() {
for i := 0; i < ld.ResourceLogs().Len(); i++ {
rl := ld.ResourceLogs().At(i)
res := rl.Resource()
res.Attributes().PutStr("foo", "bar")
v, ok := res.Attributes().Get("foo")
assert.True(b, ok)
assert.Equal(b, "bar", v.Str())
v.SetStr("new-bar")
assert.Equal(b, "new-bar", v.Str())
res.Attributes().Remove("foo")
for j := 0; j < rl.ScopeLogs().Len(); j++ {
sl := rl.ScopeLogs().At(j)
sl.Scope().SetName("new_test_name")
assert.Equal(b, "new_test_name", sl.Scope().Name())
for k := 0; k < sl.LogRecords().Len(); k++ {
lr := sl.LogRecords().At(k)
lr.Body().SetStr("new_body")
assert.Equal(b, "new_body", lr.Body().Str())
lr.SetTimestamp(ts)
assert.Equal(b, ts, lr.Timestamp())
}
lr := sl.LogRecords().AppendEmpty()
lr.Body().SetStr("another_log_record")
lr.SetTimestamp(ts)
lr.SetObservedTimestamp(ts)
lr.SetSeverityText("info")
lr.SetSeverityNumber(SeverityNumberInfo)
lr.Attributes().PutStr("foo", "bar")
lr.SetSpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8})
sl.LogRecords().RemoveIf(func(lr LogRecord) bool {
return lr.Body().Str() == "another_log_record"
})
}
}
}
}
func BenchmarkLogsMarshalJSON(b *testing.B) {
ld := generateTestLogs()
encoder := &JSONMarshaler{}
b.ReportAllocs()
for b.Loop() {
jsonBuf, err := encoder.MarshalLogs(ld)
require.NoError(b, err)
require.NotNil(b, jsonBuf)
}
}
================================================
FILE: pdata/plog/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package plog
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: pdata/plog/pb.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package plog // import "go.opentelemetry.io/collector/pdata/plog"
var _ MarshalSizer = (*ProtoMarshaler)(nil)
type ProtoMarshaler struct{}
func (e *ProtoMarshaler) MarshalLogs(ld Logs) ([]byte, error) {
size := ld.getOrig().SizeProto()
buf := make([]byte, size)
_ = ld.getOrig().MarshalProto(buf)
return buf, nil
}
func (e *ProtoMarshaler) LogsSize(ld Logs) int {
return ld.getOrig().SizeProto()
}
func (e *ProtoMarshaler) ResourceLogsSize(ld ResourceLogs) int {
return ld.orig.SizeProto()
}
func (e *ProtoMarshaler) ScopeLogsSize(ld ScopeLogs) int {
return ld.orig.SizeProto()
}
func (e *ProtoMarshaler) LogRecordSize(ld LogRecord) int {
return ld.orig.SizeProto()
}
var _ Unmarshaler = (*ProtoUnmarshaler)(nil)
type ProtoUnmarshaler struct{}
func (d *ProtoUnmarshaler) UnmarshalLogs(buf []byte) (Logs, error) {
ld := NewLogs()
err := ld.getOrig().UnmarshalProto(buf)
if err != nil {
return Logs{}, err
}
return ld, nil
}
================================================
FILE: pdata/plog/pb_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package plog
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlplogs "go.opentelemetry.io/proto/slim/otlp/logs/v1"
goproto "google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestLogsProtoWireCompatibility(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 Logs as pdata struct.
td := generateTestLogs()
// Marshal its underlying ProtoBuf to wire.
marshaler := &ProtoMarshaler{}
wire1, err := marshaler.MarshalLogs(td)
require.NoError(t, err)
assert.NotNil(t, wire1)
// Unmarshal from the wire to OTLP Protobuf in goproto's representation.
var goprotoMessage gootlplogs.LogsData
err = goproto.Unmarshal(wire1, &goprotoMessage)
require.NoError(t, err)
// Marshal to the wire again.
wire2, err := goproto.Marshal(&goprotoMessage)
require.NoError(t, err)
assert.NotNil(t, wire2)
// Unmarshal from the wire into gogoproto's representation.
var td2 Logs
unmarshaler := &ProtoUnmarshaler{}
td2, err = unmarshaler.UnmarshalLogs(wire2)
require.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.Equal(t, td, td2)
}
func TestProtoLogsUnmarshalerError(t *testing.T) {
p := &ProtoUnmarshaler{}
_, err := p.UnmarshalLogs([]byte("+$%"))
assert.Error(t, err)
}
func TestProtoSizer(t *testing.T) {
marshaler := &ProtoMarshaler{}
ld := NewLogs()
ld.ResourceLogs().AppendEmpty().ScopeLogs().AppendEmpty().LogRecords().AppendEmpty().SetSeverityText("error")
size := marshaler.LogsSize(ld)
bytes, err := marshaler.MarshalLogs(ld)
require.NoError(t, err)
assert.Equal(t, len(bytes), size)
}
func TestProtoSizerEmptyLogs(t *testing.T) {
sizer := &ProtoMarshaler{}
assert.Equal(t, 0, sizer.LogsSize(NewLogs()))
}
func BenchmarkLogsToProto2k(b *testing.B) {
marshaler := &ProtoMarshaler{}
logs := generateBenchmarkLogs(2_000)
for b.Loop() {
buf, err := marshaler.MarshalLogs(logs)
require.NoError(b, err)
assert.NotEmpty(b, buf)
}
}
func BenchmarkLogsFromProto2k(b *testing.B) {
marshaler := &ProtoMarshaler{}
unmarshaler := &ProtoUnmarshaler{}
baseLogs := generateBenchmarkLogs(2_000)
buf, err := marshaler.MarshalLogs(baseLogs)
require.NoError(b, err)
assert.NotEmpty(b, buf)
b.ReportAllocs()
for b.Loop() {
logs, err := unmarshaler.UnmarshalLogs(buf)
require.NoError(b, err)
assert.Equal(b, baseLogs.ResourceLogs().Len(), logs.ResourceLogs().Len())
}
}
func generateBenchmarkLogs(logsCount int) Logs {
endTime := pcommon.NewTimestampFromTime(time.Now())
md := NewLogs()
ilm := md.ResourceLogs().AppendEmpty().ScopeLogs().AppendEmpty()
ilm.LogRecords().EnsureCapacity(logsCount)
for range logsCount {
im := ilm.LogRecords().AppendEmpty()
im.SetTimestamp(endTime)
}
return md
}
================================================
FILE: pdata/plog/plogotlp/fuzz_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package plogotlp // import "go.opentelemetry.io/collector/pdata/plog/plogotlp"
import (
"bytes"
"testing"
"github.com/stretchr/testify/require"
)
var unexpectedBytes = "expected the same bytes from unmarshaling and marshaling."
func FuzzRequestUnmarshalJSON(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
er := NewExportRequest()
err := er.UnmarshalJSON(data)
if err != nil {
return
}
b1, err := er.MarshalJSON()
require.NoError(t, err, "failed to marshal valid struct")
er = NewExportRequest()
require.NoError(t, er.UnmarshalJSON(b1), "failed to unmarshal valid bytes")
b2, err := er.MarshalJSON()
require.NoError(t, err, "failed to marshal valid struct")
require.True(t, bytes.Equal(b1, b2), "%s. \nexpected %d but got %d\n", unexpectedBytes, b1, b2)
})
}
func FuzzResponseUnmarshalJSON(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
er := NewExportResponse()
err := er.UnmarshalJSON(data)
if err != nil {
return
}
b1, err := er.MarshalJSON()
require.NoError(t, err, "failed to marshal valid struct")
er = NewExportResponse()
require.NoError(t, er.UnmarshalJSON(b1), "failed to unmarshal valid bytes")
b2, err := er.MarshalJSON()
require.NoError(t, err, "failed to marshal valid struct")
require.True(t, bytes.Equal(b1, b2), "%s. \nexpected %d but got %d\n", unexpectedBytes, b1, b2)
})
}
func FuzzRequestUnmarshalProto(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
er := NewExportRequest()
err := er.UnmarshalProto(data)
if err != nil {
return
}
b1, err := er.MarshalProto()
require.NoError(t, err, "failed to marshal valid struct")
er = NewExportRequest()
require.NoError(t, er.UnmarshalProto(b1), "failed to unmarshal valid bytes")
b2, err := er.MarshalProto()
require.NoError(t, err, "failed to marshal valid struct")
require.True(t, bytes.Equal(b1, b2), "%s. \nexpected %d but got %d\n", unexpectedBytes, b1, b2)
})
}
func FuzzResponseUnmarshalProto(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
er := NewExportResponse()
err := er.UnmarshalProto(data)
if err != nil {
return
}
b1, err := er.MarshalProto()
require.NoError(t, err, "failed to marshal valid struct")
er = NewExportResponse()
require.NoError(t, er.UnmarshalProto(b1), "failed to unmarshal valid bytes")
b2, err := er.MarshalProto()
require.NoError(t, err, "failed to marshal valid struct")
require.True(t, bytes.Equal(b1, b2), "%s. \nexpected %d but got %d\n", unexpectedBytes, b1, b2)
})
}
================================================
FILE: pdata/plog/plogotlp/generated_exportpartialsuccess.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package plogotlp
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// ExportPartialSuccess represents the details of a partially successful export request.
//
// This is a reference type, if passed by value and callee modifies it the
// caller will see the modification.
//
// Must use NewExportPartialSuccess function to create new instances.
// Important: zero-initialized instance is not valid for use.
type ExportPartialSuccess struct {
orig *internal.ExportLogsPartialSuccess
state *internal.State
}
func newExportPartialSuccess(orig *internal.ExportLogsPartialSuccess, state *internal.State) ExportPartialSuccess {
return ExportPartialSuccess{orig: orig, state: state}
}
// NewExportPartialSuccess creates a new empty ExportPartialSuccess.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewExportPartialSuccess() ExportPartialSuccess {
return newExportPartialSuccess(internal.NewExportLogsPartialSuccess(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms ExportPartialSuccess) MoveTo(dest ExportPartialSuccess) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteExportLogsPartialSuccess(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// RejectedLogRecords returns the rejectedlogrecords associated with this ExportPartialSuccess.
func (ms ExportPartialSuccess) RejectedLogRecords() int64 {
return ms.orig.RejectedLogRecords
}
// SetRejectedLogRecords replaces the rejectedlogrecords associated with this ExportPartialSuccess.
func (ms ExportPartialSuccess) SetRejectedLogRecords(v int64) {
ms.state.AssertMutable()
ms.orig.RejectedLogRecords = v
}
// ErrorMessage returns the errormessage associated with this ExportPartialSuccess.
func (ms ExportPartialSuccess) ErrorMessage() string {
return ms.orig.ErrorMessage
}
// SetErrorMessage replaces the errormessage associated with this ExportPartialSuccess.
func (ms ExportPartialSuccess) SetErrorMessage(v string) {
ms.state.AssertMutable()
ms.orig.ErrorMessage = v
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms ExportPartialSuccess) CopyTo(dest ExportPartialSuccess) {
dest.state.AssertMutable()
internal.CopyExportLogsPartialSuccess(dest.orig, ms.orig)
}
================================================
FILE: pdata/plog/plogotlp/generated_exportpartialsuccess_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package plogotlp
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestExportPartialSuccess_MoveTo(t *testing.T) {
ms := generateTestExportPartialSuccess()
dest := NewExportPartialSuccess()
ms.MoveTo(dest)
assert.Equal(t, NewExportPartialSuccess(), ms)
assert.Equal(t, generateTestExportPartialSuccess(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestExportPartialSuccess(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newExportPartialSuccess(internal.NewExportLogsPartialSuccess(), sharedState)) })
assert.Panics(t, func() { newExportPartialSuccess(internal.NewExportLogsPartialSuccess(), sharedState).MoveTo(dest) })
}
func TestExportPartialSuccess_CopyTo(t *testing.T) {
ms := NewExportPartialSuccess()
orig := NewExportPartialSuccess()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestExportPartialSuccess()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newExportPartialSuccess(internal.NewExportLogsPartialSuccess(), sharedState)) })
}
func TestExportPartialSuccess_RejectedLogRecords(t *testing.T) {
ms := NewExportPartialSuccess()
assert.Equal(t, int64(0), ms.RejectedLogRecords())
ms.SetRejectedLogRecords(int64(13))
assert.Equal(t, int64(13), ms.RejectedLogRecords())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() {
newExportPartialSuccess(internal.NewExportLogsPartialSuccess(), sharedState).SetRejectedLogRecords(int64(13))
})
}
func TestExportPartialSuccess_ErrorMessage(t *testing.T) {
ms := NewExportPartialSuccess()
assert.Empty(t, ms.ErrorMessage())
ms.SetErrorMessage("test_errormessage")
assert.Equal(t, "test_errormessage", ms.ErrorMessage())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() {
newExportPartialSuccess(internal.NewExportLogsPartialSuccess(), sharedState).SetErrorMessage("test_errormessage")
})
}
func generateTestExportPartialSuccess() ExportPartialSuccess {
return newExportPartialSuccess(internal.GenTestExportLogsPartialSuccess(), internal.NewState())
}
================================================
FILE: pdata/plog/plogotlp/generated_exportresponse.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package plogotlp
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// ExportResponse represents the response for gRPC/HTTP client/server.
//
// This is a reference type, if passed by value and callee modifies it the
// caller will see the modification.
//
// Must use NewExportResponse function to create new instances.
// Important: zero-initialized instance is not valid for use.
type ExportResponse struct {
orig *internal.ExportLogsServiceResponse
state *internal.State
}
func newExportResponse(orig *internal.ExportLogsServiceResponse, state *internal.State) ExportResponse {
return ExportResponse{orig: orig, state: state}
}
// NewExportResponse creates a new empty ExportResponse.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewExportResponse() ExportResponse {
return newExportResponse(internal.NewExportLogsServiceResponse(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms ExportResponse) MoveTo(dest ExportResponse) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteExportLogsServiceResponse(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// PartialSuccess returns the partialsuccess associated with this ExportResponse.
func (ms ExportResponse) PartialSuccess() ExportPartialSuccess {
return newExportPartialSuccess(&ms.orig.PartialSuccess, ms.state)
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms ExportResponse) CopyTo(dest ExportResponse) {
dest.state.AssertMutable()
internal.CopyExportLogsServiceResponse(dest.orig, ms.orig)
}
================================================
FILE: pdata/plog/plogotlp/generated_exportresponse_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package plogotlp
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestExportResponse_MoveTo(t *testing.T) {
ms := generateTestExportResponse()
dest := NewExportResponse()
ms.MoveTo(dest)
assert.Equal(t, NewExportResponse(), ms)
assert.Equal(t, generateTestExportResponse(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestExportResponse(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newExportResponse(internal.NewExportLogsServiceResponse(), sharedState)) })
assert.Panics(t, func() { newExportResponse(internal.NewExportLogsServiceResponse(), sharedState).MoveTo(dest) })
}
func TestExportResponse_CopyTo(t *testing.T) {
ms := NewExportResponse()
orig := NewExportResponse()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestExportResponse()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newExportResponse(internal.NewExportLogsServiceResponse(), sharedState)) })
}
func TestExportResponse_PartialSuccess(t *testing.T) {
ms := NewExportResponse()
assert.Equal(t, NewExportPartialSuccess(), ms.PartialSuccess())
ms.orig.PartialSuccess = *internal.GenTestExportLogsPartialSuccess()
assert.Equal(t, generateTestExportPartialSuccess(), ms.PartialSuccess())
}
func generateTestExportResponse() ExportResponse {
return newExportResponse(internal.GenTestExportLogsServiceResponse(), internal.NewState())
}
================================================
FILE: pdata/plog/plogotlp/grpc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package plogotlp // import "go.opentelemetry.io/collector/pdata/plog/plogotlp"
import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/internal/otelgrpc"
"go.opentelemetry.io/collector/pdata/internal/otlp"
)
// GRPCClient is the client API for OTLP-GRPC Logs service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type GRPCClient interface {
// Export plog.Logs to the server.
//
// For performance reasons, it is recommended to keep this RPC
// alive for the entire life of the application.
Export(ctx context.Context, request ExportRequest, opts ...grpc.CallOption) (ExportResponse, error)
// unexported disallow implementation of the GRPCClient.
unexported()
}
// NewGRPCClient returns a new GRPCClient connected using the given connection.
func NewGRPCClient(cc *grpc.ClientConn) GRPCClient {
return &grpcClient{rawClient: otelgrpc.NewLogsServiceClient(cc)}
}
type grpcClient struct {
rawClient otelgrpc.LogsServiceClient
}
func (c *grpcClient) Export(ctx context.Context, request ExportRequest, opts ...grpc.CallOption) (ExportResponse, error) {
rsp, err := c.rawClient.Export(ctx, request.orig, opts...)
if err != nil {
return ExportResponse{}, err
}
return ExportResponse{orig: rsp, state: internal.NewState()}, err
}
func (c *grpcClient) unexported() {}
// GRPCServer is the server API for OTLP gRPC LogsService service.
// Implementations MUST embed UnimplementedGRPCServer.
type GRPCServer interface {
// Export is called every time a new request is received.
//
// For performance reasons, it is recommended to keep this RPC
// alive for the entire life of the application.
Export(context.Context, ExportRequest) (ExportResponse, error)
// unexported disallow implementation of the GRPCServer.
unexported()
}
var _ GRPCServer = (*UnimplementedGRPCServer)(nil)
// UnimplementedGRPCServer MUST be embedded to have forward compatible implementations.
type UnimplementedGRPCServer struct{}
func (*UnimplementedGRPCServer) Export(context.Context, ExportRequest) (ExportResponse, error) {
return ExportResponse{}, status.Errorf(codes.Unimplemented, "method Export not implemented")
}
func (*UnimplementedGRPCServer) unexported() {}
// RegisterGRPCServer registers the Server to the grpc.Server.
func RegisterGRPCServer(s *grpc.Server, srv GRPCServer) {
otelgrpc.RegisterLogsServiceServer(s, &rawLogsServer{srv: srv})
}
type rawLogsServer struct {
srv GRPCServer
}
func (s rawLogsServer) Export(ctx context.Context, request *internal.ExportLogsServiceRequest) (*internal.ExportLogsServiceResponse, error) {
otlp.MigrateLogs(request.ResourceLogs)
rsp, err := s.srv.Export(ctx, ExportRequest{orig: request, state: internal.NewState()})
return rsp.orig, err
}
================================================
FILE: pdata/plog/plogotlp/grpc_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package plogotlp
import (
"context"
"errors"
"net"
"sync"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/resolver"
"google.golang.org/grpc/status"
"google.golang.org/grpc/test/bufconn"
"go.opentelemetry.io/collector/pdata/plog"
)
func TestGrpc(t *testing.T) {
lis := bufconn.Listen(1024 * 1024)
s := grpc.NewServer()
RegisterGRPCServer(s, &fakeLogsServer{t: t})
wg := sync.WaitGroup{}
wg.Go(func() {
assert.NoError(t, s.Serve(lis))
})
t.Cleanup(func() {
s.Stop()
wg.Wait()
})
resolver.SetDefaultScheme("passthrough")
cc, err := grpc.NewClient("bufnet",
grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
return lis.Dial()
}),
grpc.WithTransportCredentials(insecure.NewCredentials()))
require.NoError(t, err)
t.Cleanup(func() {
assert.NoError(t, cc.Close())
})
logClient := NewGRPCClient(cc)
resp, err := logClient.Export(context.Background(), generateLogsRequest())
require.NoError(t, err)
assert.Equal(t, NewExportResponse(), resp)
}
func TestGrpcError(t *testing.T) {
lis := bufconn.Listen(1024 * 1024)
s := grpc.NewServer()
RegisterGRPCServer(s, &fakeLogsServer{t: t, err: errors.New("my error")})
wg := sync.WaitGroup{}
wg.Go(func() {
assert.NoError(t, s.Serve(lis))
})
t.Cleanup(func() {
s.Stop()
wg.Wait()
})
cc, err := grpc.NewClient("bufnet",
grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
return lis.Dial()
}),
grpc.WithTransportCredentials(insecure.NewCredentials()))
require.NoError(t, err)
t.Cleanup(func() {
assert.NoError(t, cc.Close())
})
logClient := NewGRPCClient(cc)
resp, err := logClient.Export(context.Background(), generateLogsRequest())
require.Error(t, err)
st, okSt := status.FromError(err)
require.True(t, okSt)
assert.Equal(t, "my error", st.Message())
assert.Equal(t, codes.Unknown, st.Code())
assert.Equal(t, ExportResponse{}, resp)
}
type fakeLogsServer struct {
UnimplementedGRPCServer
t *testing.T
err error
}
func (f fakeLogsServer) Export(_ context.Context, request ExportRequest) (ExportResponse, error) {
assert.Equal(f.t, generateLogsRequest(), request)
return NewExportResponse(), f.err
}
func generateLogsRequest() ExportRequest {
ld := plog.NewLogs()
ld.ResourceLogs().AppendEmpty().ScopeLogs().AppendEmpty().LogRecords().AppendEmpty().Body().SetStr("test_log_record")
return NewExportRequestFromLogs(ld)
}
================================================
FILE: pdata/plog/plogotlp/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package plogotlp
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: pdata/plog/plogotlp/request.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package plogotlp // import "go.opentelemetry.io/collector/pdata/plog/plogotlp"
import (
"slices"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/otlp"
"go.opentelemetry.io/collector/pdata/plog"
)
// ExportRequest represents the request for gRPC/HTTP client/server.
// It's a wrapper for plog.Logs data.
type ExportRequest struct {
orig *internal.ExportLogsServiceRequest
state *internal.State
}
// NewExportRequest returns an empty ExportRequest.
func NewExportRequest() ExportRequest {
return ExportRequest{
orig: &internal.ExportLogsServiceRequest{},
state: internal.NewState(),
}
}
// NewExportRequestFromLogs returns a ExportRequest from plog.Logs.
// Because ExportRequest is a wrapper for plog.Logs,
// any changes to the provided Logs struct will be reflected in the ExportRequest and vice versa.
func NewExportRequestFromLogs(ld plog.Logs) ExportRequest {
return ExportRequest{
orig: internal.GetLogsOrig(internal.LogsWrapper(ld)),
state: internal.GetLogsState(internal.LogsWrapper(ld)),
}
}
// MarshalProto marshals ExportRequest into proto bytes.
func (ms ExportRequest) MarshalProto() ([]byte, error) {
size := ms.orig.SizeProto()
buf := make([]byte, size)
_ = ms.orig.MarshalProto(buf)
return buf, nil
}
// UnmarshalProto unmarshalls ExportRequest from proto bytes.
func (ms ExportRequest) UnmarshalProto(data []byte) error {
err := ms.orig.UnmarshalProto(data)
if err != nil {
return err
}
otlp.MigrateLogs(ms.orig.ResourceLogs)
return nil
}
// MarshalJSON marshals ExportRequest into JSON bytes.
func (ms ExportRequest) MarshalJSON() ([]byte, error) {
dest := json.BorrowStream(nil)
defer json.ReturnStream(dest)
ms.orig.MarshalJSON(dest)
if dest.Error() != nil {
return nil, dest.Error()
}
return slices.Clone(dest.Buffer()), nil
}
// UnmarshalJSON unmarshalls ExportRequest from JSON bytes.
func (ms ExportRequest) UnmarshalJSON(data []byte) error {
iter := json.BorrowIterator(data)
defer json.ReturnIterator(iter)
ms.orig.UnmarshalJSON(iter)
return iter.Error()
}
func (ms ExportRequest) Logs() plog.Logs {
return plog.Logs(internal.NewLogsWrapper(ms.orig, ms.state))
}
================================================
FILE: pdata/plog/plogotlp/request_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package plogotlp
import (
"encoding/json"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpcollectorlogs "go.opentelemetry.io/proto/slim/otlp/collector/logs/v1"
goproto "google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/internal/otlp"
"go.opentelemetry.io/collector/pdata/plog"
)
var (
_ json.Unmarshaler = ExportRequest{}
_ json.Marshaler = ExportRequest{}
)
var logsRequestJSON = []byte(`
{
"resourceLogs": [
{
"resource": {},
"scopeLogs": [
{
"scope": {},
"logRecords": [
{
"body": {
"stringValue": "test_log_record"
}
}
]
}
]
}
]
}`)
func TestRequestToPData(t *testing.T) {
tr := NewExportRequest()
assert.Equal(t, 0, tr.Logs().LogRecordCount())
tr.Logs().ResourceLogs().AppendEmpty().ScopeLogs().AppendEmpty().LogRecords().AppendEmpty()
assert.Equal(t, 1, tr.Logs().LogRecordCount())
}
func TestRequestJSON(t *testing.T) {
lr := NewExportRequest()
require.NoError(t, lr.UnmarshalJSON(logsRequestJSON))
assert.Equal(t, "test_log_record", lr.Logs().ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().At(0).Body().AsString())
got, err := lr.MarshalJSON()
require.NoError(t, err)
assert.Equal(t, strings.Join(strings.Fields(string(logsRequestJSON)), ""), string(got))
}
func TestLogsProtoWireCompatibility(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 Logs as pdata struct.
ld := NewExportRequestFromLogs(plog.Logs(internal.GenTestLogsWrapper()))
// Marshal its underlying ProtoBuf to wire.
wire1, err := ld.MarshalProto()
require.NoError(t, err)
assert.NotNil(t, wire1)
// Unmarshal from the wire to OTLP Protobuf in goproto's representation.
var goprotoMessage gootlpcollectorlogs.ExportLogsServiceRequest
err = goproto.Unmarshal(wire1, &goprotoMessage)
require.NoError(t, err)
// Marshal to the wire again.
wire2, err := goproto.Marshal(&goprotoMessage)
require.NoError(t, err)
assert.NotNil(t, wire2)
// Unmarshal from the wire into gogoproto's representation.
ld2 := NewExportRequest()
err = ld2.UnmarshalProto(wire2)
require.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.
// Migration logic will run, so run it on the original message as well.
otlp.MigrateLogs(ld.orig.ResourceLogs)
assert.Equal(t, ld, ld2)
}
================================================
FILE: pdata/plog/plogotlp/response.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package plogotlp // import "go.opentelemetry.io/collector/pdata/plog/plogotlp"
import (
"slices"
"go.opentelemetry.io/collector/pdata/internal/json"
)
// MarshalProto marshals ExportResponse into proto bytes.
func (ms ExportResponse) MarshalProto() ([]byte, error) {
size := ms.orig.SizeProto()
buf := make([]byte, size)
_ = ms.orig.MarshalProto(buf)
return buf, nil
}
// UnmarshalProto unmarshalls ExportResponse from proto bytes.
func (ms ExportResponse) UnmarshalProto(data []byte) error {
return ms.orig.UnmarshalProto(data)
}
// MarshalJSON marshals ExportResponse into JSON bytes.
func (ms ExportResponse) MarshalJSON() ([]byte, error) {
dest := json.BorrowStream(nil)
defer json.ReturnStream(dest)
ms.orig.MarshalJSON(dest)
return slices.Clone(dest.Buffer()), dest.Error()
}
// UnmarshalJSON unmarshalls ExportResponse from JSON bytes.
func (ms ExportResponse) UnmarshalJSON(data []byte) error {
iter := json.BorrowIterator(data)
defer json.ReturnIterator(iter)
ms.orig.UnmarshalJSON(iter)
return iter.Error()
}
================================================
FILE: pdata/plog/plogotlp/response_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package plogotlp
import (
stdjson "encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var (
_ stdjson.Unmarshaler = ExportResponse{}
_ stdjson.Marshaler = ExportResponse{}
)
func TestExportResponseJSON(t *testing.T) {
jsonStr := `{"partialSuccess": {"rejectedLogRecords":"1", "errorMessage":"nothing"}}`
val := NewExportResponse()
require.NoError(t, val.UnmarshalJSON([]byte(jsonStr)))
expected := NewExportResponse()
expected.PartialSuccess().SetRejectedLogRecords(1)
expected.PartialSuccess().SetErrorMessage("nothing")
assert.Equal(t, expected, val)
buf, err := val.MarshalJSON()
require.NoError(t, err)
assert.JSONEq(t, jsonStr, string(buf))
}
func TestUnmarshalJSONExportResponse(t *testing.T) {
jsonStr := `{"extra":"", "partialSuccess": {"extra":""}}`
val := NewExportResponse()
require.NoError(t, val.UnmarshalJSON([]byte(jsonStr)))
assert.Equal(t, NewExportResponse(), val)
}
================================================
FILE: pdata/plog/severity_number.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package plog // import "go.opentelemetry.io/collector/pdata/plog"
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// SeverityNumber represents severity number of a log record.
type SeverityNumber int32
const (
SeverityNumberUnspecified = SeverityNumber(internal.SeverityNumber_SEVERITY_NUMBER_UNSPECIFIED)
SeverityNumberTrace = SeverityNumber(internal.SeverityNumber_SEVERITY_NUMBER_TRACE)
SeverityNumberTrace2 = SeverityNumber(internal.SeverityNumber_SEVERITY_NUMBER_TRACE2)
SeverityNumberTrace3 = SeverityNumber(internal.SeverityNumber_SEVERITY_NUMBER_TRACE3)
SeverityNumberTrace4 = SeverityNumber(internal.SeverityNumber_SEVERITY_NUMBER_TRACE4)
SeverityNumberDebug = SeverityNumber(internal.SeverityNumber_SEVERITY_NUMBER_DEBUG)
SeverityNumberDebug2 = SeverityNumber(internal.SeverityNumber_SEVERITY_NUMBER_DEBUG2)
SeverityNumberDebug3 = SeverityNumber(internal.SeverityNumber_SEVERITY_NUMBER_DEBUG3)
SeverityNumberDebug4 = SeverityNumber(internal.SeverityNumber_SEVERITY_NUMBER_DEBUG4)
SeverityNumberInfo = SeverityNumber(internal.SeverityNumber_SEVERITY_NUMBER_INFO)
SeverityNumberInfo2 = SeverityNumber(internal.SeverityNumber_SEVERITY_NUMBER_INFO2)
SeverityNumberInfo3 = SeverityNumber(internal.SeverityNumber_SEVERITY_NUMBER_INFO3)
SeverityNumberInfo4 = SeverityNumber(internal.SeverityNumber_SEVERITY_NUMBER_INFO4)
SeverityNumberWarn = SeverityNumber(internal.SeverityNumber_SEVERITY_NUMBER_WARN)
SeverityNumberWarn2 = SeverityNumber(internal.SeverityNumber_SEVERITY_NUMBER_WARN2)
SeverityNumberWarn3 = SeverityNumber(internal.SeverityNumber_SEVERITY_NUMBER_WARN3)
SeverityNumberWarn4 = SeverityNumber(internal.SeverityNumber_SEVERITY_NUMBER_WARN4)
SeverityNumberError = SeverityNumber(internal.SeverityNumber_SEVERITY_NUMBER_ERROR)
SeverityNumberError2 = SeverityNumber(internal.SeverityNumber_SEVERITY_NUMBER_ERROR2)
SeverityNumberError3 = SeverityNumber(internal.SeverityNumber_SEVERITY_NUMBER_ERROR3)
SeverityNumberError4 = SeverityNumber(internal.SeverityNumber_SEVERITY_NUMBER_ERROR4)
SeverityNumberFatal = SeverityNumber(internal.SeverityNumber_SEVERITY_NUMBER_FATAL)
SeverityNumberFatal2 = SeverityNumber(internal.SeverityNumber_SEVERITY_NUMBER_FATAL2)
SeverityNumberFatal3 = SeverityNumber(internal.SeverityNumber_SEVERITY_NUMBER_FATAL3)
SeverityNumberFatal4 = SeverityNumber(internal.SeverityNumber_SEVERITY_NUMBER_FATAL4)
)
// String returns the string representation of the SeverityNumber.
func (sn SeverityNumber) String() string {
switch sn {
case SeverityNumberUnspecified:
return "Unspecified"
case SeverityNumberTrace:
return "Trace"
case SeverityNumberTrace2:
return "Trace2"
case SeverityNumberTrace3:
return "Trace3"
case SeverityNumberTrace4:
return "Trace4"
case SeverityNumberDebug:
return "Debug"
case SeverityNumberDebug2:
return "Debug2"
case SeverityNumberDebug3:
return "Debug3"
case SeverityNumberDebug4:
return "Debug4"
case SeverityNumberInfo:
return "Info"
case SeverityNumberInfo2:
return "Info2"
case SeverityNumberInfo3:
return "Info3"
case SeverityNumberInfo4:
return "Info4"
case SeverityNumberWarn:
return "Warn"
case SeverityNumberWarn2:
return "Warn2"
case SeverityNumberWarn3:
return "Warn3"
case SeverityNumberWarn4:
return "Warn4"
case SeverityNumberError:
return "Error"
case SeverityNumberError2:
return "Error2"
case SeverityNumberError3:
return "Error3"
case SeverityNumberError4:
return "Error4"
case SeverityNumberFatal:
return "Fatal"
case SeverityNumberFatal2:
return "Fatal2"
case SeverityNumberFatal3:
return "Fatal3"
case SeverityNumberFatal4:
return "Fatal4"
}
return ""
}
================================================
FILE: pdata/plog/severity_number_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package plog // import "go.opentelemetry.io/collector/pdata/plog"
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSeverityNumberString(t *testing.T) {
assert.Equal(t, "Unspecified", SeverityNumberUnspecified.String())
assert.Equal(t, "Trace", SeverityNumberTrace.String())
assert.Equal(t, "Trace2", SeverityNumberTrace2.String())
assert.Equal(t, "Trace3", SeverityNumberTrace3.String())
assert.Equal(t, "Trace4", SeverityNumberTrace4.String())
assert.Equal(t, "Debug", SeverityNumberDebug.String())
assert.Equal(t, "Debug2", SeverityNumberDebug2.String())
assert.Equal(t, "Debug3", SeverityNumberDebug3.String())
assert.Equal(t, "Debug4", SeverityNumberDebug4.String())
assert.Equal(t, "Info", SeverityNumberInfo.String())
assert.Equal(t, "Info2", SeverityNumberInfo2.String())
assert.Equal(t, "Info3", SeverityNumberInfo3.String())
assert.Equal(t, "Info4", SeverityNumberInfo4.String())
assert.Equal(t, "Warn", SeverityNumberWarn.String())
assert.Equal(t, "Warn2", SeverityNumberWarn2.String())
assert.Equal(t, "Warn3", SeverityNumberWarn3.String())
assert.Equal(t, "Warn4", SeverityNumberWarn4.String())
assert.Equal(t, "Error", SeverityNumberError.String())
assert.Equal(t, "Error2", SeverityNumberError2.String())
assert.Equal(t, "Error3", SeverityNumberError3.String())
assert.Equal(t, "Error4", SeverityNumberError4.String())
assert.Equal(t, "Fatal", SeverityNumberFatal.String())
assert.Equal(t, "Fatal2", SeverityNumberFatal2.String())
assert.Equal(t, "Fatal3", SeverityNumberFatal3.String())
assert.Equal(t, "Fatal4", SeverityNumberFatal4.String())
assert.Empty(t, SeverityNumber(100).String())
}
================================================
FILE: pdata/pmetric/aggregation_temporality.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pmetric // import "go.opentelemetry.io/collector/pdata/pmetric"
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// 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 (
// AggregationTemporalityUnspecified is the default AggregationTemporality, it MUST NOT be used.
AggregationTemporalityUnspecified = AggregationTemporality(internal.AggregationTemporality_AGGREGATION_TEMPORALITY_UNSPECIFIED)
// AggregationTemporalityDelta is a AggregationTemporality for a metric aggregator which reports changes since last report time.
AggregationTemporalityDelta = AggregationTemporality(internal.AggregationTemporality_AGGREGATION_TEMPORALITY_DELTA)
// AggregationTemporalityCumulative is a AggregationTemporality for a metric aggregator which reports changes since a fixed start time.
AggregationTemporalityCumulative = AggregationTemporality(internal.AggregationTemporality_AGGREGATION_TEMPORALITY_CUMULATIVE)
)
// String returns the string representation of the AggregationTemporality.
func (at AggregationTemporality) String() string {
switch at {
case AggregationTemporalityUnspecified:
return "Unspecified"
case AggregationTemporalityDelta:
return "Delta"
case AggregationTemporalityCumulative:
return "Cumulative"
}
return ""
}
================================================
FILE: pdata/pmetric/aggregation_temporality_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pmetric // import "go.opentelemetry.io/collector/pdata/pmetric"
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestAggregationTemporalityString(t *testing.T) {
assert.Equal(t, "Unspecified", AggregationTemporalityUnspecified.String())
assert.Equal(t, "Delta", AggregationTemporalityDelta.String())
assert.Equal(t, "Cumulative", AggregationTemporalityCumulative.String())
assert.Empty(t, (AggregationTemporalityCumulative + 1).String())
}
================================================
FILE: pdata/pmetric/doc_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pmetric_test
import (
"fmt"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/pmetric"
)
func ExampleNewMetrics() {
metrics := pmetric.NewMetrics()
resourceMetrics := metrics.ResourceMetrics().AppendEmpty()
resourceMetrics.Resource().Attributes().PutStr("service.name", "my-service")
resourceMetrics.Resource().Attributes().PutStr("host.name", "server-01")
scopeMetrics := resourceMetrics.ScopeMetrics().AppendEmpty()
scopeMetrics.Scope().SetName("my-meter")
scopeMetrics.Scope().SetVersion("1.0.0")
metric := scopeMetrics.Metrics().AppendEmpty()
metric.SetName("requests_total")
metric.SetDescription("Total number of requests")
metric.SetUnit("1")
sum := metric.SetEmptySum()
sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
sum.SetIsMonotonic(true)
dataPoint := sum.DataPoints().AppendEmpty()
dataPoint.SetStartTimestamp(pcommon.Timestamp(1640991600000000000)) // 2022-01-01 00:00:00 UTC
dataPoint.SetTimestamp(pcommon.Timestamp(1640995200000000000)) // 2022-01-01 01:00:00 UTC
dataPoint.SetIntValue(1234)
dataPoint.Attributes().PutStr("endpoint", "/api/v1/users")
dataPoint.Attributes().PutStr("method", "GET")
fmt.Printf("Resource metrics count: %d\n", metrics.ResourceMetrics().Len())
fmt.Printf("Metrics count: %d\n", scopeMetrics.Metrics().Len())
fmt.Printf("Metric name: %s\n", metric.Name())
fmt.Printf("Data points count: %d\n", sum.DataPoints().Len())
// Output:
// Resource metrics count: 1
// Metrics count: 1
// Metric name: requests_total
// Data points count: 1
}
func ExampleMetric_SetEmptyGauge() {
metrics := pmetric.NewMetrics()
resourceMetrics := metrics.ResourceMetrics().AppendEmpty()
scopeMetrics := resourceMetrics.ScopeMetrics().AppendEmpty()
metric := scopeMetrics.Metrics().AppendEmpty()
metric.SetName("cpu_usage_percent")
metric.SetDescription("Current CPU usage percentage")
metric.SetUnit("%")
gauge := metric.SetEmptyGauge()
for i := range 4 {
dataPoint := gauge.DataPoints().AppendEmpty()
dataPoint.SetTimestamp(pcommon.Timestamp(1640995200000000000))
dataPoint.SetDoubleValue(45.5 + float64(i)*2.1)
dataPoint.Attributes().PutInt("cpu", int64(i))
}
fmt.Printf("Metric type: %s\n", metric.Type())
fmt.Printf("Data points count: %d\n", gauge.DataPoints().Len())
fmt.Printf("First CPU usage: %.1f%%\n", gauge.DataPoints().At(0).DoubleValue())
// Output:
// Metric type: Gauge
// Data points count: 4
// First CPU usage: 45.5%
}
func ExampleMetric_SetEmptyHistogram() {
metrics := pmetric.NewMetrics()
resourceMetrics := metrics.ResourceMetrics().AppendEmpty()
scopeMetrics := resourceMetrics.ScopeMetrics().AppendEmpty()
metric := scopeMetrics.Metrics().AppendEmpty()
metric.SetName("request_duration_seconds")
metric.SetDescription("Request duration in seconds")
metric.SetUnit("s")
histogram := metric.SetEmptyHistogram()
histogram.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
dataPoint := histogram.DataPoints().AppendEmpty()
dataPoint.SetStartTimestamp(pcommon.Timestamp(1640995140000000000)) // 2022-01-01 00:59:00 UTC
dataPoint.SetTimestamp(pcommon.Timestamp(1640995200000000000)) // 2022-01-01 01:00:00 UTC
dataPoint.SetCount(100)
dataPoint.SetSum(23.5)
dataPoint.SetMin(0.001)
dataPoint.SetMax(5.0)
dataPoint.ExplicitBounds().FromRaw([]float64{0.01, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0})
dataPoint.BucketCounts().FromRaw([]uint64{5, 10, 20, 25, 20, 15, 3, 2, 0, 0})
dataPoint.Attributes().PutStr("endpoint", "/api/health")
dataPoint.SetFlags(pmetric.DefaultDataPointFlags)
fmt.Printf("Metric type: %s\n", metric.Type())
fmt.Printf("Total count: %d\n", dataPoint.Count())
fmt.Printf("Total sum: %.1f\n", dataPoint.Sum())
fmt.Printf("Min value: %.3f\n", dataPoint.Min())
fmt.Printf("Max value: %.1f\n", dataPoint.Max())
fmt.Printf("Bucket count: %d\n", dataPoint.BucketCounts().Len())
// Output:
// Metric type: Histogram
// Total count: 100
// Total sum: 23.5
// Min value: 0.001
// Max value: 5.0
// Bucket count: 10
}
func ExampleMetric_SetEmptyExponentialHistogram() {
metrics := pmetric.NewMetrics()
resourceMetrics := metrics.ResourceMetrics().AppendEmpty()
scopeMetrics := resourceMetrics.ScopeMetrics().AppendEmpty()
metric := scopeMetrics.Metrics().AppendEmpty()
metric.SetName("response_size_bytes")
metric.SetDescription("Response size in bytes")
metric.SetUnit("By")
expHist := metric.SetEmptyExponentialHistogram()
expHist.SetAggregationTemporality(pmetric.AggregationTemporalityDelta)
dataPoint := expHist.DataPoints().AppendEmpty()
dataPoint.SetStartTimestamp(pcommon.Timestamp(1640995140000000000))
dataPoint.SetTimestamp(pcommon.Timestamp(1640995200000000000))
dataPoint.SetCount(50)
dataPoint.SetSum(1024.5)
dataPoint.SetScale(4)
dataPoint.SetZeroCount(5)
dataPoint.SetZeroThreshold(0.001)
// Set positive buckets
positive := dataPoint.Positive()
positive.SetOffset(2)
positive.BucketCounts().FromRaw([]uint64{2, 3, 7, 8, 5})
// Set negative buckets
negative := dataPoint.Negative()
negative.SetOffset(1)
negative.BucketCounts().FromRaw([]uint64{1, 2, 3})
dataPoint.Attributes().PutStr("protocol", "http")
fmt.Printf("Metric type: %s\n", metric.Type())
fmt.Printf("Scale: %d\n", dataPoint.Scale())
fmt.Printf("Zero count: %d\n", dataPoint.ZeroCount())
fmt.Printf("Positive buckets: %d\n", positive.BucketCounts().Len())
fmt.Printf("Negative buckets: %d\n", negative.BucketCounts().Len())
// Output:
// Metric type: ExponentialHistogram
// Scale: 4
// Zero count: 5
// Positive buckets: 5
// Negative buckets: 3
}
func ExampleMetric_SetEmptySummary() {
metrics := pmetric.NewMetrics()
resourceMetrics := metrics.ResourceMetrics().AppendEmpty()
scopeMetrics := resourceMetrics.ScopeMetrics().AppendEmpty()
metric := scopeMetrics.Metrics().AppendEmpty()
metric.SetName("latency_seconds")
metric.SetDescription("Request latency in seconds")
metric.SetUnit("s")
summary := metric.SetEmptySummary()
dataPoint := summary.DataPoints().AppendEmpty()
dataPoint.SetStartTimestamp(pcommon.Timestamp(1640995140000000000))
dataPoint.SetTimestamp(pcommon.Timestamp(1640995200000000000))
dataPoint.SetCount(1000)
dataPoint.SetSum(125.5)
// Add quantile values
q50 := dataPoint.QuantileValues().AppendEmpty()
q50.SetQuantile(0.5)
q50.SetValue(0.1)
q95 := dataPoint.QuantileValues().AppendEmpty()
q95.SetQuantile(0.95)
q95.SetValue(0.5)
q99 := dataPoint.QuantileValues().AppendEmpty()
q99.SetQuantile(0.99)
q99.SetValue(1.0)
dataPoint.Attributes().PutStr("service", "api")
fmt.Printf("Metric type: %s\n", metric.Type())
fmt.Printf("Count: %d\n", dataPoint.Count())
fmt.Printf("Sum: %.1f\n", dataPoint.Sum())
fmt.Printf("Quantiles: %d\n", dataPoint.QuantileValues().Len())
fmt.Printf("P95: %.1f\n", q95.Value())
// Output:
// Metric type: Summary
// Count: 1000
// Sum: 125.5
// Quantiles: 3
// P95: 0.5
}
func ExampleNumberDataPoint_Exemplars() {
metrics := pmetric.NewMetrics()
resourceMetrics := metrics.ResourceMetrics().AppendEmpty()
scopeMetrics := resourceMetrics.ScopeMetrics().AppendEmpty()
metric := scopeMetrics.Metrics().AppendEmpty()
metric.SetName("http_requests_total")
sum := metric.SetEmptySum()
dataPoint := sum.DataPoints().AppendEmpty()
dataPoint.SetIntValue(5000)
exemplar := dataPoint.Exemplars().AppendEmpty()
exemplar.SetTimestamp(pcommon.Timestamp(1640995200000000000))
exemplar.SetIntValue(1)
exemplar.FilteredAttributes().PutStr("trace_id", "abc123")
exemplar.FilteredAttributes().PutStr("span_id", "def456")
fmt.Printf("Data point value: %d\n", dataPoint.IntValue())
fmt.Printf("Exemplars count: %d\n", dataPoint.Exemplars().Len())
fmt.Printf("Exemplar value: %d\n", exemplar.IntValue())
// Output:
// Data point value: 5000
// Exemplars count: 1
// Exemplar value: 1
}
func ExampleDataPointFlags() {
metrics := pmetric.NewMetrics()
resourceMetrics := metrics.ResourceMetrics().AppendEmpty()
scopeMetrics := resourceMetrics.ScopeMetrics().AppendEmpty()
metric := scopeMetrics.Metrics().AppendEmpty()
metric.SetName("test_metric")
gauge := metric.SetEmptyGauge()
dataPoint := gauge.DataPoints().AppendEmpty()
// Test default flags
defaultFlags := pmetric.DefaultDataPointFlags
dataPoint.SetFlags(defaultFlags)
fmt.Printf("Default flags NoRecordedValue: %t\n", dataPoint.Flags().NoRecordedValue())
// Test with NoRecordedValue flag
flagsWithNoValue := defaultFlags.WithNoRecordedValue(true)
dataPoint.SetFlags(flagsWithNoValue)
fmt.Printf("With NoRecordedValue flag: %t\n", dataPoint.Flags().NoRecordedValue())
// Test removing NoRecordedValue flag
flagsWithoutNoValue := flagsWithNoValue.WithNoRecordedValue(false)
dataPoint.SetFlags(flagsWithoutNoValue)
fmt.Printf("Without NoRecordedValue flag: %t\n", dataPoint.Flags().NoRecordedValue())
// Output:
// Default flags NoRecordedValue: false
// With NoRecordedValue flag: true
// Without NoRecordedValue flag: false
}
================================================
FILE: pdata/pmetric/encoding.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pmetric // import "go.opentelemetry.io/collector/pdata/pmetric"
// MarshalSizer is the interface that groups the basic Marshal and Size methods
type MarshalSizer interface {
Marshaler
Sizer
}
// Marshaler marshals pmetric.Metrics into bytes.
type Marshaler interface {
// MarshalMetrics the given pmetric.Metrics into bytes.
// If the error is not nil, the returned bytes slice cannot be used.
MarshalMetrics(md Metrics) ([]byte, error)
}
// Unmarshaler unmarshalls bytes into pmetric.Metrics.
type Unmarshaler interface {
// UnmarshalMetrics the given bytes into pmetric.Metrics.
// If the error is not nil, the returned pmetric.Metrics cannot be used.
UnmarshalMetrics(buf []byte) (Metrics, error)
}
// Sizer is an optional interface implemented by the Marshaler, that calculates the size of a marshaled Metrics.
type Sizer interface {
// MetricsSize returns the size in bytes of a marshaled Metrics.
MetricsSize(md Metrics) int
}
================================================
FILE: pdata/pmetric/exemplar_value_type.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pmetric // import "go.opentelemetry.io/collector/pdata/pmetric"
// ExemplarValueType specifies the type of Exemplar measurement value.
type ExemplarValueType int32
const (
// ExemplarValueTypeEmpty means that exemplar value is unset.
ExemplarValueTypeEmpty ExemplarValueType = iota
ExemplarValueTypeInt
ExemplarValueTypeDouble
)
// String returns the string representation of the ExemplarValueType.
func (nt ExemplarValueType) String() string {
switch nt {
case ExemplarValueTypeEmpty:
return "Empty"
case ExemplarValueTypeInt:
return "Int"
case ExemplarValueTypeDouble:
return "Double"
}
return ""
}
================================================
FILE: pdata/pmetric/exemplar_value_type_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pmetric // import "go.opentelemetry.io/collector/pdata/pmetric"
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestExemplarValueTypeString(t *testing.T) {
assert.Equal(t, "Empty", ExemplarValueTypeEmpty.String())
assert.Equal(t, "Int", ExemplarValueTypeInt.String())
assert.Equal(t, "Double", ExemplarValueTypeDouble.String())
assert.Empty(t, (ExemplarValueTypeDouble + 1).String())
}
================================================
FILE: pdata/pmetric/fuzz_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pmetric // import "go.opentelemetry.io/collector/pdata/pmetric"
import (
"testing"
)
func FuzzUnmarshalMetrics(f *testing.F) {
f.Fuzz(func(_ *testing.T, data []byte) {
u := &JSONUnmarshaler{}
_, _ = u.UnmarshalMetrics(data)
})
}
================================================
FILE: pdata/pmetric/generated_exemplar.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// Exemplar 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 NewExemplar function to create new instances.
// Important: zero-initialized instance is not valid for use.
type Exemplar struct {
orig *internal.Exemplar
state *internal.State
}
func newExemplar(orig *internal.Exemplar, state *internal.State) Exemplar {
return Exemplar{orig: orig, state: state}
}
// NewExemplar creates a new empty Exemplar.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewExemplar() Exemplar {
return newExemplar(internal.NewExemplar(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms Exemplar) MoveTo(dest Exemplar) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteExemplar(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// FilteredAttributes returns the FilteredAttributes associated with this Exemplar.
func (ms Exemplar) FilteredAttributes() pcommon.Map {
return pcommon.Map(internal.NewMapWrapper(&ms.orig.FilteredAttributes, ms.state))
}
// Timestamp returns the timestamp associated with this Exemplar.
func (ms Exemplar) Timestamp() pcommon.Timestamp {
return pcommon.Timestamp(ms.orig.TimeUnixNano)
}
// SetTimestamp replaces the timestamp associated with this Exemplar.
func (ms Exemplar) SetTimestamp(v pcommon.Timestamp) {
ms.state.AssertMutable()
ms.orig.TimeUnixNano = uint64(v)
}
// ValueType returns the type of the value for this Exemplar.
// Calling this function on zero-initialized Exemplar will cause a panic.
func (ms Exemplar) ValueType() ExemplarValueType {
switch ms.orig.Value.(type) {
case *internal.Exemplar_AsDouble:
return ExemplarValueTypeDouble
case *internal.Exemplar_AsInt:
return ExemplarValueTypeInt
}
return ExemplarValueTypeEmpty
}
// DoubleValue returns the double associated with this Exemplar.
func (ms Exemplar) DoubleValue() float64 {
return ms.orig.GetAsDouble()
}
// SetDoubleValue replaces the double associated with this Exemplar.
func (ms Exemplar) SetDoubleValue(v float64) {
ms.state.AssertMutable()
var ov *internal.Exemplar_AsDouble
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &internal.Exemplar_AsDouble{}
} else {
ov = internal.ProtoPoolExemplar_AsDouble.Get().(*internal.Exemplar_AsDouble)
}
ov.AsDouble = v
ms.orig.Value = ov
} // IntValue returns the int associated with this Exemplar.
func (ms Exemplar) IntValue() int64 {
return ms.orig.GetAsInt()
}
// SetIntValue replaces the int associated with this Exemplar.
func (ms Exemplar) SetIntValue(v int64) {
ms.state.AssertMutable()
var ov *internal.Exemplar_AsInt
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &internal.Exemplar_AsInt{}
} else {
ov = internal.ProtoPoolExemplar_AsInt.Get().(*internal.Exemplar_AsInt)
}
ov.AsInt = v
ms.orig.Value = ov
}
// TraceID returns the traceid associated with this Exemplar.
func (ms Exemplar) TraceID() pcommon.TraceID {
return pcommon.TraceID(ms.orig.TraceId)
}
// SetTraceID replaces the traceid associated with this Exemplar.
func (ms Exemplar) SetTraceID(v pcommon.TraceID) {
ms.state.AssertMutable()
ms.orig.TraceId = internal.TraceID(v)
}
// SpanID returns the spanid associated with this Exemplar.
func (ms Exemplar) SpanID() pcommon.SpanID {
return pcommon.SpanID(ms.orig.SpanId)
}
// SetSpanID replaces the spanid associated with this Exemplar.
func (ms Exemplar) SetSpanID(v pcommon.SpanID) {
ms.state.AssertMutable()
ms.orig.SpanId = internal.SpanID(v)
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms Exemplar) CopyTo(dest Exemplar) {
dest.state.AssertMutable()
internal.CopyExemplar(dest.orig, ms.orig)
}
================================================
FILE: pdata/pmetric/generated_exemplar_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestExemplar_MoveTo(t *testing.T) {
ms := generateTestExemplar()
dest := NewExemplar()
ms.MoveTo(dest)
assert.Equal(t, NewExemplar(), ms)
assert.Equal(t, generateTestExemplar(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestExemplar(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newExemplar(internal.NewExemplar(), sharedState)) })
assert.Panics(t, func() { newExemplar(internal.NewExemplar(), sharedState).MoveTo(dest) })
}
func TestExemplar_CopyTo(t *testing.T) {
ms := NewExemplar()
orig := NewExemplar()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestExemplar()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newExemplar(internal.NewExemplar(), sharedState)) })
}
func TestExemplar_FilteredAttributes(t *testing.T) {
ms := NewExemplar()
assert.Equal(t, pcommon.NewMap(), ms.FilteredAttributes())
ms.orig.FilteredAttributes = internal.GenTestKeyValueSlice()
assert.Equal(t, pcommon.Map(internal.GenTestMapWrapper()), ms.FilteredAttributes())
}
func TestExemplar_Timestamp(t *testing.T) {
ms := NewExemplar()
assert.Equal(t, pcommon.Timestamp(0), ms.Timestamp())
testValTimestamp := pcommon.Timestamp(1234567890)
ms.SetTimestamp(testValTimestamp)
assert.Equal(t, testValTimestamp, ms.Timestamp())
}
func TestExemplar_ValueType(t *testing.T) {
tv := NewExemplar()
assert.Equal(t, ExemplarValueTypeEmpty, tv.ValueType())
}
func TestExemplar_DoubleValue(t *testing.T) {
ms := NewExemplar()
assert.InDelta(t, float64(0), ms.DoubleValue(), 0.01)
ms.SetDoubleValue(float64(3.1415926))
assert.InDelta(t, float64(3.1415926), ms.DoubleValue(), 0.01)
assert.Equal(t, ExemplarValueTypeDouble, ms.ValueType())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newExemplar(internal.NewExemplar(), sharedState).SetDoubleValue(float64(3.1415926)) })
}
func TestExemplar_IntValue(t *testing.T) {
ms := NewExemplar()
assert.Equal(t, int64(0), ms.IntValue())
ms.SetIntValue(int64(13))
assert.Equal(t, int64(13), ms.IntValue())
assert.Equal(t, ExemplarValueTypeInt, ms.ValueType())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newExemplar(internal.NewExemplar(), sharedState).SetIntValue(int64(13)) })
}
func TestExemplar_TraceID(t *testing.T) {
ms := NewExemplar()
assert.Equal(t, pcommon.TraceID(internal.TraceID([16]byte{})), ms.TraceID())
testValTraceID := pcommon.TraceID(internal.TraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}))
ms.SetTraceID(testValTraceID)
assert.Equal(t, testValTraceID, ms.TraceID())
}
func TestExemplar_SpanID(t *testing.T) {
ms := NewExemplar()
assert.Equal(t, pcommon.SpanID(internal.SpanID([8]byte{})), ms.SpanID())
testValSpanID := pcommon.SpanID(internal.SpanID([8]byte{8, 7, 6, 5, 4, 3, 2, 1}))
ms.SetSpanID(testValSpanID)
assert.Equal(t, testValSpanID, ms.SpanID())
}
func generateTestExemplar() Exemplar {
return newExemplar(internal.GenTestExemplar(), internal.NewState())
}
================================================
FILE: pdata/pmetric/generated_exemplarslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"iter"
"go.opentelemetry.io/collector/pdata/internal"
)
// ExemplarSlice logically represents a slice of Exemplar.
//
// This is a reference type. If passed by value and callee modifies it, the
// caller will see the modification.
//
// Must use NewExemplarSlice function to create new instances.
// Important: zero-initialized instance is not valid for use.
type ExemplarSlice struct {
orig *[]internal.Exemplar
state *internal.State
}
func newExemplarSlice(orig *[]internal.Exemplar, state *internal.State) ExemplarSlice {
return ExemplarSlice{orig: orig, state: state}
}
// NewExemplarSlice creates a ExemplarSliceWrapper with 0 elements.
// Can use "EnsureCapacity" to initialize with a given capacity.
func NewExemplarSlice() ExemplarSlice {
orig := []internal.Exemplar(nil)
return newExemplarSlice(&orig, internal.NewState())
}
// Len returns the number of elements in the slice.
//
// Returns "0" for a newly instance created with "NewExemplarSlice()".
func (es ExemplarSlice) 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 ExemplarSlice) At(i int) Exemplar {
return newExemplar(&(*es.orig)[i], es.state)
}
// All returns an iterator over index-value pairs in the slice.
//
// for i, v := range es.All() {
// ... // Do something with index-value pair
// }
func (es ExemplarSlice) All() iter.Seq2[int, Exemplar] {
return func(yield func(int, Exemplar) bool) {
for i := 0; i < es.Len(); i++ {
if !yield(i, es.At(i)) {
return
}
}
}
}
// EnsureCapacity is an operation that ensures the slice has at least the specified capacity.
// 1. If the newCap <= cap then no change in capacity.
// 2. If the newCap > cap then the slice capacity will be expanded to equal newCap.
//
// Here is how a new ExemplarSlice can be initialized:
//
// es := NewExemplarSlice()
// es.EnsureCapacity(4)
// for i := 0; i < 4; i++ {
// e := es.AppendEmpty()
// // Here should set all the values for e.
// }
func (es ExemplarSlice) EnsureCapacity(newCap int) {
es.state.AssertMutable()
oldCap := cap(*es.orig)
if newCap <= oldCap {
return
}
newOrig := make([]internal.Exemplar, len(*es.orig), newCap)
copy(newOrig, *es.orig)
*es.orig = newOrig
}
// AppendEmpty will append to the end of the slice an empty Exemplar.
// It returns the newly added Exemplar.
func (es ExemplarSlice) AppendEmpty() Exemplar {
es.state.AssertMutable()
*es.orig = append(*es.orig, internal.Exemplar{})
return es.At(es.Len() - 1)
}
// MoveAndAppendTo moves all elements from the current slice and appends them to the dest.
// The current slice will be cleared.
func (es ExemplarSlice) MoveAndAppendTo(dest ExemplarSlice) {
es.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if es.orig == dest.orig {
return
}
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
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (es ExemplarSlice) RemoveIf(f func(Exemplar) bool) {
es.state.AssertMutable()
newLen := 0
for i := 0; i < len(*es.orig); i++ {
if f(es.At(i)) {
internal.DeleteExemplar(&(*es.orig)[i], false)
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*es.orig)[newLen] = (*es.orig)[i]
(*es.orig)[i].Reset()
newLen++
}
*es.orig = (*es.orig)[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (es ExemplarSlice) CopyTo(dest ExemplarSlice) {
dest.state.AssertMutable()
if es.orig == dest.orig {
return
}
*dest.orig = internal.CopyExemplarSlice(*dest.orig, *es.orig)
}
================================================
FILE: pdata/pmetric/generated_exemplarslice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestExemplarSlice(t *testing.T) {
es := NewExemplarSlice()
assert.Equal(t, 0, es.Len())
es = newExemplarSlice(&[]internal.Exemplar{}, internal.NewState())
assert.Equal(t, 0, es.Len())
emptyVal := NewExemplar()
testVal := generateTestExemplar()
for i := 0; i < 7; i++ {
es.AppendEmpty()
assert.Equal(t, emptyVal, es.At(i))
(*es.orig)[i] = *internal.GenTestExemplar()
assert.Equal(t, testVal, es.At(i))
}
assert.Equal(t, 7, es.Len())
}
func TestExemplarSliceReadOnly(t *testing.T) {
sharedState := internal.NewState()
sharedState.MarkReadOnly()
es := newExemplarSlice(&[]internal.Exemplar{}, sharedState)
assert.Equal(t, 0, es.Len())
assert.Panics(t, func() { es.AppendEmpty() })
assert.Panics(t, func() { es.EnsureCapacity(2) })
es2 := NewExemplarSlice()
es.CopyTo(es2)
assert.Panics(t, func() { es2.CopyTo(es) })
assert.Panics(t, func() { es.MoveAndAppendTo(es2) })
assert.Panics(t, func() { es2.MoveAndAppendTo(es) })
}
func TestExemplarSlice_CopyTo(t *testing.T) {
dest := NewExemplarSlice()
src := generateTestExemplarSlice()
src.CopyTo(dest)
assert.Equal(t, generateTestExemplarSlice(), dest)
dest.CopyTo(dest)
assert.Equal(t, generateTestExemplarSlice(), dest)
}
func TestExemplarSlice_EnsureCapacity(t *testing.T) {
es := generateTestExemplarSlice()
// Test ensure smaller capacity.
const ensureSmallLen = 4
es.EnsureCapacity(ensureSmallLen)
assert.Less(t, ensureSmallLen, es.Len())
assert.Equal(t, es.Len(), cap(*es.orig))
assert.Equal(t, generateTestExemplarSlice(), es)
// Test ensure larger capacity
const ensureLargeLen = 9
es.EnsureCapacity(ensureLargeLen)
assert.Less(t, generateTestExemplarSlice().Len(), ensureLargeLen)
assert.Equal(t, ensureLargeLen, cap(*es.orig))
assert.Equal(t, generateTestExemplarSlice(), es)
}
func TestExemplarSlice_MoveAndAppendTo(t *testing.T) {
// Test MoveAndAppendTo to empty
expectedSlice := generateTestExemplarSlice()
dest := NewExemplarSlice()
src := generateTestExemplarSlice()
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestExemplarSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo empty slice
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestExemplarSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo not empty slice
generateTestExemplarSlice().MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
dest.MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
}
func TestExemplarSlice_RemoveIf(t *testing.T) {
// Test RemoveIf on empty slice
emptySlice := NewExemplarSlice()
emptySlice.RemoveIf(func(el Exemplar) bool {
t.Fail()
return false
})
// Test RemoveIf
filtered := generateTestExemplarSlice()
pos := 0
filtered.RemoveIf(func(el Exemplar) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, 2, filtered.Len())
}
func TestExemplarSlice_RemoveIfAll(t *testing.T) {
got := generateTestExemplarSlice()
got.RemoveIf(func(el Exemplar) bool {
return true
})
assert.Equal(t, 0, got.Len())
}
func TestExemplarSliceAll(t *testing.T) {
ms := generateTestExemplarSlice()
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func generateTestExemplarSlice() ExemplarSlice {
ms := NewExemplarSlice()
*ms.orig = internal.GenTestExemplarSlice()
return ms
}
================================================
FILE: pdata/pmetric/generated_exponentialhistogram.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// ExponentialHistogram represents the type of a metric that is calculated by aggregating
// as a ExponentialHistogram 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 NewExponentialHistogram function to create new instances.
// Important: zero-initialized instance is not valid for use.
type ExponentialHistogram struct {
orig *internal.ExponentialHistogram
state *internal.State
}
func newExponentialHistogram(orig *internal.ExponentialHistogram, state *internal.State) ExponentialHistogram {
return ExponentialHistogram{orig: orig, state: state}
}
// NewExponentialHistogram creates a new empty ExponentialHistogram.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewExponentialHistogram() ExponentialHistogram {
return newExponentialHistogram(internal.NewExponentialHistogram(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms ExponentialHistogram) MoveTo(dest ExponentialHistogram) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteExponentialHistogram(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// DataPoints returns the DataPoints associated with this ExponentialHistogram.
func (ms ExponentialHistogram) DataPoints() ExponentialHistogramDataPointSlice {
return newExponentialHistogramDataPointSlice(&ms.orig.DataPoints, ms.state)
}
// AggregationTemporality returns the aggregationtemporality associated with this ExponentialHistogram.
func (ms ExponentialHistogram) AggregationTemporality() AggregationTemporality {
return AggregationTemporality(ms.orig.AggregationTemporality)
}
// SetAggregationTemporality replaces the aggregationtemporality associated with this ExponentialHistogram.
func (ms ExponentialHistogram) SetAggregationTemporality(v AggregationTemporality) {
ms.state.AssertMutable()
ms.orig.AggregationTemporality = internal.AggregationTemporality(v)
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms ExponentialHistogram) CopyTo(dest ExponentialHistogram) {
dest.state.AssertMutable()
internal.CopyExponentialHistogram(dest.orig, ms.orig)
}
================================================
FILE: pdata/pmetric/generated_exponentialhistogram_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestExponentialHistogram_MoveTo(t *testing.T) {
ms := generateTestExponentialHistogram()
dest := NewExponentialHistogram()
ms.MoveTo(dest)
assert.Equal(t, NewExponentialHistogram(), ms)
assert.Equal(t, generateTestExponentialHistogram(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestExponentialHistogram(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newExponentialHistogram(internal.NewExponentialHistogram(), sharedState)) })
assert.Panics(t, func() { newExponentialHistogram(internal.NewExponentialHistogram(), sharedState).MoveTo(dest) })
}
func TestExponentialHistogram_CopyTo(t *testing.T) {
ms := NewExponentialHistogram()
orig := NewExponentialHistogram()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestExponentialHistogram()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newExponentialHistogram(internal.NewExponentialHistogram(), sharedState)) })
}
func TestExponentialHistogram_DataPoints(t *testing.T) {
ms := NewExponentialHistogram()
assert.Equal(t, NewExponentialHistogramDataPointSlice(), ms.DataPoints())
ms.orig.DataPoints = internal.GenTestExponentialHistogramDataPointPtrSlice()
assert.Equal(t, generateTestExponentialHistogramDataPointSlice(), ms.DataPoints())
}
func TestExponentialHistogram_AggregationTemporality(t *testing.T) {
ms := NewExponentialHistogram()
assert.Equal(t, AggregationTemporality(internal.AggregationTemporality(0)), ms.AggregationTemporality())
testValAggregationTemporality := AggregationTemporality(internal.AggregationTemporality(1))
ms.SetAggregationTemporality(testValAggregationTemporality)
assert.Equal(t, testValAggregationTemporality, ms.AggregationTemporality())
}
func generateTestExponentialHistogram() ExponentialHistogram {
return newExponentialHistogram(internal.GenTestExponentialHistogram(), internal.NewState())
}
================================================
FILE: pdata/pmetric/generated_exponentialhistogramdatapoint.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// ExponentialHistogramDataPoint is a single data point in a timeseries that describes the
// time-varying values of a ExponentialHistogram of double values. A ExponentialHistogram contains
// summary statistics for a population of values, it may optionally contain the
// distribution of those values across a set of buckets.
//
// This is a reference type, if passed by value and callee modifies it the
// caller will see the modification.
//
// Must use NewExponentialHistogramDataPoint function to create new instances.
// Important: zero-initialized instance is not valid for use.
type ExponentialHistogramDataPoint struct {
orig *internal.ExponentialHistogramDataPoint
state *internal.State
}
func newExponentialHistogramDataPoint(orig *internal.ExponentialHistogramDataPoint, state *internal.State) ExponentialHistogramDataPoint {
return ExponentialHistogramDataPoint{orig: orig, state: state}
}
// NewExponentialHistogramDataPoint creates a new empty ExponentialHistogramDataPoint.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewExponentialHistogramDataPoint() ExponentialHistogramDataPoint {
return newExponentialHistogramDataPoint(internal.NewExponentialHistogramDataPoint(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms ExponentialHistogramDataPoint) MoveTo(dest ExponentialHistogramDataPoint) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteExponentialHistogramDataPoint(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// Attributes returns the Attributes associated with this ExponentialHistogramDataPoint.
func (ms ExponentialHistogramDataPoint) Attributes() pcommon.Map {
return pcommon.Map(internal.NewMapWrapper(&ms.orig.Attributes, ms.state))
}
// StartTimestamp returns the starttimestamp associated with this ExponentialHistogramDataPoint.
func (ms ExponentialHistogramDataPoint) StartTimestamp() pcommon.Timestamp {
return pcommon.Timestamp(ms.orig.StartTimeUnixNano)
}
// SetStartTimestamp replaces the starttimestamp associated with this ExponentialHistogramDataPoint.
func (ms ExponentialHistogramDataPoint) SetStartTimestamp(v pcommon.Timestamp) {
ms.state.AssertMutable()
ms.orig.StartTimeUnixNano = uint64(v)
}
// Timestamp returns the timestamp associated with this ExponentialHistogramDataPoint.
func (ms ExponentialHistogramDataPoint) Timestamp() pcommon.Timestamp {
return pcommon.Timestamp(ms.orig.TimeUnixNano)
}
// SetTimestamp replaces the timestamp associated with this ExponentialHistogramDataPoint.
func (ms ExponentialHistogramDataPoint) SetTimestamp(v pcommon.Timestamp) {
ms.state.AssertMutable()
ms.orig.TimeUnixNano = uint64(v)
}
// Count returns the count associated with this ExponentialHistogramDataPoint.
func (ms ExponentialHistogramDataPoint) Count() uint64 {
return ms.orig.Count
}
// SetCount replaces the count associated with this ExponentialHistogramDataPoint.
func (ms ExponentialHistogramDataPoint) SetCount(v uint64) {
ms.state.AssertMutable()
ms.orig.Count = v
}
// Sum returns the sum associated with this ExponentialHistogramDataPoint.
func (ms ExponentialHistogramDataPoint) Sum() float64 {
return ms.orig.Sum
}
// HasSum returns true if the ExponentialHistogramDataPoint contains a
// Sum value otherwise.
func (ms ExponentialHistogramDataPoint) HasSum() bool {
return ms.orig.HasSum()
}
// SetSum replaces the sum associated with this ExponentialHistogramDataPoint.
func (ms ExponentialHistogramDataPoint) SetSum(v float64) {
ms.state.AssertMutable()
ms.orig.SetSum(v)
}
// RemoveSum removes the sum associated with this ExponentialHistogramDataPoint.
func (ms ExponentialHistogramDataPoint) RemoveSum() {
ms.state.AssertMutable()
ms.orig.RemoveSum()
}
// Scale returns the scale associated with this ExponentialHistogramDataPoint.
func (ms ExponentialHistogramDataPoint) Scale() int32 {
return ms.orig.Scale
}
// SetScale replaces the scale associated with this ExponentialHistogramDataPoint.
func (ms ExponentialHistogramDataPoint) SetScale(v int32) {
ms.state.AssertMutable()
ms.orig.Scale = v
}
// ZeroCount returns the zerocount associated with this ExponentialHistogramDataPoint.
func (ms ExponentialHistogramDataPoint) ZeroCount() uint64 {
return ms.orig.ZeroCount
}
// SetZeroCount replaces the zerocount associated with this ExponentialHistogramDataPoint.
func (ms ExponentialHistogramDataPoint) SetZeroCount(v uint64) {
ms.state.AssertMutable()
ms.orig.ZeroCount = v
}
// Positive returns the positive associated with this ExponentialHistogramDataPoint.
func (ms ExponentialHistogramDataPoint) Positive() ExponentialHistogramDataPointBuckets {
return newExponentialHistogramDataPointBuckets(&ms.orig.Positive, ms.state)
}
// Negative returns the negative associated with this ExponentialHistogramDataPoint.
func (ms ExponentialHistogramDataPoint) Negative() ExponentialHistogramDataPointBuckets {
return newExponentialHistogramDataPointBuckets(&ms.orig.Negative, ms.state)
}
// Flags returns the flags associated with this ExponentialHistogramDataPoint.
func (ms ExponentialHistogramDataPoint) Flags() DataPointFlags {
return DataPointFlags(ms.orig.Flags)
}
// SetFlags replaces the flags associated with this ExponentialHistogramDataPoint.
func (ms ExponentialHistogramDataPoint) SetFlags(v DataPointFlags) {
ms.state.AssertMutable()
ms.orig.Flags = uint32(v)
}
// Exemplars returns the Exemplars associated with this ExponentialHistogramDataPoint.
func (ms ExponentialHistogramDataPoint) Exemplars() ExemplarSlice {
return newExemplarSlice(&ms.orig.Exemplars, ms.state)
}
// Min returns the min associated with this ExponentialHistogramDataPoint.
func (ms ExponentialHistogramDataPoint) Min() float64 {
return ms.orig.Min
}
// HasMin returns true if the ExponentialHistogramDataPoint contains a
// Min value otherwise.
func (ms ExponentialHistogramDataPoint) HasMin() bool {
return ms.orig.HasMin()
}
// SetMin replaces the min associated with this ExponentialHistogramDataPoint.
func (ms ExponentialHistogramDataPoint) SetMin(v float64) {
ms.state.AssertMutable()
ms.orig.SetMin(v)
}
// RemoveMin removes the min associated with this ExponentialHistogramDataPoint.
func (ms ExponentialHistogramDataPoint) RemoveMin() {
ms.state.AssertMutable()
ms.orig.RemoveMin()
}
// Max returns the max associated with this ExponentialHistogramDataPoint.
func (ms ExponentialHistogramDataPoint) Max() float64 {
return ms.orig.Max
}
// HasMax returns true if the ExponentialHistogramDataPoint contains a
// Max value otherwise.
func (ms ExponentialHistogramDataPoint) HasMax() bool {
return ms.orig.HasMax()
}
// SetMax replaces the max associated with this ExponentialHistogramDataPoint.
func (ms ExponentialHistogramDataPoint) SetMax(v float64) {
ms.state.AssertMutable()
ms.orig.SetMax(v)
}
// RemoveMax removes the max associated with this ExponentialHistogramDataPoint.
func (ms ExponentialHistogramDataPoint) RemoveMax() {
ms.state.AssertMutable()
ms.orig.RemoveMax()
}
// ZeroThreshold returns the zerothreshold associated with this ExponentialHistogramDataPoint.
func (ms ExponentialHistogramDataPoint) ZeroThreshold() float64 {
return ms.orig.ZeroThreshold
}
// SetZeroThreshold replaces the zerothreshold associated with this ExponentialHistogramDataPoint.
func (ms ExponentialHistogramDataPoint) SetZeroThreshold(v float64) {
ms.state.AssertMutable()
ms.orig.ZeroThreshold = v
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms ExponentialHistogramDataPoint) CopyTo(dest ExponentialHistogramDataPoint) {
dest.state.AssertMutable()
internal.CopyExponentialHistogramDataPoint(dest.orig, ms.orig)
}
================================================
FILE: pdata/pmetric/generated_exponentialhistogramdatapoint_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestExponentialHistogramDataPoint_MoveTo(t *testing.T) {
ms := generateTestExponentialHistogramDataPoint()
dest := NewExponentialHistogramDataPoint()
ms.MoveTo(dest)
assert.Equal(t, NewExponentialHistogramDataPoint(), ms)
assert.Equal(t, generateTestExponentialHistogramDataPoint(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestExponentialHistogramDataPoint(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() {
ms.MoveTo(newExponentialHistogramDataPoint(internal.NewExponentialHistogramDataPoint(), sharedState))
})
assert.Panics(t, func() {
newExponentialHistogramDataPoint(internal.NewExponentialHistogramDataPoint(), sharedState).MoveTo(dest)
})
}
func TestExponentialHistogramDataPoint_CopyTo(t *testing.T) {
ms := NewExponentialHistogramDataPoint()
orig := NewExponentialHistogramDataPoint()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestExponentialHistogramDataPoint()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() {
ms.CopyTo(newExponentialHistogramDataPoint(internal.NewExponentialHistogramDataPoint(), sharedState))
})
}
func TestExponentialHistogramDataPoint_Attributes(t *testing.T) {
ms := NewExponentialHistogramDataPoint()
assert.Equal(t, pcommon.NewMap(), ms.Attributes())
ms.orig.Attributes = internal.GenTestKeyValueSlice()
assert.Equal(t, pcommon.Map(internal.GenTestMapWrapper()), ms.Attributes())
}
func TestExponentialHistogramDataPoint_StartTimestamp(t *testing.T) {
ms := NewExponentialHistogramDataPoint()
assert.Equal(t, pcommon.Timestamp(0), ms.StartTimestamp())
testValStartTimestamp := pcommon.Timestamp(1234567890)
ms.SetStartTimestamp(testValStartTimestamp)
assert.Equal(t, testValStartTimestamp, ms.StartTimestamp())
}
func TestExponentialHistogramDataPoint_Timestamp(t *testing.T) {
ms := NewExponentialHistogramDataPoint()
assert.Equal(t, pcommon.Timestamp(0), ms.Timestamp())
testValTimestamp := pcommon.Timestamp(1234567890)
ms.SetTimestamp(testValTimestamp)
assert.Equal(t, testValTimestamp, ms.Timestamp())
}
func TestExponentialHistogramDataPoint_Count(t *testing.T) {
ms := NewExponentialHistogramDataPoint()
assert.Equal(t, uint64(0), ms.Count())
ms.SetCount(uint64(13))
assert.Equal(t, uint64(13), ms.Count())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() {
newExponentialHistogramDataPoint(internal.NewExponentialHistogramDataPoint(), sharedState).SetCount(uint64(13))
})
}
func TestExponentialHistogramDataPoint_Sum(t *testing.T) {
ms := NewExponentialHistogramDataPoint()
assert.InDelta(t, float64(0), ms.Sum(), 0.01)
ms.SetSum(float64(3.1415926))
assert.True(t, ms.HasSum())
assert.InDelta(t, float64(3.1415926), ms.Sum(), 0.01)
ms.RemoveSum()
assert.False(t, ms.HasSum())
dest := NewExponentialHistogramDataPoint()
dest.SetSum(float64(3.1415926))
ms.CopyTo(dest)
assert.False(t, dest.HasSum())
}
func TestExponentialHistogramDataPoint_Scale(t *testing.T) {
ms := NewExponentialHistogramDataPoint()
assert.Equal(t, int32(0), ms.Scale())
ms.SetScale(int32(13))
assert.Equal(t, int32(13), ms.Scale())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() {
newExponentialHistogramDataPoint(internal.NewExponentialHistogramDataPoint(), sharedState).SetScale(int32(13))
})
}
func TestExponentialHistogramDataPoint_ZeroCount(t *testing.T) {
ms := NewExponentialHistogramDataPoint()
assert.Equal(t, uint64(0), ms.ZeroCount())
ms.SetZeroCount(uint64(13))
assert.Equal(t, uint64(13), ms.ZeroCount())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() {
newExponentialHistogramDataPoint(internal.NewExponentialHistogramDataPoint(), sharedState).SetZeroCount(uint64(13))
})
}
func TestExponentialHistogramDataPoint_Positive(t *testing.T) {
ms := NewExponentialHistogramDataPoint()
assert.Equal(t, NewExponentialHistogramDataPointBuckets(), ms.Positive())
ms.orig.Positive = *internal.GenTestExponentialHistogramDataPointBuckets()
assert.Equal(t, generateTestExponentialHistogramDataPointBuckets(), ms.Positive())
}
func TestExponentialHistogramDataPoint_Negative(t *testing.T) {
ms := NewExponentialHistogramDataPoint()
assert.Equal(t, NewExponentialHistogramDataPointBuckets(), ms.Negative())
ms.orig.Negative = *internal.GenTestExponentialHistogramDataPointBuckets()
assert.Equal(t, generateTestExponentialHistogramDataPointBuckets(), ms.Negative())
}
func TestExponentialHistogramDataPoint_Flags(t *testing.T) {
ms := NewExponentialHistogramDataPoint()
assert.Equal(t, DataPointFlags(0), ms.Flags())
testValFlags := DataPointFlags(1)
ms.SetFlags(testValFlags)
assert.Equal(t, testValFlags, ms.Flags())
}
func TestExponentialHistogramDataPoint_Exemplars(t *testing.T) {
ms := NewExponentialHistogramDataPoint()
assert.Equal(t, NewExemplarSlice(), ms.Exemplars())
ms.orig.Exemplars = internal.GenTestExemplarSlice()
assert.Equal(t, generateTestExemplarSlice(), ms.Exemplars())
}
func TestExponentialHistogramDataPoint_Min(t *testing.T) {
ms := NewExponentialHistogramDataPoint()
assert.InDelta(t, float64(0), ms.Min(), 0.01)
ms.SetMin(float64(3.1415926))
assert.True(t, ms.HasMin())
assert.InDelta(t, float64(3.1415926), ms.Min(), 0.01)
ms.RemoveMin()
assert.False(t, ms.HasMin())
dest := NewExponentialHistogramDataPoint()
dest.SetMin(float64(3.1415926))
ms.CopyTo(dest)
assert.False(t, dest.HasMin())
}
func TestExponentialHistogramDataPoint_Max(t *testing.T) {
ms := NewExponentialHistogramDataPoint()
assert.InDelta(t, float64(0), ms.Max(), 0.01)
ms.SetMax(float64(3.1415926))
assert.True(t, ms.HasMax())
assert.InDelta(t, float64(3.1415926), ms.Max(), 0.01)
ms.RemoveMax()
assert.False(t, ms.HasMax())
dest := NewExponentialHistogramDataPoint()
dest.SetMax(float64(3.1415926))
ms.CopyTo(dest)
assert.False(t, dest.HasMax())
}
func TestExponentialHistogramDataPoint_ZeroThreshold(t *testing.T) {
ms := NewExponentialHistogramDataPoint()
assert.InDelta(t, float64(0), ms.ZeroThreshold(), 0.01)
ms.SetZeroThreshold(float64(3.1415926))
assert.InDelta(t, float64(3.1415926), ms.ZeroThreshold(), 0.01)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() {
newExponentialHistogramDataPoint(internal.NewExponentialHistogramDataPoint(), sharedState).SetZeroThreshold(float64(3.1415926))
})
}
func generateTestExponentialHistogramDataPoint() ExponentialHistogramDataPoint {
return newExponentialHistogramDataPoint(internal.GenTestExponentialHistogramDataPoint(), internal.NewState())
}
================================================
FILE: pdata/pmetric/generated_exponentialhistogramdatapointbuckets.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// ExponentialHistogramDataPointBuckets are a set of bucket counts, encoded in a contiguous array of counts.
//
// This is a reference type, if passed by value and callee modifies it the
// caller will see the modification.
//
// Must use NewExponentialHistogramDataPointBuckets function to create new instances.
// Important: zero-initialized instance is not valid for use.
type ExponentialHistogramDataPointBuckets struct {
orig *internal.ExponentialHistogramDataPointBuckets
state *internal.State
}
func newExponentialHistogramDataPointBuckets(orig *internal.ExponentialHistogramDataPointBuckets, state *internal.State) ExponentialHistogramDataPointBuckets {
return ExponentialHistogramDataPointBuckets{orig: orig, state: state}
}
// NewExponentialHistogramDataPointBuckets creates a new empty ExponentialHistogramDataPointBuckets.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewExponentialHistogramDataPointBuckets() ExponentialHistogramDataPointBuckets {
return newExponentialHistogramDataPointBuckets(internal.NewExponentialHistogramDataPointBuckets(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms ExponentialHistogramDataPointBuckets) MoveTo(dest ExponentialHistogramDataPointBuckets) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteExponentialHistogramDataPointBuckets(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// Offset returns the offset associated with this ExponentialHistogramDataPointBuckets.
func (ms ExponentialHistogramDataPointBuckets) Offset() int32 {
return ms.orig.Offset
}
// SetOffset replaces the offset associated with this ExponentialHistogramDataPointBuckets.
func (ms ExponentialHistogramDataPointBuckets) SetOffset(v int32) {
ms.state.AssertMutable()
ms.orig.Offset = v
}
// BucketCounts returns the BucketCounts associated with this ExponentialHistogramDataPointBuckets.
func (ms ExponentialHistogramDataPointBuckets) BucketCounts() pcommon.UInt64Slice {
return pcommon.UInt64Slice(internal.NewUInt64SliceWrapper(&ms.orig.BucketCounts, ms.state))
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms ExponentialHistogramDataPointBuckets) CopyTo(dest ExponentialHistogramDataPointBuckets) {
dest.state.AssertMutable()
internal.CopyExponentialHistogramDataPointBuckets(dest.orig, ms.orig)
}
================================================
FILE: pdata/pmetric/generated_exponentialhistogramdatapointbuckets_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestExponentialHistogramDataPointBuckets_MoveTo(t *testing.T) {
ms := generateTestExponentialHistogramDataPointBuckets()
dest := NewExponentialHistogramDataPointBuckets()
ms.MoveTo(dest)
assert.Equal(t, NewExponentialHistogramDataPointBuckets(), ms)
assert.Equal(t, generateTestExponentialHistogramDataPointBuckets(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestExponentialHistogramDataPointBuckets(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() {
ms.MoveTo(newExponentialHistogramDataPointBuckets(internal.NewExponentialHistogramDataPointBuckets(), sharedState))
})
assert.Panics(t, func() {
newExponentialHistogramDataPointBuckets(internal.NewExponentialHistogramDataPointBuckets(), sharedState).MoveTo(dest)
})
}
func TestExponentialHistogramDataPointBuckets_CopyTo(t *testing.T) {
ms := NewExponentialHistogramDataPointBuckets()
orig := NewExponentialHistogramDataPointBuckets()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestExponentialHistogramDataPointBuckets()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() {
ms.CopyTo(newExponentialHistogramDataPointBuckets(internal.NewExponentialHistogramDataPointBuckets(), sharedState))
})
}
func TestExponentialHistogramDataPointBuckets_Offset(t *testing.T) {
ms := NewExponentialHistogramDataPointBuckets()
assert.Equal(t, int32(0), ms.Offset())
ms.SetOffset(int32(13))
assert.Equal(t, int32(13), ms.Offset())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() {
newExponentialHistogramDataPointBuckets(internal.NewExponentialHistogramDataPointBuckets(), sharedState).SetOffset(int32(13))
})
}
func TestExponentialHistogramDataPointBuckets_BucketCounts(t *testing.T) {
ms := NewExponentialHistogramDataPointBuckets()
assert.Equal(t, pcommon.NewUInt64Slice(), ms.BucketCounts())
ms.orig.BucketCounts = internal.GenTestUint64Slice()
assert.Equal(t, pcommon.UInt64Slice(internal.GenTestUInt64SliceWrapper()), ms.BucketCounts())
}
func generateTestExponentialHistogramDataPointBuckets() ExponentialHistogramDataPointBuckets {
return newExponentialHistogramDataPointBuckets(internal.GenTestExponentialHistogramDataPointBuckets(), internal.NewState())
}
================================================
FILE: pdata/pmetric/generated_exponentialhistogramdatapointslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"iter"
"sort"
"go.opentelemetry.io/collector/pdata/internal"
)
// ExponentialHistogramDataPointSlice logically represents a slice of ExponentialHistogramDataPoint.
//
// This is a reference type. If passed by value and callee modifies it, the
// caller will see the modification.
//
// Must use NewExponentialHistogramDataPointSlice function to create new instances.
// Important: zero-initialized instance is not valid for use.
type ExponentialHistogramDataPointSlice struct {
orig *[]*internal.ExponentialHistogramDataPoint
state *internal.State
}
func newExponentialHistogramDataPointSlice(orig *[]*internal.ExponentialHistogramDataPoint, state *internal.State) ExponentialHistogramDataPointSlice {
return ExponentialHistogramDataPointSlice{orig: orig, state: state}
}
// NewExponentialHistogramDataPointSlice creates a ExponentialHistogramDataPointSliceWrapper with 0 elements.
// Can use "EnsureCapacity" to initialize with a given capacity.
func NewExponentialHistogramDataPointSlice() ExponentialHistogramDataPointSlice {
orig := []*internal.ExponentialHistogramDataPoint(nil)
return newExponentialHistogramDataPointSlice(&orig, internal.NewState())
}
// Len returns the number of elements in the slice.
//
// Returns "0" for a newly instance created with "NewExponentialHistogramDataPointSlice()".
func (es ExponentialHistogramDataPointSlice) 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 ExponentialHistogramDataPointSlice) At(i int) ExponentialHistogramDataPoint {
return newExponentialHistogramDataPoint((*es.orig)[i], es.state)
}
// All returns an iterator over index-value pairs in the slice.
//
// for i, v := range es.All() {
// ... // Do something with index-value pair
// }
func (es ExponentialHistogramDataPointSlice) All() iter.Seq2[int, ExponentialHistogramDataPoint] {
return func(yield func(int, ExponentialHistogramDataPoint) bool) {
for i := 0; i < es.Len(); i++ {
if !yield(i, es.At(i)) {
return
}
}
}
}
// EnsureCapacity is an operation that ensures the slice has at least the specified capacity.
// 1. If the newCap <= cap then no change in capacity.
// 2. If the newCap > cap then the slice capacity will be expanded to equal newCap.
//
// Here is how a new ExponentialHistogramDataPointSlice can be initialized:
//
// es := NewExponentialHistogramDataPointSlice()
// es.EnsureCapacity(4)
// for i := 0; i < 4; i++ {
// e := es.AppendEmpty()
// // Here should set all the values for e.
// }
func (es ExponentialHistogramDataPointSlice) EnsureCapacity(newCap int) {
es.state.AssertMutable()
oldCap := cap(*es.orig)
if newCap <= oldCap {
return
}
newOrig := make([]*internal.ExponentialHistogramDataPoint, len(*es.orig), newCap)
copy(newOrig, *es.orig)
*es.orig = newOrig
}
// AppendEmpty will append to the end of the slice an empty ExponentialHistogramDataPoint.
// It returns the newly added ExponentialHistogramDataPoint.
func (es ExponentialHistogramDataPointSlice) AppendEmpty() ExponentialHistogramDataPoint {
es.state.AssertMutable()
*es.orig = append(*es.orig, internal.NewExponentialHistogramDataPoint())
return es.At(es.Len() - 1)
}
// MoveAndAppendTo moves all elements from the current slice and appends them to the dest.
// The current slice will be cleared.
func (es ExponentialHistogramDataPointSlice) MoveAndAppendTo(dest ExponentialHistogramDataPointSlice) {
es.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if es.orig == dest.orig {
return
}
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
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (es ExponentialHistogramDataPointSlice) RemoveIf(f func(ExponentialHistogramDataPoint) bool) {
es.state.AssertMutable()
newLen := 0
for i := 0; i < len(*es.orig); i++ {
if f(es.At(i)) {
internal.DeleteExponentialHistogramDataPoint((*es.orig)[i], true)
(*es.orig)[i] = nil
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*es.orig)[newLen] = (*es.orig)[i]
// Cannot delete here since we just move the data(or pointer to data) to a different position in the slice.
(*es.orig)[i] = nil
newLen++
}
*es.orig = (*es.orig)[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (es ExponentialHistogramDataPointSlice) CopyTo(dest ExponentialHistogramDataPointSlice) {
dest.state.AssertMutable()
if es.orig == dest.orig {
return
}
*dest.orig = internal.CopyExponentialHistogramDataPointPtrSlice(*dest.orig, *es.orig)
}
// Sort sorts the ExponentialHistogramDataPoint elements within ExponentialHistogramDataPointSlice given the
// provided less function so that two instances of ExponentialHistogramDataPointSlice
// can be compared.
func (es ExponentialHistogramDataPointSlice) Sort(less func(a, b ExponentialHistogramDataPoint) bool) {
es.state.AssertMutable()
sort.SliceStable(*es.orig, func(i, j int) bool { return less(es.At(i), es.At(j)) })
}
================================================
FILE: pdata/pmetric/generated_exponentialhistogramdatapointslice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestExponentialHistogramDataPointSlice(t *testing.T) {
es := NewExponentialHistogramDataPointSlice()
assert.Equal(t, 0, es.Len())
es = newExponentialHistogramDataPointSlice(&[]*internal.ExponentialHistogramDataPoint{}, internal.NewState())
assert.Equal(t, 0, es.Len())
emptyVal := NewExponentialHistogramDataPoint()
testVal := generateTestExponentialHistogramDataPoint()
for i := 0; i < 7; i++ {
es.AppendEmpty()
assert.Equal(t, emptyVal, es.At(i))
(*es.orig)[i] = internal.GenTestExponentialHistogramDataPoint()
assert.Equal(t, testVal, es.At(i))
}
assert.Equal(t, 7, es.Len())
}
func TestExponentialHistogramDataPointSliceReadOnly(t *testing.T) {
sharedState := internal.NewState()
sharedState.MarkReadOnly()
es := newExponentialHistogramDataPointSlice(&[]*internal.ExponentialHistogramDataPoint{}, sharedState)
assert.Equal(t, 0, es.Len())
assert.Panics(t, func() { es.AppendEmpty() })
assert.Panics(t, func() { es.EnsureCapacity(2) })
es2 := NewExponentialHistogramDataPointSlice()
es.CopyTo(es2)
assert.Panics(t, func() { es2.CopyTo(es) })
assert.Panics(t, func() { es.MoveAndAppendTo(es2) })
assert.Panics(t, func() { es2.MoveAndAppendTo(es) })
}
func TestExponentialHistogramDataPointSlice_CopyTo(t *testing.T) {
dest := NewExponentialHistogramDataPointSlice()
src := generateTestExponentialHistogramDataPointSlice()
src.CopyTo(dest)
assert.Equal(t, generateTestExponentialHistogramDataPointSlice(), dest)
dest.CopyTo(dest)
assert.Equal(t, generateTestExponentialHistogramDataPointSlice(), dest)
}
func TestExponentialHistogramDataPointSlice_EnsureCapacity(t *testing.T) {
es := generateTestExponentialHistogramDataPointSlice()
// Test ensure smaller capacity.
const ensureSmallLen = 4
es.EnsureCapacity(ensureSmallLen)
assert.Less(t, ensureSmallLen, es.Len())
assert.Equal(t, es.Len(), cap(*es.orig))
assert.Equal(t, generateTestExponentialHistogramDataPointSlice(), es)
// Test ensure larger capacity
const ensureLargeLen = 9
es.EnsureCapacity(ensureLargeLen)
assert.Less(t, generateTestExponentialHistogramDataPointSlice().Len(), ensureLargeLen)
assert.Equal(t, ensureLargeLen, cap(*es.orig))
assert.Equal(t, generateTestExponentialHistogramDataPointSlice(), es)
}
func TestExponentialHistogramDataPointSlice_MoveAndAppendTo(t *testing.T) {
// Test MoveAndAppendTo to empty
expectedSlice := generateTestExponentialHistogramDataPointSlice()
dest := NewExponentialHistogramDataPointSlice()
src := generateTestExponentialHistogramDataPointSlice()
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestExponentialHistogramDataPointSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo empty slice
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestExponentialHistogramDataPointSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo not empty slice
generateTestExponentialHistogramDataPointSlice().MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
dest.MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
}
func TestExponentialHistogramDataPointSlice_RemoveIf(t *testing.T) {
// Test RemoveIf on empty slice
emptySlice := NewExponentialHistogramDataPointSlice()
emptySlice.RemoveIf(func(el ExponentialHistogramDataPoint) bool {
t.Fail()
return false
})
// Test RemoveIf
filtered := generateTestExponentialHistogramDataPointSlice()
pos := 0
filtered.RemoveIf(func(el ExponentialHistogramDataPoint) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, 2, filtered.Len())
}
func TestExponentialHistogramDataPointSlice_RemoveIfAll(t *testing.T) {
got := generateTestExponentialHistogramDataPointSlice()
got.RemoveIf(func(el ExponentialHistogramDataPoint) bool {
return true
})
assert.Equal(t, 0, got.Len())
}
func TestExponentialHistogramDataPointSliceAll(t *testing.T) {
ms := generateTestExponentialHistogramDataPointSlice()
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestExponentialHistogramDataPointSlice_Sort(t *testing.T) {
es := generateTestExponentialHistogramDataPointSlice()
es.Sort(func(a, b ExponentialHistogramDataPoint) bool {
return uintptr(unsafe.Pointer(a.orig)) < uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Less(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
es.Sort(func(a, b ExponentialHistogramDataPoint) bool {
return uintptr(unsafe.Pointer(a.orig)) > uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Greater(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
}
func generateTestExponentialHistogramDataPointSlice() ExponentialHistogramDataPointSlice {
ms := NewExponentialHistogramDataPointSlice()
*ms.orig = internal.GenTestExponentialHistogramDataPointPtrSlice()
return ms
}
================================================
FILE: pdata/pmetric/generated_gauge.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// Gauge represents the type of a numeric 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 NewGauge function to create new instances.
// Important: zero-initialized instance is not valid for use.
type Gauge struct {
orig *internal.Gauge
state *internal.State
}
func newGauge(orig *internal.Gauge, state *internal.State) Gauge {
return Gauge{orig: orig, state: state}
}
// NewGauge creates a new empty Gauge.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewGauge() Gauge {
return newGauge(internal.NewGauge(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms Gauge) MoveTo(dest Gauge) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteGauge(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// DataPoints returns the DataPoints associated with this Gauge.
func (ms Gauge) DataPoints() NumberDataPointSlice {
return newNumberDataPointSlice(&ms.orig.DataPoints, ms.state)
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms Gauge) CopyTo(dest Gauge) {
dest.state.AssertMutable()
internal.CopyGauge(dest.orig, ms.orig)
}
================================================
FILE: pdata/pmetric/generated_gauge_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestGauge_MoveTo(t *testing.T) {
ms := generateTestGauge()
dest := NewGauge()
ms.MoveTo(dest)
assert.Equal(t, NewGauge(), ms)
assert.Equal(t, generateTestGauge(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestGauge(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newGauge(internal.NewGauge(), sharedState)) })
assert.Panics(t, func() { newGauge(internal.NewGauge(), sharedState).MoveTo(dest) })
}
func TestGauge_CopyTo(t *testing.T) {
ms := NewGauge()
orig := NewGauge()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestGauge()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newGauge(internal.NewGauge(), sharedState)) })
}
func TestGauge_DataPoints(t *testing.T) {
ms := NewGauge()
assert.Equal(t, NewNumberDataPointSlice(), ms.DataPoints())
ms.orig.DataPoints = internal.GenTestNumberDataPointPtrSlice()
assert.Equal(t, generateTestNumberDataPointSlice(), ms.DataPoints())
}
func generateTestGauge() Gauge {
return newGauge(internal.GenTestGauge(), internal.NewState())
}
================================================
FILE: pdata/pmetric/generated_histogram.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// Histogram represents the type of a metric that is calculated by aggregating as a Histogram 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 NewHistogram function to create new instances.
// Important: zero-initialized instance is not valid for use.
type Histogram struct {
orig *internal.Histogram
state *internal.State
}
func newHistogram(orig *internal.Histogram, state *internal.State) Histogram {
return Histogram{orig: orig, state: state}
}
// NewHistogram creates a new empty Histogram.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewHistogram() Histogram {
return newHistogram(internal.NewHistogram(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms Histogram) MoveTo(dest Histogram) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteHistogram(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// DataPoints returns the DataPoints associated with this Histogram.
func (ms Histogram) DataPoints() HistogramDataPointSlice {
return newHistogramDataPointSlice(&ms.orig.DataPoints, ms.state)
}
// AggregationTemporality returns the aggregationtemporality associated with this Histogram.
func (ms Histogram) AggregationTemporality() AggregationTemporality {
return AggregationTemporality(ms.orig.AggregationTemporality)
}
// SetAggregationTemporality replaces the aggregationtemporality associated with this Histogram.
func (ms Histogram) SetAggregationTemporality(v AggregationTemporality) {
ms.state.AssertMutable()
ms.orig.AggregationTemporality = internal.AggregationTemporality(v)
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms Histogram) CopyTo(dest Histogram) {
dest.state.AssertMutable()
internal.CopyHistogram(dest.orig, ms.orig)
}
================================================
FILE: pdata/pmetric/generated_histogram_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestHistogram_MoveTo(t *testing.T) {
ms := generateTestHistogram()
dest := NewHistogram()
ms.MoveTo(dest)
assert.Equal(t, NewHistogram(), ms)
assert.Equal(t, generateTestHistogram(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestHistogram(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newHistogram(internal.NewHistogram(), sharedState)) })
assert.Panics(t, func() { newHistogram(internal.NewHistogram(), sharedState).MoveTo(dest) })
}
func TestHistogram_CopyTo(t *testing.T) {
ms := NewHistogram()
orig := NewHistogram()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestHistogram()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newHistogram(internal.NewHistogram(), sharedState)) })
}
func TestHistogram_DataPoints(t *testing.T) {
ms := NewHistogram()
assert.Equal(t, NewHistogramDataPointSlice(), ms.DataPoints())
ms.orig.DataPoints = internal.GenTestHistogramDataPointPtrSlice()
assert.Equal(t, generateTestHistogramDataPointSlice(), ms.DataPoints())
}
func TestHistogram_AggregationTemporality(t *testing.T) {
ms := NewHistogram()
assert.Equal(t, AggregationTemporality(internal.AggregationTemporality(0)), ms.AggregationTemporality())
testValAggregationTemporality := AggregationTemporality(internal.AggregationTemporality(1))
ms.SetAggregationTemporality(testValAggregationTemporality)
assert.Equal(t, testValAggregationTemporality, ms.AggregationTemporality())
}
func generateTestHistogram() Histogram {
return newHistogram(internal.GenTestHistogram(), internal.NewState())
}
================================================
FILE: pdata/pmetric/generated_histogramdatapoint.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// HistogramDataPoint is a single data point in a timeseries that describes the time-varying values of a Histogram of values.
//
// This is a reference type, if passed by value and callee modifies it the
// caller will see the modification.
//
// Must use NewHistogramDataPoint function to create new instances.
// Important: zero-initialized instance is not valid for use.
type HistogramDataPoint struct {
orig *internal.HistogramDataPoint
state *internal.State
}
func newHistogramDataPoint(orig *internal.HistogramDataPoint, state *internal.State) HistogramDataPoint {
return HistogramDataPoint{orig: orig, state: state}
}
// NewHistogramDataPoint creates a new empty HistogramDataPoint.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewHistogramDataPoint() HistogramDataPoint {
return newHistogramDataPoint(internal.NewHistogramDataPoint(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms HistogramDataPoint) MoveTo(dest HistogramDataPoint) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteHistogramDataPoint(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// Attributes returns the Attributes associated with this HistogramDataPoint.
func (ms HistogramDataPoint) Attributes() pcommon.Map {
return pcommon.Map(internal.NewMapWrapper(&ms.orig.Attributes, ms.state))
}
// StartTimestamp returns the starttimestamp associated with this HistogramDataPoint.
func (ms HistogramDataPoint) StartTimestamp() pcommon.Timestamp {
return pcommon.Timestamp(ms.orig.StartTimeUnixNano)
}
// SetStartTimestamp replaces the starttimestamp associated with this HistogramDataPoint.
func (ms HistogramDataPoint) SetStartTimestamp(v pcommon.Timestamp) {
ms.state.AssertMutable()
ms.orig.StartTimeUnixNano = uint64(v)
}
// Timestamp returns the timestamp associated with this HistogramDataPoint.
func (ms HistogramDataPoint) Timestamp() pcommon.Timestamp {
return pcommon.Timestamp(ms.orig.TimeUnixNano)
}
// SetTimestamp replaces the timestamp associated with this HistogramDataPoint.
func (ms HistogramDataPoint) SetTimestamp(v pcommon.Timestamp) {
ms.state.AssertMutable()
ms.orig.TimeUnixNano = uint64(v)
}
// Count returns the count associated with this HistogramDataPoint.
func (ms HistogramDataPoint) Count() uint64 {
return ms.orig.Count
}
// SetCount replaces the count associated with this HistogramDataPoint.
func (ms HistogramDataPoint) SetCount(v uint64) {
ms.state.AssertMutable()
ms.orig.Count = v
}
// Sum returns the sum associated with this HistogramDataPoint.
func (ms HistogramDataPoint) Sum() float64 {
return ms.orig.Sum
}
// HasSum returns true if the HistogramDataPoint contains a
// Sum value otherwise.
func (ms HistogramDataPoint) HasSum() bool {
return ms.orig.HasSum()
}
// SetSum replaces the sum associated with this HistogramDataPoint.
func (ms HistogramDataPoint) SetSum(v float64) {
ms.state.AssertMutable()
ms.orig.SetSum(v)
}
// RemoveSum removes the sum associated with this HistogramDataPoint.
func (ms HistogramDataPoint) RemoveSum() {
ms.state.AssertMutable()
ms.orig.RemoveSum()
}
// BucketCounts returns the BucketCounts associated with this HistogramDataPoint.
func (ms HistogramDataPoint) BucketCounts() pcommon.UInt64Slice {
return pcommon.UInt64Slice(internal.NewUInt64SliceWrapper(&ms.orig.BucketCounts, ms.state))
}
// ExplicitBounds returns the ExplicitBounds associated with this HistogramDataPoint.
func (ms HistogramDataPoint) ExplicitBounds() pcommon.Float64Slice {
return pcommon.Float64Slice(internal.NewFloat64SliceWrapper(&ms.orig.ExplicitBounds, ms.state))
}
// Exemplars returns the Exemplars associated with this HistogramDataPoint.
func (ms HistogramDataPoint) Exemplars() ExemplarSlice {
return newExemplarSlice(&ms.orig.Exemplars, ms.state)
}
// Flags returns the flags associated with this HistogramDataPoint.
func (ms HistogramDataPoint) Flags() DataPointFlags {
return DataPointFlags(ms.orig.Flags)
}
// SetFlags replaces the flags associated with this HistogramDataPoint.
func (ms HistogramDataPoint) SetFlags(v DataPointFlags) {
ms.state.AssertMutable()
ms.orig.Flags = uint32(v)
}
// Min returns the min associated with this HistogramDataPoint.
func (ms HistogramDataPoint) Min() float64 {
return ms.orig.Min
}
// HasMin returns true if the HistogramDataPoint contains a
// Min value otherwise.
func (ms HistogramDataPoint) HasMin() bool {
return ms.orig.HasMin()
}
// SetMin replaces the min associated with this HistogramDataPoint.
func (ms HistogramDataPoint) SetMin(v float64) {
ms.state.AssertMutable()
ms.orig.SetMin(v)
}
// RemoveMin removes the min associated with this HistogramDataPoint.
func (ms HistogramDataPoint) RemoveMin() {
ms.state.AssertMutable()
ms.orig.RemoveMin()
}
// Max returns the max associated with this HistogramDataPoint.
func (ms HistogramDataPoint) Max() float64 {
return ms.orig.Max
}
// HasMax returns true if the HistogramDataPoint contains a
// Max value otherwise.
func (ms HistogramDataPoint) HasMax() bool {
return ms.orig.HasMax()
}
// SetMax replaces the max associated with this HistogramDataPoint.
func (ms HistogramDataPoint) SetMax(v float64) {
ms.state.AssertMutable()
ms.orig.SetMax(v)
}
// RemoveMax removes the max associated with this HistogramDataPoint.
func (ms HistogramDataPoint) RemoveMax() {
ms.state.AssertMutable()
ms.orig.RemoveMax()
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms HistogramDataPoint) CopyTo(dest HistogramDataPoint) {
dest.state.AssertMutable()
internal.CopyHistogramDataPoint(dest.orig, ms.orig)
}
================================================
FILE: pdata/pmetric/generated_histogramdatapoint_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestHistogramDataPoint_MoveTo(t *testing.T) {
ms := generateTestHistogramDataPoint()
dest := NewHistogramDataPoint()
ms.MoveTo(dest)
assert.Equal(t, NewHistogramDataPoint(), ms)
assert.Equal(t, generateTestHistogramDataPoint(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestHistogramDataPoint(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newHistogramDataPoint(internal.NewHistogramDataPoint(), sharedState)) })
assert.Panics(t, func() { newHistogramDataPoint(internal.NewHistogramDataPoint(), sharedState).MoveTo(dest) })
}
func TestHistogramDataPoint_CopyTo(t *testing.T) {
ms := NewHistogramDataPoint()
orig := NewHistogramDataPoint()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestHistogramDataPoint()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newHistogramDataPoint(internal.NewHistogramDataPoint(), sharedState)) })
}
func TestHistogramDataPoint_Attributes(t *testing.T) {
ms := NewHistogramDataPoint()
assert.Equal(t, pcommon.NewMap(), ms.Attributes())
ms.orig.Attributes = internal.GenTestKeyValueSlice()
assert.Equal(t, pcommon.Map(internal.GenTestMapWrapper()), ms.Attributes())
}
func TestHistogramDataPoint_StartTimestamp(t *testing.T) {
ms := NewHistogramDataPoint()
assert.Equal(t, pcommon.Timestamp(0), ms.StartTimestamp())
testValStartTimestamp := pcommon.Timestamp(1234567890)
ms.SetStartTimestamp(testValStartTimestamp)
assert.Equal(t, testValStartTimestamp, ms.StartTimestamp())
}
func TestHistogramDataPoint_Timestamp(t *testing.T) {
ms := NewHistogramDataPoint()
assert.Equal(t, pcommon.Timestamp(0), ms.Timestamp())
testValTimestamp := pcommon.Timestamp(1234567890)
ms.SetTimestamp(testValTimestamp)
assert.Equal(t, testValTimestamp, ms.Timestamp())
}
func TestHistogramDataPoint_Count(t *testing.T) {
ms := NewHistogramDataPoint()
assert.Equal(t, uint64(0), ms.Count())
ms.SetCount(uint64(13))
assert.Equal(t, uint64(13), ms.Count())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newHistogramDataPoint(internal.NewHistogramDataPoint(), sharedState).SetCount(uint64(13)) })
}
func TestHistogramDataPoint_Sum(t *testing.T) {
ms := NewHistogramDataPoint()
assert.InDelta(t, float64(0), ms.Sum(), 0.01)
ms.SetSum(float64(3.1415926))
assert.True(t, ms.HasSum())
assert.InDelta(t, float64(3.1415926), ms.Sum(), 0.01)
ms.RemoveSum()
assert.False(t, ms.HasSum())
dest := NewHistogramDataPoint()
dest.SetSum(float64(3.1415926))
ms.CopyTo(dest)
assert.False(t, dest.HasSum())
}
func TestHistogramDataPoint_BucketCounts(t *testing.T) {
ms := NewHistogramDataPoint()
assert.Equal(t, pcommon.NewUInt64Slice(), ms.BucketCounts())
ms.orig.BucketCounts = internal.GenTestUint64Slice()
assert.Equal(t, pcommon.UInt64Slice(internal.GenTestUInt64SliceWrapper()), ms.BucketCounts())
}
func TestHistogramDataPoint_ExplicitBounds(t *testing.T) {
ms := NewHistogramDataPoint()
assert.Equal(t, pcommon.NewFloat64Slice(), ms.ExplicitBounds())
ms.orig.ExplicitBounds = internal.GenTestFloat64Slice()
assert.Equal(t, pcommon.Float64Slice(internal.GenTestFloat64SliceWrapper()), ms.ExplicitBounds())
}
func TestHistogramDataPoint_Exemplars(t *testing.T) {
ms := NewHistogramDataPoint()
assert.Equal(t, NewExemplarSlice(), ms.Exemplars())
ms.orig.Exemplars = internal.GenTestExemplarSlice()
assert.Equal(t, generateTestExemplarSlice(), ms.Exemplars())
}
func TestHistogramDataPoint_Flags(t *testing.T) {
ms := NewHistogramDataPoint()
assert.Equal(t, DataPointFlags(0), ms.Flags())
testValFlags := DataPointFlags(1)
ms.SetFlags(testValFlags)
assert.Equal(t, testValFlags, ms.Flags())
}
func TestHistogramDataPoint_Min(t *testing.T) {
ms := NewHistogramDataPoint()
assert.InDelta(t, float64(0), ms.Min(), 0.01)
ms.SetMin(float64(3.1415926))
assert.True(t, ms.HasMin())
assert.InDelta(t, float64(3.1415926), ms.Min(), 0.01)
ms.RemoveMin()
assert.False(t, ms.HasMin())
dest := NewHistogramDataPoint()
dest.SetMin(float64(3.1415926))
ms.CopyTo(dest)
assert.False(t, dest.HasMin())
}
func TestHistogramDataPoint_Max(t *testing.T) {
ms := NewHistogramDataPoint()
assert.InDelta(t, float64(0), ms.Max(), 0.01)
ms.SetMax(float64(3.1415926))
assert.True(t, ms.HasMax())
assert.InDelta(t, float64(3.1415926), ms.Max(), 0.01)
ms.RemoveMax()
assert.False(t, ms.HasMax())
dest := NewHistogramDataPoint()
dest.SetMax(float64(3.1415926))
ms.CopyTo(dest)
assert.False(t, dest.HasMax())
}
func generateTestHistogramDataPoint() HistogramDataPoint {
return newHistogramDataPoint(internal.GenTestHistogramDataPoint(), internal.NewState())
}
================================================
FILE: pdata/pmetric/generated_histogramdatapointslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"iter"
"sort"
"go.opentelemetry.io/collector/pdata/internal"
)
// HistogramDataPointSlice logically represents a slice of HistogramDataPoint.
//
// This is a reference type. If passed by value and callee modifies it, the
// caller will see the modification.
//
// Must use NewHistogramDataPointSlice function to create new instances.
// Important: zero-initialized instance is not valid for use.
type HistogramDataPointSlice struct {
orig *[]*internal.HistogramDataPoint
state *internal.State
}
func newHistogramDataPointSlice(orig *[]*internal.HistogramDataPoint, state *internal.State) HistogramDataPointSlice {
return HistogramDataPointSlice{orig: orig, state: state}
}
// NewHistogramDataPointSlice creates a HistogramDataPointSliceWrapper with 0 elements.
// Can use "EnsureCapacity" to initialize with a given capacity.
func NewHistogramDataPointSlice() HistogramDataPointSlice {
orig := []*internal.HistogramDataPoint(nil)
return newHistogramDataPointSlice(&orig, internal.NewState())
}
// Len returns the number of elements in the slice.
//
// Returns "0" for a newly instance created with "NewHistogramDataPointSlice()".
func (es HistogramDataPointSlice) 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 HistogramDataPointSlice) At(i int) HistogramDataPoint {
return newHistogramDataPoint((*es.orig)[i], es.state)
}
// All returns an iterator over index-value pairs in the slice.
//
// for i, v := range es.All() {
// ... // Do something with index-value pair
// }
func (es HistogramDataPointSlice) All() iter.Seq2[int, HistogramDataPoint] {
return func(yield func(int, HistogramDataPoint) bool) {
for i := 0; i < es.Len(); i++ {
if !yield(i, es.At(i)) {
return
}
}
}
}
// EnsureCapacity is an operation that ensures the slice has at least the specified capacity.
// 1. If the newCap <= cap then no change in capacity.
// 2. If the newCap > cap then the slice capacity will be expanded to equal newCap.
//
// Here is how a new HistogramDataPointSlice can be initialized:
//
// es := NewHistogramDataPointSlice()
// es.EnsureCapacity(4)
// for i := 0; i < 4; i++ {
// e := es.AppendEmpty()
// // Here should set all the values for e.
// }
func (es HistogramDataPointSlice) EnsureCapacity(newCap int) {
es.state.AssertMutable()
oldCap := cap(*es.orig)
if newCap <= oldCap {
return
}
newOrig := make([]*internal.HistogramDataPoint, len(*es.orig), newCap)
copy(newOrig, *es.orig)
*es.orig = newOrig
}
// AppendEmpty will append to the end of the slice an empty HistogramDataPoint.
// It returns the newly added HistogramDataPoint.
func (es HistogramDataPointSlice) AppendEmpty() HistogramDataPoint {
es.state.AssertMutable()
*es.orig = append(*es.orig, internal.NewHistogramDataPoint())
return es.At(es.Len() - 1)
}
// MoveAndAppendTo moves all elements from the current slice and appends them to the dest.
// The current slice will be cleared.
func (es HistogramDataPointSlice) MoveAndAppendTo(dest HistogramDataPointSlice) {
es.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if es.orig == dest.orig {
return
}
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
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (es HistogramDataPointSlice) RemoveIf(f func(HistogramDataPoint) bool) {
es.state.AssertMutable()
newLen := 0
for i := 0; i < len(*es.orig); i++ {
if f(es.At(i)) {
internal.DeleteHistogramDataPoint((*es.orig)[i], true)
(*es.orig)[i] = nil
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*es.orig)[newLen] = (*es.orig)[i]
// Cannot delete here since we just move the data(or pointer to data) to a different position in the slice.
(*es.orig)[i] = nil
newLen++
}
*es.orig = (*es.orig)[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (es HistogramDataPointSlice) CopyTo(dest HistogramDataPointSlice) {
dest.state.AssertMutable()
if es.orig == dest.orig {
return
}
*dest.orig = internal.CopyHistogramDataPointPtrSlice(*dest.orig, *es.orig)
}
// Sort sorts the HistogramDataPoint elements within HistogramDataPointSlice given the
// provided less function so that two instances of HistogramDataPointSlice
// can be compared.
func (es HistogramDataPointSlice) Sort(less func(a, b HistogramDataPoint) bool) {
es.state.AssertMutable()
sort.SliceStable(*es.orig, func(i, j int) bool { return less(es.At(i), es.At(j)) })
}
================================================
FILE: pdata/pmetric/generated_histogramdatapointslice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestHistogramDataPointSlice(t *testing.T) {
es := NewHistogramDataPointSlice()
assert.Equal(t, 0, es.Len())
es = newHistogramDataPointSlice(&[]*internal.HistogramDataPoint{}, internal.NewState())
assert.Equal(t, 0, es.Len())
emptyVal := NewHistogramDataPoint()
testVal := generateTestHistogramDataPoint()
for i := 0; i < 7; i++ {
es.AppendEmpty()
assert.Equal(t, emptyVal, es.At(i))
(*es.orig)[i] = internal.GenTestHistogramDataPoint()
assert.Equal(t, testVal, es.At(i))
}
assert.Equal(t, 7, es.Len())
}
func TestHistogramDataPointSliceReadOnly(t *testing.T) {
sharedState := internal.NewState()
sharedState.MarkReadOnly()
es := newHistogramDataPointSlice(&[]*internal.HistogramDataPoint{}, sharedState)
assert.Equal(t, 0, es.Len())
assert.Panics(t, func() { es.AppendEmpty() })
assert.Panics(t, func() { es.EnsureCapacity(2) })
es2 := NewHistogramDataPointSlice()
es.CopyTo(es2)
assert.Panics(t, func() { es2.CopyTo(es) })
assert.Panics(t, func() { es.MoveAndAppendTo(es2) })
assert.Panics(t, func() { es2.MoveAndAppendTo(es) })
}
func TestHistogramDataPointSlice_CopyTo(t *testing.T) {
dest := NewHistogramDataPointSlice()
src := generateTestHistogramDataPointSlice()
src.CopyTo(dest)
assert.Equal(t, generateTestHistogramDataPointSlice(), dest)
dest.CopyTo(dest)
assert.Equal(t, generateTestHistogramDataPointSlice(), dest)
}
func TestHistogramDataPointSlice_EnsureCapacity(t *testing.T) {
es := generateTestHistogramDataPointSlice()
// Test ensure smaller capacity.
const ensureSmallLen = 4
es.EnsureCapacity(ensureSmallLen)
assert.Less(t, ensureSmallLen, es.Len())
assert.Equal(t, es.Len(), cap(*es.orig))
assert.Equal(t, generateTestHistogramDataPointSlice(), es)
// Test ensure larger capacity
const ensureLargeLen = 9
es.EnsureCapacity(ensureLargeLen)
assert.Less(t, generateTestHistogramDataPointSlice().Len(), ensureLargeLen)
assert.Equal(t, ensureLargeLen, cap(*es.orig))
assert.Equal(t, generateTestHistogramDataPointSlice(), es)
}
func TestHistogramDataPointSlice_MoveAndAppendTo(t *testing.T) {
// Test MoveAndAppendTo to empty
expectedSlice := generateTestHistogramDataPointSlice()
dest := NewHistogramDataPointSlice()
src := generateTestHistogramDataPointSlice()
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestHistogramDataPointSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo empty slice
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestHistogramDataPointSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo not empty slice
generateTestHistogramDataPointSlice().MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
dest.MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
}
func TestHistogramDataPointSlice_RemoveIf(t *testing.T) {
// Test RemoveIf on empty slice
emptySlice := NewHistogramDataPointSlice()
emptySlice.RemoveIf(func(el HistogramDataPoint) bool {
t.Fail()
return false
})
// Test RemoveIf
filtered := generateTestHistogramDataPointSlice()
pos := 0
filtered.RemoveIf(func(el HistogramDataPoint) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, 2, filtered.Len())
}
func TestHistogramDataPointSlice_RemoveIfAll(t *testing.T) {
got := generateTestHistogramDataPointSlice()
got.RemoveIf(func(el HistogramDataPoint) bool {
return true
})
assert.Equal(t, 0, got.Len())
}
func TestHistogramDataPointSliceAll(t *testing.T) {
ms := generateTestHistogramDataPointSlice()
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestHistogramDataPointSlice_Sort(t *testing.T) {
es := generateTestHistogramDataPointSlice()
es.Sort(func(a, b HistogramDataPoint) bool {
return uintptr(unsafe.Pointer(a.orig)) < uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Less(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
es.Sort(func(a, b HistogramDataPoint) bool {
return uintptr(unsafe.Pointer(a.orig)) > uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Greater(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
}
func generateTestHistogramDataPointSlice() HistogramDataPointSlice {
ms := NewHistogramDataPointSlice()
*ms.orig = internal.GenTestHistogramDataPointPtrSlice()
return ms
}
================================================
FILE: pdata/pmetric/generated_metric.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// Metric represents one metric as a collection of datapoints.
// See Metric definition in OTLP: https://github.com/open-telemetry/opentelemetry-proto/blob/main/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 *internal.Metric
state *internal.State
}
func newMetric(orig *internal.Metric, state *internal.State) Metric {
return Metric{orig: orig, state: state}
}
// NewMetric creates a new empty Metric.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewMetric() Metric {
return newMetric(internal.NewMetric(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms Metric) MoveTo(dest Metric) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteMetric(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// Name returns the name associated with this Metric.
func (ms Metric) Name() string {
return ms.orig.Name
}
// SetName replaces the name associated with this Metric.
func (ms Metric) SetName(v string) {
ms.state.AssertMutable()
ms.orig.Name = v
}
// Description returns the description associated with this Metric.
func (ms Metric) Description() string {
return ms.orig.Description
}
// SetDescription replaces the description associated with this Metric.
func (ms Metric) SetDescription(v string) {
ms.state.AssertMutable()
ms.orig.Description = v
}
// Unit returns the unit associated with this Metric.
func (ms Metric) Unit() string {
return ms.orig.Unit
}
// SetUnit replaces the unit associated with this Metric.
func (ms Metric) SetUnit(v string) {
ms.state.AssertMutable()
ms.orig.Unit = v
}
// Type returns the type of the data for this Metric.
// Calling this function on zero-initialized Metric will cause a panic.
func (ms Metric) Type() MetricType {
switch ms.orig.Data.(type) {
case *internal.Metric_Gauge:
return MetricTypeGauge
case *internal.Metric_Sum:
return MetricTypeSum
case *internal.Metric_Histogram:
return MetricTypeHistogram
case *internal.Metric_ExponentialHistogram:
return MetricTypeExponentialHistogram
case *internal.Metric_Summary:
return MetricTypeSummary
}
return MetricTypeEmpty
}
// Gauge returns the gauge associated with this Metric.
//
// Calling this function when Type() != MetricTypeGauge returns an invalid
// zero-initialized instance of Gauge. Note that using such Gauge instance can cause panic.
//
// Calling this function on zero-initialized Metric will cause a panic.
func (ms Metric) Gauge() Gauge {
v, ok := ms.orig.GetData().(*internal.Metric_Gauge)
if !ok {
return Gauge{}
}
return newGauge(v.Gauge, ms.state)
}
// SetEmptyGauge sets an empty gauge to this Metric.
//
// After this, Type() function will return MetricTypeGauge".
//
// Calling this function on zero-initialized Metric will cause a panic.
func (ms Metric) SetEmptyGauge() Gauge {
ms.state.AssertMutable()
var ov *internal.Metric_Gauge
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &internal.Metric_Gauge{}
} else {
ov = internal.ProtoPoolMetric_Gauge.Get().(*internal.Metric_Gauge)
}
ov.Gauge = internal.NewGauge()
ms.orig.Data = ov
return newGauge(ov.Gauge, ms.state)
} // Sum returns the sum associated with this Metric.
// Calling this function when Type() != MetricTypeSum returns an invalid
// zero-initialized instance of Sum. Note that using such Sum instance can cause panic.
//
// Calling this function on zero-initialized Metric will cause a panic.
func (ms Metric) Sum() Sum {
v, ok := ms.orig.GetData().(*internal.Metric_Sum)
if !ok {
return Sum{}
}
return newSum(v.Sum, ms.state)
}
// SetEmptySum sets an empty sum to this Metric.
//
// After this, Type() function will return MetricTypeSum".
//
// Calling this function on zero-initialized Metric will cause a panic.
func (ms Metric) SetEmptySum() Sum {
ms.state.AssertMutable()
var ov *internal.Metric_Sum
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &internal.Metric_Sum{}
} else {
ov = internal.ProtoPoolMetric_Sum.Get().(*internal.Metric_Sum)
}
ov.Sum = internal.NewSum()
ms.orig.Data = ov
return newSum(ov.Sum, ms.state)
} // Histogram returns the histogram associated with this Metric.
// Calling this function when Type() != MetricTypeHistogram returns an invalid
// zero-initialized instance of Histogram. Note that using such Histogram instance can cause panic.
//
// Calling this function on zero-initialized Metric will cause a panic.
func (ms Metric) Histogram() Histogram {
v, ok := ms.orig.GetData().(*internal.Metric_Histogram)
if !ok {
return Histogram{}
}
return newHistogram(v.Histogram, ms.state)
}
// SetEmptyHistogram sets an empty histogram to this Metric.
//
// After this, Type() function will return MetricTypeHistogram".
//
// Calling this function on zero-initialized Metric will cause a panic.
func (ms Metric) SetEmptyHistogram() Histogram {
ms.state.AssertMutable()
var ov *internal.Metric_Histogram
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &internal.Metric_Histogram{}
} else {
ov = internal.ProtoPoolMetric_Histogram.Get().(*internal.Metric_Histogram)
}
ov.Histogram = internal.NewHistogram()
ms.orig.Data = ov
return newHistogram(ov.Histogram, ms.state)
} // ExponentialHistogram returns the exponentialhistogram associated with this Metric.
// Calling this function when Type() != MetricTypeExponentialHistogram returns an invalid
// zero-initialized instance of ExponentialHistogram. Note that using such ExponentialHistogram instance can cause panic.
//
// Calling this function on zero-initialized Metric will cause a panic.
func (ms Metric) ExponentialHistogram() ExponentialHistogram {
v, ok := ms.orig.GetData().(*internal.Metric_ExponentialHistogram)
if !ok {
return ExponentialHistogram{}
}
return newExponentialHistogram(v.ExponentialHistogram, ms.state)
}
// SetEmptyExponentialHistogram sets an empty exponentialhistogram to this Metric.
//
// After this, Type() function will return MetricTypeExponentialHistogram".
//
// Calling this function on zero-initialized Metric will cause a panic.
func (ms Metric) SetEmptyExponentialHistogram() ExponentialHistogram {
ms.state.AssertMutable()
var ov *internal.Metric_ExponentialHistogram
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &internal.Metric_ExponentialHistogram{}
} else {
ov = internal.ProtoPoolMetric_ExponentialHistogram.Get().(*internal.Metric_ExponentialHistogram)
}
ov.ExponentialHistogram = internal.NewExponentialHistogram()
ms.orig.Data = ov
return newExponentialHistogram(ov.ExponentialHistogram, ms.state)
} // Summary returns the summary associated with this Metric.
// Calling this function when Type() != MetricTypeSummary returns an invalid
// zero-initialized instance of Summary. Note that using such Summary instance can cause panic.
//
// Calling this function on zero-initialized Metric will cause a panic.
func (ms Metric) Summary() Summary {
v, ok := ms.orig.GetData().(*internal.Metric_Summary)
if !ok {
return Summary{}
}
return newSummary(v.Summary, ms.state)
}
// SetEmptySummary sets an empty summary to this Metric.
//
// After this, Type() function will return MetricTypeSummary".
//
// Calling this function on zero-initialized Metric will cause a panic.
func (ms Metric) SetEmptySummary() Summary {
ms.state.AssertMutable()
var ov *internal.Metric_Summary
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &internal.Metric_Summary{}
} else {
ov = internal.ProtoPoolMetric_Summary.Get().(*internal.Metric_Summary)
}
ov.Summary = internal.NewSummary()
ms.orig.Data = ov
return newSummary(ov.Summary, ms.state)
}
// Metadata returns the Metadata associated with this Metric.
func (ms Metric) Metadata() pcommon.Map {
return pcommon.Map(internal.NewMapWrapper(&ms.orig.Metadata, ms.state))
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms Metric) CopyTo(dest Metric) {
dest.state.AssertMutable()
internal.CopyMetric(dest.orig, ms.orig)
}
================================================
FILE: pdata/pmetric/generated_metric_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestMetric_MoveTo(t *testing.T) {
ms := generateTestMetric()
dest := NewMetric()
ms.MoveTo(dest)
assert.Equal(t, NewMetric(), ms)
assert.Equal(t, generateTestMetric(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestMetric(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newMetric(internal.NewMetric(), sharedState)) })
assert.Panics(t, func() { newMetric(internal.NewMetric(), sharedState).MoveTo(dest) })
}
func TestMetric_CopyTo(t *testing.T) {
ms := NewMetric()
orig := NewMetric()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestMetric()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newMetric(internal.NewMetric(), sharedState)) })
}
func TestMetric_Name(t *testing.T) {
ms := NewMetric()
assert.Empty(t, ms.Name())
ms.SetName("test_name")
assert.Equal(t, "test_name", ms.Name())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newMetric(internal.NewMetric(), sharedState).SetName("test_name") })
}
func TestMetric_Description(t *testing.T) {
ms := NewMetric()
assert.Empty(t, ms.Description())
ms.SetDescription("test_description")
assert.Equal(t, "test_description", ms.Description())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newMetric(internal.NewMetric(), sharedState).SetDescription("test_description") })
}
func TestMetric_Unit(t *testing.T) {
ms := NewMetric()
assert.Empty(t, ms.Unit())
ms.SetUnit("test_unit")
assert.Equal(t, "test_unit", ms.Unit())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newMetric(internal.NewMetric(), sharedState).SetUnit("test_unit") })
}
func TestMetric_Type(t *testing.T) {
tv := NewMetric()
assert.Equal(t, MetricTypeEmpty, tv.Type())
}
func TestMetric_Gauge(t *testing.T) {
ms := NewMetric()
ms.SetEmptyGauge()
assert.Equal(t, NewGauge(), ms.Gauge())
ms.orig.GetData().(*internal.Metric_Gauge).Gauge = internal.GenTestGauge()
assert.Equal(t, MetricTypeGauge, ms.Type())
assert.Equal(t, generateTestGauge(), ms.Gauge())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newMetric(internal.NewMetric(), sharedState).SetEmptyGauge() })
}
func TestMetric_Sum(t *testing.T) {
ms := NewMetric()
ms.SetEmptySum()
assert.Equal(t, NewSum(), ms.Sum())
ms.orig.GetData().(*internal.Metric_Sum).Sum = internal.GenTestSum()
assert.Equal(t, MetricTypeSum, ms.Type())
assert.Equal(t, generateTestSum(), ms.Sum())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newMetric(internal.NewMetric(), sharedState).SetEmptySum() })
}
func TestMetric_Histogram(t *testing.T) {
ms := NewMetric()
ms.SetEmptyHistogram()
assert.Equal(t, NewHistogram(), ms.Histogram())
ms.orig.GetData().(*internal.Metric_Histogram).Histogram = internal.GenTestHistogram()
assert.Equal(t, MetricTypeHistogram, ms.Type())
assert.Equal(t, generateTestHistogram(), ms.Histogram())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newMetric(internal.NewMetric(), sharedState).SetEmptyHistogram() })
}
func TestMetric_ExponentialHistogram(t *testing.T) {
ms := NewMetric()
ms.SetEmptyExponentialHistogram()
assert.Equal(t, NewExponentialHistogram(), ms.ExponentialHistogram())
ms.orig.GetData().(*internal.Metric_ExponentialHistogram).ExponentialHistogram = internal.GenTestExponentialHistogram()
assert.Equal(t, MetricTypeExponentialHistogram, ms.Type())
assert.Equal(t, generateTestExponentialHistogram(), ms.ExponentialHistogram())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newMetric(internal.NewMetric(), sharedState).SetEmptyExponentialHistogram() })
}
func TestMetric_Summary(t *testing.T) {
ms := NewMetric()
ms.SetEmptySummary()
assert.Equal(t, NewSummary(), ms.Summary())
ms.orig.GetData().(*internal.Metric_Summary).Summary = internal.GenTestSummary()
assert.Equal(t, MetricTypeSummary, ms.Type())
assert.Equal(t, generateTestSummary(), ms.Summary())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newMetric(internal.NewMetric(), sharedState).SetEmptySummary() })
}
func TestMetric_Metadata(t *testing.T) {
ms := NewMetric()
assert.Equal(t, pcommon.NewMap(), ms.Metadata())
ms.orig.Metadata = internal.GenTestKeyValueSlice()
assert.Equal(t, pcommon.Map(internal.GenTestMapWrapper()), ms.Metadata())
}
func generateTestMetric() Metric {
return newMetric(internal.GenTestMetric(), internal.NewState())
}
================================================
FILE: pdata/pmetric/generated_metrics.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// Metrics is the top-level struct that is propagated through the metrics pipeline.
// Use NewMetrics to create new instance, zero-initialized instance is not valid for use.
//
// This is a reference type, if passed by value and callee modifies it the
// caller will see the modification.
//
// Must use NewMetrics function to create new instances.
// Important: zero-initialized instance is not valid for use.
type Metrics internal.MetricsWrapper
func newMetrics(orig *internal.ExportMetricsServiceRequest, state *internal.State) Metrics {
return Metrics(internal.NewMetricsWrapper(orig, state))
}
// NewMetrics creates a new empty Metrics.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewMetrics() Metrics {
return newMetrics(internal.NewExportMetricsServiceRequest(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms Metrics) MoveTo(dest Metrics) {
ms.getState().AssertMutable()
dest.getState().AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.getOrig() == dest.getOrig() {
return
}
internal.DeleteExportMetricsServiceRequest(dest.getOrig(), false)
*dest.getOrig(), *ms.getOrig() = *ms.getOrig(), *dest.getOrig()
}
// ResourceMetrics returns the ResourceMetrics associated with this Metrics.
func (ms Metrics) ResourceMetrics() ResourceMetricsSlice {
return newResourceMetricsSlice(&ms.getOrig().ResourceMetrics, ms.getState())
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms Metrics) CopyTo(dest Metrics) {
dest.getState().AssertMutable()
internal.CopyExportMetricsServiceRequest(dest.getOrig(), ms.getOrig())
}
func (ms Metrics) getOrig() *internal.ExportMetricsServiceRequest {
return internal.GetMetricsOrig(internal.MetricsWrapper(ms))
}
func (ms Metrics) getState() *internal.State {
return internal.GetMetricsState(internal.MetricsWrapper(ms))
}
================================================
FILE: pdata/pmetric/generated_metrics_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestMetrics_MoveTo(t *testing.T) {
ms := generateTestMetrics()
dest := NewMetrics()
ms.MoveTo(dest)
assert.Equal(t, NewMetrics(), ms)
assert.Equal(t, generateTestMetrics(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestMetrics(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newMetrics(internal.NewExportMetricsServiceRequest(), sharedState)) })
assert.Panics(t, func() { newMetrics(internal.NewExportMetricsServiceRequest(), sharedState).MoveTo(dest) })
}
func TestMetrics_CopyTo(t *testing.T) {
ms := NewMetrics()
orig := NewMetrics()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestMetrics()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newMetrics(internal.NewExportMetricsServiceRequest(), sharedState)) })
}
func TestMetrics_ResourceMetrics(t *testing.T) {
ms := NewMetrics()
assert.Equal(t, NewResourceMetricsSlice(), ms.ResourceMetrics())
ms.getOrig().ResourceMetrics = internal.GenTestResourceMetricsPtrSlice()
assert.Equal(t, generateTestResourceMetricsSlice(), ms.ResourceMetrics())
}
func generateTestMetrics() Metrics {
return newMetrics(internal.GenTestExportMetricsServiceRequest(), internal.NewState())
}
================================================
FILE: pdata/pmetric/generated_metricslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"iter"
"sort"
"go.opentelemetry.io/collector/pdata/internal"
)
// 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 *[]*internal.Metric
state *internal.State
}
func newMetricSlice(orig *[]*internal.Metric, state *internal.State) MetricSlice {
return MetricSlice{orig: orig, state: state}
}
// NewMetricSlice creates a MetricSliceWrapper with 0 elements.
// Can use "EnsureCapacity" to initialize with a given capacity.
func NewMetricSlice() MetricSlice {
orig := []*internal.Metric(nil)
return newMetricSlice(&orig, internal.NewState())
}
// 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(i int) Metric {
return newMetric((*es.orig)[i], es.state)
}
// All returns an iterator over index-value pairs in the slice.
//
// for i, v := range es.All() {
// ... // Do something with index-value pair
// }
func (es MetricSlice) All() iter.Seq2[int, Metric] {
return func(yield func(int, Metric) bool) {
for i := 0; i < es.Len(); i++ {
if !yield(i, es.At(i)) {
return
}
}
}
}
// EnsureCapacity is an operation that ensures the slice has at least the specified capacity.
// 1. If the newCap <= cap then no change in capacity.
// 2. If the newCap > cap then the slice capacity will be expanded to equal newCap.
//
// Here is how a new MetricSlice can be initialized:
//
// es := NewMetricSlice()
// es.EnsureCapacity(4)
// for i := 0; i < 4; i++ {
// e := es.AppendEmpty()
// // Here should set all the values for e.
// }
func (es MetricSlice) EnsureCapacity(newCap int) {
es.state.AssertMutable()
oldCap := cap(*es.orig)
if newCap <= oldCap {
return
}
newOrig := make([]*internal.Metric, len(*es.orig), newCap)
copy(newOrig, *es.orig)
*es.orig = newOrig
}
// AppendEmpty will append to the end of the slice an empty Metric.
// It returns the newly added Metric.
func (es MetricSlice) AppendEmpty() Metric {
es.state.AssertMutable()
*es.orig = append(*es.orig, internal.NewMetric())
return es.At(es.Len() - 1)
}
// 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) {
es.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if es.orig == dest.orig {
return
}
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
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (es MetricSlice) RemoveIf(f func(Metric) bool) {
es.state.AssertMutable()
newLen := 0
for i := 0; i < len(*es.orig); i++ {
if f(es.At(i)) {
internal.DeleteMetric((*es.orig)[i], true)
(*es.orig)[i] = nil
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*es.orig)[newLen] = (*es.orig)[i]
// Cannot delete here since we just move the data(or pointer to data) to a different position in the slice.
(*es.orig)[i] = nil
newLen++
}
*es.orig = (*es.orig)[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (es MetricSlice) CopyTo(dest MetricSlice) {
dest.state.AssertMutable()
if es.orig == dest.orig {
return
}
*dest.orig = internal.CopyMetricPtrSlice(*dest.orig, *es.orig)
}
// Sort sorts the Metric elements within MetricSlice given the
// provided less function so that two instances of MetricSlice
// can be compared.
func (es MetricSlice) Sort(less func(a, b Metric) bool) {
es.state.AssertMutable()
sort.SliceStable(*es.orig, func(i, j int) bool { return less(es.At(i), es.At(j)) })
}
================================================
FILE: pdata/pmetric/generated_metricslice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestMetricSlice(t *testing.T) {
es := NewMetricSlice()
assert.Equal(t, 0, es.Len())
es = newMetricSlice(&[]*internal.Metric{}, internal.NewState())
assert.Equal(t, 0, es.Len())
emptyVal := NewMetric()
testVal := generateTestMetric()
for i := 0; i < 7; i++ {
es.AppendEmpty()
assert.Equal(t, emptyVal, es.At(i))
(*es.orig)[i] = internal.GenTestMetric()
assert.Equal(t, testVal, es.At(i))
}
assert.Equal(t, 7, es.Len())
}
func TestMetricSliceReadOnly(t *testing.T) {
sharedState := internal.NewState()
sharedState.MarkReadOnly()
es := newMetricSlice(&[]*internal.Metric{}, sharedState)
assert.Equal(t, 0, es.Len())
assert.Panics(t, func() { es.AppendEmpty() })
assert.Panics(t, func() { es.EnsureCapacity(2) })
es2 := NewMetricSlice()
es.CopyTo(es2)
assert.Panics(t, func() { es2.CopyTo(es) })
assert.Panics(t, func() { es.MoveAndAppendTo(es2) })
assert.Panics(t, func() { es2.MoveAndAppendTo(es) })
}
func TestMetricSlice_CopyTo(t *testing.T) {
dest := NewMetricSlice()
src := generateTestMetricSlice()
src.CopyTo(dest)
assert.Equal(t, generateTestMetricSlice(), dest)
dest.CopyTo(dest)
assert.Equal(t, generateTestMetricSlice(), dest)
}
func TestMetricSlice_EnsureCapacity(t *testing.T) {
es := generateTestMetricSlice()
// Test ensure smaller capacity.
const ensureSmallLen = 4
es.EnsureCapacity(ensureSmallLen)
assert.Less(t, ensureSmallLen, es.Len())
assert.Equal(t, es.Len(), cap(*es.orig))
assert.Equal(t, generateTestMetricSlice(), es)
// Test ensure larger capacity
const ensureLargeLen = 9
es.EnsureCapacity(ensureLargeLen)
assert.Less(t, generateTestMetricSlice().Len(), ensureLargeLen)
assert.Equal(t, ensureLargeLen, cap(*es.orig))
assert.Equal(t, generateTestMetricSlice(), es)
}
func TestMetricSlice_MoveAndAppendTo(t *testing.T) {
// Test MoveAndAppendTo to empty
expectedSlice := generateTestMetricSlice()
dest := NewMetricSlice()
src := generateTestMetricSlice()
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestMetricSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo empty slice
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestMetricSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo not empty slice
generateTestMetricSlice().MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
dest.MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
}
func TestMetricSlice_RemoveIf(t *testing.T) {
// Test RemoveIf on empty slice
emptySlice := NewMetricSlice()
emptySlice.RemoveIf(func(el Metric) bool {
t.Fail()
return false
})
// Test RemoveIf
filtered := generateTestMetricSlice()
pos := 0
filtered.RemoveIf(func(el Metric) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, 2, filtered.Len())
}
func TestMetricSlice_RemoveIfAll(t *testing.T) {
got := generateTestMetricSlice()
got.RemoveIf(func(el Metric) bool {
return true
})
assert.Equal(t, 0, got.Len())
}
func TestMetricSliceAll(t *testing.T) {
ms := generateTestMetricSlice()
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestMetricSlice_Sort(t *testing.T) {
es := generateTestMetricSlice()
es.Sort(func(a, b Metric) bool {
return uintptr(unsafe.Pointer(a.orig)) < uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Less(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
es.Sort(func(a, b Metric) bool {
return uintptr(unsafe.Pointer(a.orig)) > uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Greater(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
}
func generateTestMetricSlice() MetricSlice {
ms := NewMetricSlice()
*ms.orig = internal.GenTestMetricPtrSlice()
return ms
}
================================================
FILE: pdata/pmetric/generated_numberdatapoint.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// NumberDataPoint is a single data point in a timeseries that describes the time-varying value of a number metric.
//
// This is a reference type, if passed by value and callee modifies it the
// caller will see the modification.
//
// Must use NewNumberDataPoint function to create new instances.
// Important: zero-initialized instance is not valid for use.
type NumberDataPoint struct {
orig *internal.NumberDataPoint
state *internal.State
}
func newNumberDataPoint(orig *internal.NumberDataPoint, state *internal.State) NumberDataPoint {
return NumberDataPoint{orig: orig, state: state}
}
// NewNumberDataPoint creates a new empty NumberDataPoint.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewNumberDataPoint() NumberDataPoint {
return newNumberDataPoint(internal.NewNumberDataPoint(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms NumberDataPoint) MoveTo(dest NumberDataPoint) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteNumberDataPoint(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// Attributes returns the Attributes associated with this NumberDataPoint.
func (ms NumberDataPoint) Attributes() pcommon.Map {
return pcommon.Map(internal.NewMapWrapper(&ms.orig.Attributes, ms.state))
}
// StartTimestamp returns the starttimestamp associated with this NumberDataPoint.
func (ms NumberDataPoint) StartTimestamp() pcommon.Timestamp {
return pcommon.Timestamp(ms.orig.StartTimeUnixNano)
}
// SetStartTimestamp replaces the starttimestamp associated with this NumberDataPoint.
func (ms NumberDataPoint) SetStartTimestamp(v pcommon.Timestamp) {
ms.state.AssertMutable()
ms.orig.StartTimeUnixNano = uint64(v)
}
// Timestamp returns the timestamp associated with this NumberDataPoint.
func (ms NumberDataPoint) Timestamp() pcommon.Timestamp {
return pcommon.Timestamp(ms.orig.TimeUnixNano)
}
// SetTimestamp replaces the timestamp associated with this NumberDataPoint.
func (ms NumberDataPoint) SetTimestamp(v pcommon.Timestamp) {
ms.state.AssertMutable()
ms.orig.TimeUnixNano = uint64(v)
}
// ValueType returns the type of the value for this NumberDataPoint.
// Calling this function on zero-initialized NumberDataPoint will cause a panic.
func (ms NumberDataPoint) ValueType() NumberDataPointValueType {
switch ms.orig.Value.(type) {
case *internal.NumberDataPoint_AsDouble:
return NumberDataPointValueTypeDouble
case *internal.NumberDataPoint_AsInt:
return NumberDataPointValueTypeInt
}
return NumberDataPointValueTypeEmpty
}
// DoubleValue returns the double associated with this NumberDataPoint.
func (ms NumberDataPoint) DoubleValue() float64 {
return ms.orig.GetAsDouble()
}
// SetDoubleValue replaces the double associated with this NumberDataPoint.
func (ms NumberDataPoint) SetDoubleValue(v float64) {
ms.state.AssertMutable()
var ov *internal.NumberDataPoint_AsDouble
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &internal.NumberDataPoint_AsDouble{}
} else {
ov = internal.ProtoPoolNumberDataPoint_AsDouble.Get().(*internal.NumberDataPoint_AsDouble)
}
ov.AsDouble = v
ms.orig.Value = ov
} // IntValue returns the int associated with this NumberDataPoint.
func (ms NumberDataPoint) IntValue() int64 {
return ms.orig.GetAsInt()
}
// SetIntValue replaces the int associated with this NumberDataPoint.
func (ms NumberDataPoint) SetIntValue(v int64) {
ms.state.AssertMutable()
var ov *internal.NumberDataPoint_AsInt
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &internal.NumberDataPoint_AsInt{}
} else {
ov = internal.ProtoPoolNumberDataPoint_AsInt.Get().(*internal.NumberDataPoint_AsInt)
}
ov.AsInt = v
ms.orig.Value = ov
}
// Exemplars returns the Exemplars associated with this NumberDataPoint.
func (ms NumberDataPoint) Exemplars() ExemplarSlice {
return newExemplarSlice(&ms.orig.Exemplars, ms.state)
}
// Flags returns the flags associated with this NumberDataPoint.
func (ms NumberDataPoint) Flags() DataPointFlags {
return DataPointFlags(ms.orig.Flags)
}
// SetFlags replaces the flags associated with this NumberDataPoint.
func (ms NumberDataPoint) SetFlags(v DataPointFlags) {
ms.state.AssertMutable()
ms.orig.Flags = uint32(v)
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms NumberDataPoint) CopyTo(dest NumberDataPoint) {
dest.state.AssertMutable()
internal.CopyNumberDataPoint(dest.orig, ms.orig)
}
================================================
FILE: pdata/pmetric/generated_numberdatapoint_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestNumberDataPoint_MoveTo(t *testing.T) {
ms := generateTestNumberDataPoint()
dest := NewNumberDataPoint()
ms.MoveTo(dest)
assert.Equal(t, NewNumberDataPoint(), ms)
assert.Equal(t, generateTestNumberDataPoint(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestNumberDataPoint(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newNumberDataPoint(internal.NewNumberDataPoint(), sharedState)) })
assert.Panics(t, func() { newNumberDataPoint(internal.NewNumberDataPoint(), sharedState).MoveTo(dest) })
}
func TestNumberDataPoint_CopyTo(t *testing.T) {
ms := NewNumberDataPoint()
orig := NewNumberDataPoint()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestNumberDataPoint()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newNumberDataPoint(internal.NewNumberDataPoint(), sharedState)) })
}
func TestNumberDataPoint_Attributes(t *testing.T) {
ms := NewNumberDataPoint()
assert.Equal(t, pcommon.NewMap(), ms.Attributes())
ms.orig.Attributes = internal.GenTestKeyValueSlice()
assert.Equal(t, pcommon.Map(internal.GenTestMapWrapper()), ms.Attributes())
}
func TestNumberDataPoint_StartTimestamp(t *testing.T) {
ms := NewNumberDataPoint()
assert.Equal(t, pcommon.Timestamp(0), ms.StartTimestamp())
testValStartTimestamp := pcommon.Timestamp(1234567890)
ms.SetStartTimestamp(testValStartTimestamp)
assert.Equal(t, testValStartTimestamp, ms.StartTimestamp())
}
func TestNumberDataPoint_Timestamp(t *testing.T) {
ms := NewNumberDataPoint()
assert.Equal(t, pcommon.Timestamp(0), ms.Timestamp())
testValTimestamp := pcommon.Timestamp(1234567890)
ms.SetTimestamp(testValTimestamp)
assert.Equal(t, testValTimestamp, ms.Timestamp())
}
func TestNumberDataPoint_ValueType(t *testing.T) {
tv := NewNumberDataPoint()
assert.Equal(t, NumberDataPointValueTypeEmpty, tv.ValueType())
}
func TestNumberDataPoint_DoubleValue(t *testing.T) {
ms := NewNumberDataPoint()
assert.InDelta(t, float64(0), ms.DoubleValue(), 0.01)
ms.SetDoubleValue(float64(3.1415926))
assert.InDelta(t, float64(3.1415926), ms.DoubleValue(), 0.01)
assert.Equal(t, NumberDataPointValueTypeDouble, ms.ValueType())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() {
newNumberDataPoint(internal.NewNumberDataPoint(), sharedState).SetDoubleValue(float64(3.1415926))
})
}
func TestNumberDataPoint_IntValue(t *testing.T) {
ms := NewNumberDataPoint()
assert.Equal(t, int64(0), ms.IntValue())
ms.SetIntValue(int64(13))
assert.Equal(t, int64(13), ms.IntValue())
assert.Equal(t, NumberDataPointValueTypeInt, ms.ValueType())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newNumberDataPoint(internal.NewNumberDataPoint(), sharedState).SetIntValue(int64(13)) })
}
func TestNumberDataPoint_Exemplars(t *testing.T) {
ms := NewNumberDataPoint()
assert.Equal(t, NewExemplarSlice(), ms.Exemplars())
ms.orig.Exemplars = internal.GenTestExemplarSlice()
assert.Equal(t, generateTestExemplarSlice(), ms.Exemplars())
}
func TestNumberDataPoint_Flags(t *testing.T) {
ms := NewNumberDataPoint()
assert.Equal(t, DataPointFlags(0), ms.Flags())
testValFlags := DataPointFlags(1)
ms.SetFlags(testValFlags)
assert.Equal(t, testValFlags, ms.Flags())
}
func generateTestNumberDataPoint() NumberDataPoint {
return newNumberDataPoint(internal.GenTestNumberDataPoint(), internal.NewState())
}
================================================
FILE: pdata/pmetric/generated_numberdatapointslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"iter"
"sort"
"go.opentelemetry.io/collector/pdata/internal"
)
// NumberDataPointSlice logically represents a slice of NumberDataPoint.
//
// This is a reference type. If passed by value and callee modifies it, the
// caller will see the modification.
//
// Must use NewNumberDataPointSlice function to create new instances.
// Important: zero-initialized instance is not valid for use.
type NumberDataPointSlice struct {
orig *[]*internal.NumberDataPoint
state *internal.State
}
func newNumberDataPointSlice(orig *[]*internal.NumberDataPoint, state *internal.State) NumberDataPointSlice {
return NumberDataPointSlice{orig: orig, state: state}
}
// NewNumberDataPointSlice creates a NumberDataPointSliceWrapper with 0 elements.
// Can use "EnsureCapacity" to initialize with a given capacity.
func NewNumberDataPointSlice() NumberDataPointSlice {
orig := []*internal.NumberDataPoint(nil)
return newNumberDataPointSlice(&orig, internal.NewState())
}
// Len returns the number of elements in the slice.
//
// Returns "0" for a newly instance created with "NewNumberDataPointSlice()".
func (es NumberDataPointSlice) 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 NumberDataPointSlice) At(i int) NumberDataPoint {
return newNumberDataPoint((*es.orig)[i], es.state)
}
// All returns an iterator over index-value pairs in the slice.
//
// for i, v := range es.All() {
// ... // Do something with index-value pair
// }
func (es NumberDataPointSlice) All() iter.Seq2[int, NumberDataPoint] {
return func(yield func(int, NumberDataPoint) bool) {
for i := 0; i < es.Len(); i++ {
if !yield(i, es.At(i)) {
return
}
}
}
}
// EnsureCapacity is an operation that ensures the slice has at least the specified capacity.
// 1. If the newCap <= cap then no change in capacity.
// 2. If the newCap > cap then the slice capacity will be expanded to equal newCap.
//
// Here is how a new NumberDataPointSlice can be initialized:
//
// es := NewNumberDataPointSlice()
// es.EnsureCapacity(4)
// for i := 0; i < 4; i++ {
// e := es.AppendEmpty()
// // Here should set all the values for e.
// }
func (es NumberDataPointSlice) EnsureCapacity(newCap int) {
es.state.AssertMutable()
oldCap := cap(*es.orig)
if newCap <= oldCap {
return
}
newOrig := make([]*internal.NumberDataPoint, len(*es.orig), newCap)
copy(newOrig, *es.orig)
*es.orig = newOrig
}
// AppendEmpty will append to the end of the slice an empty NumberDataPoint.
// It returns the newly added NumberDataPoint.
func (es NumberDataPointSlice) AppendEmpty() NumberDataPoint {
es.state.AssertMutable()
*es.orig = append(*es.orig, internal.NewNumberDataPoint())
return es.At(es.Len() - 1)
}
// MoveAndAppendTo moves all elements from the current slice and appends them to the dest.
// The current slice will be cleared.
func (es NumberDataPointSlice) MoveAndAppendTo(dest NumberDataPointSlice) {
es.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if es.orig == dest.orig {
return
}
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
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (es NumberDataPointSlice) RemoveIf(f func(NumberDataPoint) bool) {
es.state.AssertMutable()
newLen := 0
for i := 0; i < len(*es.orig); i++ {
if f(es.At(i)) {
internal.DeleteNumberDataPoint((*es.orig)[i], true)
(*es.orig)[i] = nil
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*es.orig)[newLen] = (*es.orig)[i]
// Cannot delete here since we just move the data(or pointer to data) to a different position in the slice.
(*es.orig)[i] = nil
newLen++
}
*es.orig = (*es.orig)[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (es NumberDataPointSlice) CopyTo(dest NumberDataPointSlice) {
dest.state.AssertMutable()
if es.orig == dest.orig {
return
}
*dest.orig = internal.CopyNumberDataPointPtrSlice(*dest.orig, *es.orig)
}
// Sort sorts the NumberDataPoint elements within NumberDataPointSlice given the
// provided less function so that two instances of NumberDataPointSlice
// can be compared.
func (es NumberDataPointSlice) Sort(less func(a, b NumberDataPoint) bool) {
es.state.AssertMutable()
sort.SliceStable(*es.orig, func(i, j int) bool { return less(es.At(i), es.At(j)) })
}
================================================
FILE: pdata/pmetric/generated_numberdatapointslice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestNumberDataPointSlice(t *testing.T) {
es := NewNumberDataPointSlice()
assert.Equal(t, 0, es.Len())
es = newNumberDataPointSlice(&[]*internal.NumberDataPoint{}, internal.NewState())
assert.Equal(t, 0, es.Len())
emptyVal := NewNumberDataPoint()
testVal := generateTestNumberDataPoint()
for i := 0; i < 7; i++ {
es.AppendEmpty()
assert.Equal(t, emptyVal, es.At(i))
(*es.orig)[i] = internal.GenTestNumberDataPoint()
assert.Equal(t, testVal, es.At(i))
}
assert.Equal(t, 7, es.Len())
}
func TestNumberDataPointSliceReadOnly(t *testing.T) {
sharedState := internal.NewState()
sharedState.MarkReadOnly()
es := newNumberDataPointSlice(&[]*internal.NumberDataPoint{}, sharedState)
assert.Equal(t, 0, es.Len())
assert.Panics(t, func() { es.AppendEmpty() })
assert.Panics(t, func() { es.EnsureCapacity(2) })
es2 := NewNumberDataPointSlice()
es.CopyTo(es2)
assert.Panics(t, func() { es2.CopyTo(es) })
assert.Panics(t, func() { es.MoveAndAppendTo(es2) })
assert.Panics(t, func() { es2.MoveAndAppendTo(es) })
}
func TestNumberDataPointSlice_CopyTo(t *testing.T) {
dest := NewNumberDataPointSlice()
src := generateTestNumberDataPointSlice()
src.CopyTo(dest)
assert.Equal(t, generateTestNumberDataPointSlice(), dest)
dest.CopyTo(dest)
assert.Equal(t, generateTestNumberDataPointSlice(), dest)
}
func TestNumberDataPointSlice_EnsureCapacity(t *testing.T) {
es := generateTestNumberDataPointSlice()
// Test ensure smaller capacity.
const ensureSmallLen = 4
es.EnsureCapacity(ensureSmallLen)
assert.Less(t, ensureSmallLen, es.Len())
assert.Equal(t, es.Len(), cap(*es.orig))
assert.Equal(t, generateTestNumberDataPointSlice(), es)
// Test ensure larger capacity
const ensureLargeLen = 9
es.EnsureCapacity(ensureLargeLen)
assert.Less(t, generateTestNumberDataPointSlice().Len(), ensureLargeLen)
assert.Equal(t, ensureLargeLen, cap(*es.orig))
assert.Equal(t, generateTestNumberDataPointSlice(), es)
}
func TestNumberDataPointSlice_MoveAndAppendTo(t *testing.T) {
// Test MoveAndAppendTo to empty
expectedSlice := generateTestNumberDataPointSlice()
dest := NewNumberDataPointSlice()
src := generateTestNumberDataPointSlice()
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestNumberDataPointSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo empty slice
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestNumberDataPointSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo not empty slice
generateTestNumberDataPointSlice().MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
dest.MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
}
func TestNumberDataPointSlice_RemoveIf(t *testing.T) {
// Test RemoveIf on empty slice
emptySlice := NewNumberDataPointSlice()
emptySlice.RemoveIf(func(el NumberDataPoint) bool {
t.Fail()
return false
})
// Test RemoveIf
filtered := generateTestNumberDataPointSlice()
pos := 0
filtered.RemoveIf(func(el NumberDataPoint) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, 2, filtered.Len())
}
func TestNumberDataPointSlice_RemoveIfAll(t *testing.T) {
got := generateTestNumberDataPointSlice()
got.RemoveIf(func(el NumberDataPoint) bool {
return true
})
assert.Equal(t, 0, got.Len())
}
func TestNumberDataPointSliceAll(t *testing.T) {
ms := generateTestNumberDataPointSlice()
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestNumberDataPointSlice_Sort(t *testing.T) {
es := generateTestNumberDataPointSlice()
es.Sort(func(a, b NumberDataPoint) bool {
return uintptr(unsafe.Pointer(a.orig)) < uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Less(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
es.Sort(func(a, b NumberDataPoint) bool {
return uintptr(unsafe.Pointer(a.orig)) > uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Greater(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
}
func generateTestNumberDataPointSlice() NumberDataPointSlice {
ms := NewNumberDataPointSlice()
*ms.orig = internal.GenTestNumberDataPointPtrSlice()
return ms
}
================================================
FILE: pdata/pmetric/generated_resourcemetrics.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// ResourceMetrics is a collection of metrics from a Resource.
//
// 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 *internal.ResourceMetrics
state *internal.State
}
func newResourceMetrics(orig *internal.ResourceMetrics, state *internal.State) ResourceMetrics {
return ResourceMetrics{orig: orig, state: state}
}
// NewResourceMetrics creates a new empty ResourceMetrics.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewResourceMetrics() ResourceMetrics {
return newResourceMetrics(internal.NewResourceMetrics(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms ResourceMetrics) MoveTo(dest ResourceMetrics) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteResourceMetrics(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// Resource returns the resource associated with this ResourceMetrics.
func (ms ResourceMetrics) Resource() pcommon.Resource {
return pcommon.Resource(internal.NewResourceWrapper(&ms.orig.Resource, ms.state))
}
// ScopeMetrics returns the ScopeMetrics associated with this ResourceMetrics.
func (ms ResourceMetrics) ScopeMetrics() ScopeMetricsSlice {
return newScopeMetricsSlice(&ms.orig.ScopeMetrics, ms.state)
}
// SchemaUrl returns the schemaurl associated with this ResourceMetrics.
func (ms ResourceMetrics) SchemaUrl() string {
return ms.orig.SchemaUrl
}
// SetSchemaUrl replaces the schemaurl associated with this ResourceMetrics.
func (ms ResourceMetrics) SetSchemaUrl(v string) {
ms.state.AssertMutable()
ms.orig.SchemaUrl = v
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms ResourceMetrics) CopyTo(dest ResourceMetrics) {
dest.state.AssertMutable()
internal.CopyResourceMetrics(dest.orig, ms.orig)
}
================================================
FILE: pdata/pmetric/generated_resourcemetrics_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestResourceMetrics_MoveTo(t *testing.T) {
ms := generateTestResourceMetrics()
dest := NewResourceMetrics()
ms.MoveTo(dest)
assert.Equal(t, NewResourceMetrics(), ms)
assert.Equal(t, generateTestResourceMetrics(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestResourceMetrics(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newResourceMetrics(internal.NewResourceMetrics(), sharedState)) })
assert.Panics(t, func() { newResourceMetrics(internal.NewResourceMetrics(), sharedState).MoveTo(dest) })
}
func TestResourceMetrics_CopyTo(t *testing.T) {
ms := NewResourceMetrics()
orig := NewResourceMetrics()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestResourceMetrics()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newResourceMetrics(internal.NewResourceMetrics(), sharedState)) })
}
func TestResourceMetrics_Resource(t *testing.T) {
ms := NewResourceMetrics()
assert.Equal(t, pcommon.NewResource(), ms.Resource())
ms.orig.Resource = *internal.GenTestResource()
assert.Equal(t, pcommon.Resource(internal.GenTestResourceWrapper()), ms.Resource())
}
func TestResourceMetrics_ScopeMetrics(t *testing.T) {
ms := NewResourceMetrics()
assert.Equal(t, NewScopeMetricsSlice(), ms.ScopeMetrics())
ms.orig.ScopeMetrics = internal.GenTestScopeMetricsPtrSlice()
assert.Equal(t, generateTestScopeMetricsSlice(), ms.ScopeMetrics())
}
func TestResourceMetrics_SchemaUrl(t *testing.T) {
ms := NewResourceMetrics()
assert.Empty(t, ms.SchemaUrl())
ms.SetSchemaUrl("test_schemaurl")
assert.Equal(t, "test_schemaurl", ms.SchemaUrl())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newResourceMetrics(internal.NewResourceMetrics(), sharedState).SetSchemaUrl("test_schemaurl") })
}
func generateTestResourceMetrics() ResourceMetrics {
return newResourceMetrics(internal.GenTestResourceMetrics(), internal.NewState())
}
================================================
FILE: pdata/pmetric/generated_resourcemetricsslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"iter"
"sort"
"go.opentelemetry.io/collector/pdata/internal"
)
// 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 *[]*internal.ResourceMetrics
state *internal.State
}
func newResourceMetricsSlice(orig *[]*internal.ResourceMetrics, state *internal.State) ResourceMetricsSlice {
return ResourceMetricsSlice{orig: orig, state: state}
}
// NewResourceMetricsSlice creates a ResourceMetricsSliceWrapper with 0 elements.
// Can use "EnsureCapacity" to initialize with a given capacity.
func NewResourceMetricsSlice() ResourceMetricsSlice {
orig := []*internal.ResourceMetrics(nil)
return newResourceMetricsSlice(&orig, internal.NewState())
}
// 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(i int) ResourceMetrics {
return newResourceMetrics((*es.orig)[i], es.state)
}
// All returns an iterator over index-value pairs in the slice.
//
// for i, v := range es.All() {
// ... // Do something with index-value pair
// }
func (es ResourceMetricsSlice) All() iter.Seq2[int, ResourceMetrics] {
return func(yield func(int, ResourceMetrics) bool) {
for i := 0; i < es.Len(); i++ {
if !yield(i, es.At(i)) {
return
}
}
}
}
// EnsureCapacity is an operation that ensures the slice has at least the specified capacity.
// 1. If the newCap <= cap then no change in capacity.
// 2. If the newCap > cap then the slice capacity will be expanded to equal newCap.
//
// Here is how a new ResourceMetricsSlice can be initialized:
//
// es := NewResourceMetricsSlice()
// es.EnsureCapacity(4)
// for i := 0; i < 4; i++ {
// e := es.AppendEmpty()
// // Here should set all the values for e.
// }
func (es ResourceMetricsSlice) EnsureCapacity(newCap int) {
es.state.AssertMutable()
oldCap := cap(*es.orig)
if newCap <= oldCap {
return
}
newOrig := make([]*internal.ResourceMetrics, len(*es.orig), newCap)
copy(newOrig, *es.orig)
*es.orig = newOrig
}
// AppendEmpty will append to the end of the slice an empty ResourceMetrics.
// It returns the newly added ResourceMetrics.
func (es ResourceMetricsSlice) AppendEmpty() ResourceMetrics {
es.state.AssertMutable()
*es.orig = append(*es.orig, internal.NewResourceMetrics())
return es.At(es.Len() - 1)
}
// 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) {
es.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if es.orig == dest.orig {
return
}
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
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (es ResourceMetricsSlice) RemoveIf(f func(ResourceMetrics) bool) {
es.state.AssertMutable()
newLen := 0
for i := 0; i < len(*es.orig); i++ {
if f(es.At(i)) {
internal.DeleteResourceMetrics((*es.orig)[i], true)
(*es.orig)[i] = nil
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*es.orig)[newLen] = (*es.orig)[i]
// Cannot delete here since we just move the data(or pointer to data) to a different position in the slice.
(*es.orig)[i] = nil
newLen++
}
*es.orig = (*es.orig)[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (es ResourceMetricsSlice) CopyTo(dest ResourceMetricsSlice) {
dest.state.AssertMutable()
if es.orig == dest.orig {
return
}
*dest.orig = internal.CopyResourceMetricsPtrSlice(*dest.orig, *es.orig)
}
// Sort sorts the ResourceMetrics elements within ResourceMetricsSlice given the
// provided less function so that two instances of ResourceMetricsSlice
// can be compared.
func (es ResourceMetricsSlice) Sort(less func(a, b ResourceMetrics) bool) {
es.state.AssertMutable()
sort.SliceStable(*es.orig, func(i, j int) bool { return less(es.At(i), es.At(j)) })
}
================================================
FILE: pdata/pmetric/generated_resourcemetricsslice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestResourceMetricsSlice(t *testing.T) {
es := NewResourceMetricsSlice()
assert.Equal(t, 0, es.Len())
es = newResourceMetricsSlice(&[]*internal.ResourceMetrics{}, internal.NewState())
assert.Equal(t, 0, es.Len())
emptyVal := NewResourceMetrics()
testVal := generateTestResourceMetrics()
for i := 0; i < 7; i++ {
es.AppendEmpty()
assert.Equal(t, emptyVal, es.At(i))
(*es.orig)[i] = internal.GenTestResourceMetrics()
assert.Equal(t, testVal, es.At(i))
}
assert.Equal(t, 7, es.Len())
}
func TestResourceMetricsSliceReadOnly(t *testing.T) {
sharedState := internal.NewState()
sharedState.MarkReadOnly()
es := newResourceMetricsSlice(&[]*internal.ResourceMetrics{}, sharedState)
assert.Equal(t, 0, es.Len())
assert.Panics(t, func() { es.AppendEmpty() })
assert.Panics(t, func() { es.EnsureCapacity(2) })
es2 := NewResourceMetricsSlice()
es.CopyTo(es2)
assert.Panics(t, func() { es2.CopyTo(es) })
assert.Panics(t, func() { es.MoveAndAppendTo(es2) })
assert.Panics(t, func() { es2.MoveAndAppendTo(es) })
}
func TestResourceMetricsSlice_CopyTo(t *testing.T) {
dest := NewResourceMetricsSlice()
src := generateTestResourceMetricsSlice()
src.CopyTo(dest)
assert.Equal(t, generateTestResourceMetricsSlice(), dest)
dest.CopyTo(dest)
assert.Equal(t, generateTestResourceMetricsSlice(), dest)
}
func TestResourceMetricsSlice_EnsureCapacity(t *testing.T) {
es := generateTestResourceMetricsSlice()
// Test ensure smaller capacity.
const ensureSmallLen = 4
es.EnsureCapacity(ensureSmallLen)
assert.Less(t, ensureSmallLen, es.Len())
assert.Equal(t, es.Len(), cap(*es.orig))
assert.Equal(t, generateTestResourceMetricsSlice(), es)
// Test ensure larger capacity
const ensureLargeLen = 9
es.EnsureCapacity(ensureLargeLen)
assert.Less(t, generateTestResourceMetricsSlice().Len(), ensureLargeLen)
assert.Equal(t, ensureLargeLen, cap(*es.orig))
assert.Equal(t, generateTestResourceMetricsSlice(), es)
}
func TestResourceMetricsSlice_MoveAndAppendTo(t *testing.T) {
// Test MoveAndAppendTo to empty
expectedSlice := generateTestResourceMetricsSlice()
dest := NewResourceMetricsSlice()
src := generateTestResourceMetricsSlice()
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestResourceMetricsSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo empty slice
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestResourceMetricsSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo not empty slice
generateTestResourceMetricsSlice().MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
dest.MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
}
func TestResourceMetricsSlice_RemoveIf(t *testing.T) {
// Test RemoveIf on empty slice
emptySlice := NewResourceMetricsSlice()
emptySlice.RemoveIf(func(el ResourceMetrics) bool {
t.Fail()
return false
})
// Test RemoveIf
filtered := generateTestResourceMetricsSlice()
pos := 0
filtered.RemoveIf(func(el ResourceMetrics) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, 2, filtered.Len())
}
func TestResourceMetricsSlice_RemoveIfAll(t *testing.T) {
got := generateTestResourceMetricsSlice()
got.RemoveIf(func(el ResourceMetrics) bool {
return true
})
assert.Equal(t, 0, got.Len())
}
func TestResourceMetricsSliceAll(t *testing.T) {
ms := generateTestResourceMetricsSlice()
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestResourceMetricsSlice_Sort(t *testing.T) {
es := generateTestResourceMetricsSlice()
es.Sort(func(a, b ResourceMetrics) bool {
return uintptr(unsafe.Pointer(a.orig)) < uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Less(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
es.Sort(func(a, b ResourceMetrics) bool {
return uintptr(unsafe.Pointer(a.orig)) > uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Greater(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
}
func generateTestResourceMetricsSlice() ResourceMetricsSlice {
ms := NewResourceMetricsSlice()
*ms.orig = internal.GenTestResourceMetricsPtrSlice()
return ms
}
================================================
FILE: pdata/pmetric/generated_scopemetrics.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// ScopeMetrics 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 NewScopeMetrics function to create new instances.
// Important: zero-initialized instance is not valid for use.
type ScopeMetrics struct {
orig *internal.ScopeMetrics
state *internal.State
}
func newScopeMetrics(orig *internal.ScopeMetrics, state *internal.State) ScopeMetrics {
return ScopeMetrics{orig: orig, state: state}
}
// NewScopeMetrics creates a new empty ScopeMetrics.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewScopeMetrics() ScopeMetrics {
return newScopeMetrics(internal.NewScopeMetrics(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms ScopeMetrics) MoveTo(dest ScopeMetrics) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteScopeMetrics(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// Scope returns the scope associated with this ScopeMetrics.
func (ms ScopeMetrics) Scope() pcommon.InstrumentationScope {
return pcommon.InstrumentationScope(internal.NewInstrumentationScopeWrapper(&ms.orig.Scope, ms.state))
}
// Metrics returns the Metrics associated with this ScopeMetrics.
func (ms ScopeMetrics) Metrics() MetricSlice {
return newMetricSlice(&ms.orig.Metrics, ms.state)
}
// SchemaUrl returns the schemaurl associated with this ScopeMetrics.
func (ms ScopeMetrics) SchemaUrl() string {
return ms.orig.SchemaUrl
}
// SetSchemaUrl replaces the schemaurl associated with this ScopeMetrics.
func (ms ScopeMetrics) SetSchemaUrl(v string) {
ms.state.AssertMutable()
ms.orig.SchemaUrl = v
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms ScopeMetrics) CopyTo(dest ScopeMetrics) {
dest.state.AssertMutable()
internal.CopyScopeMetrics(dest.orig, ms.orig)
}
================================================
FILE: pdata/pmetric/generated_scopemetrics_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestScopeMetrics_MoveTo(t *testing.T) {
ms := generateTestScopeMetrics()
dest := NewScopeMetrics()
ms.MoveTo(dest)
assert.Equal(t, NewScopeMetrics(), ms)
assert.Equal(t, generateTestScopeMetrics(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestScopeMetrics(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newScopeMetrics(internal.NewScopeMetrics(), sharedState)) })
assert.Panics(t, func() { newScopeMetrics(internal.NewScopeMetrics(), sharedState).MoveTo(dest) })
}
func TestScopeMetrics_CopyTo(t *testing.T) {
ms := NewScopeMetrics()
orig := NewScopeMetrics()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestScopeMetrics()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newScopeMetrics(internal.NewScopeMetrics(), sharedState)) })
}
func TestScopeMetrics_Scope(t *testing.T) {
ms := NewScopeMetrics()
assert.Equal(t, pcommon.NewInstrumentationScope(), ms.Scope())
ms.orig.Scope = *internal.GenTestInstrumentationScope()
assert.Equal(t, pcommon.InstrumentationScope(internal.GenTestInstrumentationScopeWrapper()), ms.Scope())
}
func TestScopeMetrics_Metrics(t *testing.T) {
ms := NewScopeMetrics()
assert.Equal(t, NewMetricSlice(), ms.Metrics())
ms.orig.Metrics = internal.GenTestMetricPtrSlice()
assert.Equal(t, generateTestMetricSlice(), ms.Metrics())
}
func TestScopeMetrics_SchemaUrl(t *testing.T) {
ms := NewScopeMetrics()
assert.Empty(t, ms.SchemaUrl())
ms.SetSchemaUrl("test_schemaurl")
assert.Equal(t, "test_schemaurl", ms.SchemaUrl())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newScopeMetrics(internal.NewScopeMetrics(), sharedState).SetSchemaUrl("test_schemaurl") })
}
func generateTestScopeMetrics() ScopeMetrics {
return newScopeMetrics(internal.GenTestScopeMetrics(), internal.NewState())
}
================================================
FILE: pdata/pmetric/generated_scopemetricsslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"iter"
"sort"
"go.opentelemetry.io/collector/pdata/internal"
)
// ScopeMetricsSlice logically represents a slice of ScopeMetrics.
//
// This is a reference type. If passed by value and callee modifies it, the
// caller will see the modification.
//
// Must use NewScopeMetricsSlice function to create new instances.
// Important: zero-initialized instance is not valid for use.
type ScopeMetricsSlice struct {
orig *[]*internal.ScopeMetrics
state *internal.State
}
func newScopeMetricsSlice(orig *[]*internal.ScopeMetrics, state *internal.State) ScopeMetricsSlice {
return ScopeMetricsSlice{orig: orig, state: state}
}
// NewScopeMetricsSlice creates a ScopeMetricsSliceWrapper with 0 elements.
// Can use "EnsureCapacity" to initialize with a given capacity.
func NewScopeMetricsSlice() ScopeMetricsSlice {
orig := []*internal.ScopeMetrics(nil)
return newScopeMetricsSlice(&orig, internal.NewState())
}
// Len returns the number of elements in the slice.
//
// Returns "0" for a newly instance created with "NewScopeMetricsSlice()".
func (es ScopeMetricsSlice) 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 ScopeMetricsSlice) At(i int) ScopeMetrics {
return newScopeMetrics((*es.orig)[i], es.state)
}
// All returns an iterator over index-value pairs in the slice.
//
// for i, v := range es.All() {
// ... // Do something with index-value pair
// }
func (es ScopeMetricsSlice) All() iter.Seq2[int, ScopeMetrics] {
return func(yield func(int, ScopeMetrics) bool) {
for i := 0; i < es.Len(); i++ {
if !yield(i, es.At(i)) {
return
}
}
}
}
// EnsureCapacity is an operation that ensures the slice has at least the specified capacity.
// 1. If the newCap <= cap then no change in capacity.
// 2. If the newCap > cap then the slice capacity will be expanded to equal newCap.
//
// Here is how a new ScopeMetricsSlice can be initialized:
//
// es := NewScopeMetricsSlice()
// es.EnsureCapacity(4)
// for i := 0; i < 4; i++ {
// e := es.AppendEmpty()
// // Here should set all the values for e.
// }
func (es ScopeMetricsSlice) EnsureCapacity(newCap int) {
es.state.AssertMutable()
oldCap := cap(*es.orig)
if newCap <= oldCap {
return
}
newOrig := make([]*internal.ScopeMetrics, len(*es.orig), newCap)
copy(newOrig, *es.orig)
*es.orig = newOrig
}
// AppendEmpty will append to the end of the slice an empty ScopeMetrics.
// It returns the newly added ScopeMetrics.
func (es ScopeMetricsSlice) AppendEmpty() ScopeMetrics {
es.state.AssertMutable()
*es.orig = append(*es.orig, internal.NewScopeMetrics())
return es.At(es.Len() - 1)
}
// MoveAndAppendTo moves all elements from the current slice and appends them to the dest.
// The current slice will be cleared.
func (es ScopeMetricsSlice) MoveAndAppendTo(dest ScopeMetricsSlice) {
es.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if es.orig == dest.orig {
return
}
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
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (es ScopeMetricsSlice) RemoveIf(f func(ScopeMetrics) bool) {
es.state.AssertMutable()
newLen := 0
for i := 0; i < len(*es.orig); i++ {
if f(es.At(i)) {
internal.DeleteScopeMetrics((*es.orig)[i], true)
(*es.orig)[i] = nil
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*es.orig)[newLen] = (*es.orig)[i]
// Cannot delete here since we just move the data(or pointer to data) to a different position in the slice.
(*es.orig)[i] = nil
newLen++
}
*es.orig = (*es.orig)[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (es ScopeMetricsSlice) CopyTo(dest ScopeMetricsSlice) {
dest.state.AssertMutable()
if es.orig == dest.orig {
return
}
*dest.orig = internal.CopyScopeMetricsPtrSlice(*dest.orig, *es.orig)
}
// Sort sorts the ScopeMetrics elements within ScopeMetricsSlice given the
// provided less function so that two instances of ScopeMetricsSlice
// can be compared.
func (es ScopeMetricsSlice) Sort(less func(a, b ScopeMetrics) bool) {
es.state.AssertMutable()
sort.SliceStable(*es.orig, func(i, j int) bool { return less(es.At(i), es.At(j)) })
}
================================================
FILE: pdata/pmetric/generated_scopemetricsslice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestScopeMetricsSlice(t *testing.T) {
es := NewScopeMetricsSlice()
assert.Equal(t, 0, es.Len())
es = newScopeMetricsSlice(&[]*internal.ScopeMetrics{}, internal.NewState())
assert.Equal(t, 0, es.Len())
emptyVal := NewScopeMetrics()
testVal := generateTestScopeMetrics()
for i := 0; i < 7; i++ {
es.AppendEmpty()
assert.Equal(t, emptyVal, es.At(i))
(*es.orig)[i] = internal.GenTestScopeMetrics()
assert.Equal(t, testVal, es.At(i))
}
assert.Equal(t, 7, es.Len())
}
func TestScopeMetricsSliceReadOnly(t *testing.T) {
sharedState := internal.NewState()
sharedState.MarkReadOnly()
es := newScopeMetricsSlice(&[]*internal.ScopeMetrics{}, sharedState)
assert.Equal(t, 0, es.Len())
assert.Panics(t, func() { es.AppendEmpty() })
assert.Panics(t, func() { es.EnsureCapacity(2) })
es2 := NewScopeMetricsSlice()
es.CopyTo(es2)
assert.Panics(t, func() { es2.CopyTo(es) })
assert.Panics(t, func() { es.MoveAndAppendTo(es2) })
assert.Panics(t, func() { es2.MoveAndAppendTo(es) })
}
func TestScopeMetricsSlice_CopyTo(t *testing.T) {
dest := NewScopeMetricsSlice()
src := generateTestScopeMetricsSlice()
src.CopyTo(dest)
assert.Equal(t, generateTestScopeMetricsSlice(), dest)
dest.CopyTo(dest)
assert.Equal(t, generateTestScopeMetricsSlice(), dest)
}
func TestScopeMetricsSlice_EnsureCapacity(t *testing.T) {
es := generateTestScopeMetricsSlice()
// Test ensure smaller capacity.
const ensureSmallLen = 4
es.EnsureCapacity(ensureSmallLen)
assert.Less(t, ensureSmallLen, es.Len())
assert.Equal(t, es.Len(), cap(*es.orig))
assert.Equal(t, generateTestScopeMetricsSlice(), es)
// Test ensure larger capacity
const ensureLargeLen = 9
es.EnsureCapacity(ensureLargeLen)
assert.Less(t, generateTestScopeMetricsSlice().Len(), ensureLargeLen)
assert.Equal(t, ensureLargeLen, cap(*es.orig))
assert.Equal(t, generateTestScopeMetricsSlice(), es)
}
func TestScopeMetricsSlice_MoveAndAppendTo(t *testing.T) {
// Test MoveAndAppendTo to empty
expectedSlice := generateTestScopeMetricsSlice()
dest := NewScopeMetricsSlice()
src := generateTestScopeMetricsSlice()
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestScopeMetricsSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo empty slice
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestScopeMetricsSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo not empty slice
generateTestScopeMetricsSlice().MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
dest.MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
}
func TestScopeMetricsSlice_RemoveIf(t *testing.T) {
// Test RemoveIf on empty slice
emptySlice := NewScopeMetricsSlice()
emptySlice.RemoveIf(func(el ScopeMetrics) bool {
t.Fail()
return false
})
// Test RemoveIf
filtered := generateTestScopeMetricsSlice()
pos := 0
filtered.RemoveIf(func(el ScopeMetrics) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, 2, filtered.Len())
}
func TestScopeMetricsSlice_RemoveIfAll(t *testing.T) {
got := generateTestScopeMetricsSlice()
got.RemoveIf(func(el ScopeMetrics) bool {
return true
})
assert.Equal(t, 0, got.Len())
}
func TestScopeMetricsSliceAll(t *testing.T) {
ms := generateTestScopeMetricsSlice()
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestScopeMetricsSlice_Sort(t *testing.T) {
es := generateTestScopeMetricsSlice()
es.Sort(func(a, b ScopeMetrics) bool {
return uintptr(unsafe.Pointer(a.orig)) < uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Less(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
es.Sort(func(a, b ScopeMetrics) bool {
return uintptr(unsafe.Pointer(a.orig)) > uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Greater(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
}
func generateTestScopeMetricsSlice() ScopeMetricsSlice {
ms := NewScopeMetricsSlice()
*ms.orig = internal.GenTestScopeMetricsPtrSlice()
return ms
}
================================================
FILE: pdata/pmetric/generated_sum.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// Sum represents the type of a numeric 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 NewSum function to create new instances.
// Important: zero-initialized instance is not valid for use.
type Sum struct {
orig *internal.Sum
state *internal.State
}
func newSum(orig *internal.Sum, state *internal.State) Sum {
return Sum{orig: orig, state: state}
}
// NewSum creates a new empty Sum.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewSum() Sum {
return newSum(internal.NewSum(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms Sum) MoveTo(dest Sum) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteSum(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// DataPoints returns the DataPoints associated with this Sum.
func (ms Sum) DataPoints() NumberDataPointSlice {
return newNumberDataPointSlice(&ms.orig.DataPoints, ms.state)
}
// AggregationTemporality returns the aggregationtemporality associated with this Sum.
func (ms Sum) AggregationTemporality() AggregationTemporality {
return AggregationTemporality(ms.orig.AggregationTemporality)
}
// SetAggregationTemporality replaces the aggregationtemporality associated with this Sum.
func (ms Sum) SetAggregationTemporality(v AggregationTemporality) {
ms.state.AssertMutable()
ms.orig.AggregationTemporality = internal.AggregationTemporality(v)
}
// IsMonotonic returns the ismonotonic associated with this Sum.
func (ms Sum) IsMonotonic() bool {
return ms.orig.IsMonotonic
}
// SetIsMonotonic replaces the ismonotonic associated with this Sum.
func (ms Sum) SetIsMonotonic(v bool) {
ms.state.AssertMutable()
ms.orig.IsMonotonic = v
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms Sum) CopyTo(dest Sum) {
dest.state.AssertMutable()
internal.CopySum(dest.orig, ms.orig)
}
================================================
FILE: pdata/pmetric/generated_sum_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestSum_MoveTo(t *testing.T) {
ms := generateTestSum()
dest := NewSum()
ms.MoveTo(dest)
assert.Equal(t, NewSum(), ms)
assert.Equal(t, generateTestSum(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestSum(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newSum(internal.NewSum(), sharedState)) })
assert.Panics(t, func() { newSum(internal.NewSum(), sharedState).MoveTo(dest) })
}
func TestSum_CopyTo(t *testing.T) {
ms := NewSum()
orig := NewSum()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestSum()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newSum(internal.NewSum(), sharedState)) })
}
func TestSum_DataPoints(t *testing.T) {
ms := NewSum()
assert.Equal(t, NewNumberDataPointSlice(), ms.DataPoints())
ms.orig.DataPoints = internal.GenTestNumberDataPointPtrSlice()
assert.Equal(t, generateTestNumberDataPointSlice(), ms.DataPoints())
}
func TestSum_AggregationTemporality(t *testing.T) {
ms := NewSum()
assert.Equal(t, AggregationTemporality(internal.AggregationTemporality(0)), ms.AggregationTemporality())
testValAggregationTemporality := AggregationTemporality(internal.AggregationTemporality(1))
ms.SetAggregationTemporality(testValAggregationTemporality)
assert.Equal(t, testValAggregationTemporality, ms.AggregationTemporality())
}
func TestSum_IsMonotonic(t *testing.T) {
ms := NewSum()
assert.False(t, ms.IsMonotonic())
ms.SetIsMonotonic(true)
assert.True(t, ms.IsMonotonic())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newSum(internal.NewSum(), sharedState).SetIsMonotonic(true) })
}
func generateTestSum() Sum {
return newSum(internal.GenTestSum(), internal.NewState())
}
================================================
FILE: pdata/pmetric/generated_summary.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// Summary 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 NewSummary function to create new instances.
// Important: zero-initialized instance is not valid for use.
type Summary struct {
orig *internal.Summary
state *internal.State
}
func newSummary(orig *internal.Summary, state *internal.State) Summary {
return Summary{orig: orig, state: state}
}
// NewSummary creates a new empty Summary.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewSummary() Summary {
return newSummary(internal.NewSummary(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms Summary) MoveTo(dest Summary) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteSummary(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// DataPoints returns the DataPoints associated with this Summary.
func (ms Summary) DataPoints() SummaryDataPointSlice {
return newSummaryDataPointSlice(&ms.orig.DataPoints, ms.state)
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms Summary) CopyTo(dest Summary) {
dest.state.AssertMutable()
internal.CopySummary(dest.orig, ms.orig)
}
================================================
FILE: pdata/pmetric/generated_summary_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestSummary_MoveTo(t *testing.T) {
ms := generateTestSummary()
dest := NewSummary()
ms.MoveTo(dest)
assert.Equal(t, NewSummary(), ms)
assert.Equal(t, generateTestSummary(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestSummary(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newSummary(internal.NewSummary(), sharedState)) })
assert.Panics(t, func() { newSummary(internal.NewSummary(), sharedState).MoveTo(dest) })
}
func TestSummary_CopyTo(t *testing.T) {
ms := NewSummary()
orig := NewSummary()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestSummary()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newSummary(internal.NewSummary(), sharedState)) })
}
func TestSummary_DataPoints(t *testing.T) {
ms := NewSummary()
assert.Equal(t, NewSummaryDataPointSlice(), ms.DataPoints())
ms.orig.DataPoints = internal.GenTestSummaryDataPointPtrSlice()
assert.Equal(t, generateTestSummaryDataPointSlice(), ms.DataPoints())
}
func generateTestSummary() Summary {
return newSummary(internal.GenTestSummary(), internal.NewState())
}
================================================
FILE: pdata/pmetric/generated_summarydatapoint.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// SummaryDataPoint 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 NewSummaryDataPoint function to create new instances.
// Important: zero-initialized instance is not valid for use.
type SummaryDataPoint struct {
orig *internal.SummaryDataPoint
state *internal.State
}
func newSummaryDataPoint(orig *internal.SummaryDataPoint, state *internal.State) SummaryDataPoint {
return SummaryDataPoint{orig: orig, state: state}
}
// NewSummaryDataPoint creates a new empty SummaryDataPoint.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewSummaryDataPoint() SummaryDataPoint {
return newSummaryDataPoint(internal.NewSummaryDataPoint(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms SummaryDataPoint) MoveTo(dest SummaryDataPoint) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteSummaryDataPoint(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// Attributes returns the Attributes associated with this SummaryDataPoint.
func (ms SummaryDataPoint) Attributes() pcommon.Map {
return pcommon.Map(internal.NewMapWrapper(&ms.orig.Attributes, ms.state))
}
// StartTimestamp returns the starttimestamp associated with this SummaryDataPoint.
func (ms SummaryDataPoint) StartTimestamp() pcommon.Timestamp {
return pcommon.Timestamp(ms.orig.StartTimeUnixNano)
}
// SetStartTimestamp replaces the starttimestamp associated with this SummaryDataPoint.
func (ms SummaryDataPoint) SetStartTimestamp(v pcommon.Timestamp) {
ms.state.AssertMutable()
ms.orig.StartTimeUnixNano = uint64(v)
}
// Timestamp returns the timestamp associated with this SummaryDataPoint.
func (ms SummaryDataPoint) Timestamp() pcommon.Timestamp {
return pcommon.Timestamp(ms.orig.TimeUnixNano)
}
// SetTimestamp replaces the timestamp associated with this SummaryDataPoint.
func (ms SummaryDataPoint) SetTimestamp(v pcommon.Timestamp) {
ms.state.AssertMutable()
ms.orig.TimeUnixNano = uint64(v)
}
// Count returns the count associated with this SummaryDataPoint.
func (ms SummaryDataPoint) Count() uint64 {
return ms.orig.Count
}
// SetCount replaces the count associated with this SummaryDataPoint.
func (ms SummaryDataPoint) SetCount(v uint64) {
ms.state.AssertMutable()
ms.orig.Count = v
}
// Sum returns the sum associated with this SummaryDataPoint.
func (ms SummaryDataPoint) Sum() float64 {
return ms.orig.Sum
}
// SetSum replaces the sum associated with this SummaryDataPoint.
func (ms SummaryDataPoint) SetSum(v float64) {
ms.state.AssertMutable()
ms.orig.Sum = v
}
// QuantileValues returns the QuantileValues associated with this SummaryDataPoint.
func (ms SummaryDataPoint) QuantileValues() SummaryDataPointValueAtQuantileSlice {
return newSummaryDataPointValueAtQuantileSlice(&ms.orig.QuantileValues, ms.state)
}
// Flags returns the flags associated with this SummaryDataPoint.
func (ms SummaryDataPoint) Flags() DataPointFlags {
return DataPointFlags(ms.orig.Flags)
}
// SetFlags replaces the flags associated with this SummaryDataPoint.
func (ms SummaryDataPoint) SetFlags(v DataPointFlags) {
ms.state.AssertMutable()
ms.orig.Flags = uint32(v)
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms SummaryDataPoint) CopyTo(dest SummaryDataPoint) {
dest.state.AssertMutable()
internal.CopySummaryDataPoint(dest.orig, ms.orig)
}
================================================
FILE: pdata/pmetric/generated_summarydatapoint_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestSummaryDataPoint_MoveTo(t *testing.T) {
ms := generateTestSummaryDataPoint()
dest := NewSummaryDataPoint()
ms.MoveTo(dest)
assert.Equal(t, NewSummaryDataPoint(), ms)
assert.Equal(t, generateTestSummaryDataPoint(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestSummaryDataPoint(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newSummaryDataPoint(internal.NewSummaryDataPoint(), sharedState)) })
assert.Panics(t, func() { newSummaryDataPoint(internal.NewSummaryDataPoint(), sharedState).MoveTo(dest) })
}
func TestSummaryDataPoint_CopyTo(t *testing.T) {
ms := NewSummaryDataPoint()
orig := NewSummaryDataPoint()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestSummaryDataPoint()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newSummaryDataPoint(internal.NewSummaryDataPoint(), sharedState)) })
}
func TestSummaryDataPoint_Attributes(t *testing.T) {
ms := NewSummaryDataPoint()
assert.Equal(t, pcommon.NewMap(), ms.Attributes())
ms.orig.Attributes = internal.GenTestKeyValueSlice()
assert.Equal(t, pcommon.Map(internal.GenTestMapWrapper()), ms.Attributes())
}
func TestSummaryDataPoint_StartTimestamp(t *testing.T) {
ms := NewSummaryDataPoint()
assert.Equal(t, pcommon.Timestamp(0), ms.StartTimestamp())
testValStartTimestamp := pcommon.Timestamp(1234567890)
ms.SetStartTimestamp(testValStartTimestamp)
assert.Equal(t, testValStartTimestamp, ms.StartTimestamp())
}
func TestSummaryDataPoint_Timestamp(t *testing.T) {
ms := NewSummaryDataPoint()
assert.Equal(t, pcommon.Timestamp(0), ms.Timestamp())
testValTimestamp := pcommon.Timestamp(1234567890)
ms.SetTimestamp(testValTimestamp)
assert.Equal(t, testValTimestamp, ms.Timestamp())
}
func TestSummaryDataPoint_Count(t *testing.T) {
ms := NewSummaryDataPoint()
assert.Equal(t, uint64(0), ms.Count())
ms.SetCount(uint64(13))
assert.Equal(t, uint64(13), ms.Count())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newSummaryDataPoint(internal.NewSummaryDataPoint(), sharedState).SetCount(uint64(13)) })
}
func TestSummaryDataPoint_Sum(t *testing.T) {
ms := NewSummaryDataPoint()
assert.InDelta(t, float64(0), ms.Sum(), 0.01)
ms.SetSum(float64(3.1415926))
assert.InDelta(t, float64(3.1415926), ms.Sum(), 0.01)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newSummaryDataPoint(internal.NewSummaryDataPoint(), sharedState).SetSum(float64(3.1415926)) })
}
func TestSummaryDataPoint_QuantileValues(t *testing.T) {
ms := NewSummaryDataPoint()
assert.Equal(t, NewSummaryDataPointValueAtQuantileSlice(), ms.QuantileValues())
ms.orig.QuantileValues = internal.GenTestSummaryDataPointValueAtQuantilePtrSlice()
assert.Equal(t, generateTestSummaryDataPointValueAtQuantileSlice(), ms.QuantileValues())
}
func TestSummaryDataPoint_Flags(t *testing.T) {
ms := NewSummaryDataPoint()
assert.Equal(t, DataPointFlags(0), ms.Flags())
testValFlags := DataPointFlags(1)
ms.SetFlags(testValFlags)
assert.Equal(t, testValFlags, ms.Flags())
}
func generateTestSummaryDataPoint() SummaryDataPoint {
return newSummaryDataPoint(internal.GenTestSummaryDataPoint(), internal.NewState())
}
================================================
FILE: pdata/pmetric/generated_summarydatapointslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"iter"
"sort"
"go.opentelemetry.io/collector/pdata/internal"
)
// SummaryDataPointSlice logically represents a slice of SummaryDataPoint.
//
// This is a reference type. If passed by value and callee modifies it, the
// caller will see the modification.
//
// Must use NewSummaryDataPointSlice function to create new instances.
// Important: zero-initialized instance is not valid for use.
type SummaryDataPointSlice struct {
orig *[]*internal.SummaryDataPoint
state *internal.State
}
func newSummaryDataPointSlice(orig *[]*internal.SummaryDataPoint, state *internal.State) SummaryDataPointSlice {
return SummaryDataPointSlice{orig: orig, state: state}
}
// NewSummaryDataPointSlice creates a SummaryDataPointSliceWrapper with 0 elements.
// Can use "EnsureCapacity" to initialize with a given capacity.
func NewSummaryDataPointSlice() SummaryDataPointSlice {
orig := []*internal.SummaryDataPoint(nil)
return newSummaryDataPointSlice(&orig, internal.NewState())
}
// Len returns the number of elements in the slice.
//
// Returns "0" for a newly instance created with "NewSummaryDataPointSlice()".
func (es SummaryDataPointSlice) 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 SummaryDataPointSlice) At(i int) SummaryDataPoint {
return newSummaryDataPoint((*es.orig)[i], es.state)
}
// All returns an iterator over index-value pairs in the slice.
//
// for i, v := range es.All() {
// ... // Do something with index-value pair
// }
func (es SummaryDataPointSlice) All() iter.Seq2[int, SummaryDataPoint] {
return func(yield func(int, SummaryDataPoint) bool) {
for i := 0; i < es.Len(); i++ {
if !yield(i, es.At(i)) {
return
}
}
}
}
// EnsureCapacity is an operation that ensures the slice has at least the specified capacity.
// 1. If the newCap <= cap then no change in capacity.
// 2. If the newCap > cap then the slice capacity will be expanded to equal newCap.
//
// Here is how a new SummaryDataPointSlice can be initialized:
//
// es := NewSummaryDataPointSlice()
// es.EnsureCapacity(4)
// for i := 0; i < 4; i++ {
// e := es.AppendEmpty()
// // Here should set all the values for e.
// }
func (es SummaryDataPointSlice) EnsureCapacity(newCap int) {
es.state.AssertMutable()
oldCap := cap(*es.orig)
if newCap <= oldCap {
return
}
newOrig := make([]*internal.SummaryDataPoint, len(*es.orig), newCap)
copy(newOrig, *es.orig)
*es.orig = newOrig
}
// AppendEmpty will append to the end of the slice an empty SummaryDataPoint.
// It returns the newly added SummaryDataPoint.
func (es SummaryDataPointSlice) AppendEmpty() SummaryDataPoint {
es.state.AssertMutable()
*es.orig = append(*es.orig, internal.NewSummaryDataPoint())
return es.At(es.Len() - 1)
}
// MoveAndAppendTo moves all elements from the current slice and appends them to the dest.
// The current slice will be cleared.
func (es SummaryDataPointSlice) MoveAndAppendTo(dest SummaryDataPointSlice) {
es.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if es.orig == dest.orig {
return
}
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
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (es SummaryDataPointSlice) RemoveIf(f func(SummaryDataPoint) bool) {
es.state.AssertMutable()
newLen := 0
for i := 0; i < len(*es.orig); i++ {
if f(es.At(i)) {
internal.DeleteSummaryDataPoint((*es.orig)[i], true)
(*es.orig)[i] = nil
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*es.orig)[newLen] = (*es.orig)[i]
// Cannot delete here since we just move the data(or pointer to data) to a different position in the slice.
(*es.orig)[i] = nil
newLen++
}
*es.orig = (*es.orig)[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (es SummaryDataPointSlice) CopyTo(dest SummaryDataPointSlice) {
dest.state.AssertMutable()
if es.orig == dest.orig {
return
}
*dest.orig = internal.CopySummaryDataPointPtrSlice(*dest.orig, *es.orig)
}
// Sort sorts the SummaryDataPoint elements within SummaryDataPointSlice given the
// provided less function so that two instances of SummaryDataPointSlice
// can be compared.
func (es SummaryDataPointSlice) Sort(less func(a, b SummaryDataPoint) bool) {
es.state.AssertMutable()
sort.SliceStable(*es.orig, func(i, j int) bool { return less(es.At(i), es.At(j)) })
}
================================================
FILE: pdata/pmetric/generated_summarydatapointslice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestSummaryDataPointSlice(t *testing.T) {
es := NewSummaryDataPointSlice()
assert.Equal(t, 0, es.Len())
es = newSummaryDataPointSlice(&[]*internal.SummaryDataPoint{}, internal.NewState())
assert.Equal(t, 0, es.Len())
emptyVal := NewSummaryDataPoint()
testVal := generateTestSummaryDataPoint()
for i := 0; i < 7; i++ {
es.AppendEmpty()
assert.Equal(t, emptyVal, es.At(i))
(*es.orig)[i] = internal.GenTestSummaryDataPoint()
assert.Equal(t, testVal, es.At(i))
}
assert.Equal(t, 7, es.Len())
}
func TestSummaryDataPointSliceReadOnly(t *testing.T) {
sharedState := internal.NewState()
sharedState.MarkReadOnly()
es := newSummaryDataPointSlice(&[]*internal.SummaryDataPoint{}, sharedState)
assert.Equal(t, 0, es.Len())
assert.Panics(t, func() { es.AppendEmpty() })
assert.Panics(t, func() { es.EnsureCapacity(2) })
es2 := NewSummaryDataPointSlice()
es.CopyTo(es2)
assert.Panics(t, func() { es2.CopyTo(es) })
assert.Panics(t, func() { es.MoveAndAppendTo(es2) })
assert.Panics(t, func() { es2.MoveAndAppendTo(es) })
}
func TestSummaryDataPointSlice_CopyTo(t *testing.T) {
dest := NewSummaryDataPointSlice()
src := generateTestSummaryDataPointSlice()
src.CopyTo(dest)
assert.Equal(t, generateTestSummaryDataPointSlice(), dest)
dest.CopyTo(dest)
assert.Equal(t, generateTestSummaryDataPointSlice(), dest)
}
func TestSummaryDataPointSlice_EnsureCapacity(t *testing.T) {
es := generateTestSummaryDataPointSlice()
// Test ensure smaller capacity.
const ensureSmallLen = 4
es.EnsureCapacity(ensureSmallLen)
assert.Less(t, ensureSmallLen, es.Len())
assert.Equal(t, es.Len(), cap(*es.orig))
assert.Equal(t, generateTestSummaryDataPointSlice(), es)
// Test ensure larger capacity
const ensureLargeLen = 9
es.EnsureCapacity(ensureLargeLen)
assert.Less(t, generateTestSummaryDataPointSlice().Len(), ensureLargeLen)
assert.Equal(t, ensureLargeLen, cap(*es.orig))
assert.Equal(t, generateTestSummaryDataPointSlice(), es)
}
func TestSummaryDataPointSlice_MoveAndAppendTo(t *testing.T) {
// Test MoveAndAppendTo to empty
expectedSlice := generateTestSummaryDataPointSlice()
dest := NewSummaryDataPointSlice()
src := generateTestSummaryDataPointSlice()
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestSummaryDataPointSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo empty slice
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestSummaryDataPointSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo not empty slice
generateTestSummaryDataPointSlice().MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
dest.MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
}
func TestSummaryDataPointSlice_RemoveIf(t *testing.T) {
// Test RemoveIf on empty slice
emptySlice := NewSummaryDataPointSlice()
emptySlice.RemoveIf(func(el SummaryDataPoint) bool {
t.Fail()
return false
})
// Test RemoveIf
filtered := generateTestSummaryDataPointSlice()
pos := 0
filtered.RemoveIf(func(el SummaryDataPoint) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, 2, filtered.Len())
}
func TestSummaryDataPointSlice_RemoveIfAll(t *testing.T) {
got := generateTestSummaryDataPointSlice()
got.RemoveIf(func(el SummaryDataPoint) bool {
return true
})
assert.Equal(t, 0, got.Len())
}
func TestSummaryDataPointSliceAll(t *testing.T) {
ms := generateTestSummaryDataPointSlice()
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestSummaryDataPointSlice_Sort(t *testing.T) {
es := generateTestSummaryDataPointSlice()
es.Sort(func(a, b SummaryDataPoint) bool {
return uintptr(unsafe.Pointer(a.orig)) < uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Less(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
es.Sort(func(a, b SummaryDataPoint) bool {
return uintptr(unsafe.Pointer(a.orig)) > uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Greater(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
}
func generateTestSummaryDataPointSlice() SummaryDataPointSlice {
ms := NewSummaryDataPointSlice()
*ms.orig = internal.GenTestSummaryDataPointPtrSlice()
return ms
}
================================================
FILE: pdata/pmetric/generated_summarydatapointvalueatquantile.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// SummaryDataPointValueAtQuantile 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 NewSummaryDataPointValueAtQuantile function to create new instances.
// Important: zero-initialized instance is not valid for use.
type SummaryDataPointValueAtQuantile struct {
orig *internal.SummaryDataPointValueAtQuantile
state *internal.State
}
func newSummaryDataPointValueAtQuantile(orig *internal.SummaryDataPointValueAtQuantile, state *internal.State) SummaryDataPointValueAtQuantile {
return SummaryDataPointValueAtQuantile{orig: orig, state: state}
}
// NewSummaryDataPointValueAtQuantile creates a new empty SummaryDataPointValueAtQuantile.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewSummaryDataPointValueAtQuantile() SummaryDataPointValueAtQuantile {
return newSummaryDataPointValueAtQuantile(internal.NewSummaryDataPointValueAtQuantile(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms SummaryDataPointValueAtQuantile) MoveTo(dest SummaryDataPointValueAtQuantile) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteSummaryDataPointValueAtQuantile(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// Quantile returns the quantile associated with this SummaryDataPointValueAtQuantile.
func (ms SummaryDataPointValueAtQuantile) Quantile() float64 {
return ms.orig.Quantile
}
// SetQuantile replaces the quantile associated with this SummaryDataPointValueAtQuantile.
func (ms SummaryDataPointValueAtQuantile) SetQuantile(v float64) {
ms.state.AssertMutable()
ms.orig.Quantile = v
}
// Value returns the value associated with this SummaryDataPointValueAtQuantile.
func (ms SummaryDataPointValueAtQuantile) Value() float64 {
return ms.orig.Value
}
// SetValue replaces the value associated with this SummaryDataPointValueAtQuantile.
func (ms SummaryDataPointValueAtQuantile) SetValue(v float64) {
ms.state.AssertMutable()
ms.orig.Value = v
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms SummaryDataPointValueAtQuantile) CopyTo(dest SummaryDataPointValueAtQuantile) {
dest.state.AssertMutable()
internal.CopySummaryDataPointValueAtQuantile(dest.orig, ms.orig)
}
================================================
FILE: pdata/pmetric/generated_summarydatapointvalueatquantile_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestSummaryDataPointValueAtQuantile_MoveTo(t *testing.T) {
ms := generateTestSummaryDataPointValueAtQuantile()
dest := NewSummaryDataPointValueAtQuantile()
ms.MoveTo(dest)
assert.Equal(t, NewSummaryDataPointValueAtQuantile(), ms)
assert.Equal(t, generateTestSummaryDataPointValueAtQuantile(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestSummaryDataPointValueAtQuantile(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() {
ms.MoveTo(newSummaryDataPointValueAtQuantile(internal.NewSummaryDataPointValueAtQuantile(), sharedState))
})
assert.Panics(t, func() {
newSummaryDataPointValueAtQuantile(internal.NewSummaryDataPointValueAtQuantile(), sharedState).MoveTo(dest)
})
}
func TestSummaryDataPointValueAtQuantile_CopyTo(t *testing.T) {
ms := NewSummaryDataPointValueAtQuantile()
orig := NewSummaryDataPointValueAtQuantile()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestSummaryDataPointValueAtQuantile()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() {
ms.CopyTo(newSummaryDataPointValueAtQuantile(internal.NewSummaryDataPointValueAtQuantile(), sharedState))
})
}
func TestSummaryDataPointValueAtQuantile_Quantile(t *testing.T) {
ms := NewSummaryDataPointValueAtQuantile()
assert.InDelta(t, float64(0), ms.Quantile(), 0.01)
ms.SetQuantile(float64(3.1415926))
assert.InDelta(t, float64(3.1415926), ms.Quantile(), 0.01)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() {
newSummaryDataPointValueAtQuantile(internal.NewSummaryDataPointValueAtQuantile(), sharedState).SetQuantile(float64(3.1415926))
})
}
func TestSummaryDataPointValueAtQuantile_Value(t *testing.T) {
ms := NewSummaryDataPointValueAtQuantile()
assert.InDelta(t, float64(0), ms.Value(), 0.01)
ms.SetValue(float64(3.1415926))
assert.InDelta(t, float64(3.1415926), ms.Value(), 0.01)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() {
newSummaryDataPointValueAtQuantile(internal.NewSummaryDataPointValueAtQuantile(), sharedState).SetValue(float64(3.1415926))
})
}
func generateTestSummaryDataPointValueAtQuantile() SummaryDataPointValueAtQuantile {
return newSummaryDataPointValueAtQuantile(internal.GenTestSummaryDataPointValueAtQuantile(), internal.NewState())
}
================================================
FILE: pdata/pmetric/generated_summarydatapointvalueatquantileslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"iter"
"sort"
"go.opentelemetry.io/collector/pdata/internal"
)
// SummaryDataPointValueAtQuantileSlice logically represents a slice of SummaryDataPointValueAtQuantile.
//
// This is a reference type. If passed by value and callee modifies it, the
// caller will see the modification.
//
// Must use NewSummaryDataPointValueAtQuantileSlice function to create new instances.
// Important: zero-initialized instance is not valid for use.
type SummaryDataPointValueAtQuantileSlice struct {
orig *[]*internal.SummaryDataPointValueAtQuantile
state *internal.State
}
func newSummaryDataPointValueAtQuantileSlice(orig *[]*internal.SummaryDataPointValueAtQuantile, state *internal.State) SummaryDataPointValueAtQuantileSlice {
return SummaryDataPointValueAtQuantileSlice{orig: orig, state: state}
}
// NewSummaryDataPointValueAtQuantileSlice creates a SummaryDataPointValueAtQuantileSliceWrapper with 0 elements.
// Can use "EnsureCapacity" to initialize with a given capacity.
func NewSummaryDataPointValueAtQuantileSlice() SummaryDataPointValueAtQuantileSlice {
orig := []*internal.SummaryDataPointValueAtQuantile(nil)
return newSummaryDataPointValueAtQuantileSlice(&orig, internal.NewState())
}
// Len returns the number of elements in the slice.
//
// Returns "0" for a newly instance created with "NewSummaryDataPointValueAtQuantileSlice()".
func (es SummaryDataPointValueAtQuantileSlice) 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 SummaryDataPointValueAtQuantileSlice) At(i int) SummaryDataPointValueAtQuantile {
return newSummaryDataPointValueAtQuantile((*es.orig)[i], es.state)
}
// All returns an iterator over index-value pairs in the slice.
//
// for i, v := range es.All() {
// ... // Do something with index-value pair
// }
func (es SummaryDataPointValueAtQuantileSlice) All() iter.Seq2[int, SummaryDataPointValueAtQuantile] {
return func(yield func(int, SummaryDataPointValueAtQuantile) bool) {
for i := 0; i < es.Len(); i++ {
if !yield(i, es.At(i)) {
return
}
}
}
}
// EnsureCapacity is an operation that ensures the slice has at least the specified capacity.
// 1. If the newCap <= cap then no change in capacity.
// 2. If the newCap > cap then the slice capacity will be expanded to equal newCap.
//
// Here is how a new SummaryDataPointValueAtQuantileSlice can be initialized:
//
// es := NewSummaryDataPointValueAtQuantileSlice()
// es.EnsureCapacity(4)
// for i := 0; i < 4; i++ {
// e := es.AppendEmpty()
// // Here should set all the values for e.
// }
func (es SummaryDataPointValueAtQuantileSlice) EnsureCapacity(newCap int) {
es.state.AssertMutable()
oldCap := cap(*es.orig)
if newCap <= oldCap {
return
}
newOrig := make([]*internal.SummaryDataPointValueAtQuantile, len(*es.orig), newCap)
copy(newOrig, *es.orig)
*es.orig = newOrig
}
// AppendEmpty will append to the end of the slice an empty SummaryDataPointValueAtQuantile.
// It returns the newly added SummaryDataPointValueAtQuantile.
func (es SummaryDataPointValueAtQuantileSlice) AppendEmpty() SummaryDataPointValueAtQuantile {
es.state.AssertMutable()
*es.orig = append(*es.orig, internal.NewSummaryDataPointValueAtQuantile())
return es.At(es.Len() - 1)
}
// MoveAndAppendTo moves all elements from the current slice and appends them to the dest.
// The current slice will be cleared.
func (es SummaryDataPointValueAtQuantileSlice) MoveAndAppendTo(dest SummaryDataPointValueAtQuantileSlice) {
es.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if es.orig == dest.orig {
return
}
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
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (es SummaryDataPointValueAtQuantileSlice) RemoveIf(f func(SummaryDataPointValueAtQuantile) bool) {
es.state.AssertMutable()
newLen := 0
for i := 0; i < len(*es.orig); i++ {
if f(es.At(i)) {
internal.DeleteSummaryDataPointValueAtQuantile((*es.orig)[i], true)
(*es.orig)[i] = nil
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*es.orig)[newLen] = (*es.orig)[i]
// Cannot delete here since we just move the data(or pointer to data) to a different position in the slice.
(*es.orig)[i] = nil
newLen++
}
*es.orig = (*es.orig)[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (es SummaryDataPointValueAtQuantileSlice) CopyTo(dest SummaryDataPointValueAtQuantileSlice) {
dest.state.AssertMutable()
if es.orig == dest.orig {
return
}
*dest.orig = internal.CopySummaryDataPointValueAtQuantilePtrSlice(*dest.orig, *es.orig)
}
// Sort sorts the SummaryDataPointValueAtQuantile elements within SummaryDataPointValueAtQuantileSlice given the
// provided less function so that two instances of SummaryDataPointValueAtQuantileSlice
// can be compared.
func (es SummaryDataPointValueAtQuantileSlice) Sort(less func(a, b SummaryDataPointValueAtQuantile) bool) {
es.state.AssertMutable()
sort.SliceStable(*es.orig, func(i, j int) bool { return less(es.At(i), es.At(j)) })
}
================================================
FILE: pdata/pmetric/generated_summarydatapointvalueatquantileslice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetric
import (
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestSummaryDataPointValueAtQuantileSlice(t *testing.T) {
es := NewSummaryDataPointValueAtQuantileSlice()
assert.Equal(t, 0, es.Len())
es = newSummaryDataPointValueAtQuantileSlice(&[]*internal.SummaryDataPointValueAtQuantile{}, internal.NewState())
assert.Equal(t, 0, es.Len())
emptyVal := NewSummaryDataPointValueAtQuantile()
testVal := generateTestSummaryDataPointValueAtQuantile()
for i := 0; i < 7; i++ {
es.AppendEmpty()
assert.Equal(t, emptyVal, es.At(i))
(*es.orig)[i] = internal.GenTestSummaryDataPointValueAtQuantile()
assert.Equal(t, testVal, es.At(i))
}
assert.Equal(t, 7, es.Len())
}
func TestSummaryDataPointValueAtQuantileSliceReadOnly(t *testing.T) {
sharedState := internal.NewState()
sharedState.MarkReadOnly()
es := newSummaryDataPointValueAtQuantileSlice(&[]*internal.SummaryDataPointValueAtQuantile{}, sharedState)
assert.Equal(t, 0, es.Len())
assert.Panics(t, func() { es.AppendEmpty() })
assert.Panics(t, func() { es.EnsureCapacity(2) })
es2 := NewSummaryDataPointValueAtQuantileSlice()
es.CopyTo(es2)
assert.Panics(t, func() { es2.CopyTo(es) })
assert.Panics(t, func() { es.MoveAndAppendTo(es2) })
assert.Panics(t, func() { es2.MoveAndAppendTo(es) })
}
func TestSummaryDataPointValueAtQuantileSlice_CopyTo(t *testing.T) {
dest := NewSummaryDataPointValueAtQuantileSlice()
src := generateTestSummaryDataPointValueAtQuantileSlice()
src.CopyTo(dest)
assert.Equal(t, generateTestSummaryDataPointValueAtQuantileSlice(), dest)
dest.CopyTo(dest)
assert.Equal(t, generateTestSummaryDataPointValueAtQuantileSlice(), dest)
}
func TestSummaryDataPointValueAtQuantileSlice_EnsureCapacity(t *testing.T) {
es := generateTestSummaryDataPointValueAtQuantileSlice()
// Test ensure smaller capacity.
const ensureSmallLen = 4
es.EnsureCapacity(ensureSmallLen)
assert.Less(t, ensureSmallLen, es.Len())
assert.Equal(t, es.Len(), cap(*es.orig))
assert.Equal(t, generateTestSummaryDataPointValueAtQuantileSlice(), es)
// Test ensure larger capacity
const ensureLargeLen = 9
es.EnsureCapacity(ensureLargeLen)
assert.Less(t, generateTestSummaryDataPointValueAtQuantileSlice().Len(), ensureLargeLen)
assert.Equal(t, ensureLargeLen, cap(*es.orig))
assert.Equal(t, generateTestSummaryDataPointValueAtQuantileSlice(), es)
}
func TestSummaryDataPointValueAtQuantileSlice_MoveAndAppendTo(t *testing.T) {
// Test MoveAndAppendTo to empty
expectedSlice := generateTestSummaryDataPointValueAtQuantileSlice()
dest := NewSummaryDataPointValueAtQuantileSlice()
src := generateTestSummaryDataPointValueAtQuantileSlice()
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestSummaryDataPointValueAtQuantileSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo empty slice
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestSummaryDataPointValueAtQuantileSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo not empty slice
generateTestSummaryDataPointValueAtQuantileSlice().MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
dest.MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
}
func TestSummaryDataPointValueAtQuantileSlice_RemoveIf(t *testing.T) {
// Test RemoveIf on empty slice
emptySlice := NewSummaryDataPointValueAtQuantileSlice()
emptySlice.RemoveIf(func(el SummaryDataPointValueAtQuantile) bool {
t.Fail()
return false
})
// Test RemoveIf
filtered := generateTestSummaryDataPointValueAtQuantileSlice()
pos := 0
filtered.RemoveIf(func(el SummaryDataPointValueAtQuantile) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, 2, filtered.Len())
}
func TestSummaryDataPointValueAtQuantileSlice_RemoveIfAll(t *testing.T) {
got := generateTestSummaryDataPointValueAtQuantileSlice()
got.RemoveIf(func(el SummaryDataPointValueAtQuantile) bool {
return true
})
assert.Equal(t, 0, got.Len())
}
func TestSummaryDataPointValueAtQuantileSliceAll(t *testing.T) {
ms := generateTestSummaryDataPointValueAtQuantileSlice()
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestSummaryDataPointValueAtQuantileSlice_Sort(t *testing.T) {
es := generateTestSummaryDataPointValueAtQuantileSlice()
es.Sort(func(a, b SummaryDataPointValueAtQuantile) bool {
return uintptr(unsafe.Pointer(a.orig)) < uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Less(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
es.Sort(func(a, b SummaryDataPointValueAtQuantile) bool {
return uintptr(unsafe.Pointer(a.orig)) > uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Greater(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
}
func generateTestSummaryDataPointValueAtQuantileSlice() SummaryDataPointValueAtQuantileSlice {
ms := NewSummaryDataPointValueAtQuantileSlice()
*ms.orig = internal.GenTestSummaryDataPointValueAtQuantilePtrSlice()
return ms
}
================================================
FILE: pdata/pmetric/json.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pmetric // import "go.opentelemetry.io/collector/pdata/pmetric"
import (
"slices"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/otlp"
)
var _ Marshaler = (*JSONMarshaler)(nil)
// JSONMarshaler marshals Metrics to JSON bytes using the OTLP/JSON format.
type JSONMarshaler struct{}
// MarshalMetrics to the OTLP/JSON format.
func (*JSONMarshaler) MarshalMetrics(md Metrics) ([]byte, error) {
dest := json.BorrowStream(nil)
defer json.ReturnStream(dest)
md.getOrig().MarshalJSON(dest)
if dest.Error() != nil {
return nil, dest.Error()
}
return slices.Clone(dest.Buffer()), nil
}
// JSONUnmarshaler unmarshals OTLP/JSON formatted-bytes to Metrics.
type JSONUnmarshaler struct{}
// UnmarshalMetrics from OTLP/JSON format into Metrics.
func (*JSONUnmarshaler) UnmarshalMetrics(buf []byte) (Metrics, error) {
iter := json.BorrowIterator(buf)
defer json.ReturnIterator(iter)
md := NewMetrics()
md.getOrig().UnmarshalJSON(iter)
if iter.Error() != nil {
return Metrics{}, iter.Error()
}
otlp.MigrateMetrics(md.getOrig().ResourceMetrics)
return md, nil
}
================================================
FILE: pdata/pmetric/metric_data_point_flags.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pmetric // import "go.opentelemetry.io/collector/pdata/pmetric"
const noRecordValueMask = uint32(1)
var DefaultDataPointFlags = DataPointFlags(0)
// DataPointFlags defines how a metric aggregator reports aggregated values.
// It describes how those values relate to the time interval over which they are aggregated.
type DataPointFlags uint32
// NoRecordedValue returns true if the DataPointFlags contains the NoRecordedValue flag.
func (ms DataPointFlags) NoRecordedValue() bool {
return uint32(ms)&noRecordValueMask != 0
}
// WithNoRecordedValue returns a new DataPointFlags, with the NoRecordedValue flag set to the given value.
func (ms DataPointFlags) WithNoRecordedValue(b bool) DataPointFlags {
orig := uint32(ms)
if b {
orig |= noRecordValueMask
} else {
orig &^= noRecordValueMask
}
return DataPointFlags(orig)
}
================================================
FILE: pdata/pmetric/metric_data_point_flags_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pmetric
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestLogRecordFlags(t *testing.T) {
flags := DataPointFlags(1)
assert.True(t, flags.NoRecordedValue())
assert.EqualValues(t, uint32(1), flags)
flags = flags.WithNoRecordedValue(false)
assert.False(t, flags.NoRecordedValue())
assert.EqualValues(t, uint32(0), flags)
flags = flags.WithNoRecordedValue(true)
assert.True(t, flags.NoRecordedValue())
assert.EqualValues(t, uint32(1), flags)
}
func TestDefaultLogRecordFlags(t *testing.T) {
flags := DefaultDataPointFlags
assert.False(t, flags.NoRecordedValue())
assert.EqualValues(t, uint32(0), flags)
}
================================================
FILE: pdata/pmetric/metric_type.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pmetric // import "go.opentelemetry.io/collector/pdata/pmetric"
// MetricType specifies the type of data in a Metric.
type MetricType int32
const (
// MetricTypeEmpty means that metric type is unset.
MetricTypeEmpty MetricType = iota
MetricTypeGauge
MetricTypeSum
MetricTypeHistogram
MetricTypeExponentialHistogram
MetricTypeSummary
)
// String returns the string representation of the MetricType.
func (mdt MetricType) String() string {
switch mdt {
case MetricTypeEmpty:
return "Empty"
case MetricTypeGauge:
return "Gauge"
case MetricTypeSum:
return "Sum"
case MetricTypeHistogram:
return "Histogram"
case MetricTypeExponentialHistogram:
return "ExponentialHistogram"
case MetricTypeSummary:
return "Summary"
}
return ""
}
================================================
FILE: pdata/pmetric/metric_type_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pmetric // import "go.opentelemetry.io/collector/pdata/pmetric"
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestMetricTypeString(t *testing.T) {
assert.Equal(t, "Empty", MetricTypeEmpty.String())
assert.Equal(t, "Gauge", MetricTypeGauge.String())
assert.Equal(t, "Sum", MetricTypeSum.String())
assert.Equal(t, "Histogram", MetricTypeHistogram.String())
assert.Equal(t, "ExponentialHistogram", MetricTypeExponentialHistogram.String())
assert.Equal(t, "Summary", MetricTypeSummary.String())
assert.Empty(t, (MetricTypeSummary + 1).String())
}
================================================
FILE: pdata/pmetric/metrics.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pmetric // import "go.opentelemetry.io/collector/pdata/pmetric"
// MarkReadOnly marks the Metrics as shared so that no further modifications can be done on it.
func (ms Metrics) MarkReadOnly() {
ms.getState().MarkReadOnly()
}
// IsReadOnly returns true if this Metrics instance is read-only.
func (ms Metrics) IsReadOnly() bool {
return ms.getState().IsReadOnly()
}
// MetricCount calculates the total number of metrics.
func (ms Metrics) MetricCount() int {
metricCount := 0
rms := ms.ResourceMetrics()
for i := 0; i < rms.Len(); i++ {
rm := rms.At(i)
ilms := rm.ScopeMetrics()
for j := 0; j < ilms.Len(); j++ {
ilm := ilms.At(j)
metricCount += ilm.Metrics().Len()
}
}
return metricCount
}
// DataPointCount calculates the total number of data points.
func (ms Metrics) DataPointCount() (dataPointCount int) {
rms := ms.ResourceMetrics()
for i := 0; i < rms.Len(); i++ {
rm := rms.At(i)
ilms := rm.ScopeMetrics()
for j := 0; j < ilms.Len(); j++ {
ilm := ilms.At(j)
ms := ilm.Metrics()
for k := 0; k < ms.Len(); k++ {
m := ms.At(k)
switch m.Type() {
case MetricTypeGauge:
dataPointCount += m.Gauge().DataPoints().Len()
case MetricTypeSum:
dataPointCount += m.Sum().DataPoints().Len()
case MetricTypeHistogram:
dataPointCount += m.Histogram().DataPoints().Len()
case MetricTypeExponentialHistogram:
dataPointCount += m.ExponentialHistogram().DataPoints().Len()
case MetricTypeSummary:
dataPointCount += m.Summary().DataPoints().Len()
}
}
}
}
return dataPointCount
}
================================================
FILE: pdata/pmetric/metrics_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pmetric
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/internal/testutil"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
const (
startTime = uint64(12578940000000012345)
endTime = uint64(12578940000000054321)
)
func TestMetricCount(t *testing.T) {
md := NewMetrics()
assert.Equal(t, 0, md.MetricCount())
rms := md.ResourceMetrics()
rms.EnsureCapacity(3)
rm := rms.AppendEmpty()
assert.Equal(t, 0, md.MetricCount())
ilm := rm.ScopeMetrics().AppendEmpty()
assert.Equal(t, 0, md.MetricCount())
ilm.Metrics().AppendEmpty()
assert.Equal(t, 1, md.MetricCount())
rms.AppendEmpty().ScopeMetrics().AppendEmpty()
ilmm := rms.AppendEmpty().ScopeMetrics().AppendEmpty().Metrics()
ilmm.EnsureCapacity(5)
for range 5 {
ilmm.AppendEmpty()
}
// 5 + 1 (from rms.At(0) initialized first)
assert.Equal(t, 6, md.MetricCount())
}
func TestMetricCountWithEmpty(t *testing.T) {
assert.Equal(t, 0, generateMetricsEmptyResource().MetricCount())
assert.Equal(t, 0, generateMetricsEmptyInstrumentation().MetricCount())
assert.Equal(t, 1, generateMetricsEmptyMetrics().MetricCount())
}
func TestMetricAndDataPointCount(t *testing.T) {
md := NewMetrics()
dps := md.DataPointCount()
assert.Equal(t, 0, dps)
rms := md.ResourceMetrics()
rms.AppendEmpty()
dps = md.DataPointCount()
assert.Equal(t, 0, dps)
ilms := md.ResourceMetrics().At(0).ScopeMetrics()
ilms.AppendEmpty()
dps = md.DataPointCount()
assert.Equal(t, 0, dps)
ilms.At(0).Metrics().AppendEmpty()
dps = md.DataPointCount()
assert.Equal(t, 0, dps)
intSum := ilms.At(0).Metrics().At(0).SetEmptySum()
intSum.DataPoints().AppendEmpty()
intSum.DataPoints().AppendEmpty()
intSum.DataPoints().AppendEmpty()
assert.Equal(t, 3, md.DataPointCount())
md = NewMetrics()
rms = md.ResourceMetrics()
rms.EnsureCapacity(3)
rms.AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty()
rms.AppendEmpty().ScopeMetrics().AppendEmpty()
rms.AppendEmpty().ScopeMetrics().AppendEmpty()
ilms = rms.At(2).ScopeMetrics()
ilm := ilms.At(0).Metrics()
for range 5 {
ilm.AppendEmpty()
}
assert.Equal(t, 0, md.DataPointCount())
ilm.At(0).SetEmptyGauge().DataPoints().AppendEmpty()
assert.Equal(t, 1, md.DataPointCount())
ilm.At(1).SetEmptySum().DataPoints().AppendEmpty()
assert.Equal(t, 2, md.DataPointCount())
ilm.At(2).SetEmptyHistogram().DataPoints().AppendEmpty()
assert.Equal(t, 3, md.DataPointCount())
ilm.At(3).SetEmptyExponentialHistogram().DataPoints().AppendEmpty()
assert.Equal(t, 4, md.DataPointCount())
ilm.At(4).SetEmptySummary().DataPoints().AppendEmpty()
assert.Equal(t, 5, md.DataPointCount())
}
func TestDataPointCountWithEmpty(t *testing.T) {
assert.Equal(t, 0, generateMetricsEmptyResource().DataPointCount())
assert.Equal(t, 0, generateMetricsEmptyInstrumentation().DataPointCount())
assert.Equal(t, 0, generateMetricsEmptyMetrics().DataPointCount())
assert.Equal(t, 1, generateMetricsEmptyDataPoints().DataPointCount())
}
func TestDataPointCountWithNilDataPoints(t *testing.T) {
md := NewMetrics()
ilm := md.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty()
ilm.Metrics().AppendEmpty().SetEmptyGauge()
ilm.Metrics().AppendEmpty().SetEmptySum()
ilm.Metrics().AppendEmpty().SetEmptyHistogram()
ilm.Metrics().AppendEmpty().SetEmptyExponentialHistogram()
ilm.Metrics().AppendEmpty().SetEmptySummary()
assert.Equal(t, 0, md.DataPointCount())
}
func TestHistogramWithNilSum(t *testing.T) {
md := NewMetrics()
ilm := md.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty()
histo := ilm.Metrics().AppendEmpty()
histogramDataPoints := histo.SetEmptyHistogram().DataPoints()
histogramDataPoints.AppendEmpty()
dest := ilm.Metrics().AppendEmpty()
histo.CopyTo(dest)
assert.Equal(t, histo, dest)
}
func TestHistogramWithValidSum(t *testing.T) {
md := NewMetrics()
ilm := md.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty()
histo := ilm.Metrics().AppendEmpty()
histogramDataPoints := histo.SetEmptyHistogram().DataPoints()
histogramDataPoints.AppendEmpty()
histogramDataPoints.At(0).SetSum(10)
dest := ilm.Metrics().AppendEmpty()
histo.CopyTo(dest)
assert.Equal(t, histo, dest)
}
func TestOtlpToInternalReadOnly(t *testing.T) {
md := newMetrics(&internal.ExportMetricsServiceRequest{
ResourceMetrics: []*internal.ResourceMetrics{
{
Resource: generateTestProtoResource(),
ScopeMetrics: []*internal.ScopeMetrics{
{
Scope: generateTestProtoInstrumentationScope(),
Metrics: []*internal.Metric{generateTestProtoGaugeMetric(), generateTestProtoSumMetric(), generateTestProtoHistogramMetric()},
},
},
},
},
}, new(internal.State))
resourceMetrics := md.ResourceMetrics()
assert.Equal(t, 1, resourceMetrics.Len())
resourceMetric := resourceMetrics.At(0)
assert.Equal(t, map[string]any{
"string": "string-resource",
}, resourceMetric.Resource().Attributes().AsRaw())
metrics := resourceMetric.ScopeMetrics().At(0).Metrics()
assert.Equal(t, 3, metrics.Len())
// Check int64 metric
metricInt := metrics.At(0)
assert.Equal(t, "my_metric_int", metricInt.Name())
assert.Equal(t, "My metric", metricInt.Description())
assert.Equal(t, "ms", metricInt.Unit())
assert.Equal(t, MetricTypeGauge, metricInt.Type())
gaugeDataPoints := metricInt.Gauge().DataPoints()
assert.Equal(t, 2, gaugeDataPoints.Len())
// First point
assert.EqualValues(t, startTime, gaugeDataPoints.At(0).StartTimestamp())
assert.EqualValues(t, endTime, gaugeDataPoints.At(0).Timestamp())
assert.InDelta(t, 123.1, gaugeDataPoints.At(0).DoubleValue(), 0.01)
assert.Equal(t, map[string]any{"key0": "value0"}, gaugeDataPoints.At(0).Attributes().AsRaw())
// Second point
assert.EqualValues(t, startTime, gaugeDataPoints.At(1).StartTimestamp())
assert.EqualValues(t, endTime, gaugeDataPoints.At(1).Timestamp())
assert.InDelta(t, 456.1, gaugeDataPoints.At(1).DoubleValue(), 0.01)
assert.Equal(t, map[string]any{"key1": "value1"}, gaugeDataPoints.At(1).Attributes().AsRaw())
// Check double metric
metricDouble := metrics.At(1)
assert.Equal(t, "my_metric_double", metricDouble.Name())
assert.Equal(t, "My metric", metricDouble.Description())
assert.Equal(t, "ms", metricDouble.Unit())
assert.Equal(t, MetricTypeSum, metricDouble.Type())
dsd := metricDouble.Sum()
assert.Equal(t, AggregationTemporalityCumulative, dsd.AggregationTemporality())
sumDataPoints := dsd.DataPoints()
assert.Equal(t, 2, sumDataPoints.Len())
// First point
assert.EqualValues(t, startTime, sumDataPoints.At(0).StartTimestamp())
assert.EqualValues(t, endTime, sumDataPoints.At(0).Timestamp())
assert.InDelta(t, 123.1, sumDataPoints.At(0).DoubleValue(), 0.01)
assert.Equal(t, map[string]any{"key0": "value0"}, sumDataPoints.At(0).Attributes().AsRaw())
// Second point
assert.EqualValues(t, startTime, sumDataPoints.At(1).StartTimestamp())
assert.EqualValues(t, endTime, sumDataPoints.At(1).Timestamp())
assert.InDelta(t, 456.1, sumDataPoints.At(1).DoubleValue(), 0.01)
assert.Equal(t, map[string]any{"key1": "value1"}, sumDataPoints.At(1).Attributes().AsRaw())
// Check histogram metric
metricHistogram := metrics.At(2)
assert.Equal(t, "my_metric_histogram", metricHistogram.Name())
assert.Equal(t, "My metric", metricHistogram.Description())
assert.Equal(t, "ms", metricHistogram.Unit())
assert.Equal(t, MetricTypeHistogram, metricHistogram.Type())
dhd := metricHistogram.Histogram()
assert.Equal(t, AggregationTemporalityDelta, dhd.AggregationTemporality())
histogramDataPoints := dhd.DataPoints()
assert.Equal(t, 2, histogramDataPoints.Len())
// First point
assert.EqualValues(t, startTime, histogramDataPoints.At(0).StartTimestamp())
assert.EqualValues(t, endTime, histogramDataPoints.At(0).Timestamp())
assert.Equal(t, []float64{1, 2}, histogramDataPoints.At(0).ExplicitBounds().AsRaw())
assert.Equal(t, map[string]any{"key0": "value0"}, histogramDataPoints.At(0).Attributes().AsRaw())
assert.Equal(t, []uint64{10, 15, 1}, histogramDataPoints.At(0).BucketCounts().AsRaw())
// Second point
assert.EqualValues(t, startTime, histogramDataPoints.At(1).StartTimestamp())
assert.EqualValues(t, endTime, histogramDataPoints.At(1).Timestamp())
assert.Equal(t, []float64{1}, histogramDataPoints.At(1).ExplicitBounds().AsRaw())
assert.Equal(t, map[string]any{"key1": "value1"}, histogramDataPoints.At(1).Attributes().AsRaw())
assert.Equal(t, []uint64{10, 1}, histogramDataPoints.At(1).BucketCounts().AsRaw())
}
func TestOtlpToFromInternalReadOnly(t *testing.T) {
md := newMetrics(&internal.ExportMetricsServiceRequest{
ResourceMetrics: []*internal.ResourceMetrics{
{
Resource: generateTestProtoResource(),
ScopeMetrics: []*internal.ScopeMetrics{
{
Scope: generateTestProtoInstrumentationScope(),
Metrics: []*internal.Metric{generateTestProtoGaugeMetric(), generateTestProtoSumMetric(), generateTestProtoHistogramMetric()},
},
},
},
},
}, new(internal.State))
// Test that nothing changed
assert.EqualValues(t, &internal.MetricsData{
ResourceMetrics: []*internal.ResourceMetrics{
{
Resource: generateTestProtoResource(),
ScopeMetrics: []*internal.ScopeMetrics{
{
Scope: generateTestProtoInstrumentationScope(),
Metrics: []*internal.Metric{generateTestProtoGaugeMetric(), generateTestProtoSumMetric(), generateTestProtoHistogramMetric()},
},
},
},
},
}, md.getOrig())
}
func TestOtlpToFromInternalGaugeMutating(t *testing.T) {
newAttributes := map[string]any{"k": "v"}
md := newMetrics(&internal.ExportMetricsServiceRequest{
ResourceMetrics: []*internal.ResourceMetrics{
{
Resource: generateTestProtoResource(),
ScopeMetrics: []*internal.ScopeMetrics{
{
Scope: generateTestProtoInstrumentationScope(),
Metrics: []*internal.Metric{generateTestProtoGaugeMetric()},
},
},
},
},
}, new(internal.State))
resourceMetrics := md.ResourceMetrics()
metric := resourceMetrics.At(0).ScopeMetrics().At(0).Metrics().At(0)
// Mutate MetricDescriptor
metric.SetName("new_my_metric_int")
assert.Equal(t, "new_my_metric_int", metric.Name())
metric.SetDescription("My new metric")
assert.Equal(t, "My new metric", metric.Description())
metric.SetUnit("1")
assert.Equal(t, "1", metric.Unit())
// Mutate DataPoints
igd := metric.Gauge()
assert.Equal(t, 2, igd.DataPoints().Len())
gaugeDataPoints := metric.SetEmptyGauge().DataPoints()
gaugeDataPoints.AppendEmpty()
assert.Equal(t, 1, gaugeDataPoints.Len())
gaugeDataPoints.At(0).SetStartTimestamp(pcommon.Timestamp(startTime + 1))
assert.EqualValues(t, startTime+1, gaugeDataPoints.At(0).StartTimestamp())
gaugeDataPoints.At(0).SetTimestamp(pcommon.Timestamp(endTime + 1))
assert.EqualValues(t, endTime+1, gaugeDataPoints.At(0).Timestamp())
gaugeDataPoints.At(0).SetDoubleValue(124.1)
assert.InDelta(t, 124.1, gaugeDataPoints.At(0).DoubleValue(), 0.01)
gaugeDataPoints.At(0).Attributes().Remove("key0")
gaugeDataPoints.At(0).Attributes().PutStr("k", "v")
assert.Equal(t, newAttributes, gaugeDataPoints.At(0).Attributes().AsRaw())
// Test that everything is updated.
assert.EqualValues(t, &internal.MetricsData{
ResourceMetrics: []*internal.ResourceMetrics{
{
Resource: generateTestProtoResource(),
ScopeMetrics: []*internal.ScopeMetrics{
{
Scope: generateTestProtoInstrumentationScope(),
Metrics: []*internal.Metric{
{
Name: "new_my_metric_int",
Description: "My new metric",
Unit: "1",
Data: &internal.Metric_Gauge{
Gauge: &internal.Gauge{
DataPoints: []*internal.NumberDataPoint{
{
Attributes: []internal.KeyValue{
{
Key: "k",
Value: internal.AnyValue{Value: &internal.AnyValue_StringValue{StringValue: "v"}},
},
},
StartTimeUnixNano: startTime + 1,
TimeUnixNano: endTime + 1,
Value: &internal.NumberDataPoint_AsDouble{
AsDouble: 124.1,
},
},
},
},
},
},
},
},
},
},
},
}, md.getOrig())
}
func TestOtlpToFromInternalSumMutating(t *testing.T) {
newAttributes := map[string]any{"k": "v"}
md := newMetrics(&internal.ExportMetricsServiceRequest{
ResourceMetrics: []*internal.ResourceMetrics{
{
Resource: generateTestProtoResource(),
ScopeMetrics: []*internal.ScopeMetrics{
{
Scope: generateTestProtoInstrumentationScope(),
Metrics: []*internal.Metric{generateTestProtoSumMetric()},
},
},
},
},
}, new(internal.State))
resourceMetrics := md.ResourceMetrics()
metric := resourceMetrics.At(0).ScopeMetrics().At(0).Metrics().At(0)
// Mutate MetricDescriptor
metric.SetName("new_my_metric_double")
assert.Equal(t, "new_my_metric_double", metric.Name())
metric.SetDescription("My new metric")
assert.Equal(t, "My new metric", metric.Description())
metric.SetUnit("1")
assert.Equal(t, "1", metric.Unit())
// Mutate DataPoints
dsd := metric.Sum()
assert.Equal(t, 2, dsd.DataPoints().Len())
metric.SetEmptySum().SetAggregationTemporality(AggregationTemporalityCumulative)
doubleDataPoints := metric.Sum().DataPoints()
doubleDataPoints.AppendEmpty()
assert.Equal(t, 1, doubleDataPoints.Len())
doubleDataPoints.At(0).SetStartTimestamp(pcommon.Timestamp(startTime + 1))
assert.EqualValues(t, startTime+1, doubleDataPoints.At(0).StartTimestamp())
doubleDataPoints.At(0).SetTimestamp(pcommon.Timestamp(endTime + 1))
assert.EqualValues(t, endTime+1, doubleDataPoints.At(0).Timestamp())
doubleDataPoints.At(0).SetDoubleValue(124.1)
assert.InDelta(t, 124.1, doubleDataPoints.At(0).DoubleValue(), 0.01)
doubleDataPoints.At(0).Attributes().Remove("key0")
doubleDataPoints.At(0).Attributes().PutStr("k", "v")
assert.Equal(t, newAttributes, doubleDataPoints.At(0).Attributes().AsRaw())
// Test that everything is updated.
assert.EqualValues(t, &internal.MetricsData{
ResourceMetrics: []*internal.ResourceMetrics{
{
Resource: generateTestProtoResource(),
ScopeMetrics: []*internal.ScopeMetrics{
{
Scope: generateTestProtoInstrumentationScope(),
Metrics: []*internal.Metric{
{
Name: "new_my_metric_double",
Description: "My new metric",
Unit: "1",
Data: &internal.Metric_Sum{
Sum: &internal.Sum{
AggregationTemporality: internal.AggregationTemporality_AGGREGATION_TEMPORALITY_CUMULATIVE,
DataPoints: []*internal.NumberDataPoint{
{
Attributes: []internal.KeyValue{
{
Key: "k",
Value: internal.AnyValue{Value: &internal.AnyValue_StringValue{StringValue: "v"}},
},
},
StartTimeUnixNano: startTime + 1,
TimeUnixNano: endTime + 1,
Value: &internal.NumberDataPoint_AsDouble{
AsDouble: 124.1,
},
},
},
},
},
},
},
},
},
},
},
}, md.getOrig())
}
func TestOtlpToFromInternalHistogramMutating(t *testing.T) {
newAttributes := map[string]any{"k": "v"}
md := newMetrics(&internal.ExportMetricsServiceRequest{
ResourceMetrics: []*internal.ResourceMetrics{
{
Resource: generateTestProtoResource(),
ScopeMetrics: []*internal.ScopeMetrics{
{
Scope: generateTestProtoInstrumentationScope(),
Metrics: []*internal.Metric{generateTestProtoHistogramMetric()},
},
},
},
},
}, new(internal.State))
resourceMetrics := md.ResourceMetrics()
metric := resourceMetrics.At(0).ScopeMetrics().At(0).Metrics().At(0)
// Mutate MetricDescriptor
metric.SetName("new_my_metric_histogram")
assert.Equal(t, "new_my_metric_histogram", metric.Name())
metric.SetDescription("My new metric")
assert.Equal(t, "My new metric", metric.Description())
metric.SetUnit("1")
assert.Equal(t, "1", metric.Unit())
// Mutate DataPoints
dhd := metric.Histogram()
assert.Equal(t, 2, dhd.DataPoints().Len())
metric.SetEmptyHistogram().SetAggregationTemporality(AggregationTemporalityDelta)
histogramDataPoints := metric.Histogram().DataPoints()
histogramDataPoints.AppendEmpty()
assert.Equal(t, 1, histogramDataPoints.Len())
histogramDataPoints.At(0).SetStartTimestamp(pcommon.Timestamp(startTime + 1))
assert.EqualValues(t, startTime+1, histogramDataPoints.At(0).StartTimestamp())
histogramDataPoints.At(0).SetTimestamp(pcommon.Timestamp(endTime + 1))
assert.EqualValues(t, endTime+1, histogramDataPoints.At(0).Timestamp())
histogramDataPoints.At(0).Attributes().Remove("key0")
histogramDataPoints.At(0).Attributes().PutStr("k", "v")
assert.Equal(t, newAttributes, histogramDataPoints.At(0).Attributes().AsRaw())
histogramDataPoints.At(0).ExplicitBounds().FromRaw([]float64{1})
assert.Equal(t, []float64{1}, histogramDataPoints.At(0).ExplicitBounds().AsRaw())
histogramDataPoints.At(0).BucketCounts().FromRaw([]uint64{21, 32})
// Test that everything is updated.
assert.EqualValues(t, &internal.MetricsData{
ResourceMetrics: []*internal.ResourceMetrics{
{
Resource: generateTestProtoResource(),
ScopeMetrics: []*internal.ScopeMetrics{
{
Scope: generateTestProtoInstrumentationScope(),
Metrics: []*internal.Metric{
{
Name: "new_my_metric_histogram",
Description: "My new metric",
Unit: "1",
Data: &internal.Metric_Histogram{
Histogram: &internal.Histogram{
AggregationTemporality: internal.AggregationTemporality_AGGREGATION_TEMPORALITY_DELTA,
DataPoints: []*internal.HistogramDataPoint{
{
Attributes: []internal.KeyValue{
{
Key: "k",
Value: internal.AnyValue{Value: &internal.AnyValue_StringValue{StringValue: "v"}},
},
},
StartTimeUnixNano: startTime + 1,
TimeUnixNano: endTime + 1,
BucketCounts: []uint64{21, 32},
ExplicitBounds: []float64{1},
},
},
},
},
},
},
},
},
},
},
}, md.getOrig())
}
func TestOtlpToFromInternalExponentialHistogramMutating(t *testing.T) {
newAttributes := map[string]any{"k": "v"}
md := newMetrics(&internal.ExportMetricsServiceRequest{
ResourceMetrics: []*internal.ResourceMetrics{
{
Resource: generateTestProtoResource(),
ScopeMetrics: []*internal.ScopeMetrics{
{
Scope: generateTestProtoInstrumentationScope(),
Metrics: []*internal.Metric{generateTestProtoHistogramMetric()},
},
},
},
},
}, new(internal.State))
resourceMetrics := md.ResourceMetrics()
metric := resourceMetrics.At(0).ScopeMetrics().At(0).Metrics().At(0)
// Mutate MetricDescriptor
metric.SetName("new_my_metric_exponential_histogram")
assert.Equal(t, "new_my_metric_exponential_histogram", metric.Name())
metric.SetDescription("My new metric")
assert.Equal(t, "My new metric", metric.Description())
metric.SetUnit("1")
assert.Equal(t, "1", metric.Unit())
// Mutate DataPoints
dhd := metric.Histogram()
assert.Equal(t, 2, dhd.DataPoints().Len())
metric.SetEmptyExponentialHistogram().SetAggregationTemporality(AggregationTemporalityDelta)
histogramDataPoints := metric.ExponentialHistogram().DataPoints()
histogramDataPoints.AppendEmpty()
assert.Equal(t, 1, histogramDataPoints.Len())
histogramDataPoints.At(0).SetStartTimestamp(pcommon.Timestamp(startTime + 1))
assert.EqualValues(t, startTime+1, histogramDataPoints.At(0).StartTimestamp())
histogramDataPoints.At(0).SetTimestamp(pcommon.Timestamp(endTime + 1))
assert.EqualValues(t, endTime+1, histogramDataPoints.At(0).Timestamp())
histogramDataPoints.At(0).Attributes().Remove("key0")
histogramDataPoints.At(0).Attributes().PutStr("k", "v")
assert.Equal(t, newAttributes, histogramDataPoints.At(0).Attributes().AsRaw())
// Test that everything is updated.
assert.EqualValues(t, &internal.MetricsData{
ResourceMetrics: []*internal.ResourceMetrics{
{
Resource: generateTestProtoResource(),
ScopeMetrics: []*internal.ScopeMetrics{
{
Scope: generateTestProtoInstrumentationScope(),
Metrics: []*internal.Metric{
{
Name: "new_my_metric_exponential_histogram",
Description: "My new metric",
Unit: "1",
Data: &internal.Metric_ExponentialHistogram{
ExponentialHistogram: &internal.ExponentialHistogram{
AggregationTemporality: internal.AggregationTemporality_AGGREGATION_TEMPORALITY_DELTA,
DataPoints: []*internal.ExponentialHistogramDataPoint{
{
Attributes: []internal.KeyValue{
{
Key: "k",
Value: internal.AnyValue{Value: &internal.AnyValue_StringValue{StringValue: "v"}},
},
},
StartTimeUnixNano: startTime + 1,
TimeUnixNano: endTime + 1,
},
},
},
},
},
},
},
},
},
},
}, md.getOrig())
}
func TestMetricsCopyTo(t *testing.T) {
md := generateTestMetrics()
metricsCopy := NewMetrics()
md.CopyTo(metricsCopy)
assert.Equal(t, md, metricsCopy)
}
func TestReadOnlyMetricsInvalidUsage(t *testing.T) {
metrics := NewMetrics()
assert.False(t, metrics.IsReadOnly())
res := metrics.ResourceMetrics().AppendEmpty().Resource()
res.Attributes().PutStr("k1", "v1")
metrics.MarkReadOnly()
assert.True(t, metrics.IsReadOnly())
assert.Panics(t, func() { res.Attributes().PutStr("k2", "v2") })
}
func BenchmarkOtlpToFromInternal_PassThrough(b *testing.B) {
testutil.SkipMemoryBench(b)
req := &internal.ExportMetricsServiceRequest{
ResourceMetrics: []*internal.ResourceMetrics{
{
Resource: generateTestProtoResource(),
ScopeMetrics: []*internal.ScopeMetrics{
{
Scope: generateTestProtoInstrumentationScope(),
Metrics: []*internal.Metric{generateTestProtoGaugeMetric(), generateTestProtoSumMetric(), generateTestProtoHistogramMetric()},
},
},
},
},
}
var state internal.State
for b.Loop() {
md := newMetrics(req, &state)
newReq := md.getOrig()
if len(req.ResourceMetrics) != len(newReq.ResourceMetrics) {
b.Fail()
}
}
}
func BenchmarkOtlpToFromInternal_Gauge_MutateOneLabel(b *testing.B) {
testutil.SkipMemoryBench(b)
req := &internal.ExportMetricsServiceRequest{
ResourceMetrics: []*internal.ResourceMetrics{
{
Resource: generateTestProtoResource(),
ScopeMetrics: []*internal.ScopeMetrics{
{
Scope: generateTestProtoInstrumentationScope(),
Metrics: []*internal.Metric{generateTestProtoGaugeMetric()},
},
},
},
},
}
var state internal.State
for b.Loop() {
md := newMetrics(req, &state)
md.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0).Gauge().DataPoints().At(0).Attributes().
PutStr("key0", "value2")
newReq := md.getOrig()
if len(req.ResourceMetrics) != len(newReq.ResourceMetrics) {
b.Fail()
}
}
}
func BenchmarkOtlpToFromInternal_Sum_MutateOneLabel(b *testing.B) {
testutil.SkipMemoryBench(b)
req := &internal.ExportMetricsServiceRequest{
ResourceMetrics: []*internal.ResourceMetrics{
{
Resource: generateTestProtoResource(),
ScopeMetrics: []*internal.ScopeMetrics{
{
Scope: generateTestProtoInstrumentationScope(),
Metrics: []*internal.Metric{generateTestProtoSumMetric()},
},
},
},
},
}
var state internal.State
for b.Loop() {
md := newMetrics(req, &state)
md.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0).Sum().DataPoints().At(0).Attributes().
PutStr("key0", "value2")
newReq := md.getOrig()
if len(req.ResourceMetrics) != len(newReq.ResourceMetrics) {
b.Fail()
}
}
}
func BenchmarkOtlpToFromInternal_HistogramPoints_MutateOneLabel(b *testing.B) {
testutil.SkipMemoryBench(b)
req := &internal.ExportMetricsServiceRequest{
ResourceMetrics: []*internal.ResourceMetrics{
{
Resource: generateTestProtoResource(),
ScopeMetrics: []*internal.ScopeMetrics{
{
Scope: generateTestProtoInstrumentationScope(),
Metrics: []*internal.Metric{generateTestProtoHistogramMetric()},
},
},
},
},
}
var state internal.State
for b.Loop() {
md := newMetrics(req, &state)
md.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0).Histogram().DataPoints().At(0).Attributes().
PutStr("key0", "value2")
newReq := md.getOrig()
if len(req.ResourceMetrics) != len(newReq.ResourceMetrics) {
b.Fail()
}
}
}
func generateTestProtoResource() internal.Resource {
return internal.Resource{
Attributes: []internal.KeyValue{
{
Key: "string",
Value: internal.AnyValue{Value: &internal.AnyValue_StringValue{StringValue: "string-resource"}},
},
},
}
}
func generateTestProtoInstrumentationScope() internal.InstrumentationScope {
return internal.InstrumentationScope{
Name: "test",
Version: "",
}
}
func generateTestProtoGaugeMetric() *internal.Metric {
return &internal.Metric{
Name: "my_metric_int",
Description: "My metric",
Unit: "ms",
Data: &internal.Metric_Gauge{
Gauge: &internal.Gauge{
DataPoints: []*internal.NumberDataPoint{
{
Attributes: []internal.KeyValue{
{
Key: "key0",
Value: internal.AnyValue{Value: &internal.AnyValue_StringValue{StringValue: "value0"}},
},
},
StartTimeUnixNano: startTime,
TimeUnixNano: endTime,
Value: &internal.NumberDataPoint_AsDouble{
AsDouble: 123.1,
},
},
{
Attributes: []internal.KeyValue{
{
Key: "key1",
Value: internal.AnyValue{Value: &internal.AnyValue_StringValue{StringValue: "value1"}},
},
},
StartTimeUnixNano: startTime,
TimeUnixNano: endTime,
Value: &internal.NumberDataPoint_AsDouble{
AsDouble: 456.1,
},
},
},
},
},
}
}
func generateTestProtoSumMetric() *internal.Metric {
return &internal.Metric{
Name: "my_metric_double",
Description: "My metric",
Unit: "ms",
Data: &internal.Metric_Sum{
Sum: &internal.Sum{
AggregationTemporality: internal.AggregationTemporality_AGGREGATION_TEMPORALITY_CUMULATIVE,
DataPoints: []*internal.NumberDataPoint{
{
Attributes: []internal.KeyValue{
{
Key: "key0",
Value: internal.AnyValue{Value: &internal.AnyValue_StringValue{StringValue: "value0"}},
},
},
StartTimeUnixNano: startTime,
TimeUnixNano: endTime,
Value: &internal.NumberDataPoint_AsDouble{
AsDouble: 123.1,
},
},
{
Attributes: []internal.KeyValue{
{
Key: "key1",
Value: internal.AnyValue{Value: &internal.AnyValue_StringValue{StringValue: "value1"}},
},
},
StartTimeUnixNano: startTime,
TimeUnixNano: endTime,
Value: &internal.NumberDataPoint_AsDouble{
AsDouble: 456.1,
},
},
},
},
},
}
}
func generateTestProtoHistogramMetric() *internal.Metric {
return &internal.Metric{
Name: "my_metric_histogram",
Description: "My metric",
Unit: "ms",
Data: &internal.Metric_Histogram{
Histogram: &internal.Histogram{
AggregationTemporality: internal.AggregationTemporality_AGGREGATION_TEMPORALITY_DELTA,
DataPoints: []*internal.HistogramDataPoint{
{
Attributes: []internal.KeyValue{
{
Key: "key0",
Value: internal.AnyValue{Value: &internal.AnyValue_StringValue{StringValue: "value0"}},
},
},
StartTimeUnixNano: startTime,
TimeUnixNano: endTime,
BucketCounts: []uint64{10, 15, 1},
ExplicitBounds: []float64{1, 2},
},
{
Attributes: []internal.KeyValue{
{
Key: "key1",
Value: internal.AnyValue{Value: &internal.AnyValue_StringValue{StringValue: "value1"}},
},
},
StartTimeUnixNano: startTime,
TimeUnixNano: endTime,
BucketCounts: []uint64{10, 1},
ExplicitBounds: []float64{1},
},
},
},
},
}
}
func generateMetricsEmptyResource() Metrics {
return newMetrics(&internal.ExportMetricsServiceRequest{
ResourceMetrics: []*internal.ResourceMetrics{{}},
}, new(internal.State))
}
func generateMetricsEmptyInstrumentation() Metrics {
return newMetrics(&internal.ExportMetricsServiceRequest{
ResourceMetrics: []*internal.ResourceMetrics{
{
ScopeMetrics: []*internal.ScopeMetrics{{}},
},
},
}, new(internal.State))
}
func generateMetricsEmptyMetrics() Metrics {
return newMetrics(&internal.ExportMetricsServiceRequest{
ResourceMetrics: []*internal.ResourceMetrics{
{
ScopeMetrics: []*internal.ScopeMetrics{
{
Metrics: []*internal.Metric{{}},
},
},
},
},
}, new(internal.State))
}
func generateMetricsEmptyDataPoints() Metrics {
return newMetrics(&internal.ExportMetricsServiceRequest{
ResourceMetrics: []*internal.ResourceMetrics{
{
ScopeMetrics: []*internal.ScopeMetrics{
{
Metrics: []*internal.Metric{
{
Data: &internal.Metric_Gauge{
Gauge: &internal.Gauge{
DataPoints: []*internal.NumberDataPoint{
{},
},
},
},
},
},
},
},
},
},
}, new(internal.State))
}
func BenchmarkMetricsUsage(b *testing.B) {
md := generateTestMetrics()
ts := pcommon.NewTimestampFromTime(time.Now())
b.ReportAllocs()
for b.Loop() {
for i := 0; i < md.ResourceMetrics().Len(); i++ {
rm := md.ResourceMetrics().At(i)
res := rm.Resource()
res.Attributes().PutStr("foo", "bar")
v, ok := res.Attributes().Get("foo")
assert.True(b, ok)
assert.Equal(b, "bar", v.Str())
v.SetStr("new-bar")
assert.Equal(b, "new-bar", v.Str())
res.Attributes().Remove("foo")
for j := 0; j < rm.ScopeMetrics().Len(); j++ {
sm := rm.ScopeMetrics().At(j)
for k := 0; k < sm.Metrics().Len(); k++ {
m := sm.Metrics().At(k)
m.SetName("new_metric_name")
assert.Equal(b, "new_metric_name", m.Name())
// Only process Sum metrics to avoid nil pointer dereference
if m.Type() == MetricTypeSum {
assert.Equal(b, MetricTypeSum, m.Type())
m.Sum().SetAggregationTemporality(AggregationTemporalityCumulative)
assert.Equal(b, AggregationTemporalityCumulative, m.Sum().AggregationTemporality())
m.Sum().SetIsMonotonic(true)
assert.True(b, m.Sum().IsMonotonic())
for l := 0; l < m.Sum().DataPoints().Len(); l++ {
dp := m.Sum().DataPoints().At(l)
dp.SetIntValue(123)
assert.Equal(b, int64(123), dp.IntValue())
assert.Equal(b, NumberDataPointValueTypeInt, dp.ValueType())
dp.SetStartTimestamp(ts)
assert.Equal(b, ts, dp.StartTimestamp())
}
dp := m.Sum().DataPoints().AppendEmpty()
dp.Attributes().PutStr("foo", "bar")
dp.SetDoubleValue(123)
dp.SetStartTimestamp(ts)
dp.SetTimestamp(ts)
m.Sum().DataPoints().RemoveIf(func(dp NumberDataPoint) bool {
_, ok := dp.Attributes().Get("foo")
return ok
})
}
}
}
}
}
}
func BenchmarkMetricsMarshalJSON(b *testing.B) {
md := generateTestMetrics()
encoder := &JSONMarshaler{}
b.ReportAllocs()
for b.Loop() {
jsonBuf, err := encoder.MarshalMetrics(md)
require.NoError(b, err)
require.NotNil(b, jsonBuf)
}
}
================================================
FILE: pdata/pmetric/number_data_point_value_type.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pmetric // import "go.opentelemetry.io/collector/pdata/pmetric"
// NumberDataPointValueType specifies the type of NumberDataPoint value.
type NumberDataPointValueType int32
const (
// NumberDataPointValueTypeEmpty means that data point value is unset.
NumberDataPointValueTypeEmpty NumberDataPointValueType = iota
NumberDataPointValueTypeInt
NumberDataPointValueTypeDouble
)
// String returns the string representation of the NumberDataPointValueType.
func (nt NumberDataPointValueType) String() string {
switch nt {
case NumberDataPointValueTypeEmpty:
return "Empty"
case NumberDataPointValueTypeInt:
return "Int"
case NumberDataPointValueTypeDouble:
return "Double"
}
return ""
}
================================================
FILE: pdata/pmetric/number_data_point_value_type_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pmetric // import "go.opentelemetry.io/collector/pdata/pmetric"
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNumberDataPointValueTypeString(t *testing.T) {
assert.Equal(t, "Empty", NumberDataPointValueTypeEmpty.String())
assert.Equal(t, "Int", NumberDataPointValueTypeInt.String())
assert.Equal(t, "Double", NumberDataPointValueTypeDouble.String())
assert.Empty(t, (NumberDataPointValueTypeDouble + 1).String())
}
================================================
FILE: pdata/pmetric/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pmetric
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: pdata/pmetric/pb.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pmetric // import "go.opentelemetry.io/collector/pdata/pmetric"
var _ MarshalSizer = (*ProtoMarshaler)(nil)
type ProtoMarshaler struct{}
func (e *ProtoMarshaler) MarshalMetrics(md Metrics) ([]byte, error) {
size := md.getOrig().SizeProto()
buf := make([]byte, size)
_ = md.getOrig().MarshalProto(buf)
return buf, nil
}
func (e *ProtoMarshaler) MetricsSize(md Metrics) int {
return md.getOrig().SizeProto()
}
func (e *ProtoMarshaler) ResourceMetricsSize(md ResourceMetrics) int {
return md.orig.SizeProto()
}
func (e *ProtoMarshaler) ScopeMetricsSize(md ScopeMetrics) int {
return md.orig.SizeProto()
}
func (e *ProtoMarshaler) MetricSize(md Metric) int {
return md.orig.SizeProto()
}
func (e *ProtoMarshaler) NumberDataPointSize(md NumberDataPoint) int {
return md.orig.SizeProto()
}
func (e *ProtoMarshaler) SummaryDataPointSize(md SummaryDataPoint) int {
return md.orig.SizeProto()
}
func (e *ProtoMarshaler) HistogramDataPointSize(md HistogramDataPoint) int {
return md.orig.SizeProto()
}
func (e *ProtoMarshaler) ExponentialHistogramDataPointSize(md ExponentialHistogramDataPoint) int {
return md.orig.SizeProto()
}
type ProtoUnmarshaler struct{}
func (d *ProtoUnmarshaler) UnmarshalMetrics(buf []byte) (Metrics, error) {
md := NewMetrics()
err := md.getOrig().UnmarshalProto(buf)
if err != nil {
return Metrics{}, err
}
return md, nil
}
================================================
FILE: pdata/pmetric/pb_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pmetric
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpmetrics "go.opentelemetry.io/proto/slim/otlp/metrics/v1"
goproto "google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestMetricsProtoWireCompatibility(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 Metrics as pdata struct.
td := generateTestMetrics()
// Marshal its underlying ProtoBuf to wire.
marshaler := &ProtoMarshaler{}
wire1, err := marshaler.MarshalMetrics(td)
require.NoError(t, err)
assert.NotNil(t, wire1)
// Unmarshal from the wire to OTLP Protobuf in goproto's representation.
var goprotoMessage gootlpmetrics.MetricsData
err = goproto.Unmarshal(wire1, &goprotoMessage)
require.NoError(t, err)
// Marshal to the wire again.
wire2, err := goproto.Marshal(&goprotoMessage)
require.NoError(t, err)
assert.NotNil(t, wire2)
// Unmarshal from the wire into gogoproto's representation.
var td2 Metrics
unmarshaler := &ProtoUnmarshaler{}
td2, err = unmarshaler.UnmarshalMetrics(wire2)
require.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.Equal(t, td, td2)
}
func TestProtoMetricsUnmarshalerError(t *testing.T) {
p := &ProtoUnmarshaler{}
_, err := p.UnmarshalMetrics([]byte("+$%"))
assert.Error(t, err)
}
func TestProtoSizer(t *testing.T) {
marshaler := &ProtoMarshaler{}
md := NewMetrics()
md.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty().SetName("foo")
size := marshaler.MetricsSize(md)
bytes, err := marshaler.MarshalMetrics(md)
require.NoError(t, err)
assert.Equal(t, len(bytes), size)
}
func TestProtoSizerEmptyMetrics(t *testing.T) {
sizer := &ProtoMarshaler{}
assert.Equal(t, 0, sizer.MetricsSize(NewMetrics()))
}
func BenchmarkMetricsToProto2k(b *testing.B) {
marshaler := &ProtoMarshaler{}
metrics := generateBenchmarkMetrics(2_000)
for b.Loop() {
buf, err := marshaler.MarshalMetrics(metrics)
require.NoError(b, err)
assert.NotEmpty(b, buf)
}
}
func BenchmarkMetricsFromProto10k(b *testing.B) {
marshaler := &ProtoMarshaler{}
unmarshaler := &ProtoUnmarshaler{}
baseMetrics := generateBenchmarkMetrics(2_000)
buf, err := marshaler.MarshalMetrics(baseMetrics)
require.NoError(b, err)
assert.NotEmpty(b, buf)
b.ReportAllocs()
for b.Loop() {
metrics, err := unmarshaler.UnmarshalMetrics(buf)
require.NoError(b, err)
assert.Equal(b, baseMetrics.ResourceMetrics().Len(), metrics.ResourceMetrics().Len())
}
}
func generateBenchmarkMetrics(metricsCount int) Metrics {
now := time.Now()
startTime := pcommon.NewTimestampFromTime(now.Add(-10 * time.Second))
endTime := pcommon.NewTimestampFromTime(now)
md := NewMetrics()
ilm := md.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty()
ilm.Metrics().EnsureCapacity(metricsCount)
for range metricsCount {
im := ilm.Metrics().AppendEmpty()
im.SetName("test_name")
idp := im.SetEmptySum().DataPoints().AppendEmpty()
idp.SetStartTimestamp(startTime)
idp.SetTimestamp(endTime)
idp.SetIntValue(123)
}
return md
}
================================================
FILE: pdata/pmetric/pmetricotlp/fuzz_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pmetricotlp // import "go.opentelemetry.io/collector/pdata/pmetric/pmetricotlp"
import (
"testing"
)
func FuzzRequestUnmarshalJSON(f *testing.F) {
f.Fuzz(func(_ *testing.T, data []byte) {
er := NewExportRequest()
_ = er.UnmarshalJSON(data)
})
}
func FuzzResponseUnmarshalJSON(f *testing.F) {
f.Fuzz(func(_ *testing.T, data []byte) {
er := NewExportResponse()
_ = er.UnmarshalJSON(data)
})
}
func FuzzRequestUnmarshalProto(f *testing.F) {
f.Fuzz(func(_ *testing.T, data []byte) {
er := NewExportRequest()
_ = er.UnmarshalJSON(data)
})
}
func FuzzResponseUnmarshalProto(f *testing.F) {
f.Fuzz(func(_ *testing.T, data []byte) {
er := NewExportResponse()
_ = er.UnmarshalJSON(data)
})
}
================================================
FILE: pdata/pmetric/pmetricotlp/generated_exportpartialsuccess.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetricotlp
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// ExportPartialSuccess represents the details of a partially successful export request.
//
// This is a reference type, if passed by value and callee modifies it the
// caller will see the modification.
//
// Must use NewExportPartialSuccess function to create new instances.
// Important: zero-initialized instance is not valid for use.
type ExportPartialSuccess struct {
orig *internal.ExportMetricsPartialSuccess
state *internal.State
}
func newExportPartialSuccess(orig *internal.ExportMetricsPartialSuccess, state *internal.State) ExportPartialSuccess {
return ExportPartialSuccess{orig: orig, state: state}
}
// NewExportPartialSuccess creates a new empty ExportPartialSuccess.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewExportPartialSuccess() ExportPartialSuccess {
return newExportPartialSuccess(internal.NewExportMetricsPartialSuccess(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms ExportPartialSuccess) MoveTo(dest ExportPartialSuccess) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteExportMetricsPartialSuccess(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// RejectedDataPoints returns the rejecteddatapoints associated with this ExportPartialSuccess.
func (ms ExportPartialSuccess) RejectedDataPoints() int64 {
return ms.orig.RejectedDataPoints
}
// SetRejectedDataPoints replaces the rejecteddatapoints associated with this ExportPartialSuccess.
func (ms ExportPartialSuccess) SetRejectedDataPoints(v int64) {
ms.state.AssertMutable()
ms.orig.RejectedDataPoints = v
}
// ErrorMessage returns the errormessage associated with this ExportPartialSuccess.
func (ms ExportPartialSuccess) ErrorMessage() string {
return ms.orig.ErrorMessage
}
// SetErrorMessage replaces the errormessage associated with this ExportPartialSuccess.
func (ms ExportPartialSuccess) SetErrorMessage(v string) {
ms.state.AssertMutable()
ms.orig.ErrorMessage = v
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms ExportPartialSuccess) CopyTo(dest ExportPartialSuccess) {
dest.state.AssertMutable()
internal.CopyExportMetricsPartialSuccess(dest.orig, ms.orig)
}
================================================
FILE: pdata/pmetric/pmetricotlp/generated_exportpartialsuccess_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetricotlp
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestExportPartialSuccess_MoveTo(t *testing.T) {
ms := generateTestExportPartialSuccess()
dest := NewExportPartialSuccess()
ms.MoveTo(dest)
assert.Equal(t, NewExportPartialSuccess(), ms)
assert.Equal(t, generateTestExportPartialSuccess(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestExportPartialSuccess(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newExportPartialSuccess(internal.NewExportMetricsPartialSuccess(), sharedState)) })
assert.Panics(t, func() { newExportPartialSuccess(internal.NewExportMetricsPartialSuccess(), sharedState).MoveTo(dest) })
}
func TestExportPartialSuccess_CopyTo(t *testing.T) {
ms := NewExportPartialSuccess()
orig := NewExportPartialSuccess()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestExportPartialSuccess()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newExportPartialSuccess(internal.NewExportMetricsPartialSuccess(), sharedState)) })
}
func TestExportPartialSuccess_RejectedDataPoints(t *testing.T) {
ms := NewExportPartialSuccess()
assert.Equal(t, int64(0), ms.RejectedDataPoints())
ms.SetRejectedDataPoints(int64(13))
assert.Equal(t, int64(13), ms.RejectedDataPoints())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() {
newExportPartialSuccess(internal.NewExportMetricsPartialSuccess(), sharedState).SetRejectedDataPoints(int64(13))
})
}
func TestExportPartialSuccess_ErrorMessage(t *testing.T) {
ms := NewExportPartialSuccess()
assert.Empty(t, ms.ErrorMessage())
ms.SetErrorMessage("test_errormessage")
assert.Equal(t, "test_errormessage", ms.ErrorMessage())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() {
newExportPartialSuccess(internal.NewExportMetricsPartialSuccess(), sharedState).SetErrorMessage("test_errormessage")
})
}
func generateTestExportPartialSuccess() ExportPartialSuccess {
return newExportPartialSuccess(internal.GenTestExportMetricsPartialSuccess(), internal.NewState())
}
================================================
FILE: pdata/pmetric/pmetricotlp/generated_exportresponse.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetricotlp
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// ExportResponse represents the response for gRPC/HTTP client/server.
//
// This is a reference type, if passed by value and callee modifies it the
// caller will see the modification.
//
// Must use NewExportResponse function to create new instances.
// Important: zero-initialized instance is not valid for use.
type ExportResponse struct {
orig *internal.ExportMetricsServiceResponse
state *internal.State
}
func newExportResponse(orig *internal.ExportMetricsServiceResponse, state *internal.State) ExportResponse {
return ExportResponse{orig: orig, state: state}
}
// NewExportResponse creates a new empty ExportResponse.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewExportResponse() ExportResponse {
return newExportResponse(internal.NewExportMetricsServiceResponse(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms ExportResponse) MoveTo(dest ExportResponse) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteExportMetricsServiceResponse(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// PartialSuccess returns the partialsuccess associated with this ExportResponse.
func (ms ExportResponse) PartialSuccess() ExportPartialSuccess {
return newExportPartialSuccess(&ms.orig.PartialSuccess, ms.state)
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms ExportResponse) CopyTo(dest ExportResponse) {
dest.state.AssertMutable()
internal.CopyExportMetricsServiceResponse(dest.orig, ms.orig)
}
================================================
FILE: pdata/pmetric/pmetricotlp/generated_exportresponse_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pmetricotlp
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestExportResponse_MoveTo(t *testing.T) {
ms := generateTestExportResponse()
dest := NewExportResponse()
ms.MoveTo(dest)
assert.Equal(t, NewExportResponse(), ms)
assert.Equal(t, generateTestExportResponse(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestExportResponse(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newExportResponse(internal.NewExportMetricsServiceResponse(), sharedState)) })
assert.Panics(t, func() { newExportResponse(internal.NewExportMetricsServiceResponse(), sharedState).MoveTo(dest) })
}
func TestExportResponse_CopyTo(t *testing.T) {
ms := NewExportResponse()
orig := NewExportResponse()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestExportResponse()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newExportResponse(internal.NewExportMetricsServiceResponse(), sharedState)) })
}
func TestExportResponse_PartialSuccess(t *testing.T) {
ms := NewExportResponse()
assert.Equal(t, NewExportPartialSuccess(), ms.PartialSuccess())
ms.orig.PartialSuccess = *internal.GenTestExportMetricsPartialSuccess()
assert.Equal(t, generateTestExportPartialSuccess(), ms.PartialSuccess())
}
func generateTestExportResponse() ExportResponse {
return newExportResponse(internal.GenTestExportMetricsServiceResponse(), internal.NewState())
}
================================================
FILE: pdata/pmetric/pmetricotlp/grpc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pmetricotlp // import "go.opentelemetry.io/collector/pdata/pmetric/pmetricotlp"
import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/internal/otelgrpc"
"go.opentelemetry.io/collector/pdata/internal/otlp"
)
// GRPCClient is the client API for OTLP-GRPC Metrics service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type GRPCClient interface {
// Export pmetric.Metrics to the server.
//
// For performance reasons, it is recommended to keep this RPC
// alive for the entire life of the application.
Export(ctx context.Context, request ExportRequest, opts ...grpc.CallOption) (ExportResponse, error)
// unexported disallow implementation of the GRPCClient.
unexported()
}
// NewGRPCClient returns a new GRPCClient connected using the given connection.
func NewGRPCClient(cc *grpc.ClientConn) GRPCClient {
return &grpcClient{rawClient: otelgrpc.NewMetricsServiceClient(cc)}
}
type grpcClient struct {
rawClient otelgrpc.MetricsServiceClient
}
func (c *grpcClient) Export(ctx context.Context, request ExportRequest, opts ...grpc.CallOption) (ExportResponse, error) {
rsp, err := c.rawClient.Export(ctx, request.orig, opts...)
if err != nil {
return ExportResponse{}, err
}
return ExportResponse{orig: rsp, state: internal.NewState()}, err
}
func (c *grpcClient) unexported() {}
// GRPCServer is the server API for OTLP gRPC MetricsService service.
// Implementations MUST embed UnimplementedGRPCServer.
type GRPCServer interface {
// Export is called every time a new request is received.
//
// For performance reasons, it is recommended to keep this RPC
// alive for the entire life of the application.
Export(context.Context, ExportRequest) (ExportResponse, error)
// unexported disallow implementation of the GRPCServer.
unexported()
}
var _ GRPCServer = (*UnimplementedGRPCServer)(nil)
// UnimplementedGRPCServer MUST be embedded to have forward compatible implementations.
type UnimplementedGRPCServer struct{}
func (*UnimplementedGRPCServer) Export(context.Context, ExportRequest) (ExportResponse, error) {
return ExportResponse{}, status.Errorf(codes.Unimplemented, "method Export not implemented")
}
func (*UnimplementedGRPCServer) unexported() {}
// RegisterGRPCServer registers the GRPCServer to the grpc.Server.
func RegisterGRPCServer(s *grpc.Server, srv GRPCServer) {
otelgrpc.RegisterMetricsServiceServer(s, &rawMetricsServer{srv: srv})
}
type rawMetricsServer struct {
srv GRPCServer
}
func (s rawMetricsServer) Export(ctx context.Context, request *internal.ExportMetricsServiceRequest) (*internal.ExportMetricsServiceResponse, error) {
otlp.MigrateMetrics(request.ResourceMetrics)
rsp, err := s.srv.Export(ctx, ExportRequest{orig: request, state: internal.NewState()})
return rsp.orig, err
}
================================================
FILE: pdata/pmetric/pmetricotlp/grpc_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pmetricotlp
import (
"context"
"errors"
"net"
"sync"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/resolver"
"google.golang.org/grpc/status"
"google.golang.org/grpc/test/bufconn"
"go.opentelemetry.io/collector/pdata/pmetric"
)
func TestGrpc(t *testing.T) {
lis := bufconn.Listen(1024 * 1024)
s := grpc.NewServer()
RegisterGRPCServer(s, &fakeMetricsServer{t: t})
wg := sync.WaitGroup{}
wg.Go(func() {
assert.NoError(t, s.Serve(lis))
})
t.Cleanup(func() {
s.Stop()
wg.Wait()
})
resolver.SetDefaultScheme("passthrough")
cc, err := grpc.NewClient("bufnet",
grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
return lis.Dial()
}),
grpc.WithTransportCredentials(insecure.NewCredentials()))
require.NoError(t, err)
t.Cleanup(func() {
assert.NoError(t, cc.Close())
})
logClient := NewGRPCClient(cc)
resp, err := logClient.Export(context.Background(), generateMetricsRequest())
require.NoError(t, err)
assert.Equal(t, NewExportResponse(), resp)
}
func TestGrpcError(t *testing.T) {
lis := bufconn.Listen(1024 * 1024)
s := grpc.NewServer()
RegisterGRPCServer(s, &fakeMetricsServer{t: t, err: errors.New("my error")})
wg := sync.WaitGroup{}
wg.Go(func() {
assert.NoError(t, s.Serve(lis))
})
t.Cleanup(func() {
s.Stop()
wg.Wait()
})
cc, err := grpc.NewClient("bufnet",
grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
return lis.Dial()
}),
grpc.WithTransportCredentials(insecure.NewCredentials()))
require.NoError(t, err)
t.Cleanup(func() {
assert.NoError(t, cc.Close())
})
logClient := NewGRPCClient(cc)
resp, err := logClient.Export(context.Background(), generateMetricsRequest())
require.Error(t, err)
st, okSt := status.FromError(err)
require.True(t, okSt)
assert.Equal(t, "my error", st.Message())
assert.Equal(t, codes.Unknown, st.Code())
assert.Equal(t, ExportResponse{}, resp)
}
type fakeMetricsServer struct {
UnimplementedGRPCServer
t *testing.T
err error
}
func (f fakeMetricsServer) Export(_ context.Context, request ExportRequest) (ExportResponse, error) {
assert.Equal(f.t, generateMetricsRequest(), request)
return NewExportResponse(), f.err
}
func generateMetricsRequest() ExportRequest {
md := pmetric.NewMetrics()
m := md.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty()
m.SetName("test_metric")
m.SetEmptyGauge().DataPoints().AppendEmpty()
return NewExportRequestFromMetrics(md)
}
================================================
FILE: pdata/pmetric/pmetricotlp/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pmetricotlp
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: pdata/pmetric/pmetricotlp/request.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pmetricotlp // import "go.opentelemetry.io/collector/pdata/pmetric/pmetricotlp"
import (
"slices"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/otlp"
"go.opentelemetry.io/collector/pdata/pmetric"
)
// ExportRequest represents the request for gRPC/HTTP client/server.
// It's a wrapper for pmetric.Metrics data.
type ExportRequest struct {
orig *internal.ExportMetricsServiceRequest
state *internal.State
}
// NewExportRequest returns an empty ExportRequest.
func NewExportRequest() ExportRequest {
return ExportRequest{
orig: &internal.ExportMetricsServiceRequest{},
state: internal.NewState(),
}
}
// NewExportRequestFromMetrics returns a ExportRequest from pmetric.Metrics.
// Because ExportRequest is a wrapper for pmetric.Metrics,
// any changes to the provided Metrics struct will be reflected in the ExportRequest and vice versa.
func NewExportRequestFromMetrics(md pmetric.Metrics) ExportRequest {
return ExportRequest{
orig: internal.GetMetricsOrig(internal.MetricsWrapper(md)),
state: internal.GetMetricsState(internal.MetricsWrapper(md)),
}
}
// MarshalProto marshals ExportRequest into proto bytes.
func (ms ExportRequest) MarshalProto() ([]byte, error) {
size := ms.orig.SizeProto()
buf := make([]byte, size)
_ = ms.orig.MarshalProto(buf)
return buf, nil
}
// UnmarshalProto unmarshalls ExportRequest from proto bytes.
func (ms ExportRequest) UnmarshalProto(data []byte) error {
err := ms.orig.UnmarshalProto(data)
if err != nil {
return err
}
otlp.MigrateMetrics(ms.orig.ResourceMetrics)
return nil
}
// MarshalJSON marshals ExportRequest into JSON bytes.
func (ms ExportRequest) MarshalJSON() ([]byte, error) {
dest := json.BorrowStream(nil)
defer json.ReturnStream(dest)
ms.orig.MarshalJSON(dest)
if dest.Error() != nil {
return nil, dest.Error()
}
return slices.Clone(dest.Buffer()), nil
}
// UnmarshalJSON unmarshalls ExportRequest from JSON bytes.
func (ms ExportRequest) UnmarshalJSON(data []byte) error {
iter := json.BorrowIterator(data)
defer json.ReturnIterator(iter)
ms.orig.UnmarshalJSON(iter)
return iter.Error()
}
func (ms ExportRequest) Metrics() pmetric.Metrics {
return pmetric.Metrics(internal.NewMetricsWrapper(ms.orig, ms.state))
}
================================================
FILE: pdata/pmetric/pmetricotlp/request_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pmetricotlp
import (
"encoding/json"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpcollectormetrics "go.opentelemetry.io/proto/slim/otlp/collector/metrics/v1"
goproto "google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/internal/otlp"
"go.opentelemetry.io/collector/pdata/pmetric"
)
var (
_ json.Unmarshaler = ExportRequest{}
_ json.Marshaler = ExportRequest{}
)
var metricsRequestJSON = []byte(`
{
"resourceMetrics": [
{
"resource": {},
"scopeMetrics": [
{
"scope": {},
"metrics": [
{
"name": "test_metric"
}
]
}
]
}
]
}`)
func TestRequestToPData(t *testing.T) {
tr := NewExportRequest()
assert.Equal(t, 0, tr.Metrics().MetricCount())
tr.Metrics().ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty()
assert.Equal(t, 1, tr.Metrics().MetricCount())
}
func TestRequestJSON(t *testing.T) {
mr := NewExportRequest()
require.NoError(t, mr.UnmarshalJSON(metricsRequestJSON))
assert.Equal(t, "test_metric", mr.Metrics().ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0).Name())
got, err := mr.MarshalJSON()
require.NoError(t, err)
assert.Equal(t, strings.Join(strings.Fields(string(metricsRequestJSON)), ""), string(got))
}
func TestMetricsProtoWireCompatibility(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 Metrics as pdata struct.
md := NewExportRequestFromMetrics(pmetric.Metrics(internal.GenTestMetricsWrapper()))
// Marshal its underlying ProtoBuf to wire.
wire1, err := md.MarshalProto()
require.NoError(t, err)
assert.NotNil(t, wire1)
// Unmarshal from the wire to OTLP Protobuf in goproto's representation.
var goprotoMessage gootlpcollectormetrics.ExportMetricsServiceRequest
err = goproto.Unmarshal(wire1, &goprotoMessage)
require.NoError(t, err)
// Marshal to the wire again.
wire2, err := goproto.Marshal(&goprotoMessage)
require.NoError(t, err)
assert.NotNil(t, wire2)
// Unmarshal from the wire into gogoproto's representation.
md2 := NewExportRequest()
err = md2.UnmarshalProto(wire2)
require.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.
// Migration logic will run, so run it on the original message as well.
otlp.MigrateMetrics(md.orig.ResourceMetrics)
assert.Equal(t, md, md2)
}
================================================
FILE: pdata/pmetric/pmetricotlp/response.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pmetricotlp // import "go.opentelemetry.io/collector/pdata/pmetric/pmetricotlp"
import (
"slices"
"go.opentelemetry.io/collector/pdata/internal/json"
)
// MarshalProto marshals ExportResponse into proto bytes.
func (ms ExportResponse) MarshalProto() ([]byte, error) {
size := ms.orig.SizeProto()
buf := make([]byte, size)
_ = ms.orig.MarshalProto(buf)
return buf, nil
}
// UnmarshalProto unmarshalls ExportResponse from proto bytes.
func (ms ExportResponse) UnmarshalProto(data []byte) error {
return ms.orig.UnmarshalProto(data)
}
// MarshalJSON marshals ExportResponse into JSON bytes.
func (ms ExportResponse) MarshalJSON() ([]byte, error) {
dest := json.BorrowStream(nil)
defer json.ReturnStream(dest)
ms.orig.MarshalJSON(dest)
return slices.Clone(dest.Buffer()), dest.Error()
}
// UnmarshalJSON unmarshalls ExportResponse from JSON bytes.
func (ms ExportResponse) UnmarshalJSON(data []byte) error {
iter := json.BorrowIterator(data)
defer json.ReturnIterator(iter)
ms.orig.UnmarshalJSON(iter)
return iter.Error()
}
================================================
FILE: pdata/pmetric/pmetricotlp/response_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pmetricotlp
import (
stdjson "encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var (
_ stdjson.Unmarshaler = ExportResponse{}
_ stdjson.Marshaler = ExportResponse{}
)
func TestExportResponseJSON(t *testing.T) {
jsonStr := `{"partialSuccess": {"rejectedDataPoints":"1", "errorMessage":"nothing"}}`
val := NewExportResponse()
require.NoError(t, val.UnmarshalJSON([]byte(jsonStr)))
expected := NewExportResponse()
expected.PartialSuccess().SetRejectedDataPoints(1)
expected.PartialSuccess().SetErrorMessage("nothing")
assert.Equal(t, expected, val)
buf, err := val.MarshalJSON()
require.NoError(t, err)
assert.JSONEq(t, jsonStr, string(buf))
}
func TestUnmarshalJSONExportResponse(t *testing.T) {
jsonStr := `{"extra":"", "partialSuccess": {"extra":""}}`
val := NewExportResponse()
require.NoError(t, val.UnmarshalJSON([]byte(jsonStr)))
assert.Equal(t, NewExportResponse(), val)
}
================================================
FILE: pdata/pprofile/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: pdata/pprofile/aggregation_temporality.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile // import "go.opentelemetry.io/collector/pdata/pprofile"
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// AggregationTemporality specifies the method of aggregating metric values,
// either DELTA (change since last report) or CUMULATIVE (total since a fixed
// start time).
//
// Deprecated: [v0.146.0] Type was removed without replacement in the Profiles signal.
type AggregationTemporality int32
const (
// AggregationTemporalityUnspecified is the default AggregationTemporality, it MUST NOT be used.
//
// Deprecated: [v0.146.0] This is no longer supported by the Profiles signal.
AggregationTemporalityUnspecified = AggregationTemporality(internal.AggregationTemporality_AGGREGATION_TEMPORALITY_UNSPECIFIED)
// AggregationTemporalityDelta is a AggregationTemporality for a metric aggregator which reports changes since last report time.
//
// Deprecated: [v0.146.0] This is no longer supported by the Profiles signal.
AggregationTemporalityDelta = AggregationTemporality(internal.AggregationTemporality_AGGREGATION_TEMPORALITY_DELTA)
// AggregationTemporalityCumulative is a AggregationTemporality for a metric aggregator which reports changes since a fixed start time.
//
// Deprecated: [v0.146.0] This is no longer supported by the Profiles signal.
AggregationTemporalityCumulative = AggregationTemporality(internal.AggregationTemporality_AGGREGATION_TEMPORALITY_CUMULATIVE)
)
// String returns the string representation of the AggregationTemporality.
//
// Deprecated: [v0.146.0] Type was removed without replacement in the Profiles signal.
func (at AggregationTemporality) String() string {
switch at {
case AggregationTemporalityUnspecified:
return "Unspecified"
case AggregationTemporalityDelta:
return "Delta"
case AggregationTemporalityCumulative:
return "Cumulative"
}
return ""
}
================================================
FILE: pdata/pprofile/aggregation_temporality_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestAggregationTemporalityString(t *testing.T) {
assert.Equal(t, "Unspecified", AggregationTemporalityUnspecified.String())
assert.Equal(t, "Delta", AggregationTemporalityDelta.String())
assert.Equal(t, "Cumulative", AggregationTemporalityCumulative.String())
assert.Empty(t, (AggregationTemporalityCumulative + 1).String())
}
================================================
FILE: pdata/pprofile/attributes.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile // import "go.opentelemetry.io/collector/pdata/pprofile"
import (
"errors"
"math"
"go.opentelemetry.io/collector/pdata/pcommon"
)
type attributable interface {
AttributeIndices() pcommon.Int32Slice
}
// FromAttributeIndices builds a [pcommon.Map] containing the attributes of a
// record.
// The record can be any struct that implements an `AttributeIndices` method.
// Updates made to the return map will not be applied back to the record.
func FromAttributeIndices(table KeyValueAndUnitSlice, record attributable, dic ProfilesDictionary) pcommon.Map {
m := pcommon.NewMap()
m.EnsureCapacity(record.AttributeIndices().Len())
for i := 0; i < record.AttributeIndices().Len(); i++ {
kv := table.At(int(record.AttributeIndices().At(i)))
key := dic.StringTable().At(int(kv.KeyStrindex()))
kv.Value().CopyTo(m.PutEmpty(key))
}
return m
}
var errTooManyAttributeTableEntries = errors.New("too many entries in AttributeTable")
// SetAttribute updates an AttributeTable, adding or providing a value and
// returns its index.
func SetAttribute(table KeyValueAndUnitSlice, attr KeyValueAndUnit) (int32, error) {
for j, a := range table.All() {
if a.Equal(attr) {
if j > math.MaxInt32 {
return 0, errTooManyAttributeTableEntries
}
return int32(j), nil
}
}
if table.Len() >= math.MaxInt32 {
return 0, errTooManyAttributeTableEntries
}
attr.CopyTo(table.AppendEmpty())
return int32(table.Len() - 1), nil
}
================================================
FILE: pdata/pprofile/attributes_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/internal/testutil"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestFromAttributeIndices(t *testing.T) {
dic := NewProfilesDictionary()
dic.StringTable().Append("")
dic.StringTable().Append("hello")
dic.StringTable().Append("bonjour")
table := NewKeyValueAndUnitSlice()
att := table.AppendEmpty()
att.SetKeyStrindex(1)
att.Value().SetStr("world")
att2 := table.AppendEmpty()
att2.SetKeyStrindex(2)
att2.Value().SetStr("monde")
attrs := FromAttributeIndices(table, NewProfile(), dic)
assert.Equal(t, attrs, pcommon.NewMap())
// A Location with a single attribute
loc := NewLocation()
loc.AttributeIndices().Append(0)
attrs = FromAttributeIndices(table, loc, dic)
m := map[string]any{"hello": "world"}
assert.Equal(t, attrs.AsRaw(), m)
// A Mapping with two attributes
mapp := NewLocation()
mapp.AttributeIndices().Append(0, 1)
attrs = FromAttributeIndices(table, mapp, dic)
m = map[string]any{"hello": "world", "bonjour": "monde"}
assert.Equal(t, attrs.AsRaw(), m)
}
func BenchmarkFromAttributeIndices(b *testing.B) {
dic := NewProfilesDictionary()
table := NewKeyValueAndUnitSlice()
for i := range 10 {
dic.StringTable().Append(fmt.Sprintf("key_%d", i))
att := table.AppendEmpty()
att.SetKeyStrindex(int32(dic.StringTable().Len()))
att.Value().SetStr(fmt.Sprintf("value_%d", i))
}
obj := NewLocation()
obj.AttributeIndices().Append(1, 3, 7)
b.ReportAllocs()
for b.Loop() {
_ = FromAttributeIndices(table, obj, dic)
}
}
func TestSetAttribute(t *testing.T) {
table := NewKeyValueAndUnitSlice()
attr := NewKeyValueAndUnit()
attr.SetKeyStrindex(1)
attr.SetUnitStrindex(2)
require.NoError(t, attr.Value().FromRaw("test"))
attr2 := NewKeyValueAndUnit()
attr2.SetKeyStrindex(3)
attr2.SetUnitStrindex(4)
require.NoError(t, attr.Value().FromRaw("test2"))
// Put a first value
idx, err := SetAttribute(table, attr)
require.NoError(t, err)
assert.Equal(t, 1, table.Len())
assert.Equal(t, int32(0), idx)
// Put the same attribute
// This should be a no-op.
idx, err = SetAttribute(table, attr)
require.NoError(t, err)
assert.Equal(t, 1, table.Len())
assert.Equal(t, int32(0), idx)
// Set a new value
// This sets the index and adds to the table.
idx, err = SetAttribute(table, attr2)
require.NoError(t, err)
assert.Equal(t, 2, table.Len())
assert.Equal(t, int32(table.Len()-1), idx)
// Set an existing value
idx, err = SetAttribute(table, attr)
require.NoError(t, err)
assert.Equal(t, 2, table.Len())
assert.Equal(t, int32(0), idx)
// Set another existing value
idx, err = SetAttribute(table, attr2)
require.NoError(t, err)
assert.Equal(t, 2, table.Len())
assert.Equal(t, int32(table.Len()-1), idx)
}
func BenchmarkSetAttribute(b *testing.B) {
testutil.SkipMemoryBench(b)
for _, bb := range []struct {
name string
attr KeyValueAndUnit
runBefore func(*testing.B, KeyValueAndUnitSlice)
}{
{
name: "with a new attribute",
attr: NewKeyValueAndUnit(),
},
{
name: "with an existing attribute",
attr: func() KeyValueAndUnit {
a := NewKeyValueAndUnit()
a.SetKeyStrindex(1)
return a
}(),
runBefore: func(_ *testing.B, table KeyValueAndUnitSlice) {
a := table.AppendEmpty()
a.SetKeyStrindex(1)
},
},
{
name: "with a duplicate attribute",
attr: NewKeyValueAndUnit(),
runBefore: func(_ *testing.B, table KeyValueAndUnitSlice) {
_, err := SetAttribute(table, NewKeyValueAndUnit())
require.NoError(b, err)
},
},
{
name: "with a hundred locations to loop through",
attr: func() KeyValueAndUnit {
a := NewKeyValueAndUnit()
a.SetKeyStrindex(1)
return a
}(),
runBefore: func(_ *testing.B, table KeyValueAndUnitSlice) {
for i := range 100 {
l := table.AppendEmpty()
l.SetKeyStrindex(int32(i))
}
},
},
} {
b.Run(bb.name, func(b *testing.B) {
table := NewKeyValueAndUnitSlice()
if bb.runBefore != nil {
bb.runBefore(b, table)
}
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
_, _ = SetAttribute(table, bb.attr)
}
})
}
}
================================================
FILE: pdata/pprofile/dictionary_helpers.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile // import "go.opentelemetry.io/collector/pdata/pprofile"
import (
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// mapKeyValues returns the underlying KeyValue slice of a pcommon.Map.
func mapKeyValues(m pcommon.Map) []internal.KeyValue {
return *internal.GetMapOrig(internal.MapWrapper(m))
}
// resolveProfilesReferences walks through all profiles data after unmarshaling
// and resolves any string_value_ref and key_ref to their actual string values.
// This ensures the pdata API works transparently with referenced strings.
func resolveProfilesReferences(profiles Profiles) {
dict := profiles.Dictionary()
// Resolve references in resource attributes
for i := 0; i < profiles.ResourceProfiles().Len(); i++ {
rp := profiles.ResourceProfiles().At(i)
resolveKeyValueReferences(dict, mapKeyValues(rp.Resource().Attributes()))
// Resolve references in scope attributes
for j := 0; j < rp.ScopeProfiles().Len(); j++ {
sp := rp.ScopeProfiles().At(j)
resolveKeyValueReferences(dict, mapKeyValues(sp.Scope().Attributes()))
}
}
}
// resolveKeyValueReferences resolves key_ref and string_value_ref in a KeyValue slice
func resolveKeyValueReferences(dict ProfilesDictionary, kvs []internal.KeyValue) {
for i := range kvs {
kv := &kvs[i]
// Resolve key_ref if set
if kv.KeyStrindex >= 0 {
idx := int(kv.KeyStrindex)
if idx < dict.StringTable().Len() {
kv.Key = dict.StringTable().At(idx)
// N.b. keep KeyStrindex set to optimize re-marshaling. This is
// technically a violation of the proto spec, but acceptable
// for the in-memory pdata API since keys are immutable.
}
}
// Resolve string_value_ref if set
resolveAnyValueReference(dict, &kv.Value)
}
}
// resolveAnyValueReference resolves string_value_ref in an AnyValue
func resolveAnyValueReference(dict ProfilesDictionary, anyValue *internal.AnyValue) {
if ref, ok := anyValue.Value.(*internal.AnyValue_StringValueStrindex); ok && ref.StringValueStrindex != 0 {
idx := int(ref.StringValueStrindex)
if idx >= 0 && idx < dict.StringTable().Len() {
str := dict.StringTable().At(idx)
var ov *internal.AnyValue_StringValue
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &internal.AnyValue_StringValue{}
} else {
ov = internal.ProtoPoolAnyValue_StringValue.Get().(*internal.AnyValue_StringValue)
}
ov.StringValue = str
anyValue.Value = ov
}
} else if kvList, ok := anyValue.Value.(*internal.AnyValue_KvlistValue); ok && kvList.KvlistValue != nil {
resolveKeyValueReferences(dict, kvList.KvlistValue.Values)
} else if arrVal, ok := anyValue.Value.(*internal.AnyValue_ArrayValue); ok && arrVal.ArrayValue != nil {
for i := 0; i < len(arrVal.ArrayValue.Values); i++ {
resolveAnyValueReference(dict, &arrVal.ArrayValue.Values[i])
}
}
}
// convertProfilesToReferences walks through all profiles data before marshaling
// and converts string values to references for efficient transmission.
// This builds up the string table in the dictionary and replaces strings with refs.
func convertProfilesToReferences(profiles Profiles) {
dict := profiles.Dictionary()
stringTable := dict.StringTable()
// Map for quick string lookups - only allocate if needed
var stringIndex map[string]int32
getStringIndex := func(s string) int32 {
if stringIndex == nil {
stringIndex = make(map[string]int32, stringTable.Len())
for i := 0; i < stringTable.Len(); i++ {
stringIndex[stringTable.At(i)] = int32(i)
}
}
if idx, ok := stringIndex[s]; ok {
return idx
}
idx := int32(stringTable.Len())
stringTable.Append(s)
stringIndex[s] = idx
return idx
}
// Convert strings in resource attributes
for i := 0; i < profiles.ResourceProfiles().Len(); i++ {
rp := profiles.ResourceProfiles().At(i)
convertKeyValueToReferences(getStringIndex, mapKeyValues(rp.Resource().Attributes()))
// Convert strings in scope attributes
for j := 0; j < rp.ScopeProfiles().Len(); j++ {
sp := rp.ScopeProfiles().At(j)
convertKeyValueToReferences(getStringIndex, mapKeyValues(sp.Scope().Attributes()))
}
}
}
// convertKeyValueToReferences converts string keys and values to references in a KeyValue slice
func convertKeyValueToReferences(getStringIndex func(string) int32, kvs []internal.KeyValue) {
for i := range kvs {
kv := &kvs[i]
// Convert key to reference
if kv.Key != "" && kv.KeyStrindex == 0 {
kv.KeyStrindex = getStringIndex(kv.Key)
kv.Key = ""
}
// Convert string values to references
convertAnyValueToReference(getStringIndex, &kv.Value)
}
}
// convertAnyValueToReference converts string values to string_value_ref
func convertAnyValueToReference(getStringIndex func(string) int32, anyValue *internal.AnyValue) {
// Skip if already a reference
if _, ok := anyValue.Value.(*internal.AnyValue_StringValueStrindex); ok {
return
}
if strVal, ok := anyValue.Value.(*internal.AnyValue_StringValue); ok && strVal.StringValue != "" {
// Convert to reference
idx := getStringIndex(strVal.StringValue)
var ov *internal.AnyValue_StringValueStrindex
if !metadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
ov = &internal.AnyValue_StringValueStrindex{}
} else {
ov = internal.ProtoPoolAnyValue_StringValueStrindex.Get().(*internal.AnyValue_StringValueStrindex)
}
ov.StringValueStrindex = idx
anyValue.Value = ov
} else if kvList, ok := anyValue.Value.(*internal.AnyValue_KvlistValue); ok && kvList.KvlistValue != nil {
convertKeyValueToReferences(getStringIndex, kvList.KvlistValue.Values)
} else if arrVal, ok := anyValue.Value.(*internal.AnyValue_ArrayValue); ok && arrVal.ArrayValue != nil {
// Recursively convert arrays
for i := 0; i < len(arrVal.ArrayValue.Values); i++ {
convertAnyValueToReference(getStringIndex, &arrVal.ArrayValue.Values[i])
}
}
}
================================================
FILE: pdata/pprofile/dictionary_helpers_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
func TestResolveProfilesReferencesEmpty(t *testing.T) {
profiles := NewProfiles()
// Should not panic on empty profiles
resolveProfilesReferences(profiles)
assert.Equal(t, 0, profiles.ResourceProfiles().Len())
}
func TestResolveProfilesReferencesWithKeyRef(t *testing.T) {
profiles := NewProfiles()
dict := profiles.Dictionary()
dict.StringTable().Append("") // index 0
dict.StringTable().Append("test-key")
dict.StringTable().Append("test-value")
rp := profiles.ResourceProfiles().AppendEmpty()
attrs := rp.Resource().Attributes()
// Manually create a KeyValue with key_ref
mapOrig := internal.GetMapOrig(internal.MapWrapper(attrs))
*mapOrig = append(*mapOrig, internal.KeyValue{
KeyStrindex: 1, // references "test-key"
Value: internal.AnyValue{
Value: &internal.AnyValue_StringValueStrindex{
StringValueStrindex: 2, // references "test-value"
},
},
})
resolveProfilesReferences(profiles)
// Verify key_ref was resolved
kv := &(*mapOrig)[0]
assert.Equal(t, "test-key", kv.Key)
// Verify string_value_ref was resolved
strVal, ok := kv.Value.Value.(*internal.AnyValue_StringValue)
assert.True(t, ok)
assert.Equal(t, "test-value", strVal.StringValue)
}
func TestResolveProfilesReferencesInvalidIndices(t *testing.T) {
profiles := NewProfiles()
dict := profiles.Dictionary()
dict.StringTable().Append("") // index 0
dict.StringTable().Append("valid")
rp := profiles.ResourceProfiles().AppendEmpty()
attrs := rp.Resource().Attributes()
mapOrig := internal.GetMapOrig(internal.MapWrapper(attrs))
*mapOrig = append(*mapOrig, internal.KeyValue{
Key: "fallback-key",
KeyStrindex: 999, // invalid index
Value: internal.AnyValue{
Value: &internal.AnyValue_StringValueStrindex{
StringValueStrindex: 999, // invalid index
},
},
})
resolveProfilesReferences(profiles)
// Key should remain unchanged since ref is invalid
kv := &(*mapOrig)[0]
assert.Equal(t, "fallback-key", kv.Key)
// Value should remain as StringValueStrindex since index is invalid
_, ok := kv.Value.Value.(*internal.AnyValue_StringValueStrindex)
assert.True(t, ok)
}
func TestResolveAnyValueReferenceWithPooling(t *testing.T) {
// Test with pooling enabled
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), true))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
profiles := NewProfiles()
dict := profiles.Dictionary()
dict.StringTable().Append("")
dict.StringTable().Append("pooled-value")
anyVal := &internal.AnyValue{
Value: &internal.AnyValue_StringValueStrindex{
StringValueStrindex: 1,
},
}
resolveAnyValueReference(dict, anyVal)
strVal, ok := anyVal.Value.(*internal.AnyValue_StringValue)
assert.True(t, ok)
assert.Equal(t, "pooled-value", strVal.StringValue)
}
func TestResolveAnyValueReferenceNestedKvList(t *testing.T) {
profiles := NewProfiles()
dict := profiles.Dictionary()
dict.StringTable().Append("")
dict.StringTable().Append("nested-key")
dict.StringTable().Append("nested-value")
kvList := &internal.KeyValueList{
Values: []internal.KeyValue{
{
KeyStrindex: 1, // references "nested-key"
Value: internal.AnyValue{
Value: &internal.AnyValue_StringValueStrindex{
StringValueStrindex: 2, // references "nested-value"
},
},
},
},
}
anyVal := &internal.AnyValue{
Value: &internal.AnyValue_KvlistValue{
KvlistValue: kvList,
},
}
resolveAnyValueReference(dict, anyVal)
// Verify nested key_ref was resolved
assert.Equal(t, "nested-key", kvList.Values[0].Key)
// Verify nested value was resolved
strVal, ok := kvList.Values[0].Value.Value.(*internal.AnyValue_StringValue)
assert.True(t, ok)
assert.Equal(t, "nested-value", strVal.StringValue)
}
func TestResolveAnyValueReferenceNestedArray(t *testing.T) {
profiles := NewProfiles()
dict := profiles.Dictionary()
dict.StringTable().Append("")
dict.StringTable().Append("array-item-1")
dict.StringTable().Append("array-item-2")
arrVal := &internal.ArrayValue{
Values: []internal.AnyValue{
{
Value: &internal.AnyValue_StringValueStrindex{
StringValueStrindex: 1,
},
},
{
Value: &internal.AnyValue_StringValueStrindex{
StringValueStrindex: 2,
},
},
},
}
anyVal := &internal.AnyValue{
Value: &internal.AnyValue_ArrayValue{
ArrayValue: arrVal,
},
}
resolveAnyValueReference(dict, anyVal)
// Verify both array items were resolved
strVal1, ok := arrVal.Values[0].Value.(*internal.AnyValue_StringValue)
assert.True(t, ok)
assert.Equal(t, "array-item-1", strVal1.StringValue)
strVal2, ok := arrVal.Values[1].Value.(*internal.AnyValue_StringValue)
assert.True(t, ok)
assert.Equal(t, "array-item-2", strVal2.StringValue)
}
func TestConvertProfilesToReferencesEmpty(t *testing.T) {
profiles := NewProfiles()
dict := profiles.Dictionary()
dict.StringTable().Append("")
convertProfilesToReferences(profiles)
// Should only have the initial empty string
assert.Equal(t, 1, dict.StringTable().Len())
}
func TestConvertProfilesToReferencesDeduplication(t *testing.T) {
profiles := NewProfiles()
dict := profiles.Dictionary()
dict.StringTable().Append("")
rp := profiles.ResourceProfiles().AppendEmpty()
rp.Resource().Attributes().PutStr("key1", "duplicated-value")
rp.Resource().Attributes().PutStr("key2", "duplicated-value")
rp.Resource().Attributes().PutStr("key3", "unique-value")
convertProfilesToReferences(profiles)
// Should have: "", "key1", "duplicated-value", "key2", "key3", "unique-value"
// But key1, key2, key3 might share indices if they're also deduplicated
// At minimum: "", "key1", "duplicated-value", "key2", "key3", "unique-value" = 6
assert.GreaterOrEqual(t, dict.StringTable().Len(), 5)
// Verify references were created
mapOrig := internal.GetMapOrig(internal.MapWrapper(rp.Resource().Attributes()))
for i := 0; i < len(*mapOrig); i++ {
kv := &(*mapOrig)[i]
assert.NotEqual(t, int32(0), kv.KeyStrindex, "Key should have a reference")
// Values should be converted to StringValueStrindex
_, ok := kv.Value.Value.(*internal.AnyValue_StringValueStrindex)
assert.True(t, ok, "Value should be converted to StringValueStrindex")
}
}
func TestConvertAnyValueToReferenceWithPooling(t *testing.T) {
prevPooling := metadata.PdataUseProtoPoolingFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), true))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.PdataUseProtoPoolingFeatureGate.ID(), prevPooling))
}()
stringIndex := make(map[string]int32)
stringIndex["test-value"] = 5
getStringIndex := func(s string) int32 {
if idx, ok := stringIndex[s]; ok {
return idx
}
idx := int32(len(stringIndex))
stringIndex[s] = idx
return idx
}
anyVal := &internal.AnyValue{
Value: &internal.AnyValue_StringValue{
StringValue: "test-value",
},
}
convertAnyValueToReference(getStringIndex, anyVal)
refVal, ok := anyVal.Value.(*internal.AnyValue_StringValueStrindex)
assert.True(t, ok)
assert.Equal(t, int32(5), refVal.StringValueStrindex)
}
func TestConvertAnyValueToReferenceEmptyString(t *testing.T) {
stringIndex := make(map[string]int32)
stringIndex[""] = 0
getStringIndex := func(s string) int32 {
if idx, ok := stringIndex[s]; ok {
return idx
}
idx := int32(len(stringIndex))
stringIndex[s] = idx
return idx
}
anyVal := &internal.AnyValue{
Value: &internal.AnyValue_StringValue{
StringValue: "", // empty string should not be converted
},
}
convertAnyValueToReference(getStringIndex, anyVal)
// Empty string should remain as StringValue, not converted to ref
_, ok := anyVal.Value.(*internal.AnyValue_StringValue)
assert.True(t, ok)
}
func TestConvertAnyValueToReferenceNestedKvList(t *testing.T) {
stringIndex := make(map[string]int32)
stringIndex[""] = 0
counter := int32(1)
getStringIndex := func(s string) int32 {
if idx, ok := stringIndex[s]; ok {
return idx
}
idx := counter
counter++
stringIndex[s] = idx
return idx
}
kvList := &internal.KeyValueList{
Values: []internal.KeyValue{
{
Key: "nested-key",
Value: internal.AnyValue{
Value: &internal.AnyValue_StringValue{
StringValue: "nested-value",
},
},
},
},
}
anyVal := &internal.AnyValue{
Value: &internal.AnyValue_KvlistValue{
KvlistValue: kvList,
},
}
convertAnyValueToReference(getStringIndex, anyVal)
// Verify nested key was converted
assert.NotEqual(t, int32(0), kvList.Values[0].KeyStrindex)
// Verify nested value was converted
_, ok := kvList.Values[0].Value.Value.(*internal.AnyValue_StringValueStrindex)
assert.True(t, ok)
}
func TestConvertAnyValueToReferenceNestedArray(t *testing.T) {
stringIndex := make(map[string]int32)
counter := int32(0)
getStringIndex := func(s string) int32 {
if idx, ok := stringIndex[s]; ok {
return idx
}
idx := counter
counter++
stringIndex[s] = idx
return idx
}
arrVal := &internal.ArrayValue{
Values: []internal.AnyValue{
{
Value: &internal.AnyValue_StringValue{
StringValue: "array-item",
},
},
},
}
anyVal := &internal.AnyValue{
Value: &internal.AnyValue_ArrayValue{
ArrayValue: arrVal,
},
}
convertAnyValueToReference(getStringIndex, anyVal)
// Verify array item was converted
_, ok := arrVal.Values[0].Value.(*internal.AnyValue_StringValueStrindex)
assert.True(t, ok)
}
func TestConvertMapToReferencesEmptyKey(t *testing.T) {
profiles := NewProfiles()
rp := profiles.ResourceProfiles().AppendEmpty()
attrs := rp.Resource().Attributes()
// Manually add a KeyValue with empty key
mapOrig := internal.GetMapOrig(internal.MapWrapper(attrs))
*mapOrig = append(*mapOrig, internal.KeyValue{
Key: "", // empty key should not be converted
Value: internal.AnyValue{
Value: &internal.AnyValue_StringValue{
StringValue: "value",
},
},
})
getStringIndex := func(_ string) int32 {
return 1
}
convertKeyValueToReferences(getStringIndex, mapKeyValues(attrs))
// Empty key should not have KeyStrindex set
kv := &(*mapOrig)[0]
assert.Equal(t, int32(0), kv.KeyStrindex)
}
func TestConvertMapToReferencesExistingKeyRef(t *testing.T) {
profiles := NewProfiles()
rp := profiles.ResourceProfiles().AppendEmpty()
attrs := rp.Resource().Attributes()
// Manually add a KeyValue with existing KeyStrindex
mapOrig := internal.GetMapOrig(internal.MapWrapper(attrs))
*mapOrig = append(*mapOrig, internal.KeyValue{
Key: "test-key",
KeyStrindex: 5, // already has a ref
Value: internal.AnyValue{
Value: &internal.AnyValue_StringValue{
StringValue: "value",
},
},
})
getStringIndex := func(_ string) int32 {
return 99
}
convertKeyValueToReferences(getStringIndex, mapKeyValues(attrs))
// KeyStrindex should remain unchanged
kv := &(*mapOrig)[0]
assert.Equal(t, int32(5), kv.KeyStrindex)
}
func TestResolveAnyValueReferenceNonStringTypes(t *testing.T) {
profiles := NewProfiles()
dict := profiles.Dictionary()
dict.StringTable().Append("")
// Test with int value (should not be affected)
anyVal := &internal.AnyValue{
Value: &internal.AnyValue_IntValue{
IntValue: 42,
},
}
resolveAnyValueReference(dict, anyVal)
// Should remain as IntValue
intVal, ok := anyVal.Value.(*internal.AnyValue_IntValue)
assert.True(t, ok)
assert.Equal(t, int64(42), intVal.IntValue)
}
func TestConvertMapToReferencesClearsKey(t *testing.T) {
profiles := NewProfiles()
rp := profiles.ResourceProfiles().AppendEmpty()
attrs := rp.Resource().Attributes()
mapOrig := internal.GetMapOrig(internal.MapWrapper(attrs))
*mapOrig = append(*mapOrig, internal.KeyValue{
Key: "my-key",
Value: internal.AnyValue{
Value: &internal.AnyValue_StringValue{
StringValue: "my-value",
},
},
})
getStringIndex := func(s string) int32 {
if s == "my-key" {
return 1
}
return 2
}
convertKeyValueToReferences(getStringIndex, mapKeyValues(attrs))
kv := &(*mapOrig)[0]
// key_ref should be set
assert.Equal(t, int32(1), kv.KeyStrindex)
// key MUST NOT be set when key_ref is used (per proto spec)
assert.Empty(t, kv.Key, "Key must be cleared when KeyStrindex is set")
}
func TestConvertAnyValueToReferenceNestedKvListClearsKey(t *testing.T) {
stringIndex := make(map[string]int32)
counter := int32(1)
getStringIndex := func(s string) int32 {
if idx, ok := stringIndex[s]; ok {
return idx
}
idx := counter
counter++
stringIndex[s] = idx
return idx
}
kvList := &internal.KeyValueList{
Values: []internal.KeyValue{
{
Key: "nested-key",
Value: internal.AnyValue{
Value: &internal.AnyValue_StringValue{
StringValue: "nested-value",
},
},
},
},
}
anyVal := &internal.AnyValue{
Value: &internal.AnyValue_KvlistValue{
KvlistValue: kvList,
},
}
convertAnyValueToReference(getStringIndex, anyVal)
// key_ref should be set
assert.NotEqual(t, int32(0), kvList.Values[0].KeyStrindex)
// key MUST NOT be set when key_ref is used (per proto spec)
assert.Empty(t, kvList.Values[0].Key, "Key must be cleared when KeyStrindex is set in nested kvlist")
}
func TestConvertAnyValueToReferenceNonStringTypes(t *testing.T) {
getStringIndex := func(_ string) int32 {
return 0
}
// Test with bool value (should not be affected)
anyVal := &internal.AnyValue{
Value: &internal.AnyValue_BoolValue{
BoolValue: true,
},
}
convertAnyValueToReference(getStringIndex, anyVal)
// Should remain as BoolValue
boolVal, ok := anyVal.Value.(*internal.AnyValue_BoolValue)
assert.True(t, ok)
assert.True(t, boolVal.BoolValue)
}
================================================
FILE: pdata/pprofile/encoding.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile // import "go.opentelemetry.io/collector/pdata/pprofile"
// MarshalSizer is the interface that groups the basic Marshal and Size methods
type MarshalSizer interface {
Marshaler
Sizer
}
// Marshaler marshals pprofile.Profiles into bytes.
type Marshaler interface {
// MarshalProfiles the given pprofile.Profiles into bytes.
// If the error is not nil, the returned bytes slice cannot be used.
MarshalProfiles(td Profiles) ([]byte, error)
}
// Unmarshaler unmarshalls bytes into pprofile.Profiles.
type Unmarshaler interface {
// UnmarshalProfiles the given bytes into pprofile.Profiles.
// If the error is not nil, the returned pprofile.Profiles cannot be used.
UnmarshalProfiles(buf []byte) (Profiles, error)
}
// Sizer is an optional interface implemented by the Marshaler,
// that calculates the size of a marshaled Profiles.
type Sizer interface {
// ProfilesSize returns the size in bytes of a marshaled Profiles.
ProfilesSize(td Profiles) int
}
================================================
FILE: pdata/pprofile/function.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile // import "go.opentelemetry.io/collector/pdata/pprofile"
import "fmt"
// Equal checks equality with another Function
func (fn Function) Equal(val Function) bool {
return fn.NameStrindex() == val.NameStrindex() &&
fn.SystemNameStrindex() == val.SystemNameStrindex() &&
fn.FilenameStrindex() == val.FilenameStrindex() &&
fn.StartLine() == val.StartLine()
}
// switchDictionary updates the Function, switching its indices from one
// dictionary to another.
func (fn Function) switchDictionary(src, dst ProfilesDictionary) error {
if fn.NameStrindex() > 0 {
if src.StringTable().Len() <= int(fn.NameStrindex()) {
return fmt.Errorf("invalid name index %d", fn.NameStrindex())
}
idx, err := SetString(dst.StringTable(), src.StringTable().At(int(fn.NameStrindex())))
if err != nil {
return fmt.Errorf("couldn't set name: %w", err)
}
fn.SetNameStrindex(idx)
}
if fn.SystemNameStrindex() > 0 {
if src.StringTable().Len() <= int(fn.SystemNameStrindex()) {
return fmt.Errorf("invalid system name index %d", fn.SystemNameStrindex())
}
idx, err := SetString(dst.StringTable(), src.StringTable().At(int(fn.SystemNameStrindex())))
if err != nil {
return fmt.Errorf("couldn't set system name: %w", err)
}
fn.SetSystemNameStrindex(idx)
}
if fn.FilenameStrindex() > 0 {
if src.StringTable().Len() <= int(fn.FilenameStrindex()) {
return fmt.Errorf("invalid filename index %d", fn.FilenameStrindex())
}
idx, err := SetString(dst.StringTable(), src.StringTable().At(int(fn.FilenameStrindex())))
if err != nil {
return fmt.Errorf("couldn't set filename: %w", err)
}
fn.SetFilenameStrindex(idx)
}
return nil
}
================================================
FILE: pdata/pprofile/function_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/internal/testutil"
)
func TestFunctionEqual(t *testing.T) {
for _, tt := range []struct {
name string
orig Function
dest Function
want bool
}{
{
name: "empty functions",
orig: NewFunction(),
dest: NewFunction(),
want: true,
},
{
name: "non-empty identical functions",
orig: buildFunction(1, 2, 3, 4),
dest: buildFunction(1, 2, 3, 4),
want: true,
},
{
name: "with different name",
orig: buildFunction(1, 2, 3, 4),
dest: buildFunction(2, 2, 3, 4),
want: false,
},
{
name: "with different system name",
orig: buildFunction(1, 2, 3, 4),
dest: buildFunction(1, 3, 3, 4),
want: false,
},
{
name: "with different file name",
orig: buildFunction(1, 2, 3, 4),
dest: buildFunction(1, 2, 4, 4),
want: false,
},
{
name: "with different start line",
orig: buildFunction(1, 2, 3, 4),
dest: buildFunction(1, 2, 3, 5),
want: false,
},
} {
t.Run(tt.name, func(t *testing.T) {
if tt.want {
assert.True(t, tt.orig.Equal(tt.dest))
} else {
assert.False(t, tt.orig.Equal(tt.dest))
}
})
}
}
func TestFunctionSwitchDictionary(t *testing.T) {
for _, tt := range []struct {
name string
function Function
src ProfilesDictionary
dst ProfilesDictionary
wantFunction Function
wantDictionary ProfilesDictionary
wantErr error
}{
{
name: "with an empty key value and unit",
function: NewFunction(),
src: NewProfilesDictionary(),
dst: NewProfilesDictionary(),
wantFunction: NewFunction(),
wantDictionary: NewProfilesDictionary(),
},
{
name: "with an existing name",
function: func() Function {
fn := NewFunction()
fn.SetNameStrindex(1)
return fn
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
return d
}(),
dst: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "foo")
return d
}(),
wantFunction: func() Function {
fn := NewFunction()
fn.SetNameStrindex(2)
return fn
}(),
wantDictionary: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "foo", "test")
return d
}(),
},
{
name: "with a name index that does not match anything",
function: func() Function {
fn := NewFunction()
fn.SetNameStrindex(1)
return fn
}(),
src: NewProfilesDictionary(),
dst: NewProfilesDictionary(),
wantFunction: func() Function {
fn := NewFunction()
fn.SetNameStrindex(1)
return fn
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid name index 1"),
},
{
name: "with a name index equal to the source table length (boundary condition)",
function: func() Function {
fn := NewFunction()
fn.SetNameStrindex(2) // Index 2 with length 2 (indices 0,1 are valid)
return fn
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test") // Length 2: indices 0,1 valid
return d
}(),
dst: NewProfilesDictionary(),
wantFunction: func() Function {
fn := NewFunction()
fn.SetNameStrindex(2)
return fn
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid name index 2"),
},
{
name: "with an existing system name",
function: func() Function {
fn := NewFunction()
fn.SetSystemNameStrindex(1)
return fn
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
return d
}(),
dst: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "foo")
return d
}(),
wantFunction: func() Function {
fn := NewFunction()
fn.SetSystemNameStrindex(2)
return fn
}(),
wantDictionary: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "foo", "test")
return d
}(),
},
{
name: "with a system name index that does not match anything",
function: func() Function {
fn := NewFunction()
fn.SetSystemNameStrindex(1)
return fn
}(),
src: NewProfilesDictionary(),
dst: NewProfilesDictionary(),
wantFunction: func() Function {
fn := NewFunction()
fn.SetSystemNameStrindex(1)
return fn
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid system name index 1"),
},
{
name: "with a system name index equal to the source table length (boundary condition)",
function: func() Function {
fn := NewFunction()
fn.SetSystemNameStrindex(2)
return fn
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
return d
}(),
dst: NewProfilesDictionary(),
wantFunction: func() Function {
fn := NewFunction()
fn.SetSystemNameStrindex(2)
return fn
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid system name index 2"),
},
{
name: "with an existing filename",
function: func() Function {
fn := NewFunction()
fn.SetFilenameStrindex(1)
return fn
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
return d
}(),
dst: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "foo")
return d
}(),
wantFunction: func() Function {
fn := NewFunction()
fn.SetFilenameStrindex(2)
return fn
}(),
wantDictionary: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "foo", "test")
return d
}(),
},
{
name: "with a filename index that does not match anything",
function: func() Function {
fn := NewFunction()
fn.SetFilenameStrindex(1)
return fn
}(),
src: NewProfilesDictionary(),
dst: NewProfilesDictionary(),
wantFunction: func() Function {
fn := NewFunction()
fn.SetFilenameStrindex(1)
return fn
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid filename index 1"),
},
{
name: "with a filename index equal to the source table length (boundary condition)",
function: func() Function {
fn := NewFunction()
fn.SetFilenameStrindex(2)
return fn
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
return d
}(),
dst: NewProfilesDictionary(),
wantFunction: func() Function {
fn := NewFunction()
fn.SetFilenameStrindex(2)
return fn
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid filename index 2"),
},
} {
t.Run(tt.name, func(t *testing.T) {
fn := tt.function
dst := tt.dst
err := fn.switchDictionary(tt.src, dst)
if tt.wantErr == nil {
require.NoError(t, err)
} else {
require.Equal(t, tt.wantErr, err)
}
assert.Equal(t, tt.wantFunction, fn)
assert.Equal(t, tt.wantDictionary, dst)
})
}
}
func BenchmarkFunctionSwitchDictionary(b *testing.B) {
testutil.SkipMemoryBench(b)
fn := NewFunction()
fn.SetNameStrindex(1)
fn.SetSystemNameStrindex(2)
src := NewProfilesDictionary()
src.StringTable().Append("", "test", "foo")
b.ReportAllocs()
for b.Loop() {
b.StopTimer()
dst := NewProfilesDictionary()
dst.StringTable().Append("", "foo")
b.StartTimer()
_ = fn.switchDictionary(src, dst)
}
}
func buildFunction(name, sName, fileName int32, startLine int64) Function {
f := NewFunction()
f.SetNameStrindex(name)
f.SetSystemNameStrindex(sName)
f.SetFilenameStrindex(fileName)
f.SetStartLine(startLine)
return f
}
================================================
FILE: pdata/pprofile/functions.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile // import "go.opentelemetry.io/collector/pdata/pprofile"
import (
"errors"
"math"
)
var errTooManyFunctionTableEntries = errors.New("too many entries in FunctionTable")
// SetFunction updates a FunctionTable, adding or providing a value and returns
// its index.
func SetFunction(table FunctionSlice, fn Function) (int32, error) {
for j, m := range table.All() {
if m.Equal(fn) {
if j > math.MaxInt32 {
return 0, errTooManyFunctionTableEntries
}
return int32(j), nil
}
}
if table.Len() >= math.MaxInt32 {
return 0, errTooManyFunctionTableEntries
}
fn.CopyTo(table.AppendEmpty())
return int32(table.Len() - 1), nil
}
================================================
FILE: pdata/pprofile/functions_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/internal/testutil"
)
func TestSetFunction(t *testing.T) {
table := NewFunctionSlice()
f := NewFunction()
f.SetNameStrindex(1)
f2 := NewFunction()
f2.SetNameStrindex(2)
// Put a first function
idx, err := SetFunction(table, f)
require.NoError(t, err)
assert.Equal(t, 1, table.Len())
assert.Equal(t, int32(0), idx)
// Put the same function
// This should be a no-op.
idx, err = SetFunction(table, f)
require.NoError(t, err)
assert.Equal(t, 1, table.Len())
assert.Equal(t, int32(0), idx)
// Set a new function
// This sets the index and adds to the table.
idx, err = SetFunction(table, f2)
require.NoError(t, err)
assert.Equal(t, 2, table.Len())
assert.Equal(t, int32(table.Len()-1), idx)
// Set an existing function
idx, err = SetFunction(table, f)
require.NoError(t, err)
assert.Equal(t, 2, table.Len())
assert.Equal(t, int32(0), idx)
// Set another existing function
idx, err = SetFunction(table, f2)
require.NoError(t, err)
assert.Equal(t, 2, table.Len())
assert.Equal(t, int32(table.Len()-1), idx)
}
func BenchmarkSetFunction(b *testing.B) {
testutil.SkipMemoryBench(b)
for _, bb := range []struct {
name string
fn Function
runBefore func(*testing.B, FunctionSlice)
}{
{
name: "with a new function",
fn: NewFunction(),
},
{
name: "with an existing function",
fn: func() Function {
f := NewFunction()
f.SetNameStrindex(1)
return f
}(),
runBefore: func(_ *testing.B, table FunctionSlice) {
f := table.AppendEmpty()
f.SetNameStrindex(1)
},
},
{
name: "with a duplicate function",
fn: NewFunction(),
runBefore: func(b *testing.B, table FunctionSlice) {
_, err := SetFunction(table, NewFunction())
require.NoError(b, err)
},
},
{
name: "with a hundred functions to loop through",
fn: func() Function {
f := NewFunction()
f.SetNameStrindex(1)
return f
}(),
runBefore: func(_ *testing.B, table FunctionSlice) {
for i := range 100 {
f := table.AppendEmpty()
f.SetNameStrindex(int32(i))
}
},
},
} {
b.Run(bb.name, func(b *testing.B) {
table := NewFunctionSlice()
if bb.runBefore != nil {
bb.runBefore(b, table)
}
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
_, _ = SetFunction(table, bb.fn)
}
})
}
}
================================================
FILE: pdata/pprofile/fuzz_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile // import "go.opentelemetry.io/collector/pdata/pprofile"
import (
"bytes"
"testing"
"github.com/stretchr/testify/require"
)
var unexpectedBytes = "expected the same bytes from unmarshaling and marshaling."
func FuzzUnmarshalProfiles(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
u1 := &JSONUnmarshaler{}
ld1, err := u1.UnmarshalProfiles(data)
if err != nil {
return
}
m1 := &JSONMarshaler{}
b1, err := m1.MarshalProfiles(ld1)
require.NoError(t, err, "failed to marshal valid struct")
u2 := &JSONUnmarshaler{}
ld2, err := u2.UnmarshalProfiles(b1)
require.NoError(t, err, "failed to unmarshal valid bytes")
m2 := &JSONMarshaler{}
b2, err := m2.MarshalProfiles(ld2)
require.NoError(t, err, "failed to marshal valid struct")
require.True(t, bytes.Equal(b1, b2), "%s. \nexpected %d but got %d\n", unexpectedBytes, b1, b2)
})
}
================================================
FILE: pdata/pprofile/generated_function.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// Function describes a function, including its human-readable name, system name, source file, and starting line number in the source.
//
// This is a reference type, if passed by value and callee modifies it the
// caller will see the modification.
//
// Must use NewFunction function to create new instances.
// Important: zero-initialized instance is not valid for use.
type Function struct {
orig *internal.Function
state *internal.State
}
func newFunction(orig *internal.Function, state *internal.State) Function {
return Function{orig: orig, state: state}
}
// NewFunction creates a new empty Function.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewFunction() Function {
return newFunction(internal.NewFunction(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms Function) MoveTo(dest Function) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteFunction(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// NameStrindex returns the namestrindex associated with this Function.
func (ms Function) NameStrindex() int32 {
return ms.orig.NameStrindex
}
// SetNameStrindex replaces the namestrindex associated with this Function.
func (ms Function) SetNameStrindex(v int32) {
ms.state.AssertMutable()
ms.orig.NameStrindex = v
}
// SystemNameStrindex returns the systemnamestrindex associated with this Function.
func (ms Function) SystemNameStrindex() int32 {
return ms.orig.SystemNameStrindex
}
// SetSystemNameStrindex replaces the systemnamestrindex associated with this Function.
func (ms Function) SetSystemNameStrindex(v int32) {
ms.state.AssertMutable()
ms.orig.SystemNameStrindex = v
}
// FilenameStrindex returns the filenamestrindex associated with this Function.
func (ms Function) FilenameStrindex() int32 {
return ms.orig.FilenameStrindex
}
// SetFilenameStrindex replaces the filenamestrindex associated with this Function.
func (ms Function) SetFilenameStrindex(v int32) {
ms.state.AssertMutable()
ms.orig.FilenameStrindex = v
}
// StartLine returns the startline associated with this Function.
func (ms Function) StartLine() int64 {
return ms.orig.StartLine
}
// SetStartLine replaces the startline associated with this Function.
func (ms Function) SetStartLine(v int64) {
ms.state.AssertMutable()
ms.orig.StartLine = v
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms Function) CopyTo(dest Function) {
dest.state.AssertMutable()
internal.CopyFunction(dest.orig, ms.orig)
}
================================================
FILE: pdata/pprofile/generated_function_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestFunction_MoveTo(t *testing.T) {
ms := generateTestFunction()
dest := NewFunction()
ms.MoveTo(dest)
assert.Equal(t, NewFunction(), ms)
assert.Equal(t, generateTestFunction(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestFunction(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newFunction(internal.NewFunction(), sharedState)) })
assert.Panics(t, func() { newFunction(internal.NewFunction(), sharedState).MoveTo(dest) })
}
func TestFunction_CopyTo(t *testing.T) {
ms := NewFunction()
orig := NewFunction()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestFunction()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newFunction(internal.NewFunction(), sharedState)) })
}
func TestFunction_NameStrindex(t *testing.T) {
ms := NewFunction()
assert.Equal(t, int32(0), ms.NameStrindex())
ms.SetNameStrindex(int32(13))
assert.Equal(t, int32(13), ms.NameStrindex())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newFunction(internal.NewFunction(), sharedState).SetNameStrindex(int32(13)) })
}
func TestFunction_SystemNameStrindex(t *testing.T) {
ms := NewFunction()
assert.Equal(t, int32(0), ms.SystemNameStrindex())
ms.SetSystemNameStrindex(int32(13))
assert.Equal(t, int32(13), ms.SystemNameStrindex())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newFunction(internal.NewFunction(), sharedState).SetSystemNameStrindex(int32(13)) })
}
func TestFunction_FilenameStrindex(t *testing.T) {
ms := NewFunction()
assert.Equal(t, int32(0), ms.FilenameStrindex())
ms.SetFilenameStrindex(int32(13))
assert.Equal(t, int32(13), ms.FilenameStrindex())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newFunction(internal.NewFunction(), sharedState).SetFilenameStrindex(int32(13)) })
}
func TestFunction_StartLine(t *testing.T) {
ms := NewFunction()
assert.Equal(t, int64(0), ms.StartLine())
ms.SetStartLine(int64(13))
assert.Equal(t, int64(13), ms.StartLine())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newFunction(internal.NewFunction(), sharedState).SetStartLine(int64(13)) })
}
func generateTestFunction() Function {
return newFunction(internal.GenTestFunction(), internal.NewState())
}
================================================
FILE: pdata/pprofile/generated_functionslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"iter"
"sort"
"go.opentelemetry.io/collector/pdata/internal"
)
// FunctionSlice logically represents a slice of Function.
//
// This is a reference type. If passed by value and callee modifies it, the
// caller will see the modification.
//
// Must use NewFunctionSlice function to create new instances.
// Important: zero-initialized instance is not valid for use.
type FunctionSlice struct {
orig *[]*internal.Function
state *internal.State
}
func newFunctionSlice(orig *[]*internal.Function, state *internal.State) FunctionSlice {
return FunctionSlice{orig: orig, state: state}
}
// NewFunctionSlice creates a FunctionSliceWrapper with 0 elements.
// Can use "EnsureCapacity" to initialize with a given capacity.
func NewFunctionSlice() FunctionSlice {
orig := []*internal.Function(nil)
return newFunctionSlice(&orig, internal.NewState())
}
// Len returns the number of elements in the slice.
//
// Returns "0" for a newly instance created with "NewFunctionSlice()".
func (es FunctionSlice) 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 FunctionSlice) At(i int) Function {
return newFunction((*es.orig)[i], es.state)
}
// All returns an iterator over index-value pairs in the slice.
//
// for i, v := range es.All() {
// ... // Do something with index-value pair
// }
func (es FunctionSlice) All() iter.Seq2[int, Function] {
return func(yield func(int, Function) bool) {
for i := 0; i < es.Len(); i++ {
if !yield(i, es.At(i)) {
return
}
}
}
}
// EnsureCapacity is an operation that ensures the slice has at least the specified capacity.
// 1. If the newCap <= cap then no change in capacity.
// 2. If the newCap > cap then the slice capacity will be expanded to equal newCap.
//
// Here is how a new FunctionSlice can be initialized:
//
// es := NewFunctionSlice()
// es.EnsureCapacity(4)
// for i := 0; i < 4; i++ {
// e := es.AppendEmpty()
// // Here should set all the values for e.
// }
func (es FunctionSlice) EnsureCapacity(newCap int) {
es.state.AssertMutable()
oldCap := cap(*es.orig)
if newCap <= oldCap {
return
}
newOrig := make([]*internal.Function, len(*es.orig), newCap)
copy(newOrig, *es.orig)
*es.orig = newOrig
}
// AppendEmpty will append to the end of the slice an empty Function.
// It returns the newly added Function.
func (es FunctionSlice) AppendEmpty() Function {
es.state.AssertMutable()
*es.orig = append(*es.orig, internal.NewFunction())
return es.At(es.Len() - 1)
}
// MoveAndAppendTo moves all elements from the current slice and appends them to the dest.
// The current slice will be cleared.
func (es FunctionSlice) MoveAndAppendTo(dest FunctionSlice) {
es.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if es.orig == dest.orig {
return
}
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
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (es FunctionSlice) RemoveIf(f func(Function) bool) {
es.state.AssertMutable()
newLen := 0
for i := 0; i < len(*es.orig); i++ {
if f(es.At(i)) {
internal.DeleteFunction((*es.orig)[i], true)
(*es.orig)[i] = nil
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*es.orig)[newLen] = (*es.orig)[i]
// Cannot delete here since we just move the data(or pointer to data) to a different position in the slice.
(*es.orig)[i] = nil
newLen++
}
*es.orig = (*es.orig)[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (es FunctionSlice) CopyTo(dest FunctionSlice) {
dest.state.AssertMutable()
if es.orig == dest.orig {
return
}
*dest.orig = internal.CopyFunctionPtrSlice(*dest.orig, *es.orig)
}
// Sort sorts the Function elements within FunctionSlice given the
// provided less function so that two instances of FunctionSlice
// can be compared.
func (es FunctionSlice) Sort(less func(a, b Function) bool) {
es.state.AssertMutable()
sort.SliceStable(*es.orig, func(i, j int) bool { return less(es.At(i), es.At(j)) })
}
================================================
FILE: pdata/pprofile/generated_functionslice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestFunctionSlice(t *testing.T) {
es := NewFunctionSlice()
assert.Equal(t, 0, es.Len())
es = newFunctionSlice(&[]*internal.Function{}, internal.NewState())
assert.Equal(t, 0, es.Len())
emptyVal := NewFunction()
testVal := generateTestFunction()
for i := 0; i < 7; i++ {
es.AppendEmpty()
assert.Equal(t, emptyVal, es.At(i))
(*es.orig)[i] = internal.GenTestFunction()
assert.Equal(t, testVal, es.At(i))
}
assert.Equal(t, 7, es.Len())
}
func TestFunctionSliceReadOnly(t *testing.T) {
sharedState := internal.NewState()
sharedState.MarkReadOnly()
es := newFunctionSlice(&[]*internal.Function{}, sharedState)
assert.Equal(t, 0, es.Len())
assert.Panics(t, func() { es.AppendEmpty() })
assert.Panics(t, func() { es.EnsureCapacity(2) })
es2 := NewFunctionSlice()
es.CopyTo(es2)
assert.Panics(t, func() { es2.CopyTo(es) })
assert.Panics(t, func() { es.MoveAndAppendTo(es2) })
assert.Panics(t, func() { es2.MoveAndAppendTo(es) })
}
func TestFunctionSlice_CopyTo(t *testing.T) {
dest := NewFunctionSlice()
src := generateTestFunctionSlice()
src.CopyTo(dest)
assert.Equal(t, generateTestFunctionSlice(), dest)
dest.CopyTo(dest)
assert.Equal(t, generateTestFunctionSlice(), dest)
}
func TestFunctionSlice_EnsureCapacity(t *testing.T) {
es := generateTestFunctionSlice()
// Test ensure smaller capacity.
const ensureSmallLen = 4
es.EnsureCapacity(ensureSmallLen)
assert.Less(t, ensureSmallLen, es.Len())
assert.Equal(t, es.Len(), cap(*es.orig))
assert.Equal(t, generateTestFunctionSlice(), es)
// Test ensure larger capacity
const ensureLargeLen = 9
es.EnsureCapacity(ensureLargeLen)
assert.Less(t, generateTestFunctionSlice().Len(), ensureLargeLen)
assert.Equal(t, ensureLargeLen, cap(*es.orig))
assert.Equal(t, generateTestFunctionSlice(), es)
}
func TestFunctionSlice_MoveAndAppendTo(t *testing.T) {
// Test MoveAndAppendTo to empty
expectedSlice := generateTestFunctionSlice()
dest := NewFunctionSlice()
src := generateTestFunctionSlice()
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestFunctionSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo empty slice
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestFunctionSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo not empty slice
generateTestFunctionSlice().MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
dest.MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
}
func TestFunctionSlice_RemoveIf(t *testing.T) {
// Test RemoveIf on empty slice
emptySlice := NewFunctionSlice()
emptySlice.RemoveIf(func(el Function) bool {
t.Fail()
return false
})
// Test RemoveIf
filtered := generateTestFunctionSlice()
pos := 0
filtered.RemoveIf(func(el Function) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, 2, filtered.Len())
}
func TestFunctionSlice_RemoveIfAll(t *testing.T) {
got := generateTestFunctionSlice()
got.RemoveIf(func(el Function) bool {
return true
})
assert.Equal(t, 0, got.Len())
}
func TestFunctionSliceAll(t *testing.T) {
ms := generateTestFunctionSlice()
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestFunctionSlice_Sort(t *testing.T) {
es := generateTestFunctionSlice()
es.Sort(func(a, b Function) bool {
return uintptr(unsafe.Pointer(a.orig)) < uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Less(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
es.Sort(func(a, b Function) bool {
return uintptr(unsafe.Pointer(a.orig)) > uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Greater(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
}
func generateTestFunctionSlice() FunctionSlice {
ms := NewFunctionSlice()
*ms.orig = internal.GenTestFunctionPtrSlice()
return ms
}
================================================
FILE: pdata/pprofile/generated_keyvalueandunit.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// KeyValueAndUnit represents a custom 'dictionary native'
// style of encoding attributes which is more convenient
// for profiles than opentelemetry.proto.common.v1.KeyValue.
//
// This is a reference type, if passed by value and callee modifies it the
// caller will see the modification.
//
// Must use NewKeyValueAndUnit function to create new instances.
// Important: zero-initialized instance is not valid for use.
type KeyValueAndUnit struct {
orig *internal.KeyValueAndUnit
state *internal.State
}
func newKeyValueAndUnit(orig *internal.KeyValueAndUnit, state *internal.State) KeyValueAndUnit {
return KeyValueAndUnit{orig: orig, state: state}
}
// NewKeyValueAndUnit creates a new empty KeyValueAndUnit.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewKeyValueAndUnit() KeyValueAndUnit {
return newKeyValueAndUnit(internal.NewKeyValueAndUnit(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms KeyValueAndUnit) MoveTo(dest KeyValueAndUnit) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteKeyValueAndUnit(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// KeyStrindex returns the keystrindex associated with this KeyValueAndUnit.
func (ms KeyValueAndUnit) KeyStrindex() int32 {
return ms.orig.KeyStrindex
}
// SetKeyStrindex replaces the keystrindex associated with this KeyValueAndUnit.
func (ms KeyValueAndUnit) SetKeyStrindex(v int32) {
ms.state.AssertMutable()
ms.orig.KeyStrindex = v
}
// Value returns the value associated with this KeyValueAndUnit.
func (ms KeyValueAndUnit) Value() pcommon.Value {
return pcommon.Value(internal.NewValueWrapper(&ms.orig.Value, ms.state))
}
// UnitStrindex returns the unitstrindex associated with this KeyValueAndUnit.
func (ms KeyValueAndUnit) UnitStrindex() int32 {
return ms.orig.UnitStrindex
}
// SetUnitStrindex replaces the unitstrindex associated with this KeyValueAndUnit.
func (ms KeyValueAndUnit) SetUnitStrindex(v int32) {
ms.state.AssertMutable()
ms.orig.UnitStrindex = v
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms KeyValueAndUnit) CopyTo(dest KeyValueAndUnit) {
dest.state.AssertMutable()
internal.CopyKeyValueAndUnit(dest.orig, ms.orig)
}
================================================
FILE: pdata/pprofile/generated_keyvalueandunit_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestKeyValueAndUnit_MoveTo(t *testing.T) {
ms := generateTestKeyValueAndUnit()
dest := NewKeyValueAndUnit()
ms.MoveTo(dest)
assert.Equal(t, NewKeyValueAndUnit(), ms)
assert.Equal(t, generateTestKeyValueAndUnit(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestKeyValueAndUnit(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newKeyValueAndUnit(internal.NewKeyValueAndUnit(), sharedState)) })
assert.Panics(t, func() { newKeyValueAndUnit(internal.NewKeyValueAndUnit(), sharedState).MoveTo(dest) })
}
func TestKeyValueAndUnit_CopyTo(t *testing.T) {
ms := NewKeyValueAndUnit()
orig := NewKeyValueAndUnit()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestKeyValueAndUnit()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newKeyValueAndUnit(internal.NewKeyValueAndUnit(), sharedState)) })
}
func TestKeyValueAndUnit_KeyStrindex(t *testing.T) {
ms := NewKeyValueAndUnit()
assert.Equal(t, int32(0), ms.KeyStrindex())
ms.SetKeyStrindex(int32(13))
assert.Equal(t, int32(13), ms.KeyStrindex())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newKeyValueAndUnit(internal.NewKeyValueAndUnit(), sharedState).SetKeyStrindex(int32(13)) })
}
func TestKeyValueAndUnit_Value(t *testing.T) {
ms := NewKeyValueAndUnit()
assert.Equal(t, pcommon.NewValueEmpty(), ms.Value())
ms.orig.Value = *internal.GenTestAnyValue()
assert.Equal(t, pcommon.Value(internal.GenTestValueWrapper()), ms.Value())
}
func TestKeyValueAndUnit_UnitStrindex(t *testing.T) {
ms := NewKeyValueAndUnit()
assert.Equal(t, int32(0), ms.UnitStrindex())
ms.SetUnitStrindex(int32(13))
assert.Equal(t, int32(13), ms.UnitStrindex())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newKeyValueAndUnit(internal.NewKeyValueAndUnit(), sharedState).SetUnitStrindex(int32(13)) })
}
func generateTestKeyValueAndUnit() KeyValueAndUnit {
return newKeyValueAndUnit(internal.GenTestKeyValueAndUnit(), internal.NewState())
}
================================================
FILE: pdata/pprofile/generated_keyvalueandunitslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"iter"
"sort"
"go.opentelemetry.io/collector/pdata/internal"
)
// KeyValueAndUnitSlice logically represents a slice of KeyValueAndUnit.
//
// This is a reference type. If passed by value and callee modifies it, the
// caller will see the modification.
//
// Must use NewKeyValueAndUnitSlice function to create new instances.
// Important: zero-initialized instance is not valid for use.
type KeyValueAndUnitSlice struct {
orig *[]*internal.KeyValueAndUnit
state *internal.State
}
func newKeyValueAndUnitSlice(orig *[]*internal.KeyValueAndUnit, state *internal.State) KeyValueAndUnitSlice {
return KeyValueAndUnitSlice{orig: orig, state: state}
}
// NewKeyValueAndUnitSlice creates a KeyValueAndUnitSliceWrapper with 0 elements.
// Can use "EnsureCapacity" to initialize with a given capacity.
func NewKeyValueAndUnitSlice() KeyValueAndUnitSlice {
orig := []*internal.KeyValueAndUnit(nil)
return newKeyValueAndUnitSlice(&orig, internal.NewState())
}
// Len returns the number of elements in the slice.
//
// Returns "0" for a newly instance created with "NewKeyValueAndUnitSlice()".
func (es KeyValueAndUnitSlice) 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 KeyValueAndUnitSlice) At(i int) KeyValueAndUnit {
return newKeyValueAndUnit((*es.orig)[i], es.state)
}
// All returns an iterator over index-value pairs in the slice.
//
// for i, v := range es.All() {
// ... // Do something with index-value pair
// }
func (es KeyValueAndUnitSlice) All() iter.Seq2[int, KeyValueAndUnit] {
return func(yield func(int, KeyValueAndUnit) bool) {
for i := 0; i < es.Len(); i++ {
if !yield(i, es.At(i)) {
return
}
}
}
}
// EnsureCapacity is an operation that ensures the slice has at least the specified capacity.
// 1. If the newCap <= cap then no change in capacity.
// 2. If the newCap > cap then the slice capacity will be expanded to equal newCap.
//
// Here is how a new KeyValueAndUnitSlice can be initialized:
//
// es := NewKeyValueAndUnitSlice()
// es.EnsureCapacity(4)
// for i := 0; i < 4; i++ {
// e := es.AppendEmpty()
// // Here should set all the values for e.
// }
func (es KeyValueAndUnitSlice) EnsureCapacity(newCap int) {
es.state.AssertMutable()
oldCap := cap(*es.orig)
if newCap <= oldCap {
return
}
newOrig := make([]*internal.KeyValueAndUnit, len(*es.orig), newCap)
copy(newOrig, *es.orig)
*es.orig = newOrig
}
// AppendEmpty will append to the end of the slice an empty KeyValueAndUnit.
// It returns the newly added KeyValueAndUnit.
func (es KeyValueAndUnitSlice) AppendEmpty() KeyValueAndUnit {
es.state.AssertMutable()
*es.orig = append(*es.orig, internal.NewKeyValueAndUnit())
return es.At(es.Len() - 1)
}
// MoveAndAppendTo moves all elements from the current slice and appends them to the dest.
// The current slice will be cleared.
func (es KeyValueAndUnitSlice) MoveAndAppendTo(dest KeyValueAndUnitSlice) {
es.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if es.orig == dest.orig {
return
}
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
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (es KeyValueAndUnitSlice) RemoveIf(f func(KeyValueAndUnit) bool) {
es.state.AssertMutable()
newLen := 0
for i := 0; i < len(*es.orig); i++ {
if f(es.At(i)) {
internal.DeleteKeyValueAndUnit((*es.orig)[i], true)
(*es.orig)[i] = nil
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*es.orig)[newLen] = (*es.orig)[i]
// Cannot delete here since we just move the data(or pointer to data) to a different position in the slice.
(*es.orig)[i] = nil
newLen++
}
*es.orig = (*es.orig)[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (es KeyValueAndUnitSlice) CopyTo(dest KeyValueAndUnitSlice) {
dest.state.AssertMutable()
if es.orig == dest.orig {
return
}
*dest.orig = internal.CopyKeyValueAndUnitPtrSlice(*dest.orig, *es.orig)
}
// Sort sorts the KeyValueAndUnit elements within KeyValueAndUnitSlice given the
// provided less function so that two instances of KeyValueAndUnitSlice
// can be compared.
func (es KeyValueAndUnitSlice) Sort(less func(a, b KeyValueAndUnit) bool) {
es.state.AssertMutable()
sort.SliceStable(*es.orig, func(i, j int) bool { return less(es.At(i), es.At(j)) })
}
================================================
FILE: pdata/pprofile/generated_keyvalueandunitslice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestKeyValueAndUnitSlice(t *testing.T) {
es := NewKeyValueAndUnitSlice()
assert.Equal(t, 0, es.Len())
es = newKeyValueAndUnitSlice(&[]*internal.KeyValueAndUnit{}, internal.NewState())
assert.Equal(t, 0, es.Len())
emptyVal := NewKeyValueAndUnit()
testVal := generateTestKeyValueAndUnit()
for i := 0; i < 7; i++ {
es.AppendEmpty()
assert.Equal(t, emptyVal, es.At(i))
(*es.orig)[i] = internal.GenTestKeyValueAndUnit()
assert.Equal(t, testVal, es.At(i))
}
assert.Equal(t, 7, es.Len())
}
func TestKeyValueAndUnitSliceReadOnly(t *testing.T) {
sharedState := internal.NewState()
sharedState.MarkReadOnly()
es := newKeyValueAndUnitSlice(&[]*internal.KeyValueAndUnit{}, sharedState)
assert.Equal(t, 0, es.Len())
assert.Panics(t, func() { es.AppendEmpty() })
assert.Panics(t, func() { es.EnsureCapacity(2) })
es2 := NewKeyValueAndUnitSlice()
es.CopyTo(es2)
assert.Panics(t, func() { es2.CopyTo(es) })
assert.Panics(t, func() { es.MoveAndAppendTo(es2) })
assert.Panics(t, func() { es2.MoveAndAppendTo(es) })
}
func TestKeyValueAndUnitSlice_CopyTo(t *testing.T) {
dest := NewKeyValueAndUnitSlice()
src := generateTestKeyValueAndUnitSlice()
src.CopyTo(dest)
assert.Equal(t, generateTestKeyValueAndUnitSlice(), dest)
dest.CopyTo(dest)
assert.Equal(t, generateTestKeyValueAndUnitSlice(), dest)
}
func TestKeyValueAndUnitSlice_EnsureCapacity(t *testing.T) {
es := generateTestKeyValueAndUnitSlice()
// Test ensure smaller capacity.
const ensureSmallLen = 4
es.EnsureCapacity(ensureSmallLen)
assert.Less(t, ensureSmallLen, es.Len())
assert.Equal(t, es.Len(), cap(*es.orig))
assert.Equal(t, generateTestKeyValueAndUnitSlice(), es)
// Test ensure larger capacity
const ensureLargeLen = 9
es.EnsureCapacity(ensureLargeLen)
assert.Less(t, generateTestKeyValueAndUnitSlice().Len(), ensureLargeLen)
assert.Equal(t, ensureLargeLen, cap(*es.orig))
assert.Equal(t, generateTestKeyValueAndUnitSlice(), es)
}
func TestKeyValueAndUnitSlice_MoveAndAppendTo(t *testing.T) {
// Test MoveAndAppendTo to empty
expectedSlice := generateTestKeyValueAndUnitSlice()
dest := NewKeyValueAndUnitSlice()
src := generateTestKeyValueAndUnitSlice()
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestKeyValueAndUnitSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo empty slice
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestKeyValueAndUnitSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo not empty slice
generateTestKeyValueAndUnitSlice().MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
dest.MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
}
func TestKeyValueAndUnitSlice_RemoveIf(t *testing.T) {
// Test RemoveIf on empty slice
emptySlice := NewKeyValueAndUnitSlice()
emptySlice.RemoveIf(func(el KeyValueAndUnit) bool {
t.Fail()
return false
})
// Test RemoveIf
filtered := generateTestKeyValueAndUnitSlice()
pos := 0
filtered.RemoveIf(func(el KeyValueAndUnit) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, 2, filtered.Len())
}
func TestKeyValueAndUnitSlice_RemoveIfAll(t *testing.T) {
got := generateTestKeyValueAndUnitSlice()
got.RemoveIf(func(el KeyValueAndUnit) bool {
return true
})
assert.Equal(t, 0, got.Len())
}
func TestKeyValueAndUnitSliceAll(t *testing.T) {
ms := generateTestKeyValueAndUnitSlice()
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestKeyValueAndUnitSlice_Sort(t *testing.T) {
es := generateTestKeyValueAndUnitSlice()
es.Sort(func(a, b KeyValueAndUnit) bool {
return uintptr(unsafe.Pointer(a.orig)) < uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Less(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
es.Sort(func(a, b KeyValueAndUnit) bool {
return uintptr(unsafe.Pointer(a.orig)) > uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Greater(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
}
func generateTestKeyValueAndUnitSlice() KeyValueAndUnitSlice {
ms := NewKeyValueAndUnitSlice()
*ms.orig = internal.GenTestKeyValueAndUnitPtrSlice()
return ms
}
================================================
FILE: pdata/pprofile/generated_line.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// Line details a specific line in a source code, linked to a function.
//
// This is a reference type, if passed by value and callee modifies it the
// caller will see the modification.
//
// Must use NewLine function to create new instances.
// Important: zero-initialized instance is not valid for use.
type Line struct {
orig *internal.Line
state *internal.State
}
func newLine(orig *internal.Line, state *internal.State) Line {
return Line{orig: orig, state: state}
}
// NewLine creates a new empty Line.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewLine() Line {
return newLine(internal.NewLine(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms Line) MoveTo(dest Line) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteLine(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// FunctionIndex returns the functionindex associated with this Line.
func (ms Line) FunctionIndex() int32 {
return ms.orig.FunctionIndex
}
// SetFunctionIndex replaces the functionindex associated with this Line.
func (ms Line) SetFunctionIndex(v int32) {
ms.state.AssertMutable()
ms.orig.FunctionIndex = v
}
// Line returns the line associated with this Line.
func (ms Line) Line() int64 {
return ms.orig.Line
}
// SetLine replaces the line associated with this Line.
func (ms Line) SetLine(v int64) {
ms.state.AssertMutable()
ms.orig.Line = v
}
// Column returns the column associated with this Line.
func (ms Line) Column() int64 {
return ms.orig.Column
}
// SetColumn replaces the column associated with this Line.
func (ms Line) SetColumn(v int64) {
ms.state.AssertMutable()
ms.orig.Column = v
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms Line) CopyTo(dest Line) {
dest.state.AssertMutable()
internal.CopyLine(dest.orig, ms.orig)
}
================================================
FILE: pdata/pprofile/generated_line_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestLine_MoveTo(t *testing.T) {
ms := generateTestLine()
dest := NewLine()
ms.MoveTo(dest)
assert.Equal(t, NewLine(), ms)
assert.Equal(t, generateTestLine(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestLine(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newLine(internal.NewLine(), sharedState)) })
assert.Panics(t, func() { newLine(internal.NewLine(), sharedState).MoveTo(dest) })
}
func TestLine_CopyTo(t *testing.T) {
ms := NewLine()
orig := NewLine()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestLine()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newLine(internal.NewLine(), sharedState)) })
}
func TestLine_FunctionIndex(t *testing.T) {
ms := NewLine()
assert.Equal(t, int32(0), ms.FunctionIndex())
ms.SetFunctionIndex(int32(13))
assert.Equal(t, int32(13), ms.FunctionIndex())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newLine(internal.NewLine(), sharedState).SetFunctionIndex(int32(13)) })
}
func TestLine_Line(t *testing.T) {
ms := NewLine()
assert.Equal(t, int64(0), ms.Line())
ms.SetLine(int64(13))
assert.Equal(t, int64(13), ms.Line())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newLine(internal.NewLine(), sharedState).SetLine(int64(13)) })
}
func TestLine_Column(t *testing.T) {
ms := NewLine()
assert.Equal(t, int64(0), ms.Column())
ms.SetColumn(int64(13))
assert.Equal(t, int64(13), ms.Column())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newLine(internal.NewLine(), sharedState).SetColumn(int64(13)) })
}
func generateTestLine() Line {
return newLine(internal.GenTestLine(), internal.NewState())
}
================================================
FILE: pdata/pprofile/generated_lineslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"iter"
"sort"
"go.opentelemetry.io/collector/pdata/internal"
)
// LineSlice logically represents a slice of Line.
//
// This is a reference type. If passed by value and callee modifies it, the
// caller will see the modification.
//
// Must use NewLineSlice function to create new instances.
// Important: zero-initialized instance is not valid for use.
type LineSlice struct {
orig *[]*internal.Line
state *internal.State
}
func newLineSlice(orig *[]*internal.Line, state *internal.State) LineSlice {
return LineSlice{orig: orig, state: state}
}
// NewLineSlice creates a LineSliceWrapper with 0 elements.
// Can use "EnsureCapacity" to initialize with a given capacity.
func NewLineSlice() LineSlice {
orig := []*internal.Line(nil)
return newLineSlice(&orig, internal.NewState())
}
// Len returns the number of elements in the slice.
//
// Returns "0" for a newly instance created with "NewLineSlice()".
func (es LineSlice) 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 LineSlice) At(i int) Line {
return newLine((*es.orig)[i], es.state)
}
// All returns an iterator over index-value pairs in the slice.
//
// for i, v := range es.All() {
// ... // Do something with index-value pair
// }
func (es LineSlice) All() iter.Seq2[int, Line] {
return func(yield func(int, Line) bool) {
for i := 0; i < es.Len(); i++ {
if !yield(i, es.At(i)) {
return
}
}
}
}
// EnsureCapacity is an operation that ensures the slice has at least the specified capacity.
// 1. If the newCap <= cap then no change in capacity.
// 2. If the newCap > cap then the slice capacity will be expanded to equal newCap.
//
// Here is how a new LineSlice can be initialized:
//
// es := NewLineSlice()
// es.EnsureCapacity(4)
// for i := 0; i < 4; i++ {
// e := es.AppendEmpty()
// // Here should set all the values for e.
// }
func (es LineSlice) EnsureCapacity(newCap int) {
es.state.AssertMutable()
oldCap := cap(*es.orig)
if newCap <= oldCap {
return
}
newOrig := make([]*internal.Line, len(*es.orig), newCap)
copy(newOrig, *es.orig)
*es.orig = newOrig
}
// AppendEmpty will append to the end of the slice an empty Line.
// It returns the newly added Line.
func (es LineSlice) AppendEmpty() Line {
es.state.AssertMutable()
*es.orig = append(*es.orig, internal.NewLine())
return es.At(es.Len() - 1)
}
// MoveAndAppendTo moves all elements from the current slice and appends them to the dest.
// The current slice will be cleared.
func (es LineSlice) MoveAndAppendTo(dest LineSlice) {
es.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if es.orig == dest.orig {
return
}
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
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (es LineSlice) RemoveIf(f func(Line) bool) {
es.state.AssertMutable()
newLen := 0
for i := 0; i < len(*es.orig); i++ {
if f(es.At(i)) {
internal.DeleteLine((*es.orig)[i], true)
(*es.orig)[i] = nil
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*es.orig)[newLen] = (*es.orig)[i]
// Cannot delete here since we just move the data(or pointer to data) to a different position in the slice.
(*es.orig)[i] = nil
newLen++
}
*es.orig = (*es.orig)[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (es LineSlice) CopyTo(dest LineSlice) {
dest.state.AssertMutable()
if es.orig == dest.orig {
return
}
*dest.orig = internal.CopyLinePtrSlice(*dest.orig, *es.orig)
}
// Sort sorts the Line elements within LineSlice given the
// provided less function so that two instances of LineSlice
// can be compared.
func (es LineSlice) Sort(less func(a, b Line) bool) {
es.state.AssertMutable()
sort.SliceStable(*es.orig, func(i, j int) bool { return less(es.At(i), es.At(j)) })
}
================================================
FILE: pdata/pprofile/generated_lineslice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestLineSlice(t *testing.T) {
es := NewLineSlice()
assert.Equal(t, 0, es.Len())
es = newLineSlice(&[]*internal.Line{}, internal.NewState())
assert.Equal(t, 0, es.Len())
emptyVal := NewLine()
testVal := generateTestLine()
for i := 0; i < 7; i++ {
es.AppendEmpty()
assert.Equal(t, emptyVal, es.At(i))
(*es.orig)[i] = internal.GenTestLine()
assert.Equal(t, testVal, es.At(i))
}
assert.Equal(t, 7, es.Len())
}
func TestLineSliceReadOnly(t *testing.T) {
sharedState := internal.NewState()
sharedState.MarkReadOnly()
es := newLineSlice(&[]*internal.Line{}, sharedState)
assert.Equal(t, 0, es.Len())
assert.Panics(t, func() { es.AppendEmpty() })
assert.Panics(t, func() { es.EnsureCapacity(2) })
es2 := NewLineSlice()
es.CopyTo(es2)
assert.Panics(t, func() { es2.CopyTo(es) })
assert.Panics(t, func() { es.MoveAndAppendTo(es2) })
assert.Panics(t, func() { es2.MoveAndAppendTo(es) })
}
func TestLineSlice_CopyTo(t *testing.T) {
dest := NewLineSlice()
src := generateTestLineSlice()
src.CopyTo(dest)
assert.Equal(t, generateTestLineSlice(), dest)
dest.CopyTo(dest)
assert.Equal(t, generateTestLineSlice(), dest)
}
func TestLineSlice_EnsureCapacity(t *testing.T) {
es := generateTestLineSlice()
// Test ensure smaller capacity.
const ensureSmallLen = 4
es.EnsureCapacity(ensureSmallLen)
assert.Less(t, ensureSmallLen, es.Len())
assert.Equal(t, es.Len(), cap(*es.orig))
assert.Equal(t, generateTestLineSlice(), es)
// Test ensure larger capacity
const ensureLargeLen = 9
es.EnsureCapacity(ensureLargeLen)
assert.Less(t, generateTestLineSlice().Len(), ensureLargeLen)
assert.Equal(t, ensureLargeLen, cap(*es.orig))
assert.Equal(t, generateTestLineSlice(), es)
}
func TestLineSlice_MoveAndAppendTo(t *testing.T) {
// Test MoveAndAppendTo to empty
expectedSlice := generateTestLineSlice()
dest := NewLineSlice()
src := generateTestLineSlice()
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestLineSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo empty slice
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestLineSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo not empty slice
generateTestLineSlice().MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
dest.MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
}
func TestLineSlice_RemoveIf(t *testing.T) {
// Test RemoveIf on empty slice
emptySlice := NewLineSlice()
emptySlice.RemoveIf(func(el Line) bool {
t.Fail()
return false
})
// Test RemoveIf
filtered := generateTestLineSlice()
pos := 0
filtered.RemoveIf(func(el Line) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, 2, filtered.Len())
}
func TestLineSlice_RemoveIfAll(t *testing.T) {
got := generateTestLineSlice()
got.RemoveIf(func(el Line) bool {
return true
})
assert.Equal(t, 0, got.Len())
}
func TestLineSliceAll(t *testing.T) {
ms := generateTestLineSlice()
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestLineSlice_Sort(t *testing.T) {
es := generateTestLineSlice()
es.Sort(func(a, b Line) bool {
return uintptr(unsafe.Pointer(a.orig)) < uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Less(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
es.Sort(func(a, b Line) bool {
return uintptr(unsafe.Pointer(a.orig)) > uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Greater(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
}
func generateTestLineSlice() LineSlice {
ms := NewLineSlice()
*ms.orig = internal.GenTestLinePtrSlice()
return ms
}
================================================
FILE: pdata/pprofile/generated_link.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// Link represents a pointer from a profile Sample to a trace Span.
//
// This is a reference type, if passed by value and callee modifies it the
// caller will see the modification.
//
// Must use NewLink function to create new instances.
// Important: zero-initialized instance is not valid for use.
type Link struct {
orig *internal.Link
state *internal.State
}
func newLink(orig *internal.Link, state *internal.State) Link {
return Link{orig: orig, state: state}
}
// NewLink creates a new empty Link.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewLink() Link {
return newLink(internal.NewLink(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms Link) MoveTo(dest Link) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteLink(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// TraceID returns the traceid associated with this Link.
func (ms Link) TraceID() pcommon.TraceID {
return pcommon.TraceID(ms.orig.TraceId)
}
// SetTraceID replaces the traceid associated with this Link.
func (ms Link) SetTraceID(v pcommon.TraceID) {
ms.state.AssertMutable()
ms.orig.TraceId = internal.TraceID(v)
}
// SpanID returns the spanid associated with this Link.
func (ms Link) SpanID() pcommon.SpanID {
return pcommon.SpanID(ms.orig.SpanId)
}
// SetSpanID replaces the spanid associated with this Link.
func (ms Link) SetSpanID(v pcommon.SpanID) {
ms.state.AssertMutable()
ms.orig.SpanId = internal.SpanID(v)
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms Link) CopyTo(dest Link) {
dest.state.AssertMutable()
internal.CopyLink(dest.orig, ms.orig)
}
================================================
FILE: pdata/pprofile/generated_link_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestLink_MoveTo(t *testing.T) {
ms := generateTestLink()
dest := NewLink()
ms.MoveTo(dest)
assert.Equal(t, NewLink(), ms)
assert.Equal(t, generateTestLink(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestLink(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newLink(internal.NewLink(), sharedState)) })
assert.Panics(t, func() { newLink(internal.NewLink(), sharedState).MoveTo(dest) })
}
func TestLink_CopyTo(t *testing.T) {
ms := NewLink()
orig := NewLink()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestLink()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newLink(internal.NewLink(), sharedState)) })
}
func TestLink_TraceID(t *testing.T) {
ms := NewLink()
assert.Equal(t, pcommon.TraceID(internal.TraceID([16]byte{})), ms.TraceID())
testValTraceID := pcommon.TraceID(internal.TraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}))
ms.SetTraceID(testValTraceID)
assert.Equal(t, testValTraceID, ms.TraceID())
}
func TestLink_SpanID(t *testing.T) {
ms := NewLink()
assert.Equal(t, pcommon.SpanID(internal.SpanID([8]byte{})), ms.SpanID())
testValSpanID := pcommon.SpanID(internal.SpanID([8]byte{8, 7, 6, 5, 4, 3, 2, 1}))
ms.SetSpanID(testValSpanID)
assert.Equal(t, testValSpanID, ms.SpanID())
}
func generateTestLink() Link {
return newLink(internal.GenTestLink(), internal.NewState())
}
================================================
FILE: pdata/pprofile/generated_linkslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"iter"
"sort"
"go.opentelemetry.io/collector/pdata/internal"
)
// LinkSlice logically represents a slice of Link.
//
// This is a reference type. If passed by value and callee modifies it, the
// caller will see the modification.
//
// Must use NewLinkSlice function to create new instances.
// Important: zero-initialized instance is not valid for use.
type LinkSlice struct {
orig *[]*internal.Link
state *internal.State
}
func newLinkSlice(orig *[]*internal.Link, state *internal.State) LinkSlice {
return LinkSlice{orig: orig, state: state}
}
// NewLinkSlice creates a LinkSliceWrapper with 0 elements.
// Can use "EnsureCapacity" to initialize with a given capacity.
func NewLinkSlice() LinkSlice {
orig := []*internal.Link(nil)
return newLinkSlice(&orig, internal.NewState())
}
// Len returns the number of elements in the slice.
//
// Returns "0" for a newly instance created with "NewLinkSlice()".
func (es LinkSlice) 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 LinkSlice) At(i int) Link {
return newLink((*es.orig)[i], es.state)
}
// All returns an iterator over index-value pairs in the slice.
//
// for i, v := range es.All() {
// ... // Do something with index-value pair
// }
func (es LinkSlice) All() iter.Seq2[int, Link] {
return func(yield func(int, Link) bool) {
for i := 0; i < es.Len(); i++ {
if !yield(i, es.At(i)) {
return
}
}
}
}
// EnsureCapacity is an operation that ensures the slice has at least the specified capacity.
// 1. If the newCap <= cap then no change in capacity.
// 2. If the newCap > cap then the slice capacity will be expanded to equal newCap.
//
// Here is how a new LinkSlice can be initialized:
//
// es := NewLinkSlice()
// es.EnsureCapacity(4)
// for i := 0; i < 4; i++ {
// e := es.AppendEmpty()
// // Here should set all the values for e.
// }
func (es LinkSlice) EnsureCapacity(newCap int) {
es.state.AssertMutable()
oldCap := cap(*es.orig)
if newCap <= oldCap {
return
}
newOrig := make([]*internal.Link, len(*es.orig), newCap)
copy(newOrig, *es.orig)
*es.orig = newOrig
}
// AppendEmpty will append to the end of the slice an empty Link.
// It returns the newly added Link.
func (es LinkSlice) AppendEmpty() Link {
es.state.AssertMutable()
*es.orig = append(*es.orig, internal.NewLink())
return es.At(es.Len() - 1)
}
// MoveAndAppendTo moves all elements from the current slice and appends them to the dest.
// The current slice will be cleared.
func (es LinkSlice) MoveAndAppendTo(dest LinkSlice) {
es.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if es.orig == dest.orig {
return
}
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
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (es LinkSlice) RemoveIf(f func(Link) bool) {
es.state.AssertMutable()
newLen := 0
for i := 0; i < len(*es.orig); i++ {
if f(es.At(i)) {
internal.DeleteLink((*es.orig)[i], true)
(*es.orig)[i] = nil
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*es.orig)[newLen] = (*es.orig)[i]
// Cannot delete here since we just move the data(or pointer to data) to a different position in the slice.
(*es.orig)[i] = nil
newLen++
}
*es.orig = (*es.orig)[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (es LinkSlice) CopyTo(dest LinkSlice) {
dest.state.AssertMutable()
if es.orig == dest.orig {
return
}
*dest.orig = internal.CopyLinkPtrSlice(*dest.orig, *es.orig)
}
// Sort sorts the Link elements within LinkSlice given the
// provided less function so that two instances of LinkSlice
// can be compared.
func (es LinkSlice) Sort(less func(a, b Link) bool) {
es.state.AssertMutable()
sort.SliceStable(*es.orig, func(i, j int) bool { return less(es.At(i), es.At(j)) })
}
================================================
FILE: pdata/pprofile/generated_linkslice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestLinkSlice(t *testing.T) {
es := NewLinkSlice()
assert.Equal(t, 0, es.Len())
es = newLinkSlice(&[]*internal.Link{}, internal.NewState())
assert.Equal(t, 0, es.Len())
emptyVal := NewLink()
testVal := generateTestLink()
for i := 0; i < 7; i++ {
es.AppendEmpty()
assert.Equal(t, emptyVal, es.At(i))
(*es.orig)[i] = internal.GenTestLink()
assert.Equal(t, testVal, es.At(i))
}
assert.Equal(t, 7, es.Len())
}
func TestLinkSliceReadOnly(t *testing.T) {
sharedState := internal.NewState()
sharedState.MarkReadOnly()
es := newLinkSlice(&[]*internal.Link{}, sharedState)
assert.Equal(t, 0, es.Len())
assert.Panics(t, func() { es.AppendEmpty() })
assert.Panics(t, func() { es.EnsureCapacity(2) })
es2 := NewLinkSlice()
es.CopyTo(es2)
assert.Panics(t, func() { es2.CopyTo(es) })
assert.Panics(t, func() { es.MoveAndAppendTo(es2) })
assert.Panics(t, func() { es2.MoveAndAppendTo(es) })
}
func TestLinkSlice_CopyTo(t *testing.T) {
dest := NewLinkSlice()
src := generateTestLinkSlice()
src.CopyTo(dest)
assert.Equal(t, generateTestLinkSlice(), dest)
dest.CopyTo(dest)
assert.Equal(t, generateTestLinkSlice(), dest)
}
func TestLinkSlice_EnsureCapacity(t *testing.T) {
es := generateTestLinkSlice()
// Test ensure smaller capacity.
const ensureSmallLen = 4
es.EnsureCapacity(ensureSmallLen)
assert.Less(t, ensureSmallLen, es.Len())
assert.Equal(t, es.Len(), cap(*es.orig))
assert.Equal(t, generateTestLinkSlice(), es)
// Test ensure larger capacity
const ensureLargeLen = 9
es.EnsureCapacity(ensureLargeLen)
assert.Less(t, generateTestLinkSlice().Len(), ensureLargeLen)
assert.Equal(t, ensureLargeLen, cap(*es.orig))
assert.Equal(t, generateTestLinkSlice(), es)
}
func TestLinkSlice_MoveAndAppendTo(t *testing.T) {
// Test MoveAndAppendTo to empty
expectedSlice := generateTestLinkSlice()
dest := NewLinkSlice()
src := generateTestLinkSlice()
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestLinkSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo empty slice
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestLinkSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo not empty slice
generateTestLinkSlice().MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
dest.MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
}
func TestLinkSlice_RemoveIf(t *testing.T) {
// Test RemoveIf on empty slice
emptySlice := NewLinkSlice()
emptySlice.RemoveIf(func(el Link) bool {
t.Fail()
return false
})
// Test RemoveIf
filtered := generateTestLinkSlice()
pos := 0
filtered.RemoveIf(func(el Link) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, 2, filtered.Len())
}
func TestLinkSlice_RemoveIfAll(t *testing.T) {
got := generateTestLinkSlice()
got.RemoveIf(func(el Link) bool {
return true
})
assert.Equal(t, 0, got.Len())
}
func TestLinkSliceAll(t *testing.T) {
ms := generateTestLinkSlice()
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestLinkSlice_Sort(t *testing.T) {
es := generateTestLinkSlice()
es.Sort(func(a, b Link) bool {
return uintptr(unsafe.Pointer(a.orig)) < uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Less(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
es.Sort(func(a, b Link) bool {
return uintptr(unsafe.Pointer(a.orig)) > uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Greater(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
}
func generateTestLinkSlice() LinkSlice {
ms := NewLinkSlice()
*ms.orig = internal.GenTestLinkPtrSlice()
return ms
}
================================================
FILE: pdata/pprofile/generated_location.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// Location describes function and line table debug information.
//
// This is a reference type, if passed by value and callee modifies it the
// caller will see the modification.
//
// Must use NewLocation function to create new instances.
// Important: zero-initialized instance is not valid for use.
type Location struct {
orig *internal.Location
state *internal.State
}
func newLocation(orig *internal.Location, state *internal.State) Location {
return Location{orig: orig, state: state}
}
// NewLocation creates a new empty Location.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewLocation() Location {
return newLocation(internal.NewLocation(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms Location) MoveTo(dest Location) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteLocation(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// MappingIndex returns the mappingindex associated with this Location.
func (ms Location) MappingIndex() int32 {
return ms.orig.MappingIndex
}
// SetMappingIndex replaces the mappingindex associated with this Location.
func (ms Location) SetMappingIndex(v int32) {
ms.state.AssertMutable()
ms.orig.MappingIndex = v
}
// Address returns the address associated with this Location.
func (ms Location) Address() uint64 {
return ms.orig.Address
}
// SetAddress replaces the address associated with this Location.
func (ms Location) SetAddress(v uint64) {
ms.state.AssertMutable()
ms.orig.Address = v
}
// Lines returns the Lines associated with this Location.
func (ms Location) Lines() LineSlice {
return newLineSlice(&ms.orig.Lines, ms.state)
}
// AttributeIndices returns the AttributeIndices associated with this Location.
func (ms Location) AttributeIndices() pcommon.Int32Slice {
return pcommon.Int32Slice(internal.NewInt32SliceWrapper(&ms.orig.AttributeIndices, ms.state))
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms Location) CopyTo(dest Location) {
dest.state.AssertMutable()
internal.CopyLocation(dest.orig, ms.orig)
}
================================================
FILE: pdata/pprofile/generated_location_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestLocation_MoveTo(t *testing.T) {
ms := generateTestLocation()
dest := NewLocation()
ms.MoveTo(dest)
assert.Equal(t, NewLocation(), ms)
assert.Equal(t, generateTestLocation(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestLocation(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newLocation(internal.NewLocation(), sharedState)) })
assert.Panics(t, func() { newLocation(internal.NewLocation(), sharedState).MoveTo(dest) })
}
func TestLocation_CopyTo(t *testing.T) {
ms := NewLocation()
orig := NewLocation()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestLocation()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newLocation(internal.NewLocation(), sharedState)) })
}
func TestLocation_MappingIndex(t *testing.T) {
ms := NewLocation()
assert.Equal(t, int32(0), ms.MappingIndex())
ms.SetMappingIndex(int32(13))
assert.Equal(t, int32(13), ms.MappingIndex())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newLocation(internal.NewLocation(), sharedState).SetMappingIndex(int32(13)) })
}
func TestLocation_Address(t *testing.T) {
ms := NewLocation()
assert.Equal(t, uint64(0), ms.Address())
ms.SetAddress(uint64(13))
assert.Equal(t, uint64(13), ms.Address())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newLocation(internal.NewLocation(), sharedState).SetAddress(uint64(13)) })
}
func TestLocation_Lines(t *testing.T) {
ms := NewLocation()
assert.Equal(t, NewLineSlice(), ms.Lines())
ms.orig.Lines = internal.GenTestLinePtrSlice()
assert.Equal(t, generateTestLineSlice(), ms.Lines())
}
func TestLocation_AttributeIndices(t *testing.T) {
ms := NewLocation()
assert.Equal(t, pcommon.NewInt32Slice(), ms.AttributeIndices())
ms.orig.AttributeIndices = internal.GenTestInt32Slice()
assert.Equal(t, pcommon.Int32Slice(internal.GenTestInt32SliceWrapper()), ms.AttributeIndices())
}
func generateTestLocation() Location {
return newLocation(internal.GenTestLocation(), internal.NewState())
}
================================================
FILE: pdata/pprofile/generated_locationslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"iter"
"sort"
"go.opentelemetry.io/collector/pdata/internal"
)
// LocationSlice logically represents a slice of Location.
//
// This is a reference type. If passed by value and callee modifies it, the
// caller will see the modification.
//
// Must use NewLocationSlice function to create new instances.
// Important: zero-initialized instance is not valid for use.
type LocationSlice struct {
orig *[]*internal.Location
state *internal.State
}
func newLocationSlice(orig *[]*internal.Location, state *internal.State) LocationSlice {
return LocationSlice{orig: orig, state: state}
}
// NewLocationSlice creates a LocationSliceWrapper with 0 elements.
// Can use "EnsureCapacity" to initialize with a given capacity.
func NewLocationSlice() LocationSlice {
orig := []*internal.Location(nil)
return newLocationSlice(&orig, internal.NewState())
}
// Len returns the number of elements in the slice.
//
// Returns "0" for a newly instance created with "NewLocationSlice()".
func (es LocationSlice) 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 LocationSlice) At(i int) Location {
return newLocation((*es.orig)[i], es.state)
}
// All returns an iterator over index-value pairs in the slice.
//
// for i, v := range es.All() {
// ... // Do something with index-value pair
// }
func (es LocationSlice) All() iter.Seq2[int, Location] {
return func(yield func(int, Location) bool) {
for i := 0; i < es.Len(); i++ {
if !yield(i, es.At(i)) {
return
}
}
}
}
// EnsureCapacity is an operation that ensures the slice has at least the specified capacity.
// 1. If the newCap <= cap then no change in capacity.
// 2. If the newCap > cap then the slice capacity will be expanded to equal newCap.
//
// Here is how a new LocationSlice can be initialized:
//
// es := NewLocationSlice()
// es.EnsureCapacity(4)
// for i := 0; i < 4; i++ {
// e := es.AppendEmpty()
// // Here should set all the values for e.
// }
func (es LocationSlice) EnsureCapacity(newCap int) {
es.state.AssertMutable()
oldCap := cap(*es.orig)
if newCap <= oldCap {
return
}
newOrig := make([]*internal.Location, len(*es.orig), newCap)
copy(newOrig, *es.orig)
*es.orig = newOrig
}
// AppendEmpty will append to the end of the slice an empty Location.
// It returns the newly added Location.
func (es LocationSlice) AppendEmpty() Location {
es.state.AssertMutable()
*es.orig = append(*es.orig, internal.NewLocation())
return es.At(es.Len() - 1)
}
// MoveAndAppendTo moves all elements from the current slice and appends them to the dest.
// The current slice will be cleared.
func (es LocationSlice) MoveAndAppendTo(dest LocationSlice) {
es.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if es.orig == dest.orig {
return
}
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
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (es LocationSlice) RemoveIf(f func(Location) bool) {
es.state.AssertMutable()
newLen := 0
for i := 0; i < len(*es.orig); i++ {
if f(es.At(i)) {
internal.DeleteLocation((*es.orig)[i], true)
(*es.orig)[i] = nil
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*es.orig)[newLen] = (*es.orig)[i]
// Cannot delete here since we just move the data(or pointer to data) to a different position in the slice.
(*es.orig)[i] = nil
newLen++
}
*es.orig = (*es.orig)[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (es LocationSlice) CopyTo(dest LocationSlice) {
dest.state.AssertMutable()
if es.orig == dest.orig {
return
}
*dest.orig = internal.CopyLocationPtrSlice(*dest.orig, *es.orig)
}
// Sort sorts the Location elements within LocationSlice given the
// provided less function so that two instances of LocationSlice
// can be compared.
func (es LocationSlice) Sort(less func(a, b Location) bool) {
es.state.AssertMutable()
sort.SliceStable(*es.orig, func(i, j int) bool { return less(es.At(i), es.At(j)) })
}
================================================
FILE: pdata/pprofile/generated_locationslice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestLocationSlice(t *testing.T) {
es := NewLocationSlice()
assert.Equal(t, 0, es.Len())
es = newLocationSlice(&[]*internal.Location{}, internal.NewState())
assert.Equal(t, 0, es.Len())
emptyVal := NewLocation()
testVal := generateTestLocation()
for i := 0; i < 7; i++ {
es.AppendEmpty()
assert.Equal(t, emptyVal, es.At(i))
(*es.orig)[i] = internal.GenTestLocation()
assert.Equal(t, testVal, es.At(i))
}
assert.Equal(t, 7, es.Len())
}
func TestLocationSliceReadOnly(t *testing.T) {
sharedState := internal.NewState()
sharedState.MarkReadOnly()
es := newLocationSlice(&[]*internal.Location{}, sharedState)
assert.Equal(t, 0, es.Len())
assert.Panics(t, func() { es.AppendEmpty() })
assert.Panics(t, func() { es.EnsureCapacity(2) })
es2 := NewLocationSlice()
es.CopyTo(es2)
assert.Panics(t, func() { es2.CopyTo(es) })
assert.Panics(t, func() { es.MoveAndAppendTo(es2) })
assert.Panics(t, func() { es2.MoveAndAppendTo(es) })
}
func TestLocationSlice_CopyTo(t *testing.T) {
dest := NewLocationSlice()
src := generateTestLocationSlice()
src.CopyTo(dest)
assert.Equal(t, generateTestLocationSlice(), dest)
dest.CopyTo(dest)
assert.Equal(t, generateTestLocationSlice(), dest)
}
func TestLocationSlice_EnsureCapacity(t *testing.T) {
es := generateTestLocationSlice()
// Test ensure smaller capacity.
const ensureSmallLen = 4
es.EnsureCapacity(ensureSmallLen)
assert.Less(t, ensureSmallLen, es.Len())
assert.Equal(t, es.Len(), cap(*es.orig))
assert.Equal(t, generateTestLocationSlice(), es)
// Test ensure larger capacity
const ensureLargeLen = 9
es.EnsureCapacity(ensureLargeLen)
assert.Less(t, generateTestLocationSlice().Len(), ensureLargeLen)
assert.Equal(t, ensureLargeLen, cap(*es.orig))
assert.Equal(t, generateTestLocationSlice(), es)
}
func TestLocationSlice_MoveAndAppendTo(t *testing.T) {
// Test MoveAndAppendTo to empty
expectedSlice := generateTestLocationSlice()
dest := NewLocationSlice()
src := generateTestLocationSlice()
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestLocationSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo empty slice
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestLocationSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo not empty slice
generateTestLocationSlice().MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
dest.MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
}
func TestLocationSlice_RemoveIf(t *testing.T) {
// Test RemoveIf on empty slice
emptySlice := NewLocationSlice()
emptySlice.RemoveIf(func(el Location) bool {
t.Fail()
return false
})
// Test RemoveIf
filtered := generateTestLocationSlice()
pos := 0
filtered.RemoveIf(func(el Location) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, 2, filtered.Len())
}
func TestLocationSlice_RemoveIfAll(t *testing.T) {
got := generateTestLocationSlice()
got.RemoveIf(func(el Location) bool {
return true
})
assert.Equal(t, 0, got.Len())
}
func TestLocationSliceAll(t *testing.T) {
ms := generateTestLocationSlice()
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestLocationSlice_Sort(t *testing.T) {
es := generateTestLocationSlice()
es.Sort(func(a, b Location) bool {
return uintptr(unsafe.Pointer(a.orig)) < uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Less(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
es.Sort(func(a, b Location) bool {
return uintptr(unsafe.Pointer(a.orig)) > uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Greater(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
}
func generateTestLocationSlice() LocationSlice {
ms := NewLocationSlice()
*ms.orig = internal.GenTestLocationPtrSlice()
return ms
}
================================================
FILE: pdata/pprofile/generated_mapping.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// Mapping describes the mapping of a binary in memory, including its address range, file offset, and metadata like build ID
//
// This is a reference type, if passed by value and callee modifies it the
// caller will see the modification.
//
// Must use NewMapping function to create new instances.
// Important: zero-initialized instance is not valid for use.
type Mapping struct {
orig *internal.Mapping
state *internal.State
}
func newMapping(orig *internal.Mapping, state *internal.State) Mapping {
return Mapping{orig: orig, state: state}
}
// NewMapping creates a new empty Mapping.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewMapping() Mapping {
return newMapping(internal.NewMapping(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms Mapping) MoveTo(dest Mapping) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteMapping(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// MemoryStart returns the memorystart associated with this Mapping.
func (ms Mapping) MemoryStart() uint64 {
return ms.orig.MemoryStart
}
// SetMemoryStart replaces the memorystart associated with this Mapping.
func (ms Mapping) SetMemoryStart(v uint64) {
ms.state.AssertMutable()
ms.orig.MemoryStart = v
}
// MemoryLimit returns the memorylimit associated with this Mapping.
func (ms Mapping) MemoryLimit() uint64 {
return ms.orig.MemoryLimit
}
// SetMemoryLimit replaces the memorylimit associated with this Mapping.
func (ms Mapping) SetMemoryLimit(v uint64) {
ms.state.AssertMutable()
ms.orig.MemoryLimit = v
}
// FileOffset returns the fileoffset associated with this Mapping.
func (ms Mapping) FileOffset() uint64 {
return ms.orig.FileOffset
}
// SetFileOffset replaces the fileoffset associated with this Mapping.
func (ms Mapping) SetFileOffset(v uint64) {
ms.state.AssertMutable()
ms.orig.FileOffset = v
}
// FilenameStrindex returns the filenamestrindex associated with this Mapping.
func (ms Mapping) FilenameStrindex() int32 {
return ms.orig.FilenameStrindex
}
// SetFilenameStrindex replaces the filenamestrindex associated with this Mapping.
func (ms Mapping) SetFilenameStrindex(v int32) {
ms.state.AssertMutable()
ms.orig.FilenameStrindex = v
}
// AttributeIndices returns the AttributeIndices associated with this Mapping.
func (ms Mapping) AttributeIndices() pcommon.Int32Slice {
return pcommon.Int32Slice(internal.NewInt32SliceWrapper(&ms.orig.AttributeIndices, ms.state))
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms Mapping) CopyTo(dest Mapping) {
dest.state.AssertMutable()
internal.CopyMapping(dest.orig, ms.orig)
}
================================================
FILE: pdata/pprofile/generated_mapping_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestMapping_MoveTo(t *testing.T) {
ms := generateTestMapping()
dest := NewMapping()
ms.MoveTo(dest)
assert.Equal(t, NewMapping(), ms)
assert.Equal(t, generateTestMapping(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestMapping(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newMapping(internal.NewMapping(), sharedState)) })
assert.Panics(t, func() { newMapping(internal.NewMapping(), sharedState).MoveTo(dest) })
}
func TestMapping_CopyTo(t *testing.T) {
ms := NewMapping()
orig := NewMapping()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestMapping()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newMapping(internal.NewMapping(), sharedState)) })
}
func TestMapping_MemoryStart(t *testing.T) {
ms := NewMapping()
assert.Equal(t, uint64(0), ms.MemoryStart())
ms.SetMemoryStart(uint64(13))
assert.Equal(t, uint64(13), ms.MemoryStart())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newMapping(internal.NewMapping(), sharedState).SetMemoryStart(uint64(13)) })
}
func TestMapping_MemoryLimit(t *testing.T) {
ms := NewMapping()
assert.Equal(t, uint64(0), ms.MemoryLimit())
ms.SetMemoryLimit(uint64(13))
assert.Equal(t, uint64(13), ms.MemoryLimit())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newMapping(internal.NewMapping(), sharedState).SetMemoryLimit(uint64(13)) })
}
func TestMapping_FileOffset(t *testing.T) {
ms := NewMapping()
assert.Equal(t, uint64(0), ms.FileOffset())
ms.SetFileOffset(uint64(13))
assert.Equal(t, uint64(13), ms.FileOffset())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newMapping(internal.NewMapping(), sharedState).SetFileOffset(uint64(13)) })
}
func TestMapping_FilenameStrindex(t *testing.T) {
ms := NewMapping()
assert.Equal(t, int32(0), ms.FilenameStrindex())
ms.SetFilenameStrindex(int32(13))
assert.Equal(t, int32(13), ms.FilenameStrindex())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newMapping(internal.NewMapping(), sharedState).SetFilenameStrindex(int32(13)) })
}
func TestMapping_AttributeIndices(t *testing.T) {
ms := NewMapping()
assert.Equal(t, pcommon.NewInt32Slice(), ms.AttributeIndices())
ms.orig.AttributeIndices = internal.GenTestInt32Slice()
assert.Equal(t, pcommon.Int32Slice(internal.GenTestInt32SliceWrapper()), ms.AttributeIndices())
}
func generateTestMapping() Mapping {
return newMapping(internal.GenTestMapping(), internal.NewState())
}
================================================
FILE: pdata/pprofile/generated_mappingslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"iter"
"sort"
"go.opentelemetry.io/collector/pdata/internal"
)
// MappingSlice logically represents a slice of Mapping.
//
// This is a reference type. If passed by value and callee modifies it, the
// caller will see the modification.
//
// Must use NewMappingSlice function to create new instances.
// Important: zero-initialized instance is not valid for use.
type MappingSlice struct {
orig *[]*internal.Mapping
state *internal.State
}
func newMappingSlice(orig *[]*internal.Mapping, state *internal.State) MappingSlice {
return MappingSlice{orig: orig, state: state}
}
// NewMappingSlice creates a MappingSliceWrapper with 0 elements.
// Can use "EnsureCapacity" to initialize with a given capacity.
func NewMappingSlice() MappingSlice {
orig := []*internal.Mapping(nil)
return newMappingSlice(&orig, internal.NewState())
}
// Len returns the number of elements in the slice.
//
// Returns "0" for a newly instance created with "NewMappingSlice()".
func (es MappingSlice) 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 MappingSlice) At(i int) Mapping {
return newMapping((*es.orig)[i], es.state)
}
// All returns an iterator over index-value pairs in the slice.
//
// for i, v := range es.All() {
// ... // Do something with index-value pair
// }
func (es MappingSlice) All() iter.Seq2[int, Mapping] {
return func(yield func(int, Mapping) bool) {
for i := 0; i < es.Len(); i++ {
if !yield(i, es.At(i)) {
return
}
}
}
}
// EnsureCapacity is an operation that ensures the slice has at least the specified capacity.
// 1. If the newCap <= cap then no change in capacity.
// 2. If the newCap > cap then the slice capacity will be expanded to equal newCap.
//
// Here is how a new MappingSlice can be initialized:
//
// es := NewMappingSlice()
// es.EnsureCapacity(4)
// for i := 0; i < 4; i++ {
// e := es.AppendEmpty()
// // Here should set all the values for e.
// }
func (es MappingSlice) EnsureCapacity(newCap int) {
es.state.AssertMutable()
oldCap := cap(*es.orig)
if newCap <= oldCap {
return
}
newOrig := make([]*internal.Mapping, len(*es.orig), newCap)
copy(newOrig, *es.orig)
*es.orig = newOrig
}
// AppendEmpty will append to the end of the slice an empty Mapping.
// It returns the newly added Mapping.
func (es MappingSlice) AppendEmpty() Mapping {
es.state.AssertMutable()
*es.orig = append(*es.orig, internal.NewMapping())
return es.At(es.Len() - 1)
}
// MoveAndAppendTo moves all elements from the current slice and appends them to the dest.
// The current slice will be cleared.
func (es MappingSlice) MoveAndAppendTo(dest MappingSlice) {
es.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if es.orig == dest.orig {
return
}
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
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (es MappingSlice) RemoveIf(f func(Mapping) bool) {
es.state.AssertMutable()
newLen := 0
for i := 0; i < len(*es.orig); i++ {
if f(es.At(i)) {
internal.DeleteMapping((*es.orig)[i], true)
(*es.orig)[i] = nil
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*es.orig)[newLen] = (*es.orig)[i]
// Cannot delete here since we just move the data(or pointer to data) to a different position in the slice.
(*es.orig)[i] = nil
newLen++
}
*es.orig = (*es.orig)[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (es MappingSlice) CopyTo(dest MappingSlice) {
dest.state.AssertMutable()
if es.orig == dest.orig {
return
}
*dest.orig = internal.CopyMappingPtrSlice(*dest.orig, *es.orig)
}
// Sort sorts the Mapping elements within MappingSlice given the
// provided less function so that two instances of MappingSlice
// can be compared.
func (es MappingSlice) Sort(less func(a, b Mapping) bool) {
es.state.AssertMutable()
sort.SliceStable(*es.orig, func(i, j int) bool { return less(es.At(i), es.At(j)) })
}
================================================
FILE: pdata/pprofile/generated_mappingslice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestMappingSlice(t *testing.T) {
es := NewMappingSlice()
assert.Equal(t, 0, es.Len())
es = newMappingSlice(&[]*internal.Mapping{}, internal.NewState())
assert.Equal(t, 0, es.Len())
emptyVal := NewMapping()
testVal := generateTestMapping()
for i := 0; i < 7; i++ {
es.AppendEmpty()
assert.Equal(t, emptyVal, es.At(i))
(*es.orig)[i] = internal.GenTestMapping()
assert.Equal(t, testVal, es.At(i))
}
assert.Equal(t, 7, es.Len())
}
func TestMappingSliceReadOnly(t *testing.T) {
sharedState := internal.NewState()
sharedState.MarkReadOnly()
es := newMappingSlice(&[]*internal.Mapping{}, sharedState)
assert.Equal(t, 0, es.Len())
assert.Panics(t, func() { es.AppendEmpty() })
assert.Panics(t, func() { es.EnsureCapacity(2) })
es2 := NewMappingSlice()
es.CopyTo(es2)
assert.Panics(t, func() { es2.CopyTo(es) })
assert.Panics(t, func() { es.MoveAndAppendTo(es2) })
assert.Panics(t, func() { es2.MoveAndAppendTo(es) })
}
func TestMappingSlice_CopyTo(t *testing.T) {
dest := NewMappingSlice()
src := generateTestMappingSlice()
src.CopyTo(dest)
assert.Equal(t, generateTestMappingSlice(), dest)
dest.CopyTo(dest)
assert.Equal(t, generateTestMappingSlice(), dest)
}
func TestMappingSlice_EnsureCapacity(t *testing.T) {
es := generateTestMappingSlice()
// Test ensure smaller capacity.
const ensureSmallLen = 4
es.EnsureCapacity(ensureSmallLen)
assert.Less(t, ensureSmallLen, es.Len())
assert.Equal(t, es.Len(), cap(*es.orig))
assert.Equal(t, generateTestMappingSlice(), es)
// Test ensure larger capacity
const ensureLargeLen = 9
es.EnsureCapacity(ensureLargeLen)
assert.Less(t, generateTestMappingSlice().Len(), ensureLargeLen)
assert.Equal(t, ensureLargeLen, cap(*es.orig))
assert.Equal(t, generateTestMappingSlice(), es)
}
func TestMappingSlice_MoveAndAppendTo(t *testing.T) {
// Test MoveAndAppendTo to empty
expectedSlice := generateTestMappingSlice()
dest := NewMappingSlice()
src := generateTestMappingSlice()
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestMappingSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo empty slice
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestMappingSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo not empty slice
generateTestMappingSlice().MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
dest.MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
}
func TestMappingSlice_RemoveIf(t *testing.T) {
// Test RemoveIf on empty slice
emptySlice := NewMappingSlice()
emptySlice.RemoveIf(func(el Mapping) bool {
t.Fail()
return false
})
// Test RemoveIf
filtered := generateTestMappingSlice()
pos := 0
filtered.RemoveIf(func(el Mapping) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, 2, filtered.Len())
}
func TestMappingSlice_RemoveIfAll(t *testing.T) {
got := generateTestMappingSlice()
got.RemoveIf(func(el Mapping) bool {
return true
})
assert.Equal(t, 0, got.Len())
}
func TestMappingSliceAll(t *testing.T) {
ms := generateTestMappingSlice()
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestMappingSlice_Sort(t *testing.T) {
es := generateTestMappingSlice()
es.Sort(func(a, b Mapping) bool {
return uintptr(unsafe.Pointer(a.orig)) < uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Less(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
es.Sort(func(a, b Mapping) bool {
return uintptr(unsafe.Pointer(a.orig)) > uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Greater(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
}
func generateTestMappingSlice() MappingSlice {
ms := NewMappingSlice()
*ms.orig = internal.GenTestMappingPtrSlice()
return ms
}
================================================
FILE: pdata/pprofile/generated_profile.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// Profile are an implementation of the pprofextended data model.
// This is a reference type, if passed by value and callee modifies it the
// caller will see the modification.
//
// Must use NewProfile function to create new instances.
// Important: zero-initialized instance is not valid for use.
type Profile struct {
orig *internal.Profile
state *internal.State
}
func newProfile(orig *internal.Profile, state *internal.State) Profile {
return Profile{orig: orig, state: state}
}
// NewProfile creates a new empty Profile.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewProfile() Profile {
return newProfile(internal.NewProfile(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms Profile) MoveTo(dest Profile) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteProfile(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// SampleType returns the sampletype associated with this Profile.
func (ms Profile) SampleType() ValueType {
return newValueType(&ms.orig.SampleType, ms.state)
}
// Samples returns the Samples associated with this Profile.
func (ms Profile) Samples() SampleSlice {
return newSampleSlice(&ms.orig.Samples, ms.state)
}
// Time returns the time associated with this Profile.
func (ms Profile) Time() pcommon.Timestamp {
return pcommon.Timestamp(ms.orig.TimeUnixNano)
}
// SetTime replaces the time associated with this Profile.
func (ms Profile) SetTime(v pcommon.Timestamp) {
ms.state.AssertMutable()
ms.orig.TimeUnixNano = uint64(v)
}
// DurationNano returns the durationnano associated with this Profile.
func (ms Profile) DurationNano() uint64 {
return ms.orig.DurationNano
}
// SetDurationNano replaces the durationnano associated with this Profile.
func (ms Profile) SetDurationNano(v uint64) {
ms.state.AssertMutable()
ms.orig.DurationNano = v
}
// PeriodType returns the periodtype associated with this Profile.
func (ms Profile) PeriodType() ValueType {
return newValueType(&ms.orig.PeriodType, ms.state)
}
// Period returns the period associated with this Profile.
func (ms Profile) Period() int64 {
return ms.orig.Period
}
// SetPeriod replaces the period associated with this Profile.
func (ms Profile) SetPeriod(v int64) {
ms.state.AssertMutable()
ms.orig.Period = v
}
// ProfileID returns the profileid associated with this Profile.
func (ms Profile) ProfileID() ProfileID {
return ProfileID(ms.orig.ProfileId)
}
// SetProfileID replaces the profileid associated with this Profile.
func (ms Profile) SetProfileID(v ProfileID) {
ms.state.AssertMutable()
ms.orig.ProfileId = internal.ProfileID(v)
}
// DroppedAttributesCount returns the droppedattributescount associated with this Profile.
func (ms Profile) DroppedAttributesCount() uint32 {
return ms.orig.DroppedAttributesCount
}
// SetDroppedAttributesCount replaces the droppedattributescount associated with this Profile.
func (ms Profile) SetDroppedAttributesCount(v uint32) {
ms.state.AssertMutable()
ms.orig.DroppedAttributesCount = v
}
// OriginalPayloadFormat returns the originalpayloadformat associated with this Profile.
func (ms Profile) OriginalPayloadFormat() string {
return ms.orig.OriginalPayloadFormat
}
// SetOriginalPayloadFormat replaces the originalpayloadformat associated with this Profile.
func (ms Profile) SetOriginalPayloadFormat(v string) {
ms.state.AssertMutable()
ms.orig.OriginalPayloadFormat = v
}
// OriginalPayload returns the OriginalPayload associated with this Profile.
func (ms Profile) OriginalPayload() pcommon.ByteSlice {
return pcommon.ByteSlice(internal.NewByteSliceWrapper(&ms.orig.OriginalPayload, ms.state))
}
// AttributeIndices returns the AttributeIndices associated with this Profile.
func (ms Profile) AttributeIndices() pcommon.Int32Slice {
return pcommon.Int32Slice(internal.NewInt32SliceWrapper(&ms.orig.AttributeIndices, ms.state))
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms Profile) CopyTo(dest Profile) {
dest.state.AssertMutable()
internal.CopyProfile(dest.orig, ms.orig)
}
================================================
FILE: pdata/pprofile/generated_profile_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestProfile_MoveTo(t *testing.T) {
ms := generateTestProfile()
dest := NewProfile()
ms.MoveTo(dest)
assert.Equal(t, NewProfile(), ms)
assert.Equal(t, generateTestProfile(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestProfile(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newProfile(internal.NewProfile(), sharedState)) })
assert.Panics(t, func() { newProfile(internal.NewProfile(), sharedState).MoveTo(dest) })
}
func TestProfile_CopyTo(t *testing.T) {
ms := NewProfile()
orig := NewProfile()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestProfile()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newProfile(internal.NewProfile(), sharedState)) })
}
func TestProfile_SampleType(t *testing.T) {
ms := NewProfile()
assert.Equal(t, NewValueType(), ms.SampleType())
ms.orig.SampleType = *internal.GenTestValueType()
assert.Equal(t, generateTestValueType(), ms.SampleType())
}
func TestProfile_Samples(t *testing.T) {
ms := NewProfile()
assert.Equal(t, NewSampleSlice(), ms.Samples())
ms.orig.Samples = internal.GenTestSamplePtrSlice()
assert.Equal(t, generateTestSampleSlice(), ms.Samples())
}
func TestProfile_Time(t *testing.T) {
ms := NewProfile()
assert.Equal(t, pcommon.Timestamp(0), ms.Time())
testValTime := pcommon.Timestamp(1234567890)
ms.SetTime(testValTime)
assert.Equal(t, testValTime, ms.Time())
}
func TestProfile_DurationNano(t *testing.T) {
ms := NewProfile()
assert.Equal(t, uint64(0), ms.DurationNano())
ms.SetDurationNano(uint64(13))
assert.Equal(t, uint64(13), ms.DurationNano())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newProfile(internal.NewProfile(), sharedState).SetDurationNano(uint64(13)) })
}
func TestProfile_PeriodType(t *testing.T) {
ms := NewProfile()
assert.Equal(t, NewValueType(), ms.PeriodType())
ms.orig.PeriodType = *internal.GenTestValueType()
assert.Equal(t, generateTestValueType(), ms.PeriodType())
}
func TestProfile_Period(t *testing.T) {
ms := NewProfile()
assert.Equal(t, int64(0), ms.Period())
ms.SetPeriod(int64(13))
assert.Equal(t, int64(13), ms.Period())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newProfile(internal.NewProfile(), sharedState).SetPeriod(int64(13)) })
}
func TestProfile_ProfileID(t *testing.T) {
ms := NewProfile()
assert.Equal(t, ProfileID(internal.ProfileID([16]byte{})), ms.ProfileID())
testValProfileID := ProfileID(internal.ProfileID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}))
ms.SetProfileID(testValProfileID)
assert.Equal(t, testValProfileID, ms.ProfileID())
}
func TestProfile_DroppedAttributesCount(t *testing.T) {
ms := NewProfile()
assert.Equal(t, uint32(0), ms.DroppedAttributesCount())
ms.SetDroppedAttributesCount(uint32(13))
assert.Equal(t, uint32(13), ms.DroppedAttributesCount())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newProfile(internal.NewProfile(), sharedState).SetDroppedAttributesCount(uint32(13)) })
}
func TestProfile_OriginalPayloadFormat(t *testing.T) {
ms := NewProfile()
assert.Empty(t, ms.OriginalPayloadFormat())
ms.SetOriginalPayloadFormat("test_originalpayloadformat")
assert.Equal(t, "test_originalpayloadformat", ms.OriginalPayloadFormat())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() {
newProfile(internal.NewProfile(), sharedState).SetOriginalPayloadFormat("test_originalpayloadformat")
})
}
func TestProfile_OriginalPayload(t *testing.T) {
ms := NewProfile()
assert.Equal(t, pcommon.NewByteSlice(), ms.OriginalPayload())
ms.orig.OriginalPayload = internal.GenTestByteSlice()
assert.Equal(t, pcommon.ByteSlice(internal.GenTestByteSliceWrapper()), ms.OriginalPayload())
}
func TestProfile_AttributeIndices(t *testing.T) {
ms := NewProfile()
assert.Equal(t, pcommon.NewInt32Slice(), ms.AttributeIndices())
ms.orig.AttributeIndices = internal.GenTestInt32Slice()
assert.Equal(t, pcommon.Int32Slice(internal.GenTestInt32SliceWrapper()), ms.AttributeIndices())
}
func generateTestProfile() Profile {
return newProfile(internal.GenTestProfile(), internal.NewState())
}
================================================
FILE: pdata/pprofile/generated_profiles.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// Profiles is the top-level struct that is propagated through the profiles pipeline.
// Use NewProfiles to create new instance, zero-initialized instance is not valid for use.
//
// This is a reference type, if passed by value and callee modifies it the
// caller will see the modification.
//
// Must use NewProfiles function to create new instances.
// Important: zero-initialized instance is not valid for use.
type Profiles internal.ProfilesWrapper
func newProfiles(orig *internal.ExportProfilesServiceRequest, state *internal.State) Profiles {
return Profiles(internal.NewProfilesWrapper(orig, state))
}
// NewProfiles creates a new empty Profiles.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewProfiles() Profiles {
return newProfiles(internal.NewExportProfilesServiceRequest(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms Profiles) MoveTo(dest Profiles) {
ms.getState().AssertMutable()
dest.getState().AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.getOrig() == dest.getOrig() {
return
}
internal.DeleteExportProfilesServiceRequest(dest.getOrig(), false)
*dest.getOrig(), *ms.getOrig() = *ms.getOrig(), *dest.getOrig()
}
// ResourceProfiles returns the ResourceProfiles associated with this Profiles.
func (ms Profiles) ResourceProfiles() ResourceProfilesSlice {
return newResourceProfilesSlice(&ms.getOrig().ResourceProfiles, ms.getState())
}
// Dictionary returns the dictionary associated with this Profiles.
func (ms Profiles) Dictionary() ProfilesDictionary {
return newProfilesDictionary(&ms.getOrig().Dictionary, ms.getState())
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms Profiles) CopyTo(dest Profiles) {
dest.getState().AssertMutable()
internal.CopyExportProfilesServiceRequest(dest.getOrig(), ms.getOrig())
}
func (ms Profiles) getOrig() *internal.ExportProfilesServiceRequest {
return internal.GetProfilesOrig(internal.ProfilesWrapper(ms))
}
func (ms Profiles) getState() *internal.State {
return internal.GetProfilesState(internal.ProfilesWrapper(ms))
}
================================================
FILE: pdata/pprofile/generated_profiles_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestProfiles_MoveTo(t *testing.T) {
ms := generateTestProfiles()
dest := NewProfiles()
ms.MoveTo(dest)
assert.Equal(t, NewProfiles(), ms)
assert.Equal(t, generateTestProfiles(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestProfiles(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newProfiles(internal.NewExportProfilesServiceRequest(), sharedState)) })
assert.Panics(t, func() { newProfiles(internal.NewExportProfilesServiceRequest(), sharedState).MoveTo(dest) })
}
func TestProfiles_CopyTo(t *testing.T) {
ms := NewProfiles()
orig := NewProfiles()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestProfiles()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newProfiles(internal.NewExportProfilesServiceRequest(), sharedState)) })
}
func TestProfiles_ResourceProfiles(t *testing.T) {
ms := NewProfiles()
assert.Equal(t, NewResourceProfilesSlice(), ms.ResourceProfiles())
ms.getOrig().ResourceProfiles = internal.GenTestResourceProfilesPtrSlice()
assert.Equal(t, generateTestResourceProfilesSlice(), ms.ResourceProfiles())
}
func TestProfiles_Dictionary(t *testing.T) {
ms := NewProfiles()
assert.Equal(t, NewProfilesDictionary(), ms.Dictionary())
ms.getOrig().Dictionary = *internal.GenTestProfilesDictionary()
assert.Equal(t, generateTestProfilesDictionary(), ms.Dictionary())
}
func generateTestProfiles() Profiles {
return newProfiles(internal.GenTestExportProfilesServiceRequest(), internal.NewState())
}
================================================
FILE: pdata/pprofile/generated_profilesdata.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// ProfilesData represents the profiles data that can be stored in persistent storage,
// OR can be embedded by other protocols that transfer OTLP profiles data but do not
// implement the OTLP protocol.
//
// This is a reference type, if passed by value and callee modifies it the
// caller will see the modification.
//
// Must use NewProfilesData function to create new instances.
// Important: zero-initialized instance is not valid for use.
type ProfilesData internal.ProfilesDataWrapper
func newProfilesData(orig *internal.ProfilesData, state *internal.State) ProfilesData {
return ProfilesData(internal.NewProfilesDataWrapper(orig, state))
}
// NewProfilesData creates a new empty ProfilesData.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewProfilesData() ProfilesData {
return newProfilesData(internal.NewProfilesData(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms ProfilesData) MoveTo(dest ProfilesData) {
ms.getState().AssertMutable()
dest.getState().AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.getOrig() == dest.getOrig() {
return
}
internal.DeleteProfilesData(dest.getOrig(), false)
*dest.getOrig(), *ms.getOrig() = *ms.getOrig(), *dest.getOrig()
}
// ResourceProfiles returns the ResourceProfiles associated with this ProfilesData.
func (ms ProfilesData) ResourceProfiles() ResourceProfilesSlice {
return newResourceProfilesSlice(&ms.getOrig().ResourceProfiles, ms.getState())
}
// Dictionary returns the dictionary associated with this ProfilesData.
func (ms ProfilesData) Dictionary() ProfilesDictionary {
return newProfilesDictionary(&ms.getOrig().Dictionary, ms.getState())
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms ProfilesData) CopyTo(dest ProfilesData) {
dest.getState().AssertMutable()
internal.CopyProfilesData(dest.getOrig(), ms.getOrig())
}
func (ms ProfilesData) getOrig() *internal.ProfilesData {
return internal.GetProfilesDataOrig(internal.ProfilesDataWrapper(ms))
}
func (ms ProfilesData) getState() *internal.State {
return internal.GetProfilesDataState(internal.ProfilesDataWrapper(ms))
}
================================================
FILE: pdata/pprofile/generated_profilesdata_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestProfilesData_MoveTo(t *testing.T) {
ms := generateTestProfilesData()
dest := NewProfilesData()
ms.MoveTo(dest)
assert.Equal(t, NewProfilesData(), ms)
assert.Equal(t, generateTestProfilesData(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestProfilesData(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newProfilesData(internal.NewProfilesData(), sharedState)) })
assert.Panics(t, func() { newProfilesData(internal.NewProfilesData(), sharedState).MoveTo(dest) })
}
func TestProfilesData_CopyTo(t *testing.T) {
ms := NewProfilesData()
orig := NewProfilesData()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestProfilesData()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newProfilesData(internal.NewProfilesData(), sharedState)) })
}
func TestProfilesData_ResourceProfiles(t *testing.T) {
ms := NewProfilesData()
assert.Equal(t, NewResourceProfilesSlice(), ms.ResourceProfiles())
ms.getOrig().ResourceProfiles = internal.GenTestResourceProfilesPtrSlice()
assert.Equal(t, generateTestResourceProfilesSlice(), ms.ResourceProfiles())
}
func TestProfilesData_Dictionary(t *testing.T) {
ms := NewProfilesData()
assert.Equal(t, NewProfilesDictionary(), ms.Dictionary())
ms.getOrig().Dictionary = *internal.GenTestProfilesDictionary()
assert.Equal(t, generateTestProfilesDictionary(), ms.Dictionary())
}
func generateTestProfilesData() ProfilesData {
return newProfilesData(internal.GenTestProfilesData(), internal.NewState())
}
================================================
FILE: pdata/pprofile/generated_profilesdictionary.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// ProfilesDictionary is the reference table containing all data shared by profiles across the message being sent.
//
// This is a reference type, if passed by value and callee modifies it the
// caller will see the modification.
//
// Must use NewProfilesDictionary function to create new instances.
// Important: zero-initialized instance is not valid for use.
type ProfilesDictionary struct {
orig *internal.ProfilesDictionary
state *internal.State
}
func newProfilesDictionary(orig *internal.ProfilesDictionary, state *internal.State) ProfilesDictionary {
return ProfilesDictionary{orig: orig, state: state}
}
// NewProfilesDictionary creates a new empty ProfilesDictionary.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewProfilesDictionary() ProfilesDictionary {
return newProfilesDictionary(internal.NewProfilesDictionary(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms ProfilesDictionary) MoveTo(dest ProfilesDictionary) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteProfilesDictionary(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// MappingTable returns the MappingTable associated with this ProfilesDictionary.
func (ms ProfilesDictionary) MappingTable() MappingSlice {
return newMappingSlice(&ms.orig.MappingTable, ms.state)
}
// LocationTable returns the LocationTable associated with this ProfilesDictionary.
func (ms ProfilesDictionary) LocationTable() LocationSlice {
return newLocationSlice(&ms.orig.LocationTable, ms.state)
}
// FunctionTable returns the FunctionTable associated with this ProfilesDictionary.
func (ms ProfilesDictionary) FunctionTable() FunctionSlice {
return newFunctionSlice(&ms.orig.FunctionTable, ms.state)
}
// LinkTable returns the LinkTable associated with this ProfilesDictionary.
func (ms ProfilesDictionary) LinkTable() LinkSlice {
return newLinkSlice(&ms.orig.LinkTable, ms.state)
}
// StringTable returns the StringTable associated with this ProfilesDictionary.
func (ms ProfilesDictionary) StringTable() pcommon.StringSlice {
return pcommon.StringSlice(internal.NewStringSliceWrapper(&ms.orig.StringTable, ms.state))
}
// AttributeTable returns the AttributeTable associated with this ProfilesDictionary.
func (ms ProfilesDictionary) AttributeTable() KeyValueAndUnitSlice {
return newKeyValueAndUnitSlice(&ms.orig.AttributeTable, ms.state)
}
// StackTable returns the StackTable associated with this ProfilesDictionary.
func (ms ProfilesDictionary) StackTable() StackSlice {
return newStackSlice(&ms.orig.StackTable, ms.state)
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms ProfilesDictionary) CopyTo(dest ProfilesDictionary) {
dest.state.AssertMutable()
internal.CopyProfilesDictionary(dest.orig, ms.orig)
}
================================================
FILE: pdata/pprofile/generated_profilesdictionary_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestProfilesDictionary_MoveTo(t *testing.T) {
ms := generateTestProfilesDictionary()
dest := NewProfilesDictionary()
ms.MoveTo(dest)
assert.Equal(t, NewProfilesDictionary(), ms)
assert.Equal(t, generateTestProfilesDictionary(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestProfilesDictionary(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newProfilesDictionary(internal.NewProfilesDictionary(), sharedState)) })
assert.Panics(t, func() { newProfilesDictionary(internal.NewProfilesDictionary(), sharedState).MoveTo(dest) })
}
func TestProfilesDictionary_CopyTo(t *testing.T) {
ms := NewProfilesDictionary()
orig := NewProfilesDictionary()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestProfilesDictionary()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newProfilesDictionary(internal.NewProfilesDictionary(), sharedState)) })
}
func TestProfilesDictionary_MappingTable(t *testing.T) {
ms := NewProfilesDictionary()
assert.Equal(t, NewMappingSlice(), ms.MappingTable())
ms.orig.MappingTable = internal.GenTestMappingPtrSlice()
assert.Equal(t, generateTestMappingSlice(), ms.MappingTable())
}
func TestProfilesDictionary_LocationTable(t *testing.T) {
ms := NewProfilesDictionary()
assert.Equal(t, NewLocationSlice(), ms.LocationTable())
ms.orig.LocationTable = internal.GenTestLocationPtrSlice()
assert.Equal(t, generateTestLocationSlice(), ms.LocationTable())
}
func TestProfilesDictionary_FunctionTable(t *testing.T) {
ms := NewProfilesDictionary()
assert.Equal(t, NewFunctionSlice(), ms.FunctionTable())
ms.orig.FunctionTable = internal.GenTestFunctionPtrSlice()
assert.Equal(t, generateTestFunctionSlice(), ms.FunctionTable())
}
func TestProfilesDictionary_LinkTable(t *testing.T) {
ms := NewProfilesDictionary()
assert.Equal(t, NewLinkSlice(), ms.LinkTable())
ms.orig.LinkTable = internal.GenTestLinkPtrSlice()
assert.Equal(t, generateTestLinkSlice(), ms.LinkTable())
}
func TestProfilesDictionary_StringTable(t *testing.T) {
ms := NewProfilesDictionary()
assert.Equal(t, pcommon.NewStringSlice(), ms.StringTable())
ms.orig.StringTable = internal.GenTestStringSlice()
assert.Equal(t, pcommon.StringSlice(internal.GenTestStringSliceWrapper()), ms.StringTable())
}
func TestProfilesDictionary_AttributeTable(t *testing.T) {
ms := NewProfilesDictionary()
assert.Equal(t, NewKeyValueAndUnitSlice(), ms.AttributeTable())
ms.orig.AttributeTable = internal.GenTestKeyValueAndUnitPtrSlice()
assert.Equal(t, generateTestKeyValueAndUnitSlice(), ms.AttributeTable())
}
func TestProfilesDictionary_StackTable(t *testing.T) {
ms := NewProfilesDictionary()
assert.Equal(t, NewStackSlice(), ms.StackTable())
ms.orig.StackTable = internal.GenTestStackPtrSlice()
assert.Equal(t, generateTestStackSlice(), ms.StackTable())
}
func generateTestProfilesDictionary() ProfilesDictionary {
return newProfilesDictionary(internal.GenTestProfilesDictionary(), internal.NewState())
}
================================================
FILE: pdata/pprofile/generated_profilesslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"iter"
"sort"
"go.opentelemetry.io/collector/pdata/internal"
)
// ProfilesSlice logically represents a slice of Profile.
//
// This is a reference type. If passed by value and callee modifies it, the
// caller will see the modification.
//
// Must use NewProfilesSlice function to create new instances.
// Important: zero-initialized instance is not valid for use.
type ProfilesSlice struct {
orig *[]*internal.Profile
state *internal.State
}
func newProfilesSlice(orig *[]*internal.Profile, state *internal.State) ProfilesSlice {
return ProfilesSlice{orig: orig, state: state}
}
// NewProfilesSlice creates a ProfilesSliceWrapper with 0 elements.
// Can use "EnsureCapacity" to initialize with a given capacity.
func NewProfilesSlice() ProfilesSlice {
orig := []*internal.Profile(nil)
return newProfilesSlice(&orig, internal.NewState())
}
// Len returns the number of elements in the slice.
//
// Returns "0" for a newly instance created with "NewProfilesSlice()".
func (es ProfilesSlice) 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 ProfilesSlice) At(i int) Profile {
return newProfile((*es.orig)[i], es.state)
}
// All returns an iterator over index-value pairs in the slice.
//
// for i, v := range es.All() {
// ... // Do something with index-value pair
// }
func (es ProfilesSlice) All() iter.Seq2[int, Profile] {
return func(yield func(int, Profile) bool) {
for i := 0; i < es.Len(); i++ {
if !yield(i, es.At(i)) {
return
}
}
}
}
// EnsureCapacity is an operation that ensures the slice has at least the specified capacity.
// 1. If the newCap <= cap then no change in capacity.
// 2. If the newCap > cap then the slice capacity will be expanded to equal newCap.
//
// Here is how a new ProfilesSlice can be initialized:
//
// es := NewProfilesSlice()
// es.EnsureCapacity(4)
// for i := 0; i < 4; i++ {
// e := es.AppendEmpty()
// // Here should set all the values for e.
// }
func (es ProfilesSlice) EnsureCapacity(newCap int) {
es.state.AssertMutable()
oldCap := cap(*es.orig)
if newCap <= oldCap {
return
}
newOrig := make([]*internal.Profile, len(*es.orig), newCap)
copy(newOrig, *es.orig)
*es.orig = newOrig
}
// AppendEmpty will append to the end of the slice an empty Profile.
// It returns the newly added Profile.
func (es ProfilesSlice) AppendEmpty() Profile {
es.state.AssertMutable()
*es.orig = append(*es.orig, internal.NewProfile())
return es.At(es.Len() - 1)
}
// MoveAndAppendTo moves all elements from the current slice and appends them to the dest.
// The current slice will be cleared.
func (es ProfilesSlice) MoveAndAppendTo(dest ProfilesSlice) {
es.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if es.orig == dest.orig {
return
}
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
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (es ProfilesSlice) RemoveIf(f func(Profile) bool) {
es.state.AssertMutable()
newLen := 0
for i := 0; i < len(*es.orig); i++ {
if f(es.At(i)) {
internal.DeleteProfile((*es.orig)[i], true)
(*es.orig)[i] = nil
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*es.orig)[newLen] = (*es.orig)[i]
// Cannot delete here since we just move the data(or pointer to data) to a different position in the slice.
(*es.orig)[i] = nil
newLen++
}
*es.orig = (*es.orig)[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (es ProfilesSlice) CopyTo(dest ProfilesSlice) {
dest.state.AssertMutable()
if es.orig == dest.orig {
return
}
*dest.orig = internal.CopyProfilePtrSlice(*dest.orig, *es.orig)
}
// Sort sorts the Profile elements within ProfilesSlice given the
// provided less function so that two instances of ProfilesSlice
// can be compared.
func (es ProfilesSlice) Sort(less func(a, b Profile) bool) {
es.state.AssertMutable()
sort.SliceStable(*es.orig, func(i, j int) bool { return less(es.At(i), es.At(j)) })
}
================================================
FILE: pdata/pprofile/generated_profilesslice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestProfilesSlice(t *testing.T) {
es := NewProfilesSlice()
assert.Equal(t, 0, es.Len())
es = newProfilesSlice(&[]*internal.Profile{}, internal.NewState())
assert.Equal(t, 0, es.Len())
emptyVal := NewProfile()
testVal := generateTestProfile()
for i := 0; i < 7; i++ {
es.AppendEmpty()
assert.Equal(t, emptyVal, es.At(i))
(*es.orig)[i] = internal.GenTestProfile()
assert.Equal(t, testVal, es.At(i))
}
assert.Equal(t, 7, es.Len())
}
func TestProfilesSliceReadOnly(t *testing.T) {
sharedState := internal.NewState()
sharedState.MarkReadOnly()
es := newProfilesSlice(&[]*internal.Profile{}, sharedState)
assert.Equal(t, 0, es.Len())
assert.Panics(t, func() { es.AppendEmpty() })
assert.Panics(t, func() { es.EnsureCapacity(2) })
es2 := NewProfilesSlice()
es.CopyTo(es2)
assert.Panics(t, func() { es2.CopyTo(es) })
assert.Panics(t, func() { es.MoveAndAppendTo(es2) })
assert.Panics(t, func() { es2.MoveAndAppendTo(es) })
}
func TestProfilesSlice_CopyTo(t *testing.T) {
dest := NewProfilesSlice()
src := generateTestProfilesSlice()
src.CopyTo(dest)
assert.Equal(t, generateTestProfilesSlice(), dest)
dest.CopyTo(dest)
assert.Equal(t, generateTestProfilesSlice(), dest)
}
func TestProfilesSlice_EnsureCapacity(t *testing.T) {
es := generateTestProfilesSlice()
// Test ensure smaller capacity.
const ensureSmallLen = 4
es.EnsureCapacity(ensureSmallLen)
assert.Less(t, ensureSmallLen, es.Len())
assert.Equal(t, es.Len(), cap(*es.orig))
assert.Equal(t, generateTestProfilesSlice(), es)
// Test ensure larger capacity
const ensureLargeLen = 9
es.EnsureCapacity(ensureLargeLen)
assert.Less(t, generateTestProfilesSlice().Len(), ensureLargeLen)
assert.Equal(t, ensureLargeLen, cap(*es.orig))
assert.Equal(t, generateTestProfilesSlice(), es)
}
func TestProfilesSlice_MoveAndAppendTo(t *testing.T) {
// Test MoveAndAppendTo to empty
expectedSlice := generateTestProfilesSlice()
dest := NewProfilesSlice()
src := generateTestProfilesSlice()
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestProfilesSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo empty slice
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestProfilesSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo not empty slice
generateTestProfilesSlice().MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
dest.MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
}
func TestProfilesSlice_RemoveIf(t *testing.T) {
// Test RemoveIf on empty slice
emptySlice := NewProfilesSlice()
emptySlice.RemoveIf(func(el Profile) bool {
t.Fail()
return false
})
// Test RemoveIf
filtered := generateTestProfilesSlice()
pos := 0
filtered.RemoveIf(func(el Profile) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, 2, filtered.Len())
}
func TestProfilesSlice_RemoveIfAll(t *testing.T) {
got := generateTestProfilesSlice()
got.RemoveIf(func(el Profile) bool {
return true
})
assert.Equal(t, 0, got.Len())
}
func TestProfilesSliceAll(t *testing.T) {
ms := generateTestProfilesSlice()
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestProfilesSlice_Sort(t *testing.T) {
es := generateTestProfilesSlice()
es.Sort(func(a, b Profile) bool {
return uintptr(unsafe.Pointer(a.orig)) < uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Less(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
es.Sort(func(a, b Profile) bool {
return uintptr(unsafe.Pointer(a.orig)) > uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Greater(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
}
func generateTestProfilesSlice() ProfilesSlice {
ms := NewProfilesSlice()
*ms.orig = internal.GenTestProfilePtrSlice()
return ms
}
================================================
FILE: pdata/pprofile/generated_resourceprofiles.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// ResourceProfiles is a collection of profiles from a Resource.
//
// This is a reference type, if passed by value and callee modifies it the
// caller will see the modification.
//
// Must use NewResourceProfiles function to create new instances.
// Important: zero-initialized instance is not valid for use.
type ResourceProfiles struct {
orig *internal.ResourceProfiles
state *internal.State
}
func newResourceProfiles(orig *internal.ResourceProfiles, state *internal.State) ResourceProfiles {
return ResourceProfiles{orig: orig, state: state}
}
// NewResourceProfiles creates a new empty ResourceProfiles.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewResourceProfiles() ResourceProfiles {
return newResourceProfiles(internal.NewResourceProfiles(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms ResourceProfiles) MoveTo(dest ResourceProfiles) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteResourceProfiles(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// Resource returns the resource associated with this ResourceProfiles.
func (ms ResourceProfiles) Resource() pcommon.Resource {
return pcommon.Resource(internal.NewResourceWrapper(&ms.orig.Resource, ms.state))
}
// ScopeProfiles returns the ScopeProfiles associated with this ResourceProfiles.
func (ms ResourceProfiles) ScopeProfiles() ScopeProfilesSlice {
return newScopeProfilesSlice(&ms.orig.ScopeProfiles, ms.state)
}
// SchemaUrl returns the schemaurl associated with this ResourceProfiles.
func (ms ResourceProfiles) SchemaUrl() string {
return ms.orig.SchemaUrl
}
// SetSchemaUrl replaces the schemaurl associated with this ResourceProfiles.
func (ms ResourceProfiles) SetSchemaUrl(v string) {
ms.state.AssertMutable()
ms.orig.SchemaUrl = v
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms ResourceProfiles) CopyTo(dest ResourceProfiles) {
dest.state.AssertMutable()
internal.CopyResourceProfiles(dest.orig, ms.orig)
}
================================================
FILE: pdata/pprofile/generated_resourceprofiles_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestResourceProfiles_MoveTo(t *testing.T) {
ms := generateTestResourceProfiles()
dest := NewResourceProfiles()
ms.MoveTo(dest)
assert.Equal(t, NewResourceProfiles(), ms)
assert.Equal(t, generateTestResourceProfiles(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestResourceProfiles(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newResourceProfiles(internal.NewResourceProfiles(), sharedState)) })
assert.Panics(t, func() { newResourceProfiles(internal.NewResourceProfiles(), sharedState).MoveTo(dest) })
}
func TestResourceProfiles_CopyTo(t *testing.T) {
ms := NewResourceProfiles()
orig := NewResourceProfiles()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestResourceProfiles()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newResourceProfiles(internal.NewResourceProfiles(), sharedState)) })
}
func TestResourceProfiles_Resource(t *testing.T) {
ms := NewResourceProfiles()
assert.Equal(t, pcommon.NewResource(), ms.Resource())
ms.orig.Resource = *internal.GenTestResource()
assert.Equal(t, pcommon.Resource(internal.GenTestResourceWrapper()), ms.Resource())
}
func TestResourceProfiles_ScopeProfiles(t *testing.T) {
ms := NewResourceProfiles()
assert.Equal(t, NewScopeProfilesSlice(), ms.ScopeProfiles())
ms.orig.ScopeProfiles = internal.GenTestScopeProfilesPtrSlice()
assert.Equal(t, generateTestScopeProfilesSlice(), ms.ScopeProfiles())
}
func TestResourceProfiles_SchemaUrl(t *testing.T) {
ms := NewResourceProfiles()
assert.Empty(t, ms.SchemaUrl())
ms.SetSchemaUrl("test_schemaurl")
assert.Equal(t, "test_schemaurl", ms.SchemaUrl())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() {
newResourceProfiles(internal.NewResourceProfiles(), sharedState).SetSchemaUrl("test_schemaurl")
})
}
func generateTestResourceProfiles() ResourceProfiles {
return newResourceProfiles(internal.GenTestResourceProfiles(), internal.NewState())
}
================================================
FILE: pdata/pprofile/generated_resourceprofilesslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"iter"
"sort"
"go.opentelemetry.io/collector/pdata/internal"
)
// ResourceProfilesSlice logically represents a slice of ResourceProfiles.
//
// This is a reference type. If passed by value and callee modifies it, the
// caller will see the modification.
//
// Must use NewResourceProfilesSlice function to create new instances.
// Important: zero-initialized instance is not valid for use.
type ResourceProfilesSlice struct {
orig *[]*internal.ResourceProfiles
state *internal.State
}
func newResourceProfilesSlice(orig *[]*internal.ResourceProfiles, state *internal.State) ResourceProfilesSlice {
return ResourceProfilesSlice{orig: orig, state: state}
}
// NewResourceProfilesSlice creates a ResourceProfilesSliceWrapper with 0 elements.
// Can use "EnsureCapacity" to initialize with a given capacity.
func NewResourceProfilesSlice() ResourceProfilesSlice {
orig := []*internal.ResourceProfiles(nil)
return newResourceProfilesSlice(&orig, internal.NewState())
}
// Len returns the number of elements in the slice.
//
// Returns "0" for a newly instance created with "NewResourceProfilesSlice()".
func (es ResourceProfilesSlice) 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 ResourceProfilesSlice) At(i int) ResourceProfiles {
return newResourceProfiles((*es.orig)[i], es.state)
}
// All returns an iterator over index-value pairs in the slice.
//
// for i, v := range es.All() {
// ... // Do something with index-value pair
// }
func (es ResourceProfilesSlice) All() iter.Seq2[int, ResourceProfiles] {
return func(yield func(int, ResourceProfiles) bool) {
for i := 0; i < es.Len(); i++ {
if !yield(i, es.At(i)) {
return
}
}
}
}
// EnsureCapacity is an operation that ensures the slice has at least the specified capacity.
// 1. If the newCap <= cap then no change in capacity.
// 2. If the newCap > cap then the slice capacity will be expanded to equal newCap.
//
// Here is how a new ResourceProfilesSlice can be initialized:
//
// es := NewResourceProfilesSlice()
// es.EnsureCapacity(4)
// for i := 0; i < 4; i++ {
// e := es.AppendEmpty()
// // Here should set all the values for e.
// }
func (es ResourceProfilesSlice) EnsureCapacity(newCap int) {
es.state.AssertMutable()
oldCap := cap(*es.orig)
if newCap <= oldCap {
return
}
newOrig := make([]*internal.ResourceProfiles, len(*es.orig), newCap)
copy(newOrig, *es.orig)
*es.orig = newOrig
}
// AppendEmpty will append to the end of the slice an empty ResourceProfiles.
// It returns the newly added ResourceProfiles.
func (es ResourceProfilesSlice) AppendEmpty() ResourceProfiles {
es.state.AssertMutable()
*es.orig = append(*es.orig, internal.NewResourceProfiles())
return es.At(es.Len() - 1)
}
// MoveAndAppendTo moves all elements from the current slice and appends them to the dest.
// The current slice will be cleared.
func (es ResourceProfilesSlice) MoveAndAppendTo(dest ResourceProfilesSlice) {
es.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if es.orig == dest.orig {
return
}
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
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (es ResourceProfilesSlice) RemoveIf(f func(ResourceProfiles) bool) {
es.state.AssertMutable()
newLen := 0
for i := 0; i < len(*es.orig); i++ {
if f(es.At(i)) {
internal.DeleteResourceProfiles((*es.orig)[i], true)
(*es.orig)[i] = nil
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*es.orig)[newLen] = (*es.orig)[i]
// Cannot delete here since we just move the data(or pointer to data) to a different position in the slice.
(*es.orig)[i] = nil
newLen++
}
*es.orig = (*es.orig)[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (es ResourceProfilesSlice) CopyTo(dest ResourceProfilesSlice) {
dest.state.AssertMutable()
if es.orig == dest.orig {
return
}
*dest.orig = internal.CopyResourceProfilesPtrSlice(*dest.orig, *es.orig)
}
// Sort sorts the ResourceProfiles elements within ResourceProfilesSlice given the
// provided less function so that two instances of ResourceProfilesSlice
// can be compared.
func (es ResourceProfilesSlice) Sort(less func(a, b ResourceProfiles) bool) {
es.state.AssertMutable()
sort.SliceStable(*es.orig, func(i, j int) bool { return less(es.At(i), es.At(j)) })
}
================================================
FILE: pdata/pprofile/generated_resourceprofilesslice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestResourceProfilesSlice(t *testing.T) {
es := NewResourceProfilesSlice()
assert.Equal(t, 0, es.Len())
es = newResourceProfilesSlice(&[]*internal.ResourceProfiles{}, internal.NewState())
assert.Equal(t, 0, es.Len())
emptyVal := NewResourceProfiles()
testVal := generateTestResourceProfiles()
for i := 0; i < 7; i++ {
es.AppendEmpty()
assert.Equal(t, emptyVal, es.At(i))
(*es.orig)[i] = internal.GenTestResourceProfiles()
assert.Equal(t, testVal, es.At(i))
}
assert.Equal(t, 7, es.Len())
}
func TestResourceProfilesSliceReadOnly(t *testing.T) {
sharedState := internal.NewState()
sharedState.MarkReadOnly()
es := newResourceProfilesSlice(&[]*internal.ResourceProfiles{}, sharedState)
assert.Equal(t, 0, es.Len())
assert.Panics(t, func() { es.AppendEmpty() })
assert.Panics(t, func() { es.EnsureCapacity(2) })
es2 := NewResourceProfilesSlice()
es.CopyTo(es2)
assert.Panics(t, func() { es2.CopyTo(es) })
assert.Panics(t, func() { es.MoveAndAppendTo(es2) })
assert.Panics(t, func() { es2.MoveAndAppendTo(es) })
}
func TestResourceProfilesSlice_CopyTo(t *testing.T) {
dest := NewResourceProfilesSlice()
src := generateTestResourceProfilesSlice()
src.CopyTo(dest)
assert.Equal(t, generateTestResourceProfilesSlice(), dest)
dest.CopyTo(dest)
assert.Equal(t, generateTestResourceProfilesSlice(), dest)
}
func TestResourceProfilesSlice_EnsureCapacity(t *testing.T) {
es := generateTestResourceProfilesSlice()
// Test ensure smaller capacity.
const ensureSmallLen = 4
es.EnsureCapacity(ensureSmallLen)
assert.Less(t, ensureSmallLen, es.Len())
assert.Equal(t, es.Len(), cap(*es.orig))
assert.Equal(t, generateTestResourceProfilesSlice(), es)
// Test ensure larger capacity
const ensureLargeLen = 9
es.EnsureCapacity(ensureLargeLen)
assert.Less(t, generateTestResourceProfilesSlice().Len(), ensureLargeLen)
assert.Equal(t, ensureLargeLen, cap(*es.orig))
assert.Equal(t, generateTestResourceProfilesSlice(), es)
}
func TestResourceProfilesSlice_MoveAndAppendTo(t *testing.T) {
// Test MoveAndAppendTo to empty
expectedSlice := generateTestResourceProfilesSlice()
dest := NewResourceProfilesSlice()
src := generateTestResourceProfilesSlice()
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestResourceProfilesSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo empty slice
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestResourceProfilesSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo not empty slice
generateTestResourceProfilesSlice().MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
dest.MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
}
func TestResourceProfilesSlice_RemoveIf(t *testing.T) {
// Test RemoveIf on empty slice
emptySlice := NewResourceProfilesSlice()
emptySlice.RemoveIf(func(el ResourceProfiles) bool {
t.Fail()
return false
})
// Test RemoveIf
filtered := generateTestResourceProfilesSlice()
pos := 0
filtered.RemoveIf(func(el ResourceProfiles) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, 2, filtered.Len())
}
func TestResourceProfilesSlice_RemoveIfAll(t *testing.T) {
got := generateTestResourceProfilesSlice()
got.RemoveIf(func(el ResourceProfiles) bool {
return true
})
assert.Equal(t, 0, got.Len())
}
func TestResourceProfilesSliceAll(t *testing.T) {
ms := generateTestResourceProfilesSlice()
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestResourceProfilesSlice_Sort(t *testing.T) {
es := generateTestResourceProfilesSlice()
es.Sort(func(a, b ResourceProfiles) bool {
return uintptr(unsafe.Pointer(a.orig)) < uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Less(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
es.Sort(func(a, b ResourceProfiles) bool {
return uintptr(unsafe.Pointer(a.orig)) > uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Greater(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
}
func generateTestResourceProfilesSlice() ResourceProfilesSlice {
ms := NewResourceProfilesSlice()
*ms.orig = internal.GenTestResourceProfilesPtrSlice()
return ms
}
================================================
FILE: pdata/pprofile/generated_sample.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// Sample represents each record value encountered within a profiled program.
//
// This is a reference type, if passed by value and callee modifies it the
// caller will see the modification.
//
// Must use NewSample function to create new instances.
// Important: zero-initialized instance is not valid for use.
type Sample struct {
orig *internal.Sample
state *internal.State
}
func newSample(orig *internal.Sample, state *internal.State) Sample {
return Sample{orig: orig, state: state}
}
// NewSample creates a new empty Sample.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewSample() Sample {
return newSample(internal.NewSample(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms Sample) MoveTo(dest Sample) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteSample(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// StackIndex returns the stackindex associated with this Sample.
func (ms Sample) StackIndex() int32 {
return ms.orig.StackIndex
}
// SetStackIndex replaces the stackindex associated with this Sample.
func (ms Sample) SetStackIndex(v int32) {
ms.state.AssertMutable()
ms.orig.StackIndex = v
}
// AttributeIndices returns the AttributeIndices associated with this Sample.
func (ms Sample) AttributeIndices() pcommon.Int32Slice {
return pcommon.Int32Slice(internal.NewInt32SliceWrapper(&ms.orig.AttributeIndices, ms.state))
}
// LinkIndex returns the linkindex associated with this Sample.
func (ms Sample) LinkIndex() int32 {
return ms.orig.LinkIndex
}
// SetLinkIndex replaces the linkindex associated with this Sample.
func (ms Sample) SetLinkIndex(v int32) {
ms.state.AssertMutable()
ms.orig.LinkIndex = v
}
// Values returns the Values associated with this Sample.
func (ms Sample) Values() pcommon.Int64Slice {
return pcommon.Int64Slice(internal.NewInt64SliceWrapper(&ms.orig.Values, ms.state))
}
// TimestampsUnixNano returns the TimestampsUnixNano associated with this Sample.
func (ms Sample) TimestampsUnixNano() pcommon.UInt64Slice {
return pcommon.UInt64Slice(internal.NewUInt64SliceWrapper(&ms.orig.TimestampsUnixNano, ms.state))
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms Sample) CopyTo(dest Sample) {
dest.state.AssertMutable()
internal.CopySample(dest.orig, ms.orig)
}
================================================
FILE: pdata/pprofile/generated_sample_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestSample_MoveTo(t *testing.T) {
ms := generateTestSample()
dest := NewSample()
ms.MoveTo(dest)
assert.Equal(t, NewSample(), ms)
assert.Equal(t, generateTestSample(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestSample(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newSample(internal.NewSample(), sharedState)) })
assert.Panics(t, func() { newSample(internal.NewSample(), sharedState).MoveTo(dest) })
}
func TestSample_CopyTo(t *testing.T) {
ms := NewSample()
orig := NewSample()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestSample()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newSample(internal.NewSample(), sharedState)) })
}
func TestSample_StackIndex(t *testing.T) {
ms := NewSample()
assert.Equal(t, int32(0), ms.StackIndex())
ms.SetStackIndex(int32(13))
assert.Equal(t, int32(13), ms.StackIndex())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newSample(internal.NewSample(), sharedState).SetStackIndex(int32(13)) })
}
func TestSample_AttributeIndices(t *testing.T) {
ms := NewSample()
assert.Equal(t, pcommon.NewInt32Slice(), ms.AttributeIndices())
ms.orig.AttributeIndices = internal.GenTestInt32Slice()
assert.Equal(t, pcommon.Int32Slice(internal.GenTestInt32SliceWrapper()), ms.AttributeIndices())
}
func TestSample_LinkIndex(t *testing.T) {
ms := NewSample()
assert.Equal(t, int32(0), ms.LinkIndex())
ms.SetLinkIndex(int32(13))
assert.Equal(t, int32(13), ms.LinkIndex())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newSample(internal.NewSample(), sharedState).SetLinkIndex(int32(13)) })
}
func TestSample_Values(t *testing.T) {
ms := NewSample()
assert.Equal(t, pcommon.NewInt64Slice(), ms.Values())
ms.orig.Values = internal.GenTestInt64Slice()
assert.Equal(t, pcommon.Int64Slice(internal.GenTestInt64SliceWrapper()), ms.Values())
}
func TestSample_TimestampsUnixNano(t *testing.T) {
ms := NewSample()
assert.Equal(t, pcommon.NewUInt64Slice(), ms.TimestampsUnixNano())
ms.orig.TimestampsUnixNano = internal.GenTestUint64Slice()
assert.Equal(t, pcommon.UInt64Slice(internal.GenTestUInt64SliceWrapper()), ms.TimestampsUnixNano())
}
func generateTestSample() Sample {
return newSample(internal.GenTestSample(), internal.NewState())
}
================================================
FILE: pdata/pprofile/generated_sampleslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"iter"
"sort"
"go.opentelemetry.io/collector/pdata/internal"
)
// SampleSlice logically represents a slice of Sample.
//
// This is a reference type. If passed by value and callee modifies it, the
// caller will see the modification.
//
// Must use NewSampleSlice function to create new instances.
// Important: zero-initialized instance is not valid for use.
type SampleSlice struct {
orig *[]*internal.Sample
state *internal.State
}
func newSampleSlice(orig *[]*internal.Sample, state *internal.State) SampleSlice {
return SampleSlice{orig: orig, state: state}
}
// NewSampleSlice creates a SampleSliceWrapper with 0 elements.
// Can use "EnsureCapacity" to initialize with a given capacity.
func NewSampleSlice() SampleSlice {
orig := []*internal.Sample(nil)
return newSampleSlice(&orig, internal.NewState())
}
// Len returns the number of elements in the slice.
//
// Returns "0" for a newly instance created with "NewSampleSlice()".
func (es SampleSlice) 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 SampleSlice) At(i int) Sample {
return newSample((*es.orig)[i], es.state)
}
// All returns an iterator over index-value pairs in the slice.
//
// for i, v := range es.All() {
// ... // Do something with index-value pair
// }
func (es SampleSlice) All() iter.Seq2[int, Sample] {
return func(yield func(int, Sample) bool) {
for i := 0; i < es.Len(); i++ {
if !yield(i, es.At(i)) {
return
}
}
}
}
// EnsureCapacity is an operation that ensures the slice has at least the specified capacity.
// 1. If the newCap <= cap then no change in capacity.
// 2. If the newCap > cap then the slice capacity will be expanded to equal newCap.
//
// Here is how a new SampleSlice can be initialized:
//
// es := NewSampleSlice()
// es.EnsureCapacity(4)
// for i := 0; i < 4; i++ {
// e := es.AppendEmpty()
// // Here should set all the values for e.
// }
func (es SampleSlice) EnsureCapacity(newCap int) {
es.state.AssertMutable()
oldCap := cap(*es.orig)
if newCap <= oldCap {
return
}
newOrig := make([]*internal.Sample, len(*es.orig), newCap)
copy(newOrig, *es.orig)
*es.orig = newOrig
}
// AppendEmpty will append to the end of the slice an empty Sample.
// It returns the newly added Sample.
func (es SampleSlice) AppendEmpty() Sample {
es.state.AssertMutable()
*es.orig = append(*es.orig, internal.NewSample())
return es.At(es.Len() - 1)
}
// MoveAndAppendTo moves all elements from the current slice and appends them to the dest.
// The current slice will be cleared.
func (es SampleSlice) MoveAndAppendTo(dest SampleSlice) {
es.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if es.orig == dest.orig {
return
}
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
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (es SampleSlice) RemoveIf(f func(Sample) bool) {
es.state.AssertMutable()
newLen := 0
for i := 0; i < len(*es.orig); i++ {
if f(es.At(i)) {
internal.DeleteSample((*es.orig)[i], true)
(*es.orig)[i] = nil
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*es.orig)[newLen] = (*es.orig)[i]
// Cannot delete here since we just move the data(or pointer to data) to a different position in the slice.
(*es.orig)[i] = nil
newLen++
}
*es.orig = (*es.orig)[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (es SampleSlice) CopyTo(dest SampleSlice) {
dest.state.AssertMutable()
if es.orig == dest.orig {
return
}
*dest.orig = internal.CopySamplePtrSlice(*dest.orig, *es.orig)
}
// Sort sorts the Sample elements within SampleSlice given the
// provided less function so that two instances of SampleSlice
// can be compared.
func (es SampleSlice) Sort(less func(a, b Sample) bool) {
es.state.AssertMutable()
sort.SliceStable(*es.orig, func(i, j int) bool { return less(es.At(i), es.At(j)) })
}
================================================
FILE: pdata/pprofile/generated_sampleslice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestSampleSlice(t *testing.T) {
es := NewSampleSlice()
assert.Equal(t, 0, es.Len())
es = newSampleSlice(&[]*internal.Sample{}, internal.NewState())
assert.Equal(t, 0, es.Len())
emptyVal := NewSample()
testVal := generateTestSample()
for i := 0; i < 7; i++ {
es.AppendEmpty()
assert.Equal(t, emptyVal, es.At(i))
(*es.orig)[i] = internal.GenTestSample()
assert.Equal(t, testVal, es.At(i))
}
assert.Equal(t, 7, es.Len())
}
func TestSampleSliceReadOnly(t *testing.T) {
sharedState := internal.NewState()
sharedState.MarkReadOnly()
es := newSampleSlice(&[]*internal.Sample{}, sharedState)
assert.Equal(t, 0, es.Len())
assert.Panics(t, func() { es.AppendEmpty() })
assert.Panics(t, func() { es.EnsureCapacity(2) })
es2 := NewSampleSlice()
es.CopyTo(es2)
assert.Panics(t, func() { es2.CopyTo(es) })
assert.Panics(t, func() { es.MoveAndAppendTo(es2) })
assert.Panics(t, func() { es2.MoveAndAppendTo(es) })
}
func TestSampleSlice_CopyTo(t *testing.T) {
dest := NewSampleSlice()
src := generateTestSampleSlice()
src.CopyTo(dest)
assert.Equal(t, generateTestSampleSlice(), dest)
dest.CopyTo(dest)
assert.Equal(t, generateTestSampleSlice(), dest)
}
func TestSampleSlice_EnsureCapacity(t *testing.T) {
es := generateTestSampleSlice()
// Test ensure smaller capacity.
const ensureSmallLen = 4
es.EnsureCapacity(ensureSmallLen)
assert.Less(t, ensureSmallLen, es.Len())
assert.Equal(t, es.Len(), cap(*es.orig))
assert.Equal(t, generateTestSampleSlice(), es)
// Test ensure larger capacity
const ensureLargeLen = 9
es.EnsureCapacity(ensureLargeLen)
assert.Less(t, generateTestSampleSlice().Len(), ensureLargeLen)
assert.Equal(t, ensureLargeLen, cap(*es.orig))
assert.Equal(t, generateTestSampleSlice(), es)
}
func TestSampleSlice_MoveAndAppendTo(t *testing.T) {
// Test MoveAndAppendTo to empty
expectedSlice := generateTestSampleSlice()
dest := NewSampleSlice()
src := generateTestSampleSlice()
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestSampleSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo empty slice
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestSampleSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo not empty slice
generateTestSampleSlice().MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
dest.MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
}
func TestSampleSlice_RemoveIf(t *testing.T) {
// Test RemoveIf on empty slice
emptySlice := NewSampleSlice()
emptySlice.RemoveIf(func(el Sample) bool {
t.Fail()
return false
})
// Test RemoveIf
filtered := generateTestSampleSlice()
pos := 0
filtered.RemoveIf(func(el Sample) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, 2, filtered.Len())
}
func TestSampleSlice_RemoveIfAll(t *testing.T) {
got := generateTestSampleSlice()
got.RemoveIf(func(el Sample) bool {
return true
})
assert.Equal(t, 0, got.Len())
}
func TestSampleSliceAll(t *testing.T) {
ms := generateTestSampleSlice()
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestSampleSlice_Sort(t *testing.T) {
es := generateTestSampleSlice()
es.Sort(func(a, b Sample) bool {
return uintptr(unsafe.Pointer(a.orig)) < uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Less(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
es.Sort(func(a, b Sample) bool {
return uintptr(unsafe.Pointer(a.orig)) > uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Greater(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
}
func generateTestSampleSlice() SampleSlice {
ms := NewSampleSlice()
*ms.orig = internal.GenTestSamplePtrSlice()
return ms
}
================================================
FILE: pdata/pprofile/generated_scopeprofiles.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// ScopeProfiles is a collection of profiles from a LibraryInstrumentation.
//
// This is a reference type, if passed by value and callee modifies it the
// caller will see the modification.
//
// Must use NewScopeProfiles function to create new instances.
// Important: zero-initialized instance is not valid for use.
type ScopeProfiles struct {
orig *internal.ScopeProfiles
state *internal.State
}
func newScopeProfiles(orig *internal.ScopeProfiles, state *internal.State) ScopeProfiles {
return ScopeProfiles{orig: orig, state: state}
}
// NewScopeProfiles creates a new empty ScopeProfiles.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewScopeProfiles() ScopeProfiles {
return newScopeProfiles(internal.NewScopeProfiles(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms ScopeProfiles) MoveTo(dest ScopeProfiles) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteScopeProfiles(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// Scope returns the scope associated with this ScopeProfiles.
func (ms ScopeProfiles) Scope() pcommon.InstrumentationScope {
return pcommon.InstrumentationScope(internal.NewInstrumentationScopeWrapper(&ms.orig.Scope, ms.state))
}
// Profiles returns the Profiles associated with this ScopeProfiles.
func (ms ScopeProfiles) Profiles() ProfilesSlice {
return newProfilesSlice(&ms.orig.Profiles, ms.state)
}
// SchemaUrl returns the schemaurl associated with this ScopeProfiles.
func (ms ScopeProfiles) SchemaUrl() string {
return ms.orig.SchemaUrl
}
// SetSchemaUrl replaces the schemaurl associated with this ScopeProfiles.
func (ms ScopeProfiles) SetSchemaUrl(v string) {
ms.state.AssertMutable()
ms.orig.SchemaUrl = v
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms ScopeProfiles) CopyTo(dest ScopeProfiles) {
dest.state.AssertMutable()
internal.CopyScopeProfiles(dest.orig, ms.orig)
}
================================================
FILE: pdata/pprofile/generated_scopeprofiles_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestScopeProfiles_MoveTo(t *testing.T) {
ms := generateTestScopeProfiles()
dest := NewScopeProfiles()
ms.MoveTo(dest)
assert.Equal(t, NewScopeProfiles(), ms)
assert.Equal(t, generateTestScopeProfiles(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestScopeProfiles(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newScopeProfiles(internal.NewScopeProfiles(), sharedState)) })
assert.Panics(t, func() { newScopeProfiles(internal.NewScopeProfiles(), sharedState).MoveTo(dest) })
}
func TestScopeProfiles_CopyTo(t *testing.T) {
ms := NewScopeProfiles()
orig := NewScopeProfiles()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestScopeProfiles()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newScopeProfiles(internal.NewScopeProfiles(), sharedState)) })
}
func TestScopeProfiles_Scope(t *testing.T) {
ms := NewScopeProfiles()
assert.Equal(t, pcommon.NewInstrumentationScope(), ms.Scope())
ms.orig.Scope = *internal.GenTestInstrumentationScope()
assert.Equal(t, pcommon.InstrumentationScope(internal.GenTestInstrumentationScopeWrapper()), ms.Scope())
}
func TestScopeProfiles_Profiles(t *testing.T) {
ms := NewScopeProfiles()
assert.Equal(t, NewProfilesSlice(), ms.Profiles())
ms.orig.Profiles = internal.GenTestProfilePtrSlice()
assert.Equal(t, generateTestProfilesSlice(), ms.Profiles())
}
func TestScopeProfiles_SchemaUrl(t *testing.T) {
ms := NewScopeProfiles()
assert.Empty(t, ms.SchemaUrl())
ms.SetSchemaUrl("test_schemaurl")
assert.Equal(t, "test_schemaurl", ms.SchemaUrl())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newScopeProfiles(internal.NewScopeProfiles(), sharedState).SetSchemaUrl("test_schemaurl") })
}
func generateTestScopeProfiles() ScopeProfiles {
return newScopeProfiles(internal.GenTestScopeProfiles(), internal.NewState())
}
================================================
FILE: pdata/pprofile/generated_scopeprofilesslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"iter"
"sort"
"go.opentelemetry.io/collector/pdata/internal"
)
// ScopeProfilesSlice logically represents a slice of ScopeProfiles.
//
// This is a reference type. If passed by value and callee modifies it, the
// caller will see the modification.
//
// Must use NewScopeProfilesSlice function to create new instances.
// Important: zero-initialized instance is not valid for use.
type ScopeProfilesSlice struct {
orig *[]*internal.ScopeProfiles
state *internal.State
}
func newScopeProfilesSlice(orig *[]*internal.ScopeProfiles, state *internal.State) ScopeProfilesSlice {
return ScopeProfilesSlice{orig: orig, state: state}
}
// NewScopeProfilesSlice creates a ScopeProfilesSliceWrapper with 0 elements.
// Can use "EnsureCapacity" to initialize with a given capacity.
func NewScopeProfilesSlice() ScopeProfilesSlice {
orig := []*internal.ScopeProfiles(nil)
return newScopeProfilesSlice(&orig, internal.NewState())
}
// Len returns the number of elements in the slice.
//
// Returns "0" for a newly instance created with "NewScopeProfilesSlice()".
func (es ScopeProfilesSlice) 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 ScopeProfilesSlice) At(i int) ScopeProfiles {
return newScopeProfiles((*es.orig)[i], es.state)
}
// All returns an iterator over index-value pairs in the slice.
//
// for i, v := range es.All() {
// ... // Do something with index-value pair
// }
func (es ScopeProfilesSlice) All() iter.Seq2[int, ScopeProfiles] {
return func(yield func(int, ScopeProfiles) bool) {
for i := 0; i < es.Len(); i++ {
if !yield(i, es.At(i)) {
return
}
}
}
}
// EnsureCapacity is an operation that ensures the slice has at least the specified capacity.
// 1. If the newCap <= cap then no change in capacity.
// 2. If the newCap > cap then the slice capacity will be expanded to equal newCap.
//
// Here is how a new ScopeProfilesSlice can be initialized:
//
// es := NewScopeProfilesSlice()
// es.EnsureCapacity(4)
// for i := 0; i < 4; i++ {
// e := es.AppendEmpty()
// // Here should set all the values for e.
// }
func (es ScopeProfilesSlice) EnsureCapacity(newCap int) {
es.state.AssertMutable()
oldCap := cap(*es.orig)
if newCap <= oldCap {
return
}
newOrig := make([]*internal.ScopeProfiles, len(*es.orig), newCap)
copy(newOrig, *es.orig)
*es.orig = newOrig
}
// AppendEmpty will append to the end of the slice an empty ScopeProfiles.
// It returns the newly added ScopeProfiles.
func (es ScopeProfilesSlice) AppendEmpty() ScopeProfiles {
es.state.AssertMutable()
*es.orig = append(*es.orig, internal.NewScopeProfiles())
return es.At(es.Len() - 1)
}
// MoveAndAppendTo moves all elements from the current slice and appends them to the dest.
// The current slice will be cleared.
func (es ScopeProfilesSlice) MoveAndAppendTo(dest ScopeProfilesSlice) {
es.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if es.orig == dest.orig {
return
}
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
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (es ScopeProfilesSlice) RemoveIf(f func(ScopeProfiles) bool) {
es.state.AssertMutable()
newLen := 0
for i := 0; i < len(*es.orig); i++ {
if f(es.At(i)) {
internal.DeleteScopeProfiles((*es.orig)[i], true)
(*es.orig)[i] = nil
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*es.orig)[newLen] = (*es.orig)[i]
// Cannot delete here since we just move the data(or pointer to data) to a different position in the slice.
(*es.orig)[i] = nil
newLen++
}
*es.orig = (*es.orig)[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (es ScopeProfilesSlice) CopyTo(dest ScopeProfilesSlice) {
dest.state.AssertMutable()
if es.orig == dest.orig {
return
}
*dest.orig = internal.CopyScopeProfilesPtrSlice(*dest.orig, *es.orig)
}
// Sort sorts the ScopeProfiles elements within ScopeProfilesSlice given the
// provided less function so that two instances of ScopeProfilesSlice
// can be compared.
func (es ScopeProfilesSlice) Sort(less func(a, b ScopeProfiles) bool) {
es.state.AssertMutable()
sort.SliceStable(*es.orig, func(i, j int) bool { return less(es.At(i), es.At(j)) })
}
================================================
FILE: pdata/pprofile/generated_scopeprofilesslice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestScopeProfilesSlice(t *testing.T) {
es := NewScopeProfilesSlice()
assert.Equal(t, 0, es.Len())
es = newScopeProfilesSlice(&[]*internal.ScopeProfiles{}, internal.NewState())
assert.Equal(t, 0, es.Len())
emptyVal := NewScopeProfiles()
testVal := generateTestScopeProfiles()
for i := 0; i < 7; i++ {
es.AppendEmpty()
assert.Equal(t, emptyVal, es.At(i))
(*es.orig)[i] = internal.GenTestScopeProfiles()
assert.Equal(t, testVal, es.At(i))
}
assert.Equal(t, 7, es.Len())
}
func TestScopeProfilesSliceReadOnly(t *testing.T) {
sharedState := internal.NewState()
sharedState.MarkReadOnly()
es := newScopeProfilesSlice(&[]*internal.ScopeProfiles{}, sharedState)
assert.Equal(t, 0, es.Len())
assert.Panics(t, func() { es.AppendEmpty() })
assert.Panics(t, func() { es.EnsureCapacity(2) })
es2 := NewScopeProfilesSlice()
es.CopyTo(es2)
assert.Panics(t, func() { es2.CopyTo(es) })
assert.Panics(t, func() { es.MoveAndAppendTo(es2) })
assert.Panics(t, func() { es2.MoveAndAppendTo(es) })
}
func TestScopeProfilesSlice_CopyTo(t *testing.T) {
dest := NewScopeProfilesSlice()
src := generateTestScopeProfilesSlice()
src.CopyTo(dest)
assert.Equal(t, generateTestScopeProfilesSlice(), dest)
dest.CopyTo(dest)
assert.Equal(t, generateTestScopeProfilesSlice(), dest)
}
func TestScopeProfilesSlice_EnsureCapacity(t *testing.T) {
es := generateTestScopeProfilesSlice()
// Test ensure smaller capacity.
const ensureSmallLen = 4
es.EnsureCapacity(ensureSmallLen)
assert.Less(t, ensureSmallLen, es.Len())
assert.Equal(t, es.Len(), cap(*es.orig))
assert.Equal(t, generateTestScopeProfilesSlice(), es)
// Test ensure larger capacity
const ensureLargeLen = 9
es.EnsureCapacity(ensureLargeLen)
assert.Less(t, generateTestScopeProfilesSlice().Len(), ensureLargeLen)
assert.Equal(t, ensureLargeLen, cap(*es.orig))
assert.Equal(t, generateTestScopeProfilesSlice(), es)
}
func TestScopeProfilesSlice_MoveAndAppendTo(t *testing.T) {
// Test MoveAndAppendTo to empty
expectedSlice := generateTestScopeProfilesSlice()
dest := NewScopeProfilesSlice()
src := generateTestScopeProfilesSlice()
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestScopeProfilesSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo empty slice
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestScopeProfilesSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo not empty slice
generateTestScopeProfilesSlice().MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
dest.MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
}
func TestScopeProfilesSlice_RemoveIf(t *testing.T) {
// Test RemoveIf on empty slice
emptySlice := NewScopeProfilesSlice()
emptySlice.RemoveIf(func(el ScopeProfiles) bool {
t.Fail()
return false
})
// Test RemoveIf
filtered := generateTestScopeProfilesSlice()
pos := 0
filtered.RemoveIf(func(el ScopeProfiles) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, 2, filtered.Len())
}
func TestScopeProfilesSlice_RemoveIfAll(t *testing.T) {
got := generateTestScopeProfilesSlice()
got.RemoveIf(func(el ScopeProfiles) bool {
return true
})
assert.Equal(t, 0, got.Len())
}
func TestScopeProfilesSliceAll(t *testing.T) {
ms := generateTestScopeProfilesSlice()
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestScopeProfilesSlice_Sort(t *testing.T) {
es := generateTestScopeProfilesSlice()
es.Sort(func(a, b ScopeProfiles) bool {
return uintptr(unsafe.Pointer(a.orig)) < uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Less(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
es.Sort(func(a, b ScopeProfiles) bool {
return uintptr(unsafe.Pointer(a.orig)) > uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Greater(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
}
func generateTestScopeProfilesSlice() ScopeProfilesSlice {
ms := NewScopeProfilesSlice()
*ms.orig = internal.GenTestScopeProfilesPtrSlice()
return ms
}
================================================
FILE: pdata/pprofile/generated_stack.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// Stack represents a stack trace as a list of locations.
// This is a reference type, if passed by value and callee modifies it the
// caller will see the modification.
//
// Must use NewStack function to create new instances.
// Important: zero-initialized instance is not valid for use.
type Stack struct {
orig *internal.Stack
state *internal.State
}
func newStack(orig *internal.Stack, state *internal.State) Stack {
return Stack{orig: orig, state: state}
}
// NewStack creates a new empty Stack.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewStack() Stack {
return newStack(internal.NewStack(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms Stack) MoveTo(dest Stack) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteStack(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// LocationIndices returns the LocationIndices associated with this Stack.
func (ms Stack) LocationIndices() pcommon.Int32Slice {
return pcommon.Int32Slice(internal.NewInt32SliceWrapper(&ms.orig.LocationIndices, ms.state))
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms Stack) CopyTo(dest Stack) {
dest.state.AssertMutable()
internal.CopyStack(dest.orig, ms.orig)
}
================================================
FILE: pdata/pprofile/generated_stack_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestStack_MoveTo(t *testing.T) {
ms := generateTestStack()
dest := NewStack()
ms.MoveTo(dest)
assert.Equal(t, NewStack(), ms)
assert.Equal(t, generateTestStack(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestStack(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newStack(internal.NewStack(), sharedState)) })
assert.Panics(t, func() { newStack(internal.NewStack(), sharedState).MoveTo(dest) })
}
func TestStack_CopyTo(t *testing.T) {
ms := NewStack()
orig := NewStack()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestStack()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newStack(internal.NewStack(), sharedState)) })
}
func TestStack_LocationIndices(t *testing.T) {
ms := NewStack()
assert.Equal(t, pcommon.NewInt32Slice(), ms.LocationIndices())
ms.orig.LocationIndices = internal.GenTestInt32Slice()
assert.Equal(t, pcommon.Int32Slice(internal.GenTestInt32SliceWrapper()), ms.LocationIndices())
}
func generateTestStack() Stack {
return newStack(internal.GenTestStack(), internal.NewState())
}
================================================
FILE: pdata/pprofile/generated_stackslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"iter"
"sort"
"go.opentelemetry.io/collector/pdata/internal"
)
// StackSlice logically represents a slice of Stack.
//
// This is a reference type. If passed by value and callee modifies it, the
// caller will see the modification.
//
// Must use NewStackSlice function to create new instances.
// Important: zero-initialized instance is not valid for use.
type StackSlice struct {
orig *[]*internal.Stack
state *internal.State
}
func newStackSlice(orig *[]*internal.Stack, state *internal.State) StackSlice {
return StackSlice{orig: orig, state: state}
}
// NewStackSlice creates a StackSliceWrapper with 0 elements.
// Can use "EnsureCapacity" to initialize with a given capacity.
func NewStackSlice() StackSlice {
orig := []*internal.Stack(nil)
return newStackSlice(&orig, internal.NewState())
}
// Len returns the number of elements in the slice.
//
// Returns "0" for a newly instance created with "NewStackSlice()".
func (es StackSlice) 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 StackSlice) At(i int) Stack {
return newStack((*es.orig)[i], es.state)
}
// All returns an iterator over index-value pairs in the slice.
//
// for i, v := range es.All() {
// ... // Do something with index-value pair
// }
func (es StackSlice) All() iter.Seq2[int, Stack] {
return func(yield func(int, Stack) bool) {
for i := 0; i < es.Len(); i++ {
if !yield(i, es.At(i)) {
return
}
}
}
}
// EnsureCapacity is an operation that ensures the slice has at least the specified capacity.
// 1. If the newCap <= cap then no change in capacity.
// 2. If the newCap > cap then the slice capacity will be expanded to equal newCap.
//
// Here is how a new StackSlice can be initialized:
//
// es := NewStackSlice()
// es.EnsureCapacity(4)
// for i := 0; i < 4; i++ {
// e := es.AppendEmpty()
// // Here should set all the values for e.
// }
func (es StackSlice) EnsureCapacity(newCap int) {
es.state.AssertMutable()
oldCap := cap(*es.orig)
if newCap <= oldCap {
return
}
newOrig := make([]*internal.Stack, len(*es.orig), newCap)
copy(newOrig, *es.orig)
*es.orig = newOrig
}
// AppendEmpty will append to the end of the slice an empty Stack.
// It returns the newly added Stack.
func (es StackSlice) AppendEmpty() Stack {
es.state.AssertMutable()
*es.orig = append(*es.orig, internal.NewStack())
return es.At(es.Len() - 1)
}
// MoveAndAppendTo moves all elements from the current slice and appends them to the dest.
// The current slice will be cleared.
func (es StackSlice) MoveAndAppendTo(dest StackSlice) {
es.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if es.orig == dest.orig {
return
}
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
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (es StackSlice) RemoveIf(f func(Stack) bool) {
es.state.AssertMutable()
newLen := 0
for i := 0; i < len(*es.orig); i++ {
if f(es.At(i)) {
internal.DeleteStack((*es.orig)[i], true)
(*es.orig)[i] = nil
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*es.orig)[newLen] = (*es.orig)[i]
// Cannot delete here since we just move the data(or pointer to data) to a different position in the slice.
(*es.orig)[i] = nil
newLen++
}
*es.orig = (*es.orig)[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (es StackSlice) CopyTo(dest StackSlice) {
dest.state.AssertMutable()
if es.orig == dest.orig {
return
}
*dest.orig = internal.CopyStackPtrSlice(*dest.orig, *es.orig)
}
// Sort sorts the Stack elements within StackSlice given the
// provided less function so that two instances of StackSlice
// can be compared.
func (es StackSlice) Sort(less func(a, b Stack) bool) {
es.state.AssertMutable()
sort.SliceStable(*es.orig, func(i, j int) bool { return less(es.At(i), es.At(j)) })
}
================================================
FILE: pdata/pprofile/generated_stackslice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestStackSlice(t *testing.T) {
es := NewStackSlice()
assert.Equal(t, 0, es.Len())
es = newStackSlice(&[]*internal.Stack{}, internal.NewState())
assert.Equal(t, 0, es.Len())
emptyVal := NewStack()
testVal := generateTestStack()
for i := 0; i < 7; i++ {
es.AppendEmpty()
assert.Equal(t, emptyVal, es.At(i))
(*es.orig)[i] = internal.GenTestStack()
assert.Equal(t, testVal, es.At(i))
}
assert.Equal(t, 7, es.Len())
}
func TestStackSliceReadOnly(t *testing.T) {
sharedState := internal.NewState()
sharedState.MarkReadOnly()
es := newStackSlice(&[]*internal.Stack{}, sharedState)
assert.Equal(t, 0, es.Len())
assert.Panics(t, func() { es.AppendEmpty() })
assert.Panics(t, func() { es.EnsureCapacity(2) })
es2 := NewStackSlice()
es.CopyTo(es2)
assert.Panics(t, func() { es2.CopyTo(es) })
assert.Panics(t, func() { es.MoveAndAppendTo(es2) })
assert.Panics(t, func() { es2.MoveAndAppendTo(es) })
}
func TestStackSlice_CopyTo(t *testing.T) {
dest := NewStackSlice()
src := generateTestStackSlice()
src.CopyTo(dest)
assert.Equal(t, generateTestStackSlice(), dest)
dest.CopyTo(dest)
assert.Equal(t, generateTestStackSlice(), dest)
}
func TestStackSlice_EnsureCapacity(t *testing.T) {
es := generateTestStackSlice()
// Test ensure smaller capacity.
const ensureSmallLen = 4
es.EnsureCapacity(ensureSmallLen)
assert.Less(t, ensureSmallLen, es.Len())
assert.Equal(t, es.Len(), cap(*es.orig))
assert.Equal(t, generateTestStackSlice(), es)
// Test ensure larger capacity
const ensureLargeLen = 9
es.EnsureCapacity(ensureLargeLen)
assert.Less(t, generateTestStackSlice().Len(), ensureLargeLen)
assert.Equal(t, ensureLargeLen, cap(*es.orig))
assert.Equal(t, generateTestStackSlice(), es)
}
func TestStackSlice_MoveAndAppendTo(t *testing.T) {
// Test MoveAndAppendTo to empty
expectedSlice := generateTestStackSlice()
dest := NewStackSlice()
src := generateTestStackSlice()
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestStackSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo empty slice
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestStackSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo not empty slice
generateTestStackSlice().MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
dest.MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
}
func TestStackSlice_RemoveIf(t *testing.T) {
// Test RemoveIf on empty slice
emptySlice := NewStackSlice()
emptySlice.RemoveIf(func(el Stack) bool {
t.Fail()
return false
})
// Test RemoveIf
filtered := generateTestStackSlice()
pos := 0
filtered.RemoveIf(func(el Stack) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, 2, filtered.Len())
}
func TestStackSlice_RemoveIfAll(t *testing.T) {
got := generateTestStackSlice()
got.RemoveIf(func(el Stack) bool {
return true
})
assert.Equal(t, 0, got.Len())
}
func TestStackSliceAll(t *testing.T) {
ms := generateTestStackSlice()
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestStackSlice_Sort(t *testing.T) {
es := generateTestStackSlice()
es.Sort(func(a, b Stack) bool {
return uintptr(unsafe.Pointer(a.orig)) < uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Less(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
es.Sort(func(a, b Stack) bool {
return uintptr(unsafe.Pointer(a.orig)) > uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Greater(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
}
func generateTestStackSlice() StackSlice {
ms := NewStackSlice()
*ms.orig = internal.GenTestStackPtrSlice()
return ms
}
================================================
FILE: pdata/pprofile/generated_valuetype.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// ValueType describes the type and units of a value.
//
// This is a reference type, if passed by value and callee modifies it the
// caller will see the modification.
//
// Must use NewValueType function to create new instances.
// Important: zero-initialized instance is not valid for use.
type ValueType struct {
orig *internal.ValueType
state *internal.State
}
func newValueType(orig *internal.ValueType, state *internal.State) ValueType {
return ValueType{orig: orig, state: state}
}
// NewValueType creates a new empty ValueType.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewValueType() ValueType {
return newValueType(internal.NewValueType(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms ValueType) MoveTo(dest ValueType) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteValueType(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// TypeStrindex returns the typestrindex associated with this ValueType.
func (ms ValueType) TypeStrindex() int32 {
return ms.orig.TypeStrindex
}
// SetTypeStrindex replaces the typestrindex associated with this ValueType.
func (ms ValueType) SetTypeStrindex(v int32) {
ms.state.AssertMutable()
ms.orig.TypeStrindex = v
}
// UnitStrindex returns the unitstrindex associated with this ValueType.
func (ms ValueType) UnitStrindex() int32 {
return ms.orig.UnitStrindex
}
// SetUnitStrindex replaces the unitstrindex associated with this ValueType.
func (ms ValueType) SetUnitStrindex(v int32) {
ms.state.AssertMutable()
ms.orig.UnitStrindex = v
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms ValueType) CopyTo(dest ValueType) {
dest.state.AssertMutable()
internal.CopyValueType(dest.orig, ms.orig)
}
================================================
FILE: pdata/pprofile/generated_valuetype_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestValueType_MoveTo(t *testing.T) {
ms := generateTestValueType()
dest := NewValueType()
ms.MoveTo(dest)
assert.Equal(t, NewValueType(), ms)
assert.Equal(t, generateTestValueType(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestValueType(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newValueType(internal.NewValueType(), sharedState)) })
assert.Panics(t, func() { newValueType(internal.NewValueType(), sharedState).MoveTo(dest) })
}
func TestValueType_CopyTo(t *testing.T) {
ms := NewValueType()
orig := NewValueType()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestValueType()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newValueType(internal.NewValueType(), sharedState)) })
}
func TestValueType_TypeStrindex(t *testing.T) {
ms := NewValueType()
assert.Equal(t, int32(0), ms.TypeStrindex())
ms.SetTypeStrindex(int32(13))
assert.Equal(t, int32(13), ms.TypeStrindex())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newValueType(internal.NewValueType(), sharedState).SetTypeStrindex(int32(13)) })
}
func TestValueType_UnitStrindex(t *testing.T) {
ms := NewValueType()
assert.Equal(t, int32(0), ms.UnitStrindex())
ms.SetUnitStrindex(int32(13))
assert.Equal(t, int32(13), ms.UnitStrindex())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newValueType(internal.NewValueType(), sharedState).SetUnitStrindex(int32(13)) })
}
func generateTestValueType() ValueType {
return newValueType(internal.GenTestValueType(), internal.NewState())
}
================================================
FILE: pdata/pprofile/generated_valuetypeslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"iter"
"sort"
"go.opentelemetry.io/collector/pdata/internal"
)
// ValueTypeSlice logically represents a slice of ValueType.
//
// This is a reference type. If passed by value and callee modifies it, the
// caller will see the modification.
//
// Must use NewValueTypeSlice function to create new instances.
// Important: zero-initialized instance is not valid for use.
type ValueTypeSlice struct {
orig *[]*internal.ValueType
state *internal.State
}
func newValueTypeSlice(orig *[]*internal.ValueType, state *internal.State) ValueTypeSlice {
return ValueTypeSlice{orig: orig, state: state}
}
// NewValueTypeSlice creates a ValueTypeSliceWrapper with 0 elements.
// Can use "EnsureCapacity" to initialize with a given capacity.
func NewValueTypeSlice() ValueTypeSlice {
orig := []*internal.ValueType(nil)
return newValueTypeSlice(&orig, internal.NewState())
}
// Len returns the number of elements in the slice.
//
// Returns "0" for a newly instance created with "NewValueTypeSlice()".
func (es ValueTypeSlice) 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 ValueTypeSlice) At(i int) ValueType {
return newValueType((*es.orig)[i], es.state)
}
// All returns an iterator over index-value pairs in the slice.
//
// for i, v := range es.All() {
// ... // Do something with index-value pair
// }
func (es ValueTypeSlice) All() iter.Seq2[int, ValueType] {
return func(yield func(int, ValueType) bool) {
for i := 0; i < es.Len(); i++ {
if !yield(i, es.At(i)) {
return
}
}
}
}
// EnsureCapacity is an operation that ensures the slice has at least the specified capacity.
// 1. If the newCap <= cap then no change in capacity.
// 2. If the newCap > cap then the slice capacity will be expanded to equal newCap.
//
// Here is how a new ValueTypeSlice can be initialized:
//
// es := NewValueTypeSlice()
// es.EnsureCapacity(4)
// for i := 0; i < 4; i++ {
// e := es.AppendEmpty()
// // Here should set all the values for e.
// }
func (es ValueTypeSlice) EnsureCapacity(newCap int) {
es.state.AssertMutable()
oldCap := cap(*es.orig)
if newCap <= oldCap {
return
}
newOrig := make([]*internal.ValueType, len(*es.orig), newCap)
copy(newOrig, *es.orig)
*es.orig = newOrig
}
// AppendEmpty will append to the end of the slice an empty ValueType.
// It returns the newly added ValueType.
func (es ValueTypeSlice) AppendEmpty() ValueType {
es.state.AssertMutable()
*es.orig = append(*es.orig, internal.NewValueType())
return es.At(es.Len() - 1)
}
// MoveAndAppendTo moves all elements from the current slice and appends them to the dest.
// The current slice will be cleared.
func (es ValueTypeSlice) MoveAndAppendTo(dest ValueTypeSlice) {
es.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if es.orig == dest.orig {
return
}
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
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (es ValueTypeSlice) RemoveIf(f func(ValueType) bool) {
es.state.AssertMutable()
newLen := 0
for i := 0; i < len(*es.orig); i++ {
if f(es.At(i)) {
internal.DeleteValueType((*es.orig)[i], true)
(*es.orig)[i] = nil
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*es.orig)[newLen] = (*es.orig)[i]
// Cannot delete here since we just move the data(or pointer to data) to a different position in the slice.
(*es.orig)[i] = nil
newLen++
}
*es.orig = (*es.orig)[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (es ValueTypeSlice) CopyTo(dest ValueTypeSlice) {
dest.state.AssertMutable()
if es.orig == dest.orig {
return
}
*dest.orig = internal.CopyValueTypePtrSlice(*dest.orig, *es.orig)
}
// Sort sorts the ValueType elements within ValueTypeSlice given the
// provided less function so that two instances of ValueTypeSlice
// can be compared.
func (es ValueTypeSlice) Sort(less func(a, b ValueType) bool) {
es.state.AssertMutable()
sort.SliceStable(*es.orig, func(i, j int) bool { return less(es.At(i), es.At(j)) })
}
================================================
FILE: pdata/pprofile/generated_valuetypeslice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofile
import (
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestValueTypeSlice(t *testing.T) {
es := NewValueTypeSlice()
assert.Equal(t, 0, es.Len())
es = newValueTypeSlice(&[]*internal.ValueType{}, internal.NewState())
assert.Equal(t, 0, es.Len())
emptyVal := NewValueType()
testVal := generateTestValueType()
for i := 0; i < 7; i++ {
es.AppendEmpty()
assert.Equal(t, emptyVal, es.At(i))
(*es.orig)[i] = internal.GenTestValueType()
assert.Equal(t, testVal, es.At(i))
}
assert.Equal(t, 7, es.Len())
}
func TestValueTypeSliceReadOnly(t *testing.T) {
sharedState := internal.NewState()
sharedState.MarkReadOnly()
es := newValueTypeSlice(&[]*internal.ValueType{}, sharedState)
assert.Equal(t, 0, es.Len())
assert.Panics(t, func() { es.AppendEmpty() })
assert.Panics(t, func() { es.EnsureCapacity(2) })
es2 := NewValueTypeSlice()
es.CopyTo(es2)
assert.Panics(t, func() { es2.CopyTo(es) })
assert.Panics(t, func() { es.MoveAndAppendTo(es2) })
assert.Panics(t, func() { es2.MoveAndAppendTo(es) })
}
func TestValueTypeSlice_CopyTo(t *testing.T) {
dest := NewValueTypeSlice()
src := generateTestValueTypeSlice()
src.CopyTo(dest)
assert.Equal(t, generateTestValueTypeSlice(), dest)
dest.CopyTo(dest)
assert.Equal(t, generateTestValueTypeSlice(), dest)
}
func TestValueTypeSlice_EnsureCapacity(t *testing.T) {
es := generateTestValueTypeSlice()
// Test ensure smaller capacity.
const ensureSmallLen = 4
es.EnsureCapacity(ensureSmallLen)
assert.Less(t, ensureSmallLen, es.Len())
assert.Equal(t, es.Len(), cap(*es.orig))
assert.Equal(t, generateTestValueTypeSlice(), es)
// Test ensure larger capacity
const ensureLargeLen = 9
es.EnsureCapacity(ensureLargeLen)
assert.Less(t, generateTestValueTypeSlice().Len(), ensureLargeLen)
assert.Equal(t, ensureLargeLen, cap(*es.orig))
assert.Equal(t, generateTestValueTypeSlice(), es)
}
func TestValueTypeSlice_MoveAndAppendTo(t *testing.T) {
// Test MoveAndAppendTo to empty
expectedSlice := generateTestValueTypeSlice()
dest := NewValueTypeSlice()
src := generateTestValueTypeSlice()
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestValueTypeSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo empty slice
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestValueTypeSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo not empty slice
generateTestValueTypeSlice().MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
dest.MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
}
func TestValueTypeSlice_RemoveIf(t *testing.T) {
// Test RemoveIf on empty slice
emptySlice := NewValueTypeSlice()
emptySlice.RemoveIf(func(el ValueType) bool {
t.Fail()
return false
})
// Test RemoveIf
filtered := generateTestValueTypeSlice()
pos := 0
filtered.RemoveIf(func(el ValueType) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, 2, filtered.Len())
}
func TestValueTypeSlice_RemoveIfAll(t *testing.T) {
got := generateTestValueTypeSlice()
got.RemoveIf(func(el ValueType) bool {
return true
})
assert.Equal(t, 0, got.Len())
}
func TestValueTypeSliceAll(t *testing.T) {
ms := generateTestValueTypeSlice()
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestValueTypeSlice_Sort(t *testing.T) {
es := generateTestValueTypeSlice()
es.Sort(func(a, b ValueType) bool {
return uintptr(unsafe.Pointer(a.orig)) < uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Less(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
es.Sort(func(a, b ValueType) bool {
return uintptr(unsafe.Pointer(a.orig)) > uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Greater(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
}
func generateTestValueTypeSlice() ValueTypeSlice {
ms := NewValueTypeSlice()
*ms.orig = internal.GenTestValueTypePtrSlice()
return ms
}
================================================
FILE: pdata/pprofile/go.mod
================================================
module go.opentelemetry.io/collector/pdata/pprofile
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/featuregate v1.54.0
go.opentelemetry.io/collector/internal/testutil v0.148.0
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/otel v1.40.0
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0
go.uber.org/goleak v1.3.0
google.golang.org/grpc v1.79.3
google.golang.org/protobuf v1.36.11
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/proto/slim/otlp v1.10.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/pdata => ../
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
================================================
FILE: pdata/pprofile/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: pdata/pprofile/json.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile // import "go.opentelemetry.io/collector/pdata/pprofile"
import (
"slices"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/otlp"
)
// JSONMarshaler marshals pprofile.Profiles to JSON bytes using the OTLP/JSON format.
type JSONMarshaler struct{}
// MarshalProfiles to the OTLP/JSON format.
func (*JSONMarshaler) MarshalProfiles(pd Profiles) ([]byte, error) {
// Convert strings to references for efficient transmission
convertProfilesToReferences(pd)
dest := json.BorrowStream(nil)
defer json.ReturnStream(dest)
pd.getOrig().MarshalJSON(dest)
if dest.Error() != nil {
return nil, dest.Error()
}
return slices.Clone(dest.Buffer()), nil
}
// JSONUnmarshaler unmarshals OTLP/JSON formatted-bytes to pprofile.Profiles.
type JSONUnmarshaler struct{}
// UnmarshalProfiles from OTLP/JSON format into pprofile.Profiles.
func (*JSONUnmarshaler) UnmarshalProfiles(buf []byte) (Profiles, error) {
iter := json.BorrowIterator(buf)
defer json.ReturnIterator(iter)
pd := NewProfiles()
pd.getOrig().UnmarshalJSON(iter)
if iter.Error() != nil {
return Profiles{}, iter.Error()
}
otlp.MigrateProfiles(pd.getOrig().ResourceProfiles)
// Resolve all string_value_ref and key_ref to their actual strings
// so the pdata API works transparently
resolveProfilesReferences(pd)
return pd, nil
}
================================================
FILE: pdata/pprofile/json_references_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile
import (
stdjson "encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// newProfilesWithAttributes creates a Profiles with resource and scope
// attributes for testing reference conversion.
func newProfilesWithAttributes() Profiles {
profiles := NewProfiles()
profiles.Dictionary().StringTable().Append("") // index 0
rp := profiles.ResourceProfiles().AppendEmpty()
rp.Resource().Attributes().PutStr("service.name", "test-service")
rp.Resource().Attributes().PutStr("host.name", "test-host")
sp := rp.ScopeProfiles().AppendEmpty()
sp.Scope().Attributes().PutStr("scope.attr", "scope-value")
return profiles
}
func TestJSONMarshalConvertsToReferences(t *testing.T) {
marshaler := JSONMarshaler{}
jsonBytes, err := marshaler.MarshalProfiles(newProfilesWithAttributes())
require.NoError(t, err)
// Parse the JSON output to verify references were used
var parsed map[string]any
require.NoError(t, stdjson.Unmarshal(jsonBytes, &parsed))
// The dictionary's stringTable should contain the attribute keys and values
dictionary, ok := parsed["dictionary"].(map[string]any)
require.True(t, ok, "JSON output should contain a dictionary object")
stringTable, ok := dictionary["stringTable"].([]any)
require.True(t, ok, "dictionary should contain a stringTable array")
tableStrs := make([]string, len(stringTable))
for i, v := range stringTable {
tableStrs[i], _ = v.(string)
}
assert.Contains(t, tableStrs, "service.name")
assert.Contains(t, tableStrs, "test-service")
assert.Contains(t, tableStrs, "host.name")
assert.Contains(t, tableStrs, "test-host")
assert.Contains(t, tableStrs, "scope.attr")
assert.Contains(t, tableStrs, "scope-value")
}
func TestJSONUnmarshalResolvesReferences(t *testing.T) {
profiles := newProfilesWithAttributes()
// Manually convert to references before marshaling, so the JSON output
// contains key_ref/string_value_ref regardless of whether the JSON
// marshaler itself calls convertProfilesToReferences.
convertProfilesToReferences(profiles)
marshaler := JSONMarshaler{}
jsonBytes, err := marshaler.MarshalProfiles(profiles)
require.NoError(t, err)
// Unmarshal and verify references were resolved
unmarshaler := JSONUnmarshaler{}
restored, err := unmarshaler.UnmarshalProfiles(jsonBytes)
require.NoError(t, err)
rp := restored.ResourceProfiles().At(0)
serviceNameVal, ok := rp.Resource().Attributes().Get("service.name")
assert.True(t, ok, "service.name attribute should be accessible after JSON unmarshal")
assert.Equal(t, "test-service", serviceNameVal.Str())
hostNameVal, ok := rp.Resource().Attributes().Get("host.name")
assert.True(t, ok, "host.name attribute should be accessible after JSON unmarshal")
assert.Equal(t, "test-host", hostNameVal.Str())
sp := rp.ScopeProfiles().At(0)
scopeAttrVal, ok := sp.Scope().Attributes().Get("scope.attr")
assert.True(t, ok, "scope.attr should be accessible after JSON unmarshal")
assert.Equal(t, "scope-value", scopeAttrVal.Str())
}
================================================
FILE: pdata/pprofile/keyvalueandunit.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile // import "go.opentelemetry.io/collector/pdata/pprofile"
import "fmt"
// Equal checks equality with another KeyValueAndUnit
// It assumes both structs refer to the same dictionary.
func (ms KeyValueAndUnit) Equal(val KeyValueAndUnit) bool {
return ms.KeyStrindex() == val.KeyStrindex() &&
ms.UnitStrindex() == val.UnitStrindex() &&
ms.Value().Equal(val.Value())
}
// switchDictionary updates the KeyValueAndUnit, switching its indices from one
// dictionary to another.
func (ms KeyValueAndUnit) switchDictionary(src, dst ProfilesDictionary) error {
if ms.KeyStrindex() > 0 {
if src.StringTable().Len() <= int(ms.KeyStrindex()) {
return fmt.Errorf("invalid key index %d", ms.KeyStrindex())
}
idx, err := SetString(dst.StringTable(), src.StringTable().At(int(ms.KeyStrindex())))
if err != nil {
return fmt.Errorf("couldn't set key: %w", err)
}
ms.SetKeyStrindex(idx)
}
if ms.UnitStrindex() > 0 {
if src.StringTable().Len() <= int(ms.UnitStrindex()) {
return fmt.Errorf("invalid unit index %d", ms.UnitStrindex())
}
idx, err := SetString(dst.StringTable(), src.StringTable().At(int(ms.UnitStrindex())))
if err != nil {
return fmt.Errorf("couldn't set unit: %w", err)
}
ms.SetUnitStrindex(idx)
}
return nil
}
================================================
FILE: pdata/pprofile/keyvalueandunit_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/internal/testutil"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestKeyValueAndUnitEqual(t *testing.T) {
for _, tt := range []struct {
name string
orig KeyValueAndUnit
dest KeyValueAndUnit
want bool
}{
{
name: "empty keyvalueandunit",
orig: NewKeyValueAndUnit(),
dest: NewKeyValueAndUnit(),
want: true,
},
{
name: "non-empty identical keyvalueandunit",
orig: buildKeyValueAndUnit(1, 2, pcommon.NewValueStr("test")),
dest: buildKeyValueAndUnit(1, 2, pcommon.NewValueStr("test")),
want: true,
},
{
name: "with different key index",
orig: buildKeyValueAndUnit(1, 2, pcommon.NewValueStr("test")),
dest: buildKeyValueAndUnit(2, 2, pcommon.NewValueStr("test")),
want: false,
},
{
name: "with different unit index",
orig: buildKeyValueAndUnit(1, 2, pcommon.NewValueStr("test")),
dest: buildKeyValueAndUnit(1, 3, pcommon.NewValueStr("test")),
want: false,
},
{
name: "with different value",
orig: buildKeyValueAndUnit(1, 2, pcommon.NewValueStr("test")),
dest: buildKeyValueAndUnit(1, 2, pcommon.NewValueStr("hello")),
want: false,
},
} {
t.Run(tt.name, func(t *testing.T) {
if tt.want {
assert.True(t, tt.orig.Equal(tt.dest))
} else {
assert.False(t, tt.orig.Equal(tt.dest))
}
})
}
}
func TestKeyValueAndUnitSwitchDictionary(t *testing.T) {
for _, tt := range []struct {
name string
keyValueAndUnit KeyValueAndUnit
src ProfilesDictionary
dst ProfilesDictionary
wantKeyValueAndUnit KeyValueAndUnit
wantDictionary ProfilesDictionary
wantErr error
}{
{
name: "with an empty key value and unit",
keyValueAndUnit: NewKeyValueAndUnit(),
src: NewProfilesDictionary(),
dst: NewProfilesDictionary(),
wantKeyValueAndUnit: NewKeyValueAndUnit(),
wantDictionary: NewProfilesDictionary(),
},
{
name: "with an existing key",
keyValueAndUnit: func() KeyValueAndUnit {
kvu := NewKeyValueAndUnit()
kvu.SetKeyStrindex(1)
return kvu
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
return d
}(),
dst: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "foo")
return d
}(),
wantKeyValueAndUnit: func() KeyValueAndUnit {
kvu := NewKeyValueAndUnit()
kvu.SetKeyStrindex(2)
return kvu
}(),
wantDictionary: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "foo", "test")
return d
}(),
},
{
name: "with a key index that does not match anything",
keyValueAndUnit: func() KeyValueAndUnit {
kvu := NewKeyValueAndUnit()
kvu.SetKeyStrindex(1)
return kvu
}(),
src: NewProfilesDictionary(),
dst: NewProfilesDictionary(),
wantKeyValueAndUnit: func() KeyValueAndUnit {
kvu := NewKeyValueAndUnit()
kvu.SetKeyStrindex(1)
return kvu
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid key index 1"),
},
{
name: "with a key index equal to the source table length (boundary condition)",
keyValueAndUnit: func() KeyValueAndUnit {
kvu := NewKeyValueAndUnit()
kvu.SetKeyStrindex(2)
return kvu
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
return d
}(),
dst: NewProfilesDictionary(),
wantKeyValueAndUnit: func() KeyValueAndUnit {
kvu := NewKeyValueAndUnit()
kvu.SetKeyStrindex(2)
return kvu
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid key index 2"),
},
{
name: "with an existing unit",
keyValueAndUnit: func() KeyValueAndUnit {
kvu := NewKeyValueAndUnit()
kvu.SetUnitStrindex(1)
return kvu
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
return d
}(),
dst: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "foo")
return d
}(),
wantKeyValueAndUnit: func() KeyValueAndUnit {
kvu := NewKeyValueAndUnit()
kvu.SetUnitStrindex(2)
return kvu
}(),
wantDictionary: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "foo", "test")
return d
}(),
},
{
name: "with a unit index that does not match anything",
keyValueAndUnit: func() KeyValueAndUnit {
kvu := NewKeyValueAndUnit()
kvu.SetUnitStrindex(1)
return kvu
}(),
src: NewProfilesDictionary(),
dst: NewProfilesDictionary(),
wantKeyValueAndUnit: func() KeyValueAndUnit {
kvu := NewKeyValueAndUnit()
kvu.SetUnitStrindex(1)
return kvu
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid unit index 1"),
},
{
name: "with a unit index equal to the source table length (boundary condition)",
keyValueAndUnit: func() KeyValueAndUnit {
kvu := NewKeyValueAndUnit()
kvu.SetUnitStrindex(2)
return kvu
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
return d
}(),
dst: NewProfilesDictionary(),
wantKeyValueAndUnit: func() KeyValueAndUnit {
kvu := NewKeyValueAndUnit()
kvu.SetUnitStrindex(2)
return kvu
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid unit index 2"),
},
} {
t.Run(tt.name, func(t *testing.T) {
kvu := tt.keyValueAndUnit
dst := tt.dst
err := kvu.switchDictionary(tt.src, dst)
if tt.wantErr == nil {
require.NoError(t, err)
} else {
require.Equal(t, tt.wantErr, err)
}
assert.Equal(t, tt.wantKeyValueAndUnit, kvu)
assert.Equal(t, tt.wantDictionary, dst)
})
}
}
func BenchmarkKeyValueAndUnitSwitchDictionary(b *testing.B) {
testutil.SkipMemoryBench(b)
kvu := NewKeyValueAndUnit()
kvu.SetKeyStrindex(1)
src := NewProfilesDictionary()
src.StringTable().Append("", "test")
b.ReportAllocs()
for b.Loop() {
b.StopTimer()
dst := NewProfilesDictionary()
b.StartTimer()
_ = kvu.switchDictionary(src, dst)
}
}
func buildKeyValueAndUnit(keyIdx, unitIdx int32, val pcommon.Value) KeyValueAndUnit {
kvu := NewKeyValueAndUnit()
kvu.SetKeyStrindex(keyIdx)
kvu.SetUnitStrindex(unitIdx)
val.CopyTo(kvu.Value())
return kvu
}
================================================
FILE: pdata/pprofile/line.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile // import "go.opentelemetry.io/collector/pdata/pprofile"
import "fmt"
// Equal checks equality with another LineSlice
func (l LineSlice) Equal(val LineSlice) bool {
if l.Len() != val.Len() {
return false
}
for i := range l.Len() {
if !l.At(i).Equal(val.At(i)) {
return false
}
}
return true
}
// Equal checks equality with another Line
func (l Line) Equal(val Line) bool {
return l.Column() == val.Column() &&
l.FunctionIndex() == val.FunctionIndex() &&
l.Line() == val.Line()
}
// switchDictionary updates the Line, switching its indices from one
// dictionary to another.
func (l Line) switchDictionary(src, dst ProfilesDictionary) error {
if l.FunctionIndex() > 0 {
if src.FunctionTable().Len() <= int(l.FunctionIndex()) {
return fmt.Errorf("invalid function index %d", l.FunctionIndex())
}
fn := src.FunctionTable().At(int(l.FunctionIndex()))
idx, err := SetFunction(dst.FunctionTable(), fn)
if err != nil {
return fmt.Errorf("couldn't set function: %w", err)
}
l.SetFunctionIndex(idx)
}
return nil
}
================================================
FILE: pdata/pprofile/line_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/internal/testutil"
)
func TestLineSliceEqual(t *testing.T) {
for _, tt := range []struct {
name string
orig LineSlice
dest LineSlice
want bool
}{
{
name: "with empty slices",
orig: NewLineSlice(),
dest: NewLineSlice(),
want: true,
},
{
name: "with non-empty equal slices",
orig: func() LineSlice {
ls := NewLineSlice()
ls.AppendEmpty().SetLine(1)
return ls
}(),
dest: func() LineSlice {
ls := NewLineSlice()
ls.AppendEmpty().SetLine(1)
return ls
}(),
want: true,
},
{
name: "with different lengths",
orig: func() LineSlice {
ls := NewLineSlice()
ls.AppendEmpty()
return ls
}(),
dest: NewLineSlice(),
want: false,
},
{
name: "with non-equal slices",
orig: func() LineSlice {
ls := NewLineSlice()
ls.AppendEmpty().SetLine(2)
return ls
}(),
dest: func() LineSlice {
ls := NewLineSlice()
ls.AppendEmpty().SetLine(1)
return ls
}(),
want: false,
},
} {
t.Run(tt.name, func(t *testing.T) {
if tt.want {
assert.True(t, tt.orig.Equal(tt.dest))
} else {
assert.False(t, tt.orig.Equal(tt.dest))
}
})
}
}
func TestLineEqual(t *testing.T) {
for _, tt := range []struct {
name string
orig Line
dest Line
want bool
}{
{
name: "with empty lines",
orig: NewLine(),
dest: NewLine(),
want: true,
},
{
name: "with non-empty lines",
orig: buildLine(1, 2, 3),
dest: buildLine(1, 2, 3),
want: true,
},
{
name: "with non-equal column",
orig: buildLine(1, 2, 3),
dest: buildLine(2, 2, 3),
want: false,
},
{
name: "with non-equal function index",
orig: buildLine(1, 2, 3),
dest: buildLine(1, 3, 3),
want: false,
},
{
name: "with non-equal line",
orig: buildLine(1, 2, 3),
dest: buildLine(1, 2, 4),
want: false,
},
} {
t.Run(tt.name, func(t *testing.T) {
if tt.want {
assert.True(t, tt.orig.Equal(tt.dest))
} else {
assert.False(t, tt.orig.Equal(tt.dest))
}
})
}
}
func TestLineSwitchDictionary(t *testing.T) {
for _, tt := range []struct {
name string
line Line
src ProfilesDictionary
dst ProfilesDictionary
wantLine Line
wantDictionary ProfilesDictionary
wantErr error
}{
{
name: "with an empty line",
line: NewLine(),
src: NewProfilesDictionary(),
dst: NewProfilesDictionary(),
wantLine: NewLine(),
wantDictionary: NewProfilesDictionary(),
},
{
name: "with an existing function",
line: func() Line {
l := NewLine()
l.SetFunctionIndex(1)
return l
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
d.FunctionTable().AppendEmpty()
f := d.FunctionTable().AppendEmpty()
f.SetNameStrindex(1)
return d
}(),
dst: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
d.FunctionTable().AppendEmpty()
f := d.FunctionTable().AppendEmpty()
f.SetNameStrindex(1)
return d
}(),
wantLine: func() Line {
l := NewLine()
l.SetFunctionIndex(1)
return l
}(),
wantDictionary: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
d.FunctionTable().AppendEmpty()
f := d.FunctionTable().AppendEmpty()
f.SetNameStrindex(1)
return d
}(),
},
{
name: "with a function index that does not match anything",
line: func() Line {
l := NewLine()
l.SetFunctionIndex(1)
return l
}(),
src: NewProfilesDictionary(),
dst: NewProfilesDictionary(),
wantLine: func() Line {
l := NewLine()
l.SetFunctionIndex(1)
return l
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid function index 1"),
},
{
name: "with a function index equal to the source table length (boundary condition)",
line: func() Line {
l := NewLine()
l.SetFunctionIndex(2)
return l
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.FunctionTable().AppendEmpty()
d.FunctionTable().AppendEmpty() // Length 2: indices 0,1 valid
return d
}(),
dst: NewProfilesDictionary(),
wantLine: func() Line {
l := NewLine()
l.SetFunctionIndex(2)
return l
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid function index 2"),
},
} {
t.Run(tt.name, func(t *testing.T) {
line := tt.line
dst := tt.dst
err := line.switchDictionary(tt.src, dst)
if tt.wantErr == nil {
require.NoError(t, err)
} else {
require.Equal(t, tt.wantErr, err)
}
assert.Equal(t, tt.wantLine, line)
assert.Equal(t, tt.wantDictionary, dst)
})
}
}
func BenchmarkLineSwitchDictionary(b *testing.B) {
testutil.SkipMemoryBench(b)
l := NewLine()
l.SetFunctionIndex(1)
src := NewProfilesDictionary()
src.StringTable().Append("", "test")
src.FunctionTable().AppendEmpty()
src.FunctionTable().AppendEmpty().SetNameStrindex(1)
b.ReportAllocs()
for b.Loop() {
b.StopTimer()
dst := NewProfilesDictionary()
b.StartTimer()
_ = l.switchDictionary(src, dst)
}
}
func buildLine(col int64, funcIdx int32, line int64) Line {
l := NewLine()
l.SetColumn(col)
l.SetFunctionIndex(funcIdx)
l.SetLine(line)
return l
}
================================================
FILE: pdata/pprofile/link.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile // import "go.opentelemetry.io/collector/pdata/pprofile"
// Equal checks equality with another Link
func (ms Link) Equal(val Link) bool {
return ms.TraceID() == val.TraceID() &&
ms.SpanID() == val.SpanID()
}
================================================
FILE: pdata/pprofile/link_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestLinkEqual(t *testing.T) {
for _, tt := range []struct {
name string
orig Link
dest Link
want bool
}{
{
name: "empty links",
orig: NewLink(),
dest: NewLink(),
want: true,
},
{
name: "non-empty identical links",
orig: buildLink(
pcommon.TraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}),
pcommon.SpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8}),
),
dest: buildLink(
pcommon.TraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}),
pcommon.SpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8}),
),
want: true,
},
{
name: "with different trace IDs",
orig: buildLink(
pcommon.TraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}),
pcommon.SpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8}),
),
dest: buildLink(
pcommon.TraceID([16]byte{8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8}),
pcommon.SpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8}),
),
want: false,
},
{
name: "with different span IDs",
orig: buildLink(
pcommon.TraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}),
pcommon.SpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8}),
),
dest: buildLink(
pcommon.TraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}),
pcommon.SpanID([8]byte{8, 7, 6, 5, 4, 3, 2, 1}),
),
want: false,
},
} {
t.Run(tt.name, func(t *testing.T) {
if tt.want {
assert.True(t, tt.orig.Equal(tt.dest))
} else {
assert.False(t, tt.orig.Equal(tt.dest))
}
})
}
}
func buildLink(traceID pcommon.TraceID, spanID pcommon.SpanID) Link {
l := NewLink()
l.SetTraceID(traceID)
l.SetSpanID(spanID)
return l
}
================================================
FILE: pdata/pprofile/links.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile // import "go.opentelemetry.io/collector/pdata/pprofile"
import (
"errors"
"math"
)
var errTooManyLinkTableEntries = errors.New("too many entries in LinkTable")
// SetLink updates a LinkTable, adding or providing a value and returns its
// index.
func SetLink(table LinkSlice, li Link) (int32, error) {
for j, l := range table.All() {
if l.Equal(li) {
if j > math.MaxInt32 {
return 0, errTooManyLinkTableEntries
}
return int32(j), nil
}
}
if table.Len() >= math.MaxInt32 {
return 0, errTooManyLinkTableEntries
}
li.CopyTo(table.AppendEmpty())
return int32(table.Len() - 1), nil
}
================================================
FILE: pdata/pprofile/links_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/internal/testutil"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestSetLink(t *testing.T) {
table := NewLinkSlice()
l := NewLink()
l.SetTraceID(pcommon.TraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}))
l2 := NewLink()
l.SetTraceID(pcommon.TraceID([16]byte{2, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 2}))
// Put a first link
idx, err := SetLink(table, l)
require.NoError(t, err)
assert.Equal(t, 1, table.Len())
assert.Equal(t, int32(0), idx)
// Put the same link
// This should be a no-op.
idx, err = SetLink(table, l)
require.NoError(t, err)
assert.Equal(t, 1, table.Len())
assert.Equal(t, int32(0), idx)
// Set a new link
// This sets the index and adds to the table.
idx, err = SetLink(table, l2)
require.NoError(t, err)
assert.Equal(t, 2, table.Len())
assert.Equal(t, int32(table.Len()-1), idx)
// Set an existing link
idx, err = SetLink(table, l)
require.NoError(t, err)
assert.Equal(t, 2, table.Len())
assert.Equal(t, int32(0), idx)
// Set another existing link
idx, err = SetLink(table, l2)
require.NoError(t, err)
assert.Equal(t, 2, table.Len())
assert.Equal(t, int32(table.Len()-1), idx)
}
func BenchmarkSetLink(b *testing.B) {
testutil.SkipMemoryBench(b)
for _, bb := range []struct {
name string
link Link
runBefore func(*testing.B, LinkSlice)
}{
{
name: "with a new link",
link: NewLink(),
},
{
name: "with an existing link",
link: func() Link {
l := NewLink()
l.SetTraceID(pcommon.NewTraceIDEmpty())
return l
}(),
runBefore: func(_ *testing.B, table LinkSlice) {
l := table.AppendEmpty()
l.SetTraceID(pcommon.NewTraceIDEmpty())
},
},
{
name: "with a duplicate link",
link: NewLink(),
runBefore: func(b *testing.B, table LinkSlice) {
_, err := SetLink(table, NewLink())
require.NoError(b, err)
},
},
{
name: "with a hundred links to loop through",
link: func() Link {
l := NewLink()
l.SetTraceID(pcommon.NewTraceIDEmpty())
return l
}(),
runBefore: func(_ *testing.B, table LinkSlice) {
for range 100 {
table.AppendEmpty()
}
},
},
} {
b.Run(bb.name, func(b *testing.B) {
table := NewLinkSlice()
if bb.runBefore != nil {
bb.runBefore(b, table)
}
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
_, _ = SetLink(table, bb.link)
}
})
}
}
================================================
FILE: pdata/pprofile/location.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile // import "go.opentelemetry.io/collector/pdata/pprofile"
import "fmt"
// Equal checks equality with another Location
func (ms Location) Equal(val Location) bool {
return ms.MappingIndex() == val.MappingIndex() &&
ms.Address() == val.Address() &&
ms.AttributeIndices().Equal(val.AttributeIndices()) &&
ms.Lines().Equal(val.Lines())
}
// switchDictionary updates the Location, switching its indices from one
// dictionary to another.
func (ms Location) switchDictionary(src, dst ProfilesDictionary) error {
if ms.MappingIndex() > 0 {
if src.MappingTable().Len() <= int(ms.MappingIndex()) {
return fmt.Errorf("invalid mapping index %d", ms.MappingIndex())
}
mapping := src.MappingTable().At(int(ms.MappingIndex()))
idx, err := SetMapping(dst.MappingTable(), mapping)
if err != nil {
return fmt.Errorf("couldn't set mapping: %w", err)
}
ms.SetMappingIndex(idx)
}
for i, v := range ms.AttributeIndices().All() {
if src.AttributeTable().Len() <= int(v) {
return fmt.Errorf("invalid attribute index %d", v)
}
attr := src.AttributeTable().At(int(v))
idx, err := SetAttribute(dst.AttributeTable(), attr)
if err != nil {
return fmt.Errorf("couldn't set attribute %d: %w", i, err)
}
ms.AttributeIndices().SetAt(i, idx)
}
for i, v := range ms.Lines().All() {
err := v.switchDictionary(src, dst)
if err != nil {
return fmt.Errorf("couldn't switch dictionary for line %d: %w", i, err)
}
}
return nil
}
================================================
FILE: pdata/pprofile/location_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/internal/testutil"
)
func TestLocationEqual(t *testing.T) {
for _, tt := range []struct {
name string
orig Location
dest Location
want bool
}{
{
name: "empty locations",
orig: NewLocation(),
dest: NewLocation(),
want: true,
},
{
name: "non-empty locations",
orig: buildLocation(1, 2, []int32{3}, buildLine(1, 2, 3)),
dest: buildLocation(1, 2, []int32{3}, buildLine(1, 2, 3)),
want: true,
},
{
name: "with non-equal mapping index",
orig: buildLocation(1, 2, []int32{3}, buildLine(1, 2, 3)),
dest: buildLocation(2, 2, []int32{3}, buildLine(1, 2, 3)),
want: false,
},
{
name: "with non-equal address",
orig: buildLocation(1, 2, []int32{3}, buildLine(1, 2, 3)),
dest: buildLocation(1, 3, []int32{3}, buildLine(1, 2, 3)),
want: false,
},
{
name: "with non-equal attribute indices",
orig: buildLocation(1, 2, []int32{3}, buildLine(1, 2, 3)),
dest: buildLocation(1, 2, []int32{5}, buildLine(1, 2, 3)),
want: false,
},
{
name: "with non-equal lines",
orig: buildLocation(1, 2, []int32{3}, buildLine(4, 5, 6)),
dest: buildLocation(1, 2, []int32{3}, buildLine(1, 2, 3)),
want: false,
},
} {
t.Run(tt.name, func(t *testing.T) {
if tt.want {
assert.True(t, tt.orig.Equal(tt.dest))
} else {
assert.False(t, tt.orig.Equal(tt.dest))
}
})
}
}
func TestLocationSwitchDictionary(t *testing.T) {
for _, tt := range []struct {
name string
location Location
src ProfilesDictionary
dst ProfilesDictionary
wantLocation Location
wantDictionary ProfilesDictionary
wantErr error
}{
{
name: "with an empty location",
location: NewLocation(),
src: NewProfilesDictionary(),
dst: NewProfilesDictionary(),
wantLocation: NewLocation(),
wantDictionary: NewProfilesDictionary(),
},
{
name: "with an existing mapping",
location: func() Location {
l := NewLocation()
l.SetMappingIndex(1)
return l
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
d.MappingTable().AppendEmpty()
m := d.MappingTable().AppendEmpty()
m.SetFilenameStrindex(1)
return d
}(),
dst: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
d.MappingTable().AppendEmpty()
m := d.MappingTable().AppendEmpty()
m.SetFilenameStrindex(1)
return d
}(),
wantLocation: func() Location {
l := NewLocation()
l.SetMappingIndex(1)
return l
}(),
wantDictionary: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
d.MappingTable().AppendEmpty()
m := d.MappingTable().AppendEmpty()
m.SetFilenameStrindex(1)
return d
}(),
},
{
name: "with a mapping that cannot be found",
location: func() Location {
l := NewLocation()
l.SetMappingIndex(1)
return l
}(),
src: NewProfilesDictionary(),
dst: NewProfilesDictionary(),
wantLocation: func() Location {
l := NewLocation()
l.SetMappingIndex(1)
return l
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid mapping index 1"),
},
{
name: "with a mapping index equal to the source table length (boundary condition)",
location: func() Location {
l := NewLocation()
l.SetMappingIndex(2)
return l
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.MappingTable().AppendEmpty()
d.MappingTable().AppendEmpty()
return d
}(),
dst: NewProfilesDictionary(),
wantLocation: func() Location {
l := NewLocation()
l.SetMappingIndex(2)
return l
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid mapping index 2"),
},
{
name: "with an existing attribute",
location: func() Location {
l := NewLocation()
l.AttributeIndices().Append(1)
return l
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
d.AttributeTable().AppendEmpty()
a := d.AttributeTable().AppendEmpty()
a.SetKeyStrindex(1)
return d
}(),
dst: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
d.AttributeTable().AppendEmpty()
a := d.AttributeTable().AppendEmpty()
a.SetKeyStrindex(1)
return d
}(),
wantLocation: func() Location {
l := NewLocation()
l.AttributeIndices().Append(1)
return l
}(),
wantDictionary: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
d.AttributeTable().AppendEmpty()
a := d.AttributeTable().AppendEmpty()
a.SetKeyStrindex(1)
return d
}(),
},
{
name: "with an attribute index that does not match anything",
location: func() Location {
l := NewLocation()
l.AttributeIndices().Append(1)
return l
}(),
src: NewProfilesDictionary(),
dst: NewProfilesDictionary(),
wantLocation: func() Location {
l := NewLocation()
l.AttributeIndices().Append(1)
return l
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid attribute index 1"),
},
{
name: "with an attribute index equal to the source table length (boundary condition)",
location: func() Location {
l := NewLocation()
l.AttributeIndices().Append(2)
return l
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.AttributeTable().AppendEmpty()
d.AttributeTable().AppendEmpty()
return d
}(),
dst: NewProfilesDictionary(),
wantLocation: func() Location {
l := NewLocation()
l.AttributeIndices().Append(2)
return l
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid attribute index 2"),
},
{
name: "with an existing line",
location: func() Location {
l := NewLocation()
l.Lines().AppendEmpty().SetFunctionIndex(1)
return l
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
d.FunctionTable().AppendEmpty()
f := d.FunctionTable().AppendEmpty()
f.SetNameStrindex(1)
return d
}(),
dst: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
d.FunctionTable().AppendEmpty()
f := d.FunctionTable().AppendEmpty()
f.SetNameStrindex(1)
return d
}(),
wantLocation: func() Location {
l := NewLocation()
l.Lines().AppendEmpty().SetFunctionIndex(1)
return l
}(),
wantDictionary: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
d.FunctionTable().AppendEmpty()
f := d.FunctionTable().AppendEmpty()
f.SetNameStrindex(1)
return d
}(),
},
} {
t.Run(tt.name, func(t *testing.T) {
l := tt.location
dst := tt.dst
err := l.switchDictionary(tt.src, dst)
if tt.wantErr == nil {
require.NoError(t, err)
} else {
require.Equal(t, tt.wantErr, err)
}
assert.Equal(t, tt.wantLocation, l)
assert.Equal(t, tt.wantDictionary, dst)
})
}
}
func BenchmarkLocationSwitchDictionary(b *testing.B) {
testutil.SkipMemoryBench(b)
l := NewLocation()
l.AttributeIndices().Append(1, 2)
src := NewProfilesDictionary()
src.StringTable().Append("", "test")
src.AttributeTable().AppendEmpty()
src.AttributeTable().AppendEmpty().SetKeyStrindex(1)
src.AttributeTable().AppendEmpty().SetKeyStrindex(2)
b.ReportAllocs()
for b.Loop() {
b.StopTimer()
dst := NewProfilesDictionary()
dst.StringTable().Append("", "foo")
dst.AttributeTable().AppendEmpty()
dst.AttributeTable().AppendEmpty().SetKeyStrindex(1)
b.StartTimer()
_ = l.switchDictionary(src, dst)
}
}
func buildLocation(mapIdx int32, addr uint64, attrIdxs []int32, line Line) Location {
l := NewLocation()
l.SetMappingIndex(mapIdx)
l.SetAddress(addr)
l.AttributeIndices().FromRaw(attrIdxs)
line.MoveTo(l.Lines().AppendEmpty())
return l
}
================================================
FILE: pdata/pprofile/locations.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile // import "go.opentelemetry.io/collector/pdata/pprofile"
import (
"errors"
"math"
)
// FromLocationIndices builds a slice containing all the locations of a Stack.
// Updates made to the returned map will not be applied back to the Stack.
func FromLocationIndices(table LocationSlice, record Stack) LocationSlice {
m := NewLocationSlice()
m.EnsureCapacity(record.LocationIndices().Len())
for _, idx := range record.LocationIndices().All() {
l := table.At(int(idx))
l.CopyTo(m.AppendEmpty())
}
return m
}
var errTooManyLocationTableEntries = errors.New("too many entries in LocationTable")
// SetLocation updates a LocationTable, adding or providing a value and returns
// its index.
func SetLocation(table LocationSlice, loc Location) (int32, error) {
for j, a := range table.All() {
if a.Equal(loc) {
if j > math.MaxInt32 {
return 0, errTooManyLocationTableEntries
}
return int32(j), nil
}
}
if table.Len() >= math.MaxInt32 {
return 0, errTooManyLocationTableEntries
}
loc.CopyTo(table.AppendEmpty())
return int32(table.Len() - 1), nil
}
================================================
FILE: pdata/pprofile/locations_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/internal/testutil"
)
func TestFromLocationIndices(t *testing.T) {
table := NewLocationSlice()
table.AppendEmpty().SetAddress(1)
table.AppendEmpty().SetAddress(2)
stack := NewStack()
locs := FromLocationIndices(table, stack)
assert.Equal(t, locs, NewLocationSlice())
// Add a location
stack.LocationIndices().Append(0)
locs = FromLocationIndices(table, stack)
tLoc := NewLocationSlice()
tLoc.AppendEmpty().SetAddress(1)
assert.Equal(t, tLoc, locs)
// Add another location
stack.LocationIndices().Append(1)
locs = FromLocationIndices(table, stack)
assert.Equal(t, table, locs)
}
func TestSetLocation(t *testing.T) {
table := NewLocationSlice()
l := NewLocation()
l.SetAddress(1)
l2 := NewLocation()
l2.SetAddress(2)
// Put a first value
idx, err := SetLocation(table, l)
require.NoError(t, err)
assert.Equal(t, 1, table.Len())
assert.Equal(t, int32(0), idx)
// Put the same string
// This should be a no-op.
idx, err = SetLocation(table, l)
require.NoError(t, err)
assert.Equal(t, 1, table.Len())
assert.Equal(t, int32(0), idx)
// Set a new value
// This sets the index and adds to the table.
idx, err = SetLocation(table, l2)
require.NoError(t, err)
assert.Equal(t, 2, table.Len())
assert.Equal(t, int32(table.Len()-1), idx)
// Set an existing value
idx, err = SetLocation(table, l)
require.NoError(t, err)
assert.Equal(t, 2, table.Len())
assert.Equal(t, int32(0), idx)
// Set another existing value
idx, err = SetLocation(table, l2)
require.NoError(t, err)
assert.Equal(t, 2, table.Len())
assert.Equal(t, int32(table.Len()-1), idx)
}
func BenchmarkFromLocationIndices(b *testing.B) {
table := NewLocationSlice()
for i := range 100 {
table.AppendEmpty().SetAddress(uint64(i))
}
obj := NewStack()
for i := range int32(50) {
obj.LocationIndices().Append(2*i + 1)
}
b.ReportAllocs()
for b.Loop() {
_ = FromLocationIndices(table, obj)
}
}
func BenchmarkSetLocation(b *testing.B) {
testutil.SkipMemoryBench(b)
for _, bb := range []struct {
name string
location Location
runBefore func(*testing.B, LocationSlice)
}{
{
name: "with a new location",
location: NewLocation(),
},
{
name: "with an existing location",
location: func() Location {
l := NewLocation()
l.SetAddress(1)
return l
}(),
runBefore: func(_ *testing.B, table LocationSlice) {
l := table.AppendEmpty()
l.SetAddress(1)
},
},
{
name: "with a duplicate location",
location: NewLocation(),
runBefore: func(_ *testing.B, table LocationSlice) {
_, err := SetLocation(table, NewLocation())
require.NoError(b, err)
},
},
{
name: "with a hundred locations to loop through",
location: func() Location {
l := NewLocation()
l.SetMappingIndex(1)
return l
}(),
runBefore: func(_ *testing.B, table LocationSlice) {
for i := range 100 {
l := table.AppendEmpty()
l.SetAddress(uint64(i))
}
l := table.AppendEmpty()
l.SetMappingIndex(1)
},
},
} {
b.Run(bb.name, func(b *testing.B) {
table := NewLocationSlice()
if bb.runBefore != nil {
bb.runBefore(b, table)
}
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
_, _ = SetLocation(table, bb.location)
}
})
}
}
================================================
FILE: pdata/pprofile/mapping.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile // import "go.opentelemetry.io/collector/pdata/pprofile"
import "fmt"
// Equal checks equality with another Mapping
func (ms Mapping) Equal(val Mapping) bool {
return ms.MemoryStart() == val.MemoryStart() &&
ms.MemoryLimit() == val.MemoryLimit() &&
ms.FileOffset() == val.FileOffset() &&
ms.FilenameStrindex() == val.FilenameStrindex() &&
ms.AttributeIndices().Equal(val.AttributeIndices())
}
// switchDictionary updates the Mapping, switching its indices from one
// dictionary to another.
func (ms Mapping) switchDictionary(src, dst ProfilesDictionary) error {
if ms.FilenameStrindex() > 0 {
if src.StringTable().Len() <= int(ms.FilenameStrindex()) {
return fmt.Errorf("invalid filename index %d", ms.FilenameStrindex())
}
idx, err := SetString(dst.StringTable(), src.StringTable().At(int(ms.FilenameStrindex())))
if err != nil {
return fmt.Errorf("couldn't set filename: %w", err)
}
ms.SetFilenameStrindex(idx)
}
for i, v := range ms.AttributeIndices().All() {
if src.AttributeTable().Len() <= int(v) {
return fmt.Errorf("invalid attribute index %d", v)
}
attr := src.AttributeTable().At(int(v))
idx, err := SetAttribute(dst.AttributeTable(), attr)
if err != nil {
return fmt.Errorf("couldn't set attribute %d: %w", i, err)
}
ms.AttributeIndices().SetAt(i, idx)
}
return nil
}
================================================
FILE: pdata/pprofile/mapping_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/internal/testutil"
)
func TestMappingEqual(t *testing.T) {
for _, tt := range []struct {
name string
orig Mapping
dest Mapping
want bool
}{
{
name: "empty mappings",
orig: NewMapping(),
dest: NewMapping(),
want: true,
},
{
name: "non-empty identical mappings",
orig: buildMapping(1, 2, 3, 4, []int32{1, 2}),
dest: buildMapping(1, 2, 3, 4, []int32{1, 2}),
want: true,
},
{
name: "with different MemoryStart",
orig: buildMapping(1, 2, 3, 4, []int32{1, 2}),
dest: buildMapping(2, 2, 3, 4, []int32{1, 2}),
want: false,
},
{
name: "with different MemoryLimit",
orig: buildMapping(1, 2, 3, 4, []int32{1, 2}),
dest: buildMapping(1, 3, 3, 4, []int32{1, 2}),
want: false,
},
{
name: "with different FileOffset",
orig: buildMapping(1, 2, 3, 4, []int32{1, 2}),
dest: buildMapping(1, 2, 4, 4, []int32{1, 2}),
want: false,
},
{
name: "with different FilenameStrindex",
orig: buildMapping(1, 2, 3, 4, []int32{1, 2}),
dest: buildMapping(1, 2, 3, 5, []int32{1, 2}),
want: false,
},
{
name: "with different AttributeIndices",
orig: buildMapping(1, 2, 3, 4, []int32{1, 2}),
dest: buildMapping(1, 2, 3, 4, []int32{1, 3}),
want: false,
},
} {
t.Run(tt.name, func(t *testing.T) {
if tt.want {
assert.True(t, tt.orig.Equal(tt.dest))
} else {
assert.False(t, tt.orig.Equal(tt.dest))
}
})
}
}
func TestMappingSwitchDictionary(t *testing.T) {
for _, tt := range []struct {
name string
mapping Mapping
src ProfilesDictionary
dst ProfilesDictionary
wantMapping Mapping
wantDictionary ProfilesDictionary
wantErr error
}{
{
name: "with an empty mapping",
mapping: NewMapping(),
src: NewProfilesDictionary(),
dst: NewProfilesDictionary(),
wantMapping: NewMapping(),
wantDictionary: NewProfilesDictionary(),
},
{
name: "with an existing filename",
mapping: func() Mapping {
m := NewMapping()
m.SetFilenameStrindex(1)
return m
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
return d
}(),
dst: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "foo")
return d
}(),
wantMapping: func() Mapping {
m := NewMapping()
m.SetFilenameStrindex(2)
return m
}(),
wantDictionary: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "foo", "test")
return d
}(),
},
{
name: "with a filename index that does not match anything",
mapping: func() Mapping {
m := NewMapping()
m.SetFilenameStrindex(1)
return m
}(),
src: NewProfilesDictionary(),
dst: NewProfilesDictionary(),
wantMapping: func() Mapping {
m := NewMapping()
m.SetFilenameStrindex(1)
return m
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid filename index 1"),
},
{
name: "with a filename index equal to the source table length (boundary condition)",
mapping: func() Mapping {
m := NewMapping()
m.SetFilenameStrindex(2)
return m
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
return d
}(),
dst: NewProfilesDictionary(),
wantMapping: func() Mapping {
m := NewMapping()
m.SetFilenameStrindex(2)
return m
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid filename index 2"),
},
{
name: "with an existing attribute",
mapping: func() Mapping {
m := NewMapping()
m.AttributeIndices().Append(1)
return m
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
d.AttributeTable().AppendEmpty()
a := d.AttributeTable().AppendEmpty()
a.SetKeyStrindex(1)
return d
}(),
dst: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
d.AttributeTable().AppendEmpty()
a := d.AttributeTable().AppendEmpty()
a.SetKeyStrindex(1)
return d
}(),
wantMapping: func() Mapping {
m := NewMapping()
m.AttributeIndices().Append(1)
return m
}(),
wantDictionary: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
d.AttributeTable().AppendEmpty()
a := d.AttributeTable().AppendEmpty()
a.SetKeyStrindex(1)
return d
}(),
},
{
name: "with an attribute index that does not match anything",
mapping: func() Mapping {
m := NewMapping()
m.AttributeIndices().Append(1)
return m
}(),
src: NewProfilesDictionary(),
dst: NewProfilesDictionary(),
wantMapping: func() Mapping {
m := NewMapping()
m.AttributeIndices().Append(1)
return m
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid attribute index 1"),
},
{
name: "with an attribute index equal to the source table length (boundary condition)",
mapping: func() Mapping {
m := NewMapping()
m.AttributeIndices().Append(2)
return m
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.AttributeTable().AppendEmpty()
d.AttributeTable().AppendEmpty()
return d
}(),
dst: NewProfilesDictionary(),
wantMapping: func() Mapping {
m := NewMapping()
m.AttributeIndices().Append(2)
return m
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid attribute index 2"),
},
} {
t.Run(tt.name, func(t *testing.T) {
m := tt.mapping
dst := tt.dst
err := m.switchDictionary(tt.src, dst)
if tt.wantErr == nil {
require.NoError(t, err)
} else {
require.Equal(t, tt.wantErr, err)
}
assert.Equal(t, tt.wantMapping, m)
assert.Equal(t, tt.wantDictionary, dst)
})
}
}
func BenchmarkMappingSwitchDictionary(b *testing.B) {
testutil.SkipMemoryBench(b)
m := NewMapping()
m.AttributeIndices().Append(1, 2)
src := NewProfilesDictionary()
src.StringTable().Append("", "test", "foo")
src.AttributeTable().AppendEmpty()
src.AttributeTable().AppendEmpty().SetKeyStrindex(1)
src.AttributeTable().AppendEmpty().SetKeyStrindex(2)
b.ReportAllocs()
for b.Loop() {
b.StopTimer()
dst := NewProfilesDictionary()
dst.StringTable().Append("", "foo")
dst.AttributeTable().AppendEmpty()
dst.AttributeTable().AppendEmpty().SetKeyStrindex(1)
b.StartTimer()
_ = m.switchDictionary(src, dst)
}
}
func buildMapping(memStart, memLimit, fileOffset uint64, filenameIdx int32, attrIdxs []int32) Mapping {
m := NewMapping()
m.SetMemoryStart(memStart)
m.SetMemoryLimit(memLimit)
m.SetFileOffset(fileOffset)
m.SetFilenameStrindex(filenameIdx)
m.AttributeIndices().FromRaw(attrIdxs)
return m
}
================================================
FILE: pdata/pprofile/mappings.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile // import "go.opentelemetry.io/collector/pdata/pprofile"
import (
"errors"
"math"
)
var errTooManyMappingTableEntries = errors.New("too many entries in MappingTable")
// SetMapping updates a MappingTable, adding or providing a value and returns
// its index.
func SetMapping(table MappingSlice, ma Mapping) (int32, error) {
for j, m := range table.All() {
if m.Equal(ma) {
if j > math.MaxInt32 {
return 0, errTooManyMappingTableEntries
}
return int32(j), nil
}
}
if table.Len() >= math.MaxInt32 {
return 0, errTooManyMappingTableEntries
}
ma.CopyTo(table.AppendEmpty())
return int32(table.Len() - 1), nil
}
================================================
FILE: pdata/pprofile/mappings_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/internal/testutil"
)
func TestSetMapping(t *testing.T) {
table := NewMappingSlice()
m := NewMapping()
m.SetMemoryLimit(1)
m2 := NewMapping()
m2.SetMemoryLimit(2)
// Put a first mapping
idx, err := SetMapping(table, m)
require.NoError(t, err)
assert.Equal(t, 1, table.Len())
assert.Equal(t, int32(0), idx)
// Put the same mapping
// This should be a no-op.
idx, err = SetMapping(table, m)
require.NoError(t, err)
assert.Equal(t, 1, table.Len())
assert.Equal(t, int32(0), idx)
// Set a new mapping
// This sets the index and adds to the table.
idx, err = SetMapping(table, m2)
require.NoError(t, err)
assert.Equal(t, 2, table.Len())
assert.Equal(t, int32(table.Len()-1), idx)
// Set an existing mapping
idx, err = SetMapping(table, m)
require.NoError(t, err)
assert.Equal(t, 2, table.Len())
assert.Equal(t, int32(0), idx)
// Set another existing mapping
idx, err = SetMapping(table, m2)
require.NoError(t, err)
assert.Equal(t, 2, table.Len())
assert.Equal(t, int32(table.Len()-1), idx)
}
func BenchmarkSetMapping(b *testing.B) {
testutil.SkipMemoryBench(b)
for _, bb := range []struct {
name string
mapping Mapping
runBefore func(*testing.B, MappingSlice)
}{
{
name: "with a new mapping",
mapping: NewMapping(),
},
{
name: "with an existing mapping",
mapping: func() Mapping {
m := NewMapping()
m.SetMemoryLimit(1)
return m
}(),
runBefore: func(_ *testing.B, table MappingSlice) {
m := table.AppendEmpty()
m.SetMemoryLimit(1)
},
},
{
name: "with a duplicate mapping",
mapping: NewMapping(),
runBefore: func(_ *testing.B, table MappingSlice) {
_, err := SetMapping(table, NewMapping())
require.NoError(b, err)
},
},
{
name: "with a hundred mappings to loop through",
mapping: func() Mapping {
m := NewMapping()
m.SetMemoryLimit(1)
return m
}(),
runBefore: func(_ *testing.B, table MappingSlice) {
for i := range 100 {
m := table.AppendEmpty()
m.SetMemoryLimit(uint64(i))
}
},
},
} {
b.Run(bb.name, func(b *testing.B) {
table := NewMappingSlice()
if bb.runBefore != nil {
bb.runBefore(b, table)
}
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
_, _ = SetMapping(table, bb.mapping)
}
})
}
}
================================================
FILE: pdata/pprofile/metadata.yaml
================================================
type: pprofile
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pdata
codeowners:
active:
- mx-psi
- dmathieu
stability:
alpha: [profiles]
================================================
FILE: pdata/pprofile/pb.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile // import "go.opentelemetry.io/collector/pdata/pprofile"
var _ MarshalSizer = (*ProtoMarshaler)(nil)
type ProtoMarshaler struct{}
func (e *ProtoMarshaler) MarshalProfiles(pd Profiles) ([]byte, error) {
// Convert strings to references for efficient transmission
convertProfilesToReferences(pd)
size := pd.getOrig().SizeProto()
buf := make([]byte, size)
_ = pd.getOrig().MarshalProto(buf)
return buf, nil
}
func (e *ProtoMarshaler) ProfilesSize(pd Profiles) int {
return pd.getOrig().SizeProto()
}
func (e *ProtoMarshaler) ResourceProfilesSize(pd ResourceProfiles) int {
return pd.orig.SizeProto()
}
func (e *ProtoMarshaler) ScopeProfilesSize(pd ScopeProfiles) int {
return pd.orig.SizeProto()
}
func (e *ProtoMarshaler) ProfileSize(pd Profile) int {
return pd.orig.SizeProto()
}
type ProtoUnmarshaler struct{}
func (d *ProtoUnmarshaler) UnmarshalProfiles(buf []byte) (Profiles, error) {
pd := NewProfiles()
err := pd.getOrig().UnmarshalProto(buf)
if err != nil {
return Profiles{}, err
}
// Resolve all string_value_ref and key_ref to their actual strings
// so the pdata API works transparently
resolveProfilesReferences(pd)
return pd, nil
}
================================================
FILE: pdata/pprofile/pb_references_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestMarshalUnmarshalWithReferences(t *testing.T) {
profiles := NewProfiles()
dict := profiles.Dictionary()
dict.StringTable().Append("") // index 0, required empty string
rp := profiles.ResourceProfiles().AppendEmpty()
rp.Resource().Attributes().PutStr("service.name", "test-service")
rp.Resource().Attributes().PutStr("host.name", "test-host")
sp := rp.ScopeProfiles().AppendEmpty()
sp.Scope().SetName("test-scope")
sp.Scope().Attributes().PutStr("scope.attr", "scope-value")
profile := sp.Profiles().AppendEmpty()
profile.SetProfileID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})
// Marshal to proto bytes
marshaler := ProtoMarshaler{}
bytes, err := marshaler.MarshalProfiles(profiles)
require.NoError(t, err)
require.NotEmpty(t, bytes)
// Verify that string table was populated (should have more than just the empty string)
assert.Greater(t, dict.StringTable().Len(), 1, "String table should be populated during marshal")
// Verify references were created in the resource attributes
mapOrig := internal.GetMapOrig(internal.MapWrapper(rp.Resource().Attributes()))
foundRef := false
for i := 0; i < len(*mapOrig); i++ {
kv := (*mapOrig)[i]
if kv.KeyStrindex != 0 {
foundRef = true
break
}
// Check if value is a string reference
if ref, ok := kv.Value.Value.(*internal.AnyValue_StringValueStrindex); ok && ref.StringValueStrindex != 0 {
foundRef = true
break
}
}
assert.True(t, foundRef, "At least one reference should be created in attributes")
// Unmarshal from proto bytes
unmarshaler := ProtoUnmarshaler{}
profiles2, err := unmarshaler.UnmarshalProfiles(bytes)
require.NoError(t, err)
// Verify that the API works correctly - attributes should be accessible as strings
rp2 := profiles2.ResourceProfiles().At(0)
serviceNameVal, ok := rp2.Resource().Attributes().Get("service.name")
assert.True(t, ok, "service.name attribute should exist")
assert.Equal(t, "test-service", serviceNameVal.Str(), "service.name should be resolved to string")
hostNameVal, ok := rp2.Resource().Attributes().Get("host.name")
assert.True(t, ok, "host.name attribute should exist")
assert.Equal(t, "test-host", hostNameVal.Str(), "host.name should be resolved to string")
sp2 := rp2.ScopeProfiles().At(0)
scopeAttrVal, ok := sp2.Scope().Attributes().Get("scope.attr")
assert.True(t, ok, "scope.attr attribute should exist")
assert.Equal(t, "scope-value", scopeAttrVal.Str(), "scope.attr should be resolved to string")
// Verify the string table is preserved
dict2 := profiles2.Dictionary()
assert.Greater(t, dict2.StringTable().Len(), 1, "String table should be preserved after unmarshal")
}
func TestMarshalUnmarshalNestedValues(t *testing.T) {
profiles := NewProfiles()
dict := profiles.Dictionary()
dict.StringTable().Append("") // index 0
rp := profiles.ResourceProfiles().AppendEmpty()
attrs := rp.Resource().Attributes()
kvlist := attrs.PutEmptyMap("nested.map")
kvlist.PutStr("inner.key1", "inner.value1")
kvlist.PutStr("inner.key2", "inner.value2")
arr := attrs.PutEmptySlice("string.array")
arr.AppendEmpty().SetStr("string1")
arr.AppendEmpty().SetStr("string2")
arr.AppendEmpty().SetStr("string3")
// Marshal and unmarshal
marshaler := ProtoMarshaler{}
bytes, err := marshaler.MarshalProfiles(profiles)
require.NoError(t, err)
unmarshaler := ProtoUnmarshaler{}
profiles2, err := unmarshaler.UnmarshalProfiles(bytes)
require.NoError(t, err)
// Verify nested map values are accessible
rp2 := profiles2.ResourceProfiles().At(0)
kvlist2, ok := rp2.Resource().Attributes().Get("nested.map")
assert.True(t, ok)
assert.Equal(t, pcommon.ValueTypeMap, kvlist2.Type())
innerMap := kvlist2.Map()
innerVal1, ok := innerMap.Get("inner.key1")
assert.True(t, ok)
assert.Equal(t, "inner.value1", innerVal1.Str())
innerVal2, ok := innerMap.Get("inner.key2")
assert.True(t, ok)
assert.Equal(t, "inner.value2", innerVal2.Str())
// Verify array values are accessible
arr2, ok := rp2.Resource().Attributes().Get("string.array")
assert.True(t, ok)
assert.Equal(t, pcommon.ValueTypeSlice, arr2.Type())
slice := arr2.Slice()
assert.Equal(t, 3, slice.Len())
assert.Equal(t, "string1", slice.At(0).Str())
assert.Equal(t, "string2", slice.At(1).Str())
assert.Equal(t, "string3", slice.At(2).Str())
}
func TestRoundTripWithReferences(t *testing.T) {
original := NewProfiles()
dict := original.Dictionary()
dict.StringTable().Append("")
for i := range 3 {
rp := original.ResourceProfiles().AppendEmpty()
rp.Resource().Attributes().PutStr("resource.id", "resource-"+string(rune('A'+i)))
for j := range 2 {
sp := rp.ScopeProfiles().AppendEmpty()
sp.Scope().SetName("scope-" + string(rune('X'+j)))
sp.Scope().Attributes().PutStr("scope.version", "1.0.0")
profile := sp.Profiles().AppendEmpty()
profile.SetProfileID([16]byte{byte(i), byte(j)})
}
}
// Marshal
marshaler := ProtoMarshaler{}
bytes, err := marshaler.MarshalProfiles(original)
require.NoError(t, err)
// Unmarshal
unmarshaler := ProtoUnmarshaler{}
restored, err := unmarshaler.UnmarshalProfiles(bytes)
require.NoError(t, err)
// Verify structure is preserved
assert.Equal(t, 3, restored.ResourceProfiles().Len())
for i := range 3 {
rp := restored.ResourceProfiles().At(i)
resourceID, ok := rp.Resource().Attributes().Get("resource.id")
assert.True(t, ok)
assert.Equal(t, "resource-"+string(rune('A'+i)), resourceID.Str())
assert.Equal(t, 2, rp.ScopeProfiles().Len())
for j := range 2 {
sp := rp.ScopeProfiles().At(j)
assert.Equal(t, "scope-"+string(rune('X'+j)), sp.Scope().Name())
scopeVersion, ok := sp.Scope().Attributes().Get("scope.version")
assert.True(t, ok)
assert.Equal(t, "1.0.0", scopeVersion.Str())
assert.Equal(t, 1, sp.Profiles().Len())
}
}
// Verify the string table deduplication worked
// We should have fewer strings than if everything was duplicated
dictRestored := restored.Dictionary()
// At minimum we have: "", "resource.id", "resource-A", "resource-B", "resource-C",
// "scope.version", "1.0.0" = 7 entries
// May have more due to scope names
assert.LessOrEqual(t, dictRestored.StringTable().Len(), 7,
"String table should deduplicate strings efficiently")
}
================================================
FILE: pdata/pprofile/pb_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile
import (
"fmt"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
semconv "go.opentelemetry.io/otel/semconv/v1.38.0"
gootlpprofiles "go.opentelemetry.io/proto/slim/otlp/profiles/v1development"
goproto "google.golang.org/protobuf/proto"
)
func TestProfilesProtoWireCompatibility(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 Profiles as pdata struct.
td := generateTestProfiles()
// Marshal its underlying ProtoBuf to wire.
marshaler := &ProtoMarshaler{}
wire1, err := marshaler.MarshalProfiles(td)
require.NoError(t, err)
assert.NotNil(t, wire1)
// Unmarshal from the wire to OTLP Protobuf in goproto's representation.
var goprotoMessage gootlpprofiles.ProfilesData
err = goproto.Unmarshal(wire1, &goprotoMessage)
require.NoError(t, err)
// Marshal to the wire again.
wire2, err := goproto.Marshal(&goprotoMessage)
require.NoError(t, err)
assert.NotNil(t, wire2)
// Unmarshal from the wire into gogoproto's representation.
var td2 Profiles
unmarshaler := &ProtoUnmarshaler{}
td2, err = unmarshaler.UnmarshalProfiles(wire2)
require.NoError(t, err)
// After unmarshal, td2 will have resolved references (strings instead of string_value_ref/key_ref)
// while td may have references. Marshal td2 again to verify wire compatibility.
wire3, err := marshaler.MarshalProfiles(td2)
require.NoError(t, err)
// Verify full round-trip fidelity: unmarshal both wire1 and wire3 into goproto
// messages and compare them semantically. This ensures all data (attributes,
// dictionary, profiles, etc.) survives the round-trip through both libraries.
var check1, check2 gootlpprofiles.ProfilesData
require.NoError(t, goproto.Unmarshal(wire1, &check1))
require.NoError(t, goproto.Unmarshal(wire3, &check2))
assert.True(t, goproto.Equal(&check1, &check2), "round-trip through goproto did not preserve profile data")
}
func TestProtoProfilesUnmarshalerError(t *testing.T) {
p := &ProtoUnmarshaler{}
_, err := p.UnmarshalProfiles([]byte("+$%"))
assert.Error(t, err)
}
func TestProtoSizer(t *testing.T) {
marshaler := &ProtoMarshaler{}
td := NewProfiles()
td.ResourceProfiles().AppendEmpty().ScopeProfiles().AppendEmpty().Profiles().AppendEmpty()
td.Dictionary().StringTable().Append("foobar")
size := marshaler.ProfilesSize(td)
bytes, err := marshaler.MarshalProfiles(td)
require.NoError(t, err)
assert.Equal(t, len(bytes), size)
}
func TestProtoSizerEmptyProfiles(t *testing.T) {
sizer := &ProtoMarshaler{}
assert.Equal(t, 2, sizer.ProfilesSize(NewProfiles()))
}
func BenchmarkProfilesToProto(b *testing.B) {
marshaler := &ProtoMarshaler{}
profiles := generateBenchmarkProfiles(128)
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
buf, err := marshaler.MarshalProfiles(profiles)
require.NoError(b, err)
assert.NotEmpty(b, buf)
}
}
func BenchmarkProfilesFromProto(b *testing.B) {
marshaler := &ProtoMarshaler{}
unmarshaler := &ProtoUnmarshaler{}
baseProfiles := generateBenchmarkProfiles(128)
buf, err := marshaler.MarshalProfiles(baseProfiles)
require.NoError(b, err)
assert.NotEmpty(b, buf)
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
profiles, err := unmarshaler.UnmarshalProfiles(buf)
require.NoError(b, err)
assert.Equal(b, baseProfiles.ResourceProfiles().Len(), profiles.ResourceProfiles().Len())
}
}
func generateBenchmarkProfiles(samplesCount int) Profiles {
md := NewProfiles()
ilm := md.ResourceProfiles().AppendEmpty().ScopeProfiles().AppendEmpty().Profiles().AppendEmpty()
ilm.Samples().EnsureCapacity(samplesCount)
for range samplesCount {
im := ilm.Samples().AppendEmpty()
im.SetStackIndex(0)
}
return md
}
// generateProfiles creates a Profiles object with the specified number of resources, scopes, profiles, and samples.
func generateProfiles(b *testing.B, resourceCount, scopeCount, profileCount, sampleCount int) Profiles {
b.Helper()
profiles := NewProfiles()
dict := profiles.Dictionary()
// Pre-populate dictionary with common strings
dict.StringTable().Append("") // Index 0 is always empty string
dict.StringTable().Append("cpu")
dict.StringTable().Append("nanoseconds")
dict.StringTable().Append("samples")
dict.StringTable().Append("count")
// Generate resource profiles
for r := range resourceCount {
rp := profiles.ResourceProfiles().AppendEmpty()
rp.SetSchemaUrl(semconv.SchemaURL)
resource := rp.Resource()
// Add resource attributes
attrs := resource.Attributes()
attrs.PutStr(string(semconv.ServiceNameKey), fmt.Sprintf("service-%d", r))
attrs.PutStr(string(semconv.ServiceVersionKey), fmt.Sprintf("version-%d", r))
attrs.PutStr(string(semconv.ProcessPIDKey), strconv.Itoa(1000+r))
attrs.PutStr(string(semconv.K8SPodNameKey), fmt.Sprintf("pod-%d", r%10))
attrs.PutStr(string(semconv.K8SNamespaceNameKey), "default")
attrs.PutStr(string(semconv.TelemetrySDKNameKey), "opentelemetry")
// Generate scope profiles
for s := range scopeCount {
sp := rp.ScopeProfiles().AppendEmpty()
sp.SetSchemaUrl(semconv.SchemaURL)
scope := sp.Scope()
scope.SetName(fmt.Sprintf("profiler-scope-%d", s))
scope.SetVersion("1.0.0")
// Generate profiles
for range profileCount {
profile := sp.Profiles().AppendEmpty()
// Add sample types
sampleType := profile.SampleType()
sampleType.SetTypeStrindex(1) // "cpu"
sampleType.SetUnitStrindex(2) // "nanoseconds"
// Add period type
periodType := profile.PeriodType()
periodType.SetTypeStrindex(1) // "cpu"
periodType.SetUnitStrindex(2) // "nanoseconds"
profile.SetPeriod(1000000)
// Generate samples
samples := profile.Samples()
for i := range sampleCount {
sample := samples.AppendEmpty()
sample.SetStackIndex(int32(i % 100))
// Add attribute indices for samples
sample.AttributeIndices().Append(int32(i % 10))
}
}
}
}
return profiles
}
func BenchmarkUnmarshalProfiles(b *testing.B) {
testCases := []struct {
name string
resourceCount int
scopeCount int
profileCount int
sampleCount int
}{
{
name: "small",
resourceCount: 1,
scopeCount: 1,
profileCount: 1,
sampleCount: 100,
},
{
name: "medium",
resourceCount: 5,
scopeCount: 2,
profileCount: 2,
sampleCount: 500,
},
{
name: "large",
resourceCount: 20,
scopeCount: 3,
profileCount: 5,
sampleCount: 1000,
},
}
for _, tc := range testCases {
b.Run(tc.name, func(b *testing.B) {
// Generate profile data and marshal it
profiles := generateProfiles(b, tc.resourceCount, tc.scopeCount, tc.profileCount, tc.sampleCount)
marshaler := &ProtoMarshaler{}
data, err := marshaler.MarshalProfiles(profiles)
if err != nil {
b.Fatalf("failed to marshal profiles: %v", err)
}
unmarshaler := &ProtoUnmarshaler{}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
profiles, err := unmarshaler.UnmarshalProfiles(data)
if err != nil {
b.Fatalf("failed to unmarshal: %v", err)
}
_ = profiles
}
})
}
}
func BenchmarkMarshalProfiles(b *testing.B) {
testCases := []struct {
name string
resourceCount int
scopeCount int
profileCount int
sampleCount int
}{
{
name: "small",
resourceCount: 1,
scopeCount: 1,
profileCount: 1,
sampleCount: 100,
},
{
name: "medium",
resourceCount: 5,
scopeCount: 2,
profileCount: 2,
sampleCount: 500,
},
{
name: "large",
resourceCount: 20,
scopeCount: 3,
profileCount: 5,
sampleCount: 1000,
},
}
for _, tc := range testCases {
b.Run(tc.name, func(b *testing.B) {
marshaler := &ProtoMarshaler{}
// with_refs: simulate the normal ingest path where data was
// received on the wire (refs present), then unmarshaled (refs
// resolved but KeyRef kept), and is now being re-marshaled
// without any attribute modifications.
b.Run("with_refs", func(b *testing.B) {
profiles := generateProfiles(b, tc.resourceCount, tc.scopeCount, tc.profileCount, tc.sampleCount)
unmarshaler := &ProtoUnmarshaler{}
buf, err := marshaler.MarshalProfiles(profiles)
if err != nil {
b.Fatalf("failed to marshal: %v", err)
}
profiles, err = unmarshaler.UnmarshalProfiles(buf)
if err != nil {
b.Fatalf("failed to unmarshal: %v", err)
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
buf, err := marshaler.MarshalProfiles(profiles)
if err != nil {
b.Fatalf("failed to marshal: %v", err)
}
_ = buf
}
})
// without_refs: each iteration gets a fresh copy with no refs,
// simulating data that was constructed or had attributes modified.
b.Run("without_refs", func(b *testing.B) {
copies := make([]Profiles, b.N)
for i := range copies {
copies[i] = generateProfiles(b, tc.resourceCount, tc.scopeCount, tc.profileCount, tc.sampleCount)
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
buf, err := marshaler.MarshalProfiles(copies[i])
if err != nil {
b.Fatalf("failed to marshal: %v", err)
}
_ = buf
}
})
})
}
}
================================================
FILE: pdata/pprofile/pprofileotlp/fuzz_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofileotlp // import "go.opentelemetry.io/collector/pdata/pprofile/pprofileotlp"
import (
"bytes"
"testing"
"github.com/stretchr/testify/require"
)
var unexpectedBytes = "expected the same bytes from unmarshaling and marshaling."
func FuzzRequestUnmarshalJSON(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
er := NewExportRequest()
err := er.UnmarshalJSON(data)
if err != nil {
return
}
b1, err := er.MarshalJSON()
require.NoError(t, err, "failed to marshal valid struct")
er = NewExportRequest()
require.NoError(t, er.UnmarshalJSON(b1), "failed to unmarshal valid bytes")
b2, err := er.MarshalJSON()
require.NoError(t, err, "failed to marshal valid struct")
require.True(t, bytes.Equal(b1, b2), "%s. \nexpected %d but got %d\n", unexpectedBytes, b1, b2)
})
}
func FuzzResponseUnmarshalJSON(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
er := NewExportResponse()
err := er.UnmarshalJSON(data)
if err != nil {
return
}
b1, err := er.MarshalJSON()
require.NoError(t, err, "failed to marshal valid struct")
er = NewExportResponse()
require.NoError(t, er.UnmarshalJSON(b1), "failed to unmarshal valid bytes")
b2, err := er.MarshalJSON()
require.NoError(t, err, "failed to marshal valid struct")
require.True(t, bytes.Equal(b1, b2), "%s. \nexpected %d but got %d\n", unexpectedBytes, b1, b2)
})
}
func FuzzRequestUnmarshalProto(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
er := NewExportRequest()
err := er.UnmarshalProto(data)
if err != nil {
return
}
b1, err := er.MarshalProto()
require.NoError(t, err, "failed to marshal valid struct")
er = NewExportRequest()
require.NoError(t, er.UnmarshalProto(b1), "failed to unmarshal valid bytes")
b2, err := er.MarshalProto()
require.NoError(t, err, "failed to marshal valid struct")
require.True(t, bytes.Equal(b1, b2), "%s. \nexpected %d but got %d\n", unexpectedBytes, b1, b2)
})
}
func FuzzResponseUnmarshalProto(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
er := NewExportResponse()
err := er.UnmarshalProto(data)
if err != nil {
return
}
b1, err := er.MarshalProto()
require.NoError(t, err, "failed to marshal valid struct")
er = NewExportResponse()
require.NoError(t, er.UnmarshalProto(b1), "failed to unmarshal valid bytes")
b2, err := er.MarshalProto()
require.NoError(t, err, "failed to marshal valid struct")
require.True(t, bytes.Equal(b1, b2), "%s. \nexpected %d but got %d\n", unexpectedBytes, b1, b2)
})
}
================================================
FILE: pdata/pprofile/pprofileotlp/generated_exportpartialsuccess.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofileotlp
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// ExportPartialSuccess represents the details of a partially successful export request.
//
// This is a reference type, if passed by value and callee modifies it the
// caller will see the modification.
//
// Must use NewExportPartialSuccess function to create new instances.
// Important: zero-initialized instance is not valid for use.
type ExportPartialSuccess struct {
orig *internal.ExportProfilesPartialSuccess
state *internal.State
}
func newExportPartialSuccess(orig *internal.ExportProfilesPartialSuccess, state *internal.State) ExportPartialSuccess {
return ExportPartialSuccess{orig: orig, state: state}
}
// NewExportPartialSuccess creates a new empty ExportPartialSuccess.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewExportPartialSuccess() ExportPartialSuccess {
return newExportPartialSuccess(internal.NewExportProfilesPartialSuccess(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms ExportPartialSuccess) MoveTo(dest ExportPartialSuccess) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteExportProfilesPartialSuccess(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// RejectedProfiles returns the rejectedprofiles associated with this ExportPartialSuccess.
func (ms ExportPartialSuccess) RejectedProfiles() int64 {
return ms.orig.RejectedProfiles
}
// SetRejectedProfiles replaces the rejectedprofiles associated with this ExportPartialSuccess.
func (ms ExportPartialSuccess) SetRejectedProfiles(v int64) {
ms.state.AssertMutable()
ms.orig.RejectedProfiles = v
}
// ErrorMessage returns the errormessage associated with this ExportPartialSuccess.
func (ms ExportPartialSuccess) ErrorMessage() string {
return ms.orig.ErrorMessage
}
// SetErrorMessage replaces the errormessage associated with this ExportPartialSuccess.
func (ms ExportPartialSuccess) SetErrorMessage(v string) {
ms.state.AssertMutable()
ms.orig.ErrorMessage = v
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms ExportPartialSuccess) CopyTo(dest ExportPartialSuccess) {
dest.state.AssertMutable()
internal.CopyExportProfilesPartialSuccess(dest.orig, ms.orig)
}
================================================
FILE: pdata/pprofile/pprofileotlp/generated_exportpartialsuccess_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofileotlp
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestExportPartialSuccess_MoveTo(t *testing.T) {
ms := generateTestExportPartialSuccess()
dest := NewExportPartialSuccess()
ms.MoveTo(dest)
assert.Equal(t, NewExportPartialSuccess(), ms)
assert.Equal(t, generateTestExportPartialSuccess(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestExportPartialSuccess(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newExportPartialSuccess(internal.NewExportProfilesPartialSuccess(), sharedState)) })
assert.Panics(t, func() { newExportPartialSuccess(internal.NewExportProfilesPartialSuccess(), sharedState).MoveTo(dest) })
}
func TestExportPartialSuccess_CopyTo(t *testing.T) {
ms := NewExportPartialSuccess()
orig := NewExportPartialSuccess()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestExportPartialSuccess()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newExportPartialSuccess(internal.NewExportProfilesPartialSuccess(), sharedState)) })
}
func TestExportPartialSuccess_RejectedProfiles(t *testing.T) {
ms := NewExportPartialSuccess()
assert.Equal(t, int64(0), ms.RejectedProfiles())
ms.SetRejectedProfiles(int64(13))
assert.Equal(t, int64(13), ms.RejectedProfiles())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() {
newExportPartialSuccess(internal.NewExportProfilesPartialSuccess(), sharedState).SetRejectedProfiles(int64(13))
})
}
func TestExportPartialSuccess_ErrorMessage(t *testing.T) {
ms := NewExportPartialSuccess()
assert.Empty(t, ms.ErrorMessage())
ms.SetErrorMessage("test_errormessage")
assert.Equal(t, "test_errormessage", ms.ErrorMessage())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() {
newExportPartialSuccess(internal.NewExportProfilesPartialSuccess(), sharedState).SetErrorMessage("test_errormessage")
})
}
func generateTestExportPartialSuccess() ExportPartialSuccess {
return newExportPartialSuccess(internal.GenTestExportProfilesPartialSuccess(), internal.NewState())
}
================================================
FILE: pdata/pprofile/pprofileotlp/generated_exportresponse.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofileotlp
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// ExportResponse represents the response for gRPC/HTTP client/server.
//
// This is a reference type, if passed by value and callee modifies it the
// caller will see the modification.
//
// Must use NewExportResponse function to create new instances.
// Important: zero-initialized instance is not valid for use.
type ExportResponse struct {
orig *internal.ExportProfilesServiceResponse
state *internal.State
}
func newExportResponse(orig *internal.ExportProfilesServiceResponse, state *internal.State) ExportResponse {
return ExportResponse{orig: orig, state: state}
}
// NewExportResponse creates a new empty ExportResponse.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewExportResponse() ExportResponse {
return newExportResponse(internal.NewExportProfilesServiceResponse(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms ExportResponse) MoveTo(dest ExportResponse) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteExportProfilesServiceResponse(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// PartialSuccess returns the partialsuccess associated with this ExportResponse.
func (ms ExportResponse) PartialSuccess() ExportPartialSuccess {
return newExportPartialSuccess(&ms.orig.PartialSuccess, ms.state)
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms ExportResponse) CopyTo(dest ExportResponse) {
dest.state.AssertMutable()
internal.CopyExportProfilesServiceResponse(dest.orig, ms.orig)
}
================================================
FILE: pdata/pprofile/pprofileotlp/generated_exportresponse_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package pprofileotlp
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestExportResponse_MoveTo(t *testing.T) {
ms := generateTestExportResponse()
dest := NewExportResponse()
ms.MoveTo(dest)
assert.Equal(t, NewExportResponse(), ms)
assert.Equal(t, generateTestExportResponse(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestExportResponse(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newExportResponse(internal.NewExportProfilesServiceResponse(), sharedState)) })
assert.Panics(t, func() { newExportResponse(internal.NewExportProfilesServiceResponse(), sharedState).MoveTo(dest) })
}
func TestExportResponse_CopyTo(t *testing.T) {
ms := NewExportResponse()
orig := NewExportResponse()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestExportResponse()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newExportResponse(internal.NewExportProfilesServiceResponse(), sharedState)) })
}
func TestExportResponse_PartialSuccess(t *testing.T) {
ms := NewExportResponse()
assert.Equal(t, NewExportPartialSuccess(), ms.PartialSuccess())
ms.orig.PartialSuccess = *internal.GenTestExportProfilesPartialSuccess()
assert.Equal(t, generateTestExportPartialSuccess(), ms.PartialSuccess())
}
func generateTestExportResponse() ExportResponse {
return newExportResponse(internal.GenTestExportProfilesServiceResponse(), internal.NewState())
}
================================================
FILE: pdata/pprofile/pprofileotlp/grpc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofileotlp // import "go.opentelemetry.io/collector/pdata/pprofile/pprofileotlp"
import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/internal/otelgrpc"
"go.opentelemetry.io/collector/pdata/internal/otlp"
)
// GRPCClient is the client API for OTLP-GRPC Profiles service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type GRPCClient interface {
// Export pprofile.Profiles to the server.
//
// For performance reasons, it is recommended to keep this RPC
// alive for the entire life of the application.
Export(ctx context.Context, request ExportRequest, opts ...grpc.CallOption) (ExportResponse, error)
// unexported disallow implementation of the GRPCClient.
unexported()
}
// NewGRPCClient returns a new GRPCClient connected using the given connection.
func NewGRPCClient(cc *grpc.ClientConn) GRPCClient {
return &grpcClient{rawClient: otelgrpc.NewProfilesServiceClient(cc)}
}
type grpcClient struct {
rawClient otelgrpc.ProfilesServiceClient
}
// Export implements the Client interface.
func (c *grpcClient) Export(ctx context.Context, request ExportRequest, opts ...grpc.CallOption) (ExportResponse, error) {
rsp, err := c.rawClient.Export(ctx, request.orig, opts...)
if err != nil {
return ExportResponse{}, err
}
return ExportResponse{orig: rsp, state: internal.NewState()}, err
}
func (c *grpcClient) unexported() {}
// GRPCServer is the server API for OTLP gRPC ProfilesService service.
// Implementations MUST embed UnimplementedGRPCServer.
type GRPCServer interface {
// Export is called every time a new request is received.
//
// For performance reasons, it is recommended to keep this RPC
// alive for the entire life of the application.
Export(context.Context, ExportRequest) (ExportResponse, error)
// unexported disallow implementation of the GRPCServer.
unexported()
}
var _ GRPCServer = (*UnimplementedGRPCServer)(nil)
// UnimplementedGRPCServer MUST be embedded to have forward compatible implementations.
type UnimplementedGRPCServer struct{}
func (*UnimplementedGRPCServer) Export(context.Context, ExportRequest) (ExportResponse, error) {
return ExportResponse{}, status.Errorf(codes.Unimplemented, "method Export not implemented")
}
func (*UnimplementedGRPCServer) unexported() {}
// RegisterGRPCServer registers the GRPCServer to the grpc.Server.
func RegisterGRPCServer(s *grpc.Server, srv GRPCServer) {
otelgrpc.RegisterProfilesServiceServer(s, &rawProfilesServer{srv: srv})
}
type rawProfilesServer struct {
srv GRPCServer
}
func (s rawProfilesServer) Export(ctx context.Context, request *internal.ExportProfilesServiceRequest) (*internal.ExportProfilesServiceResponse, error) {
otlp.MigrateProfiles(request.ResourceProfiles)
rsp, err := s.srv.Export(ctx, ExportRequest{orig: request, state: internal.NewState()})
return rsp.orig, err
}
================================================
FILE: pdata/pprofile/pprofileotlp/grpc_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofileotlp
import (
"context"
"errors"
"net"
"sync"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/resolver"
"google.golang.org/grpc/status"
"google.golang.org/grpc/test/bufconn"
"go.opentelemetry.io/collector/pdata/pprofile"
)
func TestGrpc(t *testing.T) {
lis := bufconn.Listen(1024 * 1024)
s := grpc.NewServer()
RegisterGRPCServer(s, &fakeProfilesServer{t: t})
wg := sync.WaitGroup{}
wg.Go(func() {
assert.NoError(t, s.Serve(lis))
})
t.Cleanup(func() {
s.Stop()
wg.Wait()
})
resolver.SetDefaultScheme("passthrough")
cc, err := grpc.NewClient("bufnet",
grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
return lis.Dial()
}),
grpc.WithTransportCredentials(insecure.NewCredentials()))
require.NoError(t, err)
t.Cleanup(func() {
assert.NoError(t, cc.Close())
})
logClient := NewGRPCClient(cc)
resp, err := logClient.Export(context.Background(), generateProfilesRequest())
require.NoError(t, err)
assert.Equal(t, NewExportResponse(), resp)
}
func TestGrpcError(t *testing.T) {
lis := bufconn.Listen(1024 * 1024)
s := grpc.NewServer()
RegisterGRPCServer(s, &fakeProfilesServer{t: t, err: errors.New("my error")})
wg := sync.WaitGroup{}
wg.Go(func() {
assert.NoError(t, s.Serve(lis))
})
t.Cleanup(func() {
s.Stop()
wg.Wait()
})
cc, err := grpc.NewClient("bufnet",
grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
return lis.Dial()
}),
grpc.WithTransportCredentials(insecure.NewCredentials()))
require.NoError(t, err)
t.Cleanup(func() {
assert.NoError(t, cc.Close())
})
logClient := NewGRPCClient(cc)
resp, err := logClient.Export(context.Background(), generateProfilesRequest())
require.Error(t, err)
st, okSt := status.FromError(err)
require.True(t, okSt)
assert.Equal(t, "my error", st.Message())
assert.Equal(t, codes.Unknown, st.Code())
assert.Equal(t, ExportResponse{}, resp)
}
type fakeProfilesServer struct {
UnimplementedGRPCServer
t *testing.T
err error
}
func (f fakeProfilesServer) Export(_ context.Context, request ExportRequest) (ExportResponse, error) {
assert.Equal(f.t, generateProfilesRequest(), request)
return NewExportResponse(), f.err
}
func generateProfilesRequest() ExportRequest {
td := pprofile.NewProfiles()
td.ResourceProfiles().AppendEmpty().ScopeProfiles().AppendEmpty().Profiles().AppendEmpty()
return NewExportRequestFromProfiles(td)
}
================================================
FILE: pdata/pprofile/pprofileotlp/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofileotlp
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: pdata/pprofile/pprofileotlp/request.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofileotlp // import "go.opentelemetry.io/collector/pdata/pprofile/pprofileotlp"
import (
"slices"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/otlp"
"go.opentelemetry.io/collector/pdata/pprofile"
)
// ExportRequest represents the request for gRPC/HTTP client/server.
// It's a wrapper for pprofile.Profiles data.
type ExportRequest struct {
orig *internal.ExportProfilesServiceRequest
state *internal.State
}
// NewExportRequest returns an empty ExportRequest.
func NewExportRequest() ExportRequest {
return ExportRequest{
orig: &internal.ExportProfilesServiceRequest{},
state: internal.NewState(),
}
}
// NewExportRequestFromProfiles returns a ExportRequest from pprofile.Profiles.
// Because ExportRequest is a wrapper for pprofile.Profiles,
// any changes to the provided Profiles struct will be reflected in the ExportRequest and vice versa.
func NewExportRequestFromProfiles(td pprofile.Profiles) ExportRequest {
return ExportRequest{
orig: internal.GetProfilesOrig(internal.ProfilesWrapper(td)),
state: internal.GetProfilesState(internal.ProfilesWrapper(td)),
}
}
// MarshalProto marshals ExportRequest into proto bytes.
func (ms ExportRequest) MarshalProto() ([]byte, error) {
size := ms.orig.SizeProto()
buf := make([]byte, size)
_ = ms.orig.MarshalProto(buf)
return buf, nil
}
// UnmarshalProto unmarshalls ExportRequest from proto bytes.
func (ms ExportRequest) UnmarshalProto(data []byte) error {
err := ms.orig.UnmarshalProto(data)
if err != nil {
return err
}
otlp.MigrateProfiles(ms.orig.ResourceProfiles)
return nil
}
// MarshalJSON marshals ExportRequest into JSON bytes.
func (ms ExportRequest) MarshalJSON() ([]byte, error) {
dest := json.BorrowStream(nil)
defer json.ReturnStream(dest)
ms.orig.MarshalJSON(dest)
if dest.Error() != nil {
return nil, dest.Error()
}
return slices.Clone(dest.Buffer()), nil
}
// UnmarshalJSON unmarshalls ExportRequest from JSON bytes.
func (ms ExportRequest) UnmarshalJSON(data []byte) error {
iter := json.BorrowIterator(data)
defer json.ReturnIterator(iter)
ms.orig.UnmarshalJSON(iter)
return iter.Error()
}
func (ms ExportRequest) Profiles() pprofile.Profiles {
return pprofile.Profiles(internal.NewProfilesWrapper(ms.orig, ms.state))
}
================================================
FILE: pdata/pprofile/pprofileotlp/request_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofileotlp
import (
"encoding/json"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpcollectorprofiles "go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development"
goproto "google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/internal/otlp"
"go.opentelemetry.io/collector/pdata/pprofile"
)
var (
_ json.Unmarshaler = ExportRequest{}
_ json.Marshaler = ExportRequest{}
)
var profilesRequestJSON = []byte(`
{
"resourceProfiles": [
{
"resource": {},
"scopeProfiles": [
{
"scope": {},
"profiles": [
{
"sampleType": {},
"samples": [
{
"stackIndex": 42
}
],
"periodType": {}
}
]
}
]
}
],
"dictionary": {}
}`)
func TestRequestToPData(t *testing.T) {
tr := NewExportRequest()
assert.Equal(t, 0, tr.Profiles().SampleCount())
tr.Profiles().ResourceProfiles().AppendEmpty().ScopeProfiles().AppendEmpty().Profiles().AppendEmpty().Samples().AppendEmpty()
assert.Equal(t, 1, tr.Profiles().SampleCount())
}
func TestRequestJSON(t *testing.T) {
tr := NewExportRequest()
require.NoError(t, tr.UnmarshalJSON(profilesRequestJSON))
assert.Equal(t, int32(42), tr.Profiles().ResourceProfiles().At(0).ScopeProfiles().At(0).Profiles().At(0).Samples().At(0).StackIndex())
got, err := tr.MarshalJSON()
require.NoError(t, err)
assert.Equal(t, strings.Join(strings.Fields(string(profilesRequestJSON)), ""), string(got))
}
func TestProfilesProtoWireCompatibility(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 Profiles as pdata struct.
pd := NewExportRequestFromProfiles(pprofile.Profiles(internal.GenTestProfilesWrapper()))
// Marshal its underlying ProtoBuf to wire.
wire1, err := pd.MarshalProto()
require.NoError(t, err)
assert.NotNil(t, wire1)
// Unmarshal from the wire to OTLP Protobuf in goproto's representation.
var goprotoMessage gootlpcollectorprofiles.ExportProfilesServiceRequest
err = goproto.Unmarshal(wire1, &goprotoMessage)
require.NoError(t, err)
// Marshal to the wire again.
wire2, err := goproto.Marshal(&goprotoMessage)
require.NoError(t, err)
assert.NotNil(t, wire2)
// Unmarshal from the wire into gogoproto's representation.
pd2 := NewExportRequest()
err = pd2.UnmarshalProto(wire2)
require.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.
// Migration logic will run, so run it on the original message as well.
otlp.MigrateProfiles(pd.orig.ResourceProfiles)
assert.Equal(t, pd, pd2)
}
================================================
FILE: pdata/pprofile/pprofileotlp/response.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofileotlp // import "go.opentelemetry.io/collector/pdata/pprofile/pprofileotlp"
import (
"slices"
"go.opentelemetry.io/collector/pdata/internal/json"
)
// MarshalProto marshals ExportResponse into proto bytes.
func (ms ExportResponse) MarshalProto() ([]byte, error) {
size := ms.orig.SizeProto()
buf := make([]byte, size)
_ = ms.orig.MarshalProto(buf)
return buf, nil
}
// UnmarshalProto unmarshalls ExportResponse from proto bytes.
func (ms ExportResponse) UnmarshalProto(data []byte) error {
return ms.orig.UnmarshalProto(data)
}
// MarshalJSON marshals ExportResponse into JSON bytes.
func (ms ExportResponse) MarshalJSON() ([]byte, error) {
dest := json.BorrowStream(nil)
defer json.ReturnStream(dest)
ms.orig.MarshalJSON(dest)
return slices.Clone(dest.Buffer()), dest.Error()
}
// UnmarshalJSON unmarshalls ExportResponse from JSON bytes.
func (ms ExportResponse) UnmarshalJSON(data []byte) error {
iter := json.BorrowIterator(data)
defer json.ReturnIterator(iter)
ms.orig.UnmarshalJSON(iter)
return iter.Error()
}
================================================
FILE: pdata/pprofile/pprofileotlp/response_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofileotlp
import (
stdjson "encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var (
_ stdjson.Unmarshaler = ExportResponse{}
_ stdjson.Marshaler = ExportResponse{}
)
func TestExportResponseJSON(t *testing.T) {
jsonStr := `{"partialSuccess": {"rejectedProfiles":"1", "errorMessage":"nothing"}}`
val := NewExportResponse()
require.NoError(t, val.UnmarshalJSON([]byte(jsonStr)))
expected := NewExportResponse()
expected.PartialSuccess().SetRejectedProfiles(1)
expected.PartialSuccess().SetErrorMessage("nothing")
assert.Equal(t, expected, val)
buf, err := val.MarshalJSON()
require.NoError(t, err)
assert.JSONEq(t, jsonStr, string(buf))
}
func TestUnmarshalJSONExportResponse(t *testing.T) {
jsonStr := `{"extra":"", "partialSuccess": {"extra":""}}`
val := NewExportResponse()
require.NoError(t, val.UnmarshalJSON([]byte(jsonStr)))
assert.Equal(t, NewExportResponse(), val)
}
================================================
FILE: pdata/pprofile/profile.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile // import "go.opentelemetry.io/collector/pdata/pprofile"
import (
"fmt"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// switchDictionary updates the Profile, switching its indices from one
// dictionary to another.
func (ms Profile) switchDictionary(src, dst ProfilesDictionary) error {
for i, v := range ms.AttributeIndices().All() {
if src.AttributeTable().Len() <= int(v) {
return fmt.Errorf("invalid attribute index %d", v)
}
attr := src.AttributeTable().At(int(v))
idx, err := SetAttribute(dst.AttributeTable(), attr)
if err != nil {
return fmt.Errorf("couldn't set attribute %d: %w", i, err)
}
ms.AttributeIndices().SetAt(i, idx)
}
for i, v := range ms.Samples().All() {
err := v.switchDictionary(src, dst)
if err != nil {
return fmt.Errorf("error switching dictionary for sample %d: %w", i, err)
}
}
err := ms.PeriodType().switchDictionary(src, dst)
if err != nil {
return fmt.Errorf("error switching dictionary for period type: %w", err)
}
err = ms.SampleType().switchDictionary(src, dst)
if err != nil {
return fmt.Errorf("error switching dictionary for sample type: %w", err)
}
return nil
}
// Duration returns the duration associated with this Profile.
//
// Deprecated: Use Profile.DurationNano instead.
func (ms Profile) Duration() pcommon.Timestamp {
return pcommon.Timestamp(0)
}
// SetDuration replaces the duration associated with this Profile.
//
// Deprecated: Use Profile.SetDurationNano instead.
func (ms Profile) SetDuration(_ pcommon.Timestamp) {
}
================================================
FILE: pdata/pprofile/profile_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/internal/testutil"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestProfileSwitchDictionary(t *testing.T) {
for _, tt := range []struct {
name string
profile Profile
src ProfilesDictionary
dst ProfilesDictionary
wantProfile Profile
wantDictionary ProfilesDictionary
wantErr error
}{
{
name: "with an empty profile",
profile: NewProfile(),
src: NewProfilesDictionary(),
dst: NewProfilesDictionary(),
wantProfile: NewProfile(),
wantDictionary: NewProfilesDictionary(),
},
{
name: "with an existing attribute",
profile: func() Profile {
p := NewProfile()
p.AttributeIndices().Append(1)
return p
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
d.AttributeTable().AppendEmpty()
a := d.AttributeTable().AppendEmpty()
a.SetKeyStrindex(1)
return d
}(),
dst: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
d.AttributeTable().AppendEmpty()
d.AttributeTable().AppendEmpty()
return d
}(),
wantProfile: func() Profile {
p := NewProfile()
p.AttributeIndices().Append(2)
return p
}(),
wantDictionary: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
d.AttributeTable().AppendEmpty()
d.AttributeTable().AppendEmpty()
a := d.AttributeTable().AppendEmpty()
a.SetKeyStrindex(1)
return d
}(),
},
{
name: "with an attribute index that does not match anything",
profile: func() Profile {
p := NewProfile()
p.AttributeIndices().Append(1)
return p
}(),
src: NewProfilesDictionary(),
dst: NewProfilesDictionary(),
wantProfile: func() Profile {
p := NewProfile()
p.AttributeIndices().Append(1)
return p
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid attribute index 1"),
},
{
name: "with an attribute index equal to the source table length (boundary condition)",
profile: func() Profile {
p := NewProfile()
p.AttributeIndices().Append(2)
return p
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.AttributeTable().AppendEmpty()
d.AttributeTable().AppendEmpty()
return d
}(),
dst: NewProfilesDictionary(),
wantProfile: func() Profile {
p := NewProfile()
p.AttributeIndices().Append(2)
return p
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid attribute index 2"),
},
{
name: "with a profile that has a sample",
profile: func() Profile {
p := NewProfile()
p.Samples().AppendEmpty().SetLinkIndex(1)
return p
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.LinkTable().AppendEmpty()
l := d.LinkTable().AppendEmpty()
l.SetSpanID(pcommon.SpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8}))
return d
}(),
dst: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.LinkTable().AppendEmpty()
d.LinkTable().AppendEmpty()
return d
}(),
wantProfile: func() Profile {
p := NewProfile()
p.Samples().AppendEmpty().SetLinkIndex(2)
return p
}(),
wantDictionary: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.LinkTable().AppendEmpty()
d.LinkTable().AppendEmpty()
l := d.LinkTable().AppendEmpty()
l.SetSpanID(pcommon.SpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8}))
return d
}(),
},
{
name: "with a profile that has a period type",
profile: func() Profile {
p := NewProfile()
p.PeriodType().SetTypeStrindex(1)
return p
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
return d
}(),
dst: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "foo")
return d
}(),
wantProfile: func() Profile {
p := NewProfile()
p.PeriodType().SetTypeStrindex(2)
return p
}(),
wantDictionary: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "foo", "test")
return d
}(),
},
{
name: "with a profile that has a sample type",
profile: func() Profile {
p := NewProfile()
p.SampleType().SetTypeStrindex(1)
return p
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
return d
}(),
dst: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "foo")
return d
}(),
wantProfile: func() Profile {
p := NewProfile()
p.SampleType().SetTypeStrindex(2)
return p
}(),
wantDictionary: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "foo", "test")
return d
}(),
},
{
name: "Profile with various elements",
profile: func() Profile {
p := NewProfile()
p.SampleType().SetTypeStrindex(1)
p.SampleType().SetUnitStrindex(2)
p.PeriodType().SetTypeStrindex(3)
p.PeriodType().SetUnitStrindex(4)
p.AttributeIndices().Append(1)
return p
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
// Make sure we are conform with the protocol
d.MappingTable().AppendEmpty()
d.LocationTable().AppendEmpty()
d.FunctionTable().AppendEmpty()
d.LinkTable().AppendEmpty()
d.StringTable().Append("")
d.AttributeTable().AppendEmpty()
d.StackTable().AppendEmpty()
d.StringTable().Append("sample-type") // 1
d.StringTable().Append("sample-unit") // 2
d.StringTable().Append("period-type") // 3
d.StringTable().Append("period-unit") // 4
d.StringTable().Append("unrelated-1") // 5
d.StringTable().Append("unrelated-2") // 6
d.StringTable().Append("attribute-key") // 7
d.StringTable().Append("attribute-unit") // 8
a := d.AttributeTable().AppendEmpty()
a.SetKeyStrindex(7)
a.Value().SetStr("AnyValue")
a.SetUnitStrindex(8)
return d
}(),
dst: func() ProfilesDictionary {
d := NewProfilesDictionary()
// Make sure we are conform with the protocol
d.MappingTable().AppendEmpty()
d.LocationTable().AppendEmpty()
d.FunctionTable().AppendEmpty()
d.LinkTable().AppendEmpty()
d.StringTable().Append("")
d.AttributeTable().AppendEmpty()
d.StackTable().AppendEmpty()
return d
}(),
wantProfile: func() Profile {
p := NewProfile()
p.AttributeIndices().Append(1)
// Order of entries depend on the order of
// processing in switchDictionary()
p.SampleType().SetTypeStrindex(3)
p.SampleType().SetUnitStrindex(4)
p.PeriodType().SetTypeStrindex(1)
p.PeriodType().SetUnitStrindex(2)
return p
}(),
wantDictionary: func() ProfilesDictionary {
d := NewProfilesDictionary()
// Make sure we are conform with the protocol
d.MappingTable().AppendEmpty()
d.LocationTable().AppendEmpty()
d.FunctionTable().AppendEmpty()
d.LinkTable().AppendEmpty()
d.StringTable().Append("")
d.AttributeTable().AppendEmpty()
d.StackTable().AppendEmpty()
a := d.AttributeTable().AppendEmpty()
a.SetKeyStrindex(7)
a.SetUnitStrindex(8)
a.Value().SetStr("AnyValue")
// Order of entries depend on the order of
// processing in switchDictionary()
d.StringTable().Append("period-type") // 1
d.StringTable().Append("period-unit") // 2
d.StringTable().Append("sample-type") // 3
d.StringTable().Append("sample-unit") // 4
return d
}(),
},
} {
t.Run(tt.name, func(t *testing.T) {
profile := tt.profile
dst := tt.dst
err := profile.switchDictionary(tt.src, dst)
if tt.wantErr == nil {
require.NoError(t, err)
} else {
require.Equal(t, tt.wantErr, err)
}
assert.Equal(t, tt.wantProfile, profile)
assert.Equal(t, tt.wantDictionary, dst)
})
}
}
func BenchmarkProfileSwitchDictionary(b *testing.B) {
testutil.SkipMemoryBench(b)
p := NewProfile()
p.AttributeIndices().Append(1, 2)
src := NewProfilesDictionary()
src.StringTable().Append("", "test", "foo")
src.AttributeTable().AppendEmpty()
src.AttributeTable().AppendEmpty().SetKeyStrindex(1)
src.AttributeTable().AppendEmpty().SetKeyStrindex(2)
b.ReportAllocs()
for b.Loop() {
b.StopTimer()
dst := NewProfilesDictionary()
dst.StringTable().Append("", "foo")
dst.AttributeTable().AppendEmpty()
dst.AttributeTable().AppendEmpty().SetKeyStrindex(1)
b.StartTimer()
_ = p.switchDictionary(src, dst)
}
}
func TestProfile_Duration(_ *testing.T) {
ms := NewProfile()
ms.SetDuration(0)
ts := ms.Duration()
_ = ts
}
================================================
FILE: pdata/pprofile/profileid.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile // import "go.opentelemetry.io/collector/pdata/pprofile"
import (
"encoding/hex"
"go.opentelemetry.io/collector/pdata/internal"
)
var emptyProfileID = ProfileID([16]byte{})
// ProfileID is a profile identifier.
type ProfileID [16]byte
// NewProfileIDEmpty returns a new empty (all zero bytes) ProfileID.
func NewProfileIDEmpty() ProfileID {
return emptyProfileID
}
// String returns string representation of the ProfileID.
//
// Important: Don't rely on this method to get a string identifier of ProfileID.
// Use hex.EncodeToString explicitly instead.
// This method is meant to implement Stringer interface for display purposes only.
func (ms ProfileID) String() string {
if ms.IsEmpty() {
return ""
}
return hex.EncodeToString(ms[:])
}
// IsEmpty returns true if id doesn't contain at least one non-zero byte.
func (ms ProfileID) IsEmpty() bool {
return internal.ProfileID(ms).IsEmpty()
}
================================================
FILE: pdata/pprofile/profileid_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestProfileID(t *testing.T) {
pid := ProfileID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1})
assert.Equal(t, [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}, [16]byte(pid))
assert.False(t, pid.IsEmpty())
}
func TestNewProfileIDEmpty(t *testing.T) {
pid := NewProfileIDEmpty()
assert.Equal(t, [16]byte{}, [16]byte(pid))
assert.True(t, pid.IsEmpty())
}
func TestProfileIDString(t *testing.T) {
pid := ProfileID([16]byte{})
assert.Empty(t, pid.String())
pid = [16]byte{0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78}
assert.Equal(t, "12345678123456781234567812345678", pid.String())
}
func TestProfileIDImmutable(t *testing.T) {
initialBytes := [16]byte{0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78}
pid := ProfileID(initialBytes)
assert.Equal(t, ProfileID(initialBytes), pid)
// Get the bytes and try to mutate.
pid[4] = 0x23
// Does not change the already created ProfileID.
assert.NotEqual(t, ProfileID(initialBytes), pid)
}
================================================
FILE: pdata/pprofile/profiles.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile // import "go.opentelemetry.io/collector/pdata/pprofile"
import "fmt"
// MarkReadOnly marks the ResourceProfiles as shared so that no further modifications can be done on it.
func (ms Profiles) MarkReadOnly() {
ms.getState().MarkReadOnly()
}
// IsReadOnly returns true if this ResourceProfiles instance is read-only.
func (ms Profiles) IsReadOnly() bool {
return ms.getState().IsReadOnly()
}
// SampleCount calculates the total number of samples.
func (ms Profiles) SampleCount() int {
sampleCount := 0
rps := ms.ResourceProfiles()
for i := 0; i < rps.Len(); i++ {
rp := rps.At(i)
sps := rp.ScopeProfiles()
for j := 0; j < sps.Len(); j++ {
pcs := sps.At(j).Profiles()
for k := 0; k < pcs.Len(); k++ {
sampleCount += pcs.At(k).Samples().Len()
}
}
}
return sampleCount
}
// switchDictionary updates the Profiles, switching its indices from one
// dictionary to another.
func (ms Profiles) switchDictionary(src, dst ProfilesDictionary) error {
for i, v := range ms.Dictionary().AttributeTable().All() {
err := v.switchDictionary(src, dst)
if err != nil {
return fmt.Errorf("couldn't switch attribute %d: %w", i, err)
}
}
for i, v := range ms.Dictionary().FunctionTable().All() {
err := v.switchDictionary(src, dst)
if err != nil {
return fmt.Errorf("couldn't switch function %d: %w", i, err)
}
}
for i, v := range ms.Dictionary().MappingTable().All() {
err := v.switchDictionary(src, dst)
if err != nil {
return fmt.Errorf("couldn't switch mapping %d: %w", i, err)
}
}
for i, v := range ms.Dictionary().LocationTable().All() {
err := v.switchDictionary(src, dst)
if err != nil {
return fmt.Errorf("couldn't switch location %d: %w", i, err)
}
}
for i, v := range ms.Dictionary().StackTable().All() {
err := v.switchDictionary(src, dst)
if err != nil {
return fmt.Errorf("couldn't switch stack %d: %w", i, err)
}
}
for i, v := range ms.ResourceProfiles().All() {
err := v.switchDictionary(src, dst)
if err != nil {
return fmt.Errorf("error switching dictionary for resource profile %d: %w", i, err)
}
}
return nil
}
// ProfileCount calculates the total number of profile records.
func (ms Profiles) ProfileCount() int {
profileCount := 0
rps := ms.ResourceProfiles()
for i := 0; i < rps.Len(); i++ {
rp := rps.At(i)
sps := rp.ScopeProfiles()
for j := 0; j < sps.Len(); j++ {
profileCount += sps.At(j).Profiles().Len()
}
}
return profileCount
}
================================================
FILE: pdata/pprofile/profiles_merge.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile // import "go.opentelemetry.io/collector/pdata/pprofile"
// MergeTo merges the current Profiles into dest, updating the destination
// dictionary as needed and appending the resource profiles.
// The source Profiles is consumed and marked read-only after this operation.
func (ms Profiles) MergeTo(dest Profiles) error {
ms.getState().AssertMutable()
dest.getState().AssertMutable()
if ms.getOrig() == dest.getOrig() {
return nil
}
if err := ms.switchDictionary(ms.Dictionary(), dest.Dictionary()); err != nil {
return err
}
ms.ResourceProfiles().MoveAndAppendTo(dest.ResourceProfiles())
ms.MarkReadOnly()
return nil
}
================================================
FILE: pdata/pprofile/profiles_merge_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestProfilesMergeTo(t *testing.T) {
for _, tt := range []struct {
name string
srcProfiles Profiles
dstProfiles Profiles
// Expected results after merge
expectedDictionarySizes struct {
StringTable int
AttributeTable int
StackTable int
LocationTable int
FunctionTable int
MappingTable int
LinkTable int
}
expectedProfileCount int
}{
{
name: "Empty Profiles",
expectedDictionarySizes: struct {
StringTable int
AttributeTable int
StackTable int
LocationTable int
FunctionTable int
MappingTable int
LinkTable int
}{
StringTable: 1, // Just the empty string
AttributeTable: 1,
StackTable: 1,
LocationTable: 1,
FunctionTable: 1,
MappingTable: 1,
LinkTable: 1,
},
expectedProfileCount: 0,
srcProfiles: func() Profiles {
p := NewProfiles()
// Make sure we are conform with the protocol
p.Dictionary().MappingTable().AppendEmpty()
p.Dictionary().LocationTable().AppendEmpty()
p.Dictionary().FunctionTable().AppendEmpty()
p.Dictionary().LinkTable().AppendEmpty()
p.Dictionary().StringTable().Append("")
p.Dictionary().AttributeTable().AppendEmpty()
p.Dictionary().StackTable().AppendEmpty()
return p
}(),
dstProfiles: func() Profiles {
p := NewProfiles()
// Make sure we are conform with the protocol
p.Dictionary().MappingTable().AppendEmpty()
p.Dictionary().LocationTable().AppendEmpty()
p.Dictionary().FunctionTable().AppendEmpty()
p.Dictionary().LinkTable().AppendEmpty()
p.Dictionary().StringTable().Append("")
p.Dictionary().AttributeTable().AppendEmpty()
p.Dictionary().StackTable().AppendEmpty()
return p
}(),
},
{
name: "Single Profile",
expectedDictionarySizes: struct {
StringTable int
AttributeTable int
StackTable int
LocationTable int
FunctionTable int
MappingTable int
LinkTable int
}{
StringTable: 7, // empty + 6 strings
AttributeTable: 2, // empty + a1
StackTable: 2, // empty + st1
LocationTable: 3, // empty + loc1 + loc2
FunctionTable: 1,
MappingTable: 1,
LinkTable: 1,
},
expectedProfileCount: 1,
srcProfiles: func() Profiles {
ps := NewProfiles()
// Make sure we are conform with the protocol
ps.Dictionary().MappingTable().AppendEmpty()
ps.Dictionary().LocationTable().AppendEmpty()
ps.Dictionary().FunctionTable().AppendEmpty()
ps.Dictionary().LinkTable().AppendEmpty()
ps.Dictionary().StringTable().Append("")
ps.Dictionary().AttributeTable().AppendEmpty()
ps.Dictionary().StackTable().AppendEmpty()
ps.Dictionary().StringTable().Append("sample-type") // 1
ps.Dictionary().StringTable().Append("sample-unit") // 2
ps.Dictionary().StringTable().Append("period-type") // 3
ps.Dictionary().StringTable().Append("period-unit") // 4
ps.Dictionary().StringTable().Append("attribute1-key") // 5
ps.Dictionary().StringTable().Append("attribute1-unit") // 6
a1 := ps.Dictionary().AttributeTable().AppendEmpty()
a1.SetKeyStrindex(5)
a1.SetUnitStrindex(6)
a1.Value().SetStr("AnyValue")
st1 := ps.Dictionary().StackTable().AppendEmpty()
st1.LocationIndices().Append(1, 2)
loc1 := ps.Dictionary().LocationTable().AppendEmpty()
loc1.SetAddress(1337)
loc2 := ps.Dictionary().LocationTable().AppendEmpty()
ln1 := loc2.Lines().AppendEmpty()
ln1.SetLine(42)
rp := ps.ResourceProfiles().AppendEmpty()
rp.SetSchemaUrl("resource-schema-url")
rp.Resource().Attributes().PutStr("resource-attribute-key",
"resource-attribute-value")
sp := rp.ScopeProfiles().AppendEmpty()
sp.SetSchemaUrl("scope-schema-url")
p := sp.Profiles().AppendEmpty()
p.SampleType().SetTypeStrindex(1)
p.SampleType().SetUnitStrindex(2)
p.PeriodType().SetTypeStrindex(3)
p.PeriodType().SetUnitStrindex(4)
s1 := p.Samples().AppendEmpty()
s1.AttributeIndices().Append(1)
s1.SetStackIndex(1)
return ps
}(),
dstProfiles: func() Profiles {
p := NewProfiles()
// Make sure we are conform with the protocol
p.Dictionary().MappingTable().AppendEmpty()
p.Dictionary().LocationTable().AppendEmpty()
p.Dictionary().FunctionTable().AppendEmpty()
p.Dictionary().LinkTable().AppendEmpty()
p.Dictionary().StringTable().Append("")
p.Dictionary().AttributeTable().AppendEmpty()
p.Dictionary().StackTable().AppendEmpty()
return p
}(),
},
{
name: "Multiple Profile",
expectedDictionarySizes: struct {
StringTable int
AttributeTable int
StackTable int
LocationTable int
FunctionTable int
MappingTable int
LinkTable int
}{
StringTable: 7, // empty + 6 strings
AttributeTable: 1,
StackTable: 1,
LocationTable: 1,
FunctionTable: 1,
MappingTable: 1,
LinkTable: 1,
},
expectedProfileCount: 3,
srcProfiles: func() Profiles {
ps := NewProfiles()
// Make sure we are conform with the protocol
ps.Dictionary().MappingTable().AppendEmpty()
ps.Dictionary().LocationTable().AppendEmpty()
ps.Dictionary().FunctionTable().AppendEmpty()
ps.Dictionary().LinkTable().AppendEmpty()
ps.Dictionary().StringTable().Append("")
ps.Dictionary().AttributeTable().AppendEmpty()
ps.Dictionary().StackTable().AppendEmpty()
ps.Dictionary().StringTable().Append("sample-type-1") // 1
ps.Dictionary().StringTable().Append("sample-unit-1") // 2
ps.Dictionary().StringTable().Append("sample-type-2") // 3
ps.Dictionary().StringTable().Append("sample-unit-2") // 4
ps.Dictionary().StringTable().Append("sample-type-3") // 5
ps.Dictionary().StringTable().Append("sample-unit-3") // 6
rp := ps.ResourceProfiles().AppendEmpty()
rp.SetSchemaUrl("resource-schema-url")
rp.Resource().Attributes().PutStr("resource-attribute-key",
"resource-attribute-value")
sp := rp.ScopeProfiles().AppendEmpty()
sp.SetSchemaUrl("scope-schema-url")
p1 := sp.Profiles().AppendEmpty()
p1.SampleType().SetTypeStrindex(1)
p1.SampleType().SetUnitStrindex(2)
p2 := sp.Profiles().AppendEmpty()
p2.SampleType().SetTypeStrindex(3)
p2.SampleType().SetUnitStrindex(4)
p3 := sp.Profiles().AppendEmpty()
p3.SampleType().SetTypeStrindex(5)
p3.SampleType().SetUnitStrindex(6)
return ps
}(),
dstProfiles: func() Profiles {
p := NewProfiles()
// Make sure we are conform with the protocol
p.Dictionary().MappingTable().AppendEmpty()
p.Dictionary().LocationTable().AppendEmpty()
p.Dictionary().FunctionTable().AppendEmpty()
p.Dictionary().LinkTable().AppendEmpty()
p.Dictionary().StringTable().Append("")
p.Dictionary().AttributeTable().AppendEmpty()
p.Dictionary().StackTable().AppendEmpty()
return p
}(),
},
{
name: "Multiple Profile with partly prepopulated destination",
expectedDictionarySizes: struct {
StringTable int
AttributeTable int
StackTable int
LocationTable int
FunctionTable int
MappingTable int
LinkTable int
}{
StringTable: 10, // empty + 3 unrelated + 6 from src
AttributeTable: 1,
StackTable: 1,
LocationTable: 1,
FunctionTable: 1,
MappingTable: 1,
LinkTable: 1,
},
expectedProfileCount: 3,
srcProfiles: func() Profiles {
ps := NewProfiles()
// Make sure we are conform with the protocol
ps.Dictionary().MappingTable().AppendEmpty()
ps.Dictionary().LocationTable().AppendEmpty()
ps.Dictionary().FunctionTable().AppendEmpty()
ps.Dictionary().LinkTable().AppendEmpty()
ps.Dictionary().StringTable().Append("")
ps.Dictionary().AttributeTable().AppendEmpty()
ps.Dictionary().StackTable().AppendEmpty()
ps.Dictionary().StringTable().Append("sample-type-1") // 1
ps.Dictionary().StringTable().Append("sample-unit-1") // 2
ps.Dictionary().StringTable().Append("sample-type-2") // 3
ps.Dictionary().StringTable().Append("sample-unit-2") // 4
ps.Dictionary().StringTable().Append("sample-type-3") // 5
ps.Dictionary().StringTable().Append("sample-unit-3") // 6
rp := ps.ResourceProfiles().AppendEmpty()
rp.SetSchemaUrl("resource-schema-url")
rp.Resource().Attributes().PutStr("resource-attribute-key",
"resource-attribute-value")
sp := rp.ScopeProfiles().AppendEmpty()
sp.SetSchemaUrl("scope-schema-url")
p1 := sp.Profiles().AppendEmpty()
p1.SampleType().SetTypeStrindex(1)
p1.SampleType().SetUnitStrindex(2)
p2 := sp.Profiles().AppendEmpty()
p2.SampleType().SetTypeStrindex(3)
p2.SampleType().SetUnitStrindex(4)
p3 := sp.Profiles().AppendEmpty()
p3.SampleType().SetTypeStrindex(5)
p3.SampleType().SetUnitStrindex(6)
return ps
}(),
dstProfiles: func() Profiles {
ps := NewProfiles()
// Make sure we are conform with the protocol
ps.Dictionary().MappingTable().AppendEmpty()
ps.Dictionary().LocationTable().AppendEmpty()
ps.Dictionary().FunctionTable().AppendEmpty()
ps.Dictionary().LinkTable().AppendEmpty()
ps.Dictionary().StringTable().Append("")
ps.Dictionary().AttributeTable().AppendEmpty()
ps.Dictionary().StackTable().AppendEmpty()
ps.Dictionary().StringTable().Append("unrelated-1") // 1
ps.Dictionary().StringTable().Append("unrelated-2") // 2
ps.Dictionary().StringTable().Append("unrelated-3") // 3
return ps
}(),
},
{
name: "Multiple Profile with reused samples",
expectedDictionarySizes: struct {
StringTable int
AttributeTable int
StackTable int
LocationTable int
FunctionTable int
MappingTable int
LinkTable int
}{
StringTable: 9, // empty + 8 unique strings after merge
AttributeTable: 1,
StackTable: 3, // empty + 2 unique stacks
LocationTable: 3, // empty + 2 unique locations
FunctionTable: 2, // empty + fn1
MappingTable: 1,
LinkTable: 1,
},
expectedProfileCount: 3,
srcProfiles: func() Profiles {
ps := NewProfiles()
// Make sure we are conform with the protocol
ps.Dictionary().MappingTable().AppendEmpty()
ps.Dictionary().LocationTable().AppendEmpty()
ps.Dictionary().FunctionTable().AppendEmpty()
ps.Dictionary().LinkTable().AppendEmpty()
ps.Dictionary().StringTable().Append("")
ps.Dictionary().AttributeTable().AppendEmpty()
ps.Dictionary().StackTable().AppendEmpty()
ps.Dictionary().StringTable().Append("sample-type-1") // 1
ps.Dictionary().StringTable().Append("sample-unit-1") // 2
ps.Dictionary().StringTable().Append("sample-type-2") // 3
ps.Dictionary().StringTable().Append("sample-unit-2") // 4
ps.Dictionary().StringTable().Append("sample-type-3") // 5
ps.Dictionary().StringTable().Append("sample-unit-3") // 6
ps.Dictionary().StringTable().Append("filename-1") // 7
ps.Dictionary().StringTable().Append("functionname-1") // 8
st1 := ps.Dictionary().StackTable().AppendEmpty()
st1.LocationIndices().Append(1)
st2 := ps.Dictionary().StackTable().AppendEmpty()
st2.LocationIndices().Append(2)
loc1 := ps.Dictionary().LocationTable().AppendEmpty()
loc1.SetAddress(42)
loc2 := ps.Dictionary().LocationTable().AppendEmpty()
ln1 := loc2.Lines().AppendEmpty()
ln1.SetFunctionIndex(1)
ln1.SetLine(1337)
fn1 := ps.Dictionary().FunctionTable().AppendEmpty()
fn1.SetFilenameStrindex(7)
fn1.SetNameStrindex(8)
rp := ps.ResourceProfiles().AppendEmpty()
rp.SetSchemaUrl("resource-schema-url")
rp.Resource().Attributes().PutStr("resource-attribute-key",
"resource-attribute-value")
sp := rp.ScopeProfiles().AppendEmpty()
sp.SetSchemaUrl("scope-schema-url")
p1 := sp.Profiles().AppendEmpty()
p1.SampleType().SetTypeStrindex(1)
p1.SampleType().SetUnitStrindex(2)
s1 := p1.Samples().AppendEmpty()
s1.SetStackIndex(1)
p2 := sp.Profiles().AppendEmpty()
p2.SampleType().SetTypeStrindex(3)
p2.SampleType().SetUnitStrindex(4)
s2 := p2.Samples().AppendEmpty()
s2.SetStackIndex(2)
p3 := sp.Profiles().AppendEmpty()
p3.SampleType().SetTypeStrindex(5)
p3.SampleType().SetUnitStrindex(6)
s3 := p3.Samples().AppendEmpty()
s3.SetStackIndex(1)
return ps
}(),
dstProfiles: func() Profiles {
p := NewProfiles()
// Make sure we are conform with the protocol
p.Dictionary().MappingTable().AppendEmpty()
p.Dictionary().LocationTable().AppendEmpty()
p.Dictionary().FunctionTable().AppendEmpty()
p.Dictionary().LinkTable().AppendEmpty()
p.Dictionary().StringTable().Append("")
p.Dictionary().AttributeTable().AppendEmpty()
p.Dictionary().StackTable().AppendEmpty()
return p
}(),
},
{
name: "Multiple ResourceProfiles with reused samples",
expectedDictionarySizes: struct {
StringTable int
AttributeTable int
StackTable int
LocationTable int
FunctionTable int
MappingTable int
LinkTable int
}{
StringTable: 9, // empty + 8 unique strings
AttributeTable: 1,
StackTable: 3, // empty + 2 unique stacks
LocationTable: 3, // empty + 2 unique locations
FunctionTable: 2, // empty + fn1
MappingTable: 2, // empty + m1
LinkTable: 1,
},
expectedProfileCount: 6,
srcProfiles: func() Profiles {
ps := NewProfiles()
// Make sure we are conform with the protocol
ps.Dictionary().MappingTable().AppendEmpty()
ps.Dictionary().LocationTable().AppendEmpty()
ps.Dictionary().FunctionTable().AppendEmpty()
ps.Dictionary().LinkTable().AppendEmpty()
ps.Dictionary().StringTable().Append("")
ps.Dictionary().AttributeTable().AppendEmpty()
ps.Dictionary().StackTable().AppendEmpty()
ps.Dictionary().StringTable().Append("sample-type-1") // 1
ps.Dictionary().StringTable().Append("sample-unit-1") // 2
ps.Dictionary().StringTable().Append("sample-type-2") // 3
ps.Dictionary().StringTable().Append("sample-unit-2") // 4
ps.Dictionary().StringTable().Append("sample-type-3") // 5
ps.Dictionary().StringTable().Append("sample-unit-3") // 6
ps.Dictionary().StringTable().Append("filename-1") // 7
ps.Dictionary().StringTable().Append("functionname-1") // 8
st1 := ps.Dictionary().StackTable().AppendEmpty()
st1.LocationIndices().Append(1)
st2 := ps.Dictionary().StackTable().AppendEmpty()
st2.LocationIndices().Append(2)
loc1 := ps.Dictionary().LocationTable().AppendEmpty()
loc1.SetAddress(42)
loc1.SetMappingIndex(1)
loc2 := ps.Dictionary().LocationTable().AppendEmpty()
ln1 := loc2.Lines().AppendEmpty()
ln1.SetFunctionIndex(1)
ln1.SetLine(1337)
fn1 := ps.Dictionary().FunctionTable().AppendEmpty()
fn1.SetFilenameStrindex(7)
fn1.SetNameStrindex(8)
m1 := ps.Dictionary().MappingTable().AppendEmpty()
m1.SetFilenameStrindex(8)
rp1 := ps.ResourceProfiles().AppendEmpty()
rp1.SetSchemaUrl("resource-schema-url")
rp1.Resource().Attributes().PutStr("resource-attribute-key",
"resource-attribute-value")
sp1 := rp1.ScopeProfiles().AppendEmpty()
sp1.SetSchemaUrl("scope-schema-url")
p11 := sp1.Profiles().AppendEmpty()
p11.SampleType().SetTypeStrindex(1)
p11.SampleType().SetUnitStrindex(2)
s11 := p11.Samples().AppendEmpty()
s11.SetStackIndex(1)
p12 := sp1.Profiles().AppendEmpty()
p12.SampleType().SetTypeStrindex(3)
p12.SampleType().SetUnitStrindex(4)
s12 := p12.Samples().AppendEmpty()
s12.SetStackIndex(2)
p13 := sp1.Profiles().AppendEmpty()
p13.SampleType().SetTypeStrindex(5)
p13.SampleType().SetUnitStrindex(6)
s13 := p13.Samples().AppendEmpty()
s13.SetStackIndex(1)
rp2 := ps.ResourceProfiles().AppendEmpty()
rp2.SetSchemaUrl("resource-schema-url")
rp2.Resource().Attributes().PutStr("resource-attribute-key",
"resource-attribute-value")
sp2 := rp2.ScopeProfiles().AppendEmpty()
sp2.SetSchemaUrl("scope-schema-url")
p21 := sp2.Profiles().AppendEmpty()
p21.SampleType().SetTypeStrindex(1)
p21.SampleType().SetUnitStrindex(2)
s21 := p21.Samples().AppendEmpty()
s21.SetStackIndex(1)
p22 := sp2.Profiles().AppendEmpty()
p22.SampleType().SetTypeStrindex(3)
p22.SampleType().SetUnitStrindex(4)
s22 := p22.Samples().AppendEmpty()
s22.SetStackIndex(2)
p23 := sp2.Profiles().AppendEmpty()
p23.SampleType().SetTypeStrindex(5)
p23.SampleType().SetUnitStrindex(6)
s23 := p23.Samples().AppendEmpty()
s23.SetStackIndex(1)
return ps
}(),
dstProfiles: func() Profiles {
p := NewProfiles()
// Make sure we are conform with the protocol
p.Dictionary().MappingTable().AppendEmpty()
p.Dictionary().LocationTable().AppendEmpty()
p.Dictionary().FunctionTable().AppendEmpty()
p.Dictionary().LinkTable().AppendEmpty()
p.Dictionary().StringTable().Append("")
p.Dictionary().AttributeTable().AppendEmpty()
p.Dictionary().StackTable().AppendEmpty()
return p
}(),
},
} {
t.Run(tt.name, func(t *testing.T) {
srcProfiles := tt.srcProfiles
dstProfiles := tt.dstProfiles
err := srcProfiles.MergeTo(dstProfiles)
require.NoError(t, err)
// Verify dictionary sizes
assert.Equal(t, tt.expectedDictionarySizes.StringTable, dstProfiles.Dictionary().StringTable().Len(),
"StringTable size mismatch")
assert.Equal(t, tt.expectedDictionarySizes.AttributeTable, dstProfiles.Dictionary().AttributeTable().Len(),
"AttributeTable size mismatch")
assert.Equal(t, tt.expectedDictionarySizes.StackTable, dstProfiles.Dictionary().StackTable().Len(),
"StackTable size mismatch")
assert.Equal(t, tt.expectedDictionarySizes.LocationTable, dstProfiles.Dictionary().LocationTable().Len(),
"LocationTable size mismatch")
assert.Equal(t, tt.expectedDictionarySizes.FunctionTable, dstProfiles.Dictionary().FunctionTable().Len(),
"FunctionTable size mismatch")
assert.Equal(t, tt.expectedDictionarySizes.MappingTable, dstProfiles.Dictionary().MappingTable().Len(),
"MappingTable size mismatch")
assert.Equal(t, tt.expectedDictionarySizes.LinkTable, dstProfiles.Dictionary().LinkTable().Len(),
"LinkTable size mismatch")
// Verify profile count
totalProfiles := 0
for _, rp := range dstProfiles.ResourceProfiles().All() {
for _, sp := range rp.ScopeProfiles().All() {
totalProfiles += sp.Profiles().Len()
}
}
assert.Equal(t, tt.expectedProfileCount, totalProfiles, "Total profile count mismatch")
})
}
}
func TestProfilesMergeToSelf(t *testing.T) {
profiles := NewProfiles()
profiles.Dictionary().StringTable().Append("", "test")
profiles.ResourceProfiles().AppendEmpty()
require.NoError(t, profiles.MergeTo(profiles))
assert.Equal(t, 2, profiles.Dictionary().StringTable().Len())
assert.Equal(t, 1, profiles.ResourceProfiles().Len())
}
func TestProfilesMergeToError(t *testing.T) {
src := NewProfiles()
dest := NewProfiles()
stackTable := src.Dictionary().StackTable()
stackTable.AppendEmpty()
stack := stackTable.AppendEmpty()
stack.LocationIndices().Append(1)
locationTable := src.Dictionary().LocationTable()
locationTable.AppendEmpty()
locationTable.AppendEmpty().SetMappingIndex(1)
sample := src.ResourceProfiles().AppendEmpty().
ScopeProfiles().AppendEmpty().
Profiles().AppendEmpty().
Samples().AppendEmpty()
sample.SetStackIndex(1)
err := src.MergeTo(dest)
require.Error(t, err)
assert.Equal(t, 0, dest.ResourceProfiles().Len())
}
================================================
FILE: pdata/pprofile/profiles_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/internal/testutil"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestReadOnlyProfilesInvalidUsage(t *testing.T) {
pd := NewProfiles()
assert.False(t, pd.IsReadOnly())
res := pd.ResourceProfiles().AppendEmpty().Resource()
res.Attributes().PutStr("k1", "v1")
pd.MarkReadOnly()
assert.True(t, pd.IsReadOnly())
assert.Panics(t, func() { res.Attributes().PutStr("k2", "v2") })
}
func TestSampleCount(t *testing.T) {
pd := NewProfiles()
assert.Equal(t, 0, pd.SampleCount())
rs := pd.ResourceProfiles().AppendEmpty()
assert.Equal(t, 0, pd.SampleCount())
ils := rs.ScopeProfiles().AppendEmpty()
assert.Equal(t, 0, pd.SampleCount())
ps := ils.Profiles().AppendEmpty()
assert.Equal(t, 0, pd.SampleCount())
ps.Samples().AppendEmpty()
assert.Equal(t, 1, pd.SampleCount())
ils2 := rs.ScopeProfiles().AppendEmpty()
assert.Equal(t, 1, pd.SampleCount())
ps2 := ils2.Profiles().AppendEmpty()
assert.Equal(t, 1, pd.SampleCount())
ps2.Samples().AppendEmpty()
assert.Equal(t, 2, pd.SampleCount())
rms := pd.ResourceProfiles()
rms.EnsureCapacity(3)
rms.AppendEmpty().ScopeProfiles().AppendEmpty()
ilss := rms.AppendEmpty().ScopeProfiles().AppendEmpty().Profiles().AppendEmpty().Samples()
for range 5 {
ilss.AppendEmpty()
}
// 5 + 2 (from rms.At(0) and rms.At(1) initialized first)
assert.Equal(t, 7, pd.SampleCount())
}
func TestProfileCount(t *testing.T) {
pd := NewProfiles()
assert.Equal(t, 0, pd.ProfileCount())
rs := pd.ResourceProfiles().AppendEmpty()
assert.Equal(t, 0, pd.ProfileCount())
ils := rs.ScopeProfiles().AppendEmpty()
assert.Equal(t, 0, pd.ProfileCount())
ps := ils.Profiles().AppendEmpty()
assert.Equal(t, 1, pd.ProfileCount())
ps.Samples().AppendEmpty()
assert.Equal(t, 1, pd.ProfileCount())
ils2 := rs.ScopeProfiles().AppendEmpty()
assert.Equal(t, 1, pd.ProfileCount())
ps2 := ils2.Profiles().AppendEmpty()
assert.Equal(t, 2, pd.ProfileCount())
ps2.Samples().AppendEmpty()
assert.Equal(t, 2, pd.ProfileCount())
rms := pd.ResourceProfiles()
rms.EnsureCapacity(3)
rms.AppendEmpty().ScopeProfiles().AppendEmpty()
ilss := rms.AppendEmpty().ScopeProfiles().AppendEmpty().Profiles().AppendEmpty().Samples()
for range 5 {
ilss.AppendEmpty()
}
// 5 + 2 (from rms.At(0) and rms.At(1) initialized first)
assert.Equal(t, 3, pd.ProfileCount())
}
func TestSampleCountWithEmpty(t *testing.T) {
assert.Equal(t, 0, newProfiles(&internal.ExportProfilesServiceRequest{
ResourceProfiles: []*internal.ResourceProfiles{{}},
}, new(internal.State)).SampleCount())
assert.Equal(t, 0, newProfiles(&internal.ExportProfilesServiceRequest{
ResourceProfiles: []*internal.ResourceProfiles{
{
ScopeProfiles: []*internal.ScopeProfiles{{}},
},
},
}, new(internal.State)).SampleCount())
assert.Equal(t, 1, newProfiles(&internal.ExportProfilesServiceRequest{
ResourceProfiles: []*internal.ResourceProfiles{
{
ScopeProfiles: []*internal.ScopeProfiles{
{
Profiles: []*internal.Profile{
{
Samples: []*internal.Sample{
{},
},
},
},
},
},
},
},
}, new(internal.State)).SampleCount())
}
func TestProfilesSwitchDictionary(t *testing.T) {
for _, tt := range []struct {
name string
profiles Profiles
src ProfilesDictionary
dst ProfilesDictionary
wantProfiles Profiles
wantDictionary ProfilesDictionary
wantErr error
}{
{
name: "with an empty profiles",
profiles: NewProfiles(),
src: NewProfilesDictionary(),
dst: NewProfilesDictionary(),
wantProfiles: NewProfiles(),
wantDictionary: NewProfilesDictionary(),
},
{
name: "with a profiles that has a profile",
profiles: func() Profiles {
p := NewProfiles()
profile := p.ResourceProfiles().AppendEmpty().ScopeProfiles().AppendEmpty().Profiles().AppendEmpty()
profile.Samples().AppendEmpty().SetLinkIndex(1)
return p
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.LinkTable().AppendEmpty()
l := d.LinkTable().AppendEmpty()
l.SetSpanID(pcommon.SpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8}))
return d
}(),
dst: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.LinkTable().AppendEmpty()
d.LinkTable().AppendEmpty()
return d
}(),
wantProfiles: func() Profiles {
p := NewProfiles()
profile := p.ResourceProfiles().AppendEmpty().ScopeProfiles().AppendEmpty().Profiles().AppendEmpty()
profile.Samples().AppendEmpty().SetLinkIndex(2)
return p
}(),
wantDictionary: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.LinkTable().AppendEmpty()
d.LinkTable().AppendEmpty()
l := d.LinkTable().AppendEmpty()
l.SetSpanID(pcommon.SpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8}))
return d
}(),
},
} {
t.Run(tt.name, func(t *testing.T) {
p := tt.profiles
dst := tt.dst
err := p.switchDictionary(tt.src, dst)
if tt.wantErr == nil {
require.NoError(t, err)
} else {
require.Equal(t, tt.wantErr, err)
}
assert.Equal(t, tt.wantProfiles, p)
assert.Equal(t, tt.wantDictionary, dst)
})
}
}
func BenchmarkProfilesSwitchDictionary(b *testing.B) {
testutil.SkipMemoryBench(b)
p := NewProfiles()
profile := p.ResourceProfiles().AppendEmpty().ScopeProfiles().AppendEmpty().Profiles().AppendEmpty()
profile.Samples().AppendEmpty().SetLinkIndex(1)
src := NewProfilesDictionary()
src.LinkTable().AppendEmpty()
src.LinkTable().AppendEmpty().SetSpanID(pcommon.SpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8}))
b.ReportAllocs()
for b.Loop() {
b.StopTimer()
dst := NewProfilesDictionary()
b.StartTimer()
_ = p.switchDictionary(src, dst)
}
}
func BenchmarkProfilesUsage(b *testing.B) {
pd := generateTestProfiles()
ts := pcommon.NewTimestampFromTime(time.Now())
dur := uint64(1_000_000_000)
testValProfileID := ProfileID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1})
testSecondValProfileID := ProfileID([16]byte{2, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1})
b.ReportAllocs()
for b.Loop() {
for i := 0; i < pd.ResourceProfiles().Len(); i++ {
rs := pd.ResourceProfiles().At(i)
res := rs.Resource()
res.Attributes().PutStr("foo", "bar")
v, ok := res.Attributes().Get("foo")
assert.True(b, ok)
assert.Equal(b, "bar", v.Str())
v.SetStr("new-bar")
assert.Equal(b, "new-bar", v.Str())
res.Attributes().Remove("foo")
for j := 0; j < rs.ScopeProfiles().Len(); j++ {
iss := rs.ScopeProfiles().At(j)
iss.Scope().SetName("new_test_name")
assert.Equal(b, "new_test_name", iss.Scope().Name())
for k := 0; k < iss.Profiles().Len(); k++ {
s := iss.Profiles().At(k)
s.SetProfileID(testValProfileID)
assert.Equal(b, testValProfileID, s.ProfileID())
s.SetTime(ts)
assert.Equal(b, ts, s.Time())
s.SetDurationNano(dur)
assert.Equal(b, dur, s.DurationNano())
}
s := iss.Profiles().AppendEmpty()
s.SetProfileID(testSecondValProfileID)
s.SetTime(ts)
s.SetDurationNano(dur)
s.AttributeIndices().Append(1)
iss.Profiles().RemoveIf(func(lr Profile) bool {
return lr.ProfileID() == testSecondValProfileID
})
}
}
}
}
func BenchmarkProfilesMarshalJSON(b *testing.B) {
pd := generateTestProfiles()
encoder := &JSONMarshaler{}
b.ReportAllocs()
for b.Loop() {
jsonBuf, err := encoder.MarshalProfiles(pd)
require.NoError(b, err)
require.NotNil(b, jsonBuf)
}
}
================================================
FILE: pdata/pprofile/resourceprofiles.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile // import "go.opentelemetry.io/collector/pdata/pprofile"
import "fmt"
// switchDictionary updates the ResourceProfiles, switching its indices from one
// dictionary to another.
func (ms ResourceProfiles) switchDictionary(src, dst ProfilesDictionary) error {
for i, v := range ms.ScopeProfiles().All() {
err := v.switchDictionary(src, dst)
if err != nil {
return fmt.Errorf("error switching dictionary for scope profile %d: %w", i, err)
}
}
return nil
}
================================================
FILE: pdata/pprofile/resourceprofiles_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/internal/testutil"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestResourceProfilesSwitchDictionary(t *testing.T) {
for _, tt := range []struct {
name string
resourceProfiles ResourceProfiles
src ProfilesDictionary
dst ProfilesDictionary
wantResourceProfiles ResourceProfiles
wantDictionary ProfilesDictionary
wantErr error
}{
{
name: "with an empty resource profile",
resourceProfiles: NewResourceProfiles(),
src: NewProfilesDictionary(),
dst: NewProfilesDictionary(),
wantResourceProfiles: NewResourceProfiles(),
wantDictionary: NewProfilesDictionary(),
},
{
name: "with a resource profiles that has a profile",
resourceProfiles: func() ResourceProfiles {
r := NewResourceProfiles()
profile := r.ScopeProfiles().AppendEmpty().Profiles().AppendEmpty()
profile.Samples().AppendEmpty().SetLinkIndex(1)
return r
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.LinkTable().AppendEmpty()
l := d.LinkTable().AppendEmpty()
l.SetSpanID(pcommon.SpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8}))
return d
}(),
dst: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.LinkTable().AppendEmpty()
d.LinkTable().AppendEmpty()
return d
}(),
wantResourceProfiles: func() ResourceProfiles {
r := NewResourceProfiles()
profile := r.ScopeProfiles().AppendEmpty().Profiles().AppendEmpty()
profile.Samples().AppendEmpty().SetLinkIndex(2)
return r
}(),
wantDictionary: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.LinkTable().AppendEmpty()
d.LinkTable().AppendEmpty()
l := d.LinkTable().AppendEmpty()
l.SetSpanID(pcommon.SpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8}))
return d
}(),
},
} {
t.Run(tt.name, func(t *testing.T) {
rp := tt.resourceProfiles
dst := tt.dst
err := rp.switchDictionary(tt.src, dst)
if tt.wantErr == nil {
require.NoError(t, err)
} else {
require.Equal(t, tt.wantErr, err)
}
assert.Equal(t, tt.wantResourceProfiles, rp)
assert.Equal(t, tt.wantDictionary, dst)
})
}
}
func BenchmarkResourceProfilesSwitchDictionary(b *testing.B) {
testutil.SkipMemoryBench(b)
r := NewResourceProfiles()
profile := r.ScopeProfiles().AppendEmpty().Profiles().AppendEmpty()
profile.Samples().AppendEmpty().SetLinkIndex(1)
src := NewProfilesDictionary()
src.LinkTable().AppendEmpty()
src.LinkTable().AppendEmpty().SetSpanID(pcommon.SpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8}))
b.ReportAllocs()
for b.Loop() {
b.StopTimer()
dst := NewProfilesDictionary()
b.StartTimer()
_ = r.switchDictionary(src, dst)
}
}
================================================
FILE: pdata/pprofile/sample.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile // import "go.opentelemetry.io/collector/pdata/pprofile"
import "fmt"
// switchDictionary updates the Sample, switching its indices from one
// dictionary to another.
func (ms Sample) switchDictionary(src, dst ProfilesDictionary) error {
for i, v := range ms.AttributeIndices().All() {
if src.AttributeTable().Len() <= int(v) {
return fmt.Errorf("invalid attribute index %d", v)
}
attr := src.AttributeTable().At(int(v))
idx, err := SetAttribute(dst.AttributeTable(), attr)
if err != nil {
return fmt.Errorf("couldn't set attribute %d: %w", i, err)
}
ms.AttributeIndices().SetAt(i, idx)
}
if ms.LinkIndex() > 0 {
if src.LinkTable().Len() <= int(ms.LinkIndex()) {
return fmt.Errorf("invalid link index %d", ms.LinkIndex())
}
idx, err := SetLink(dst.LinkTable(), src.LinkTable().At(int(ms.LinkIndex())))
if err != nil {
return fmt.Errorf("couldn't set link: %w", err)
}
ms.SetLinkIndex(idx)
}
if ms.StackIndex() > 0 {
if src.StackTable().Len() <= int(ms.StackIndex()) {
return fmt.Errorf("invalid stack index %d", ms.StackIndex())
}
stack := src.StackTable().At(int(ms.StackIndex()))
idx, err := SetStack(dst.StackTable(), stack)
if err != nil {
return fmt.Errorf("couldn't set stack: %w", err)
}
ms.SetStackIndex(idx)
}
return nil
}
================================================
FILE: pdata/pprofile/sample_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/internal/testutil"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestSampleSwitchDictionary(t *testing.T) {
for _, tt := range []struct {
name string
sample Sample
src ProfilesDictionary
dst ProfilesDictionary
wantSample Sample
wantDictionary ProfilesDictionary
wantErr error
}{
{
name: "with an empty sample",
sample: NewSample(),
src: NewProfilesDictionary(),
dst: NewProfilesDictionary(),
wantSample: NewSample(),
wantDictionary: NewProfilesDictionary(),
},
{
name: "with an existing attribute",
sample: func() Sample {
s := NewSample()
s.AttributeIndices().Append(1)
return s
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
d.AttributeTable().AppendEmpty()
a := d.AttributeTable().AppendEmpty()
a.SetKeyStrindex(1)
return d
}(),
dst: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
d.AttributeTable().AppendEmpty()
d.AttributeTable().AppendEmpty()
a := d.AttributeTable().AppendEmpty()
a.SetKeyStrindex(1)
return d
}(),
wantSample: func() Sample {
s := NewSample()
s.AttributeIndices().Append(2)
return s
}(),
wantDictionary: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
d.AttributeTable().AppendEmpty()
d.AttributeTable().AppendEmpty()
a := d.AttributeTable().AppendEmpty()
a.SetKeyStrindex(1)
return d
}(),
},
{
name: "with an attribute index that does not match anything",
sample: func() Sample {
s := NewSample()
s.AttributeIndices().Append(1)
return s
}(),
src: NewProfilesDictionary(),
dst: NewProfilesDictionary(),
wantSample: func() Sample {
s := NewSample()
s.AttributeIndices().Append(1)
return s
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid attribute index 1"),
},
{
name: "with an attribute index equal to the source table length (boundary condition)",
sample: func() Sample {
s := NewSample()
s.AttributeIndices().Append(2)
return s
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.AttributeTable().AppendEmpty()
d.AttributeTable().AppendEmpty()
return d
}(),
dst: NewProfilesDictionary(),
wantSample: func() Sample {
s := NewSample()
s.AttributeIndices().Append(2)
return s
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid attribute index 2"),
},
{
name: "with an existing link",
sample: func() Sample {
s := NewSample()
s.SetLinkIndex(1)
return s
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.LinkTable().AppendEmpty()
l := d.LinkTable().AppendEmpty()
l.SetSpanID(pcommon.SpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8}))
return d
}(),
dst: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.LinkTable().AppendEmpty()
d.LinkTable().AppendEmpty()
return d
}(),
wantSample: func() Sample {
s := NewSample()
s.SetLinkIndex(2)
return s
}(),
wantDictionary: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.LinkTable().AppendEmpty()
d.LinkTable().AppendEmpty()
l := d.LinkTable().AppendEmpty()
l.SetSpanID(pcommon.SpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8}))
return d
}(),
},
{
name: "with a link index that does not match anything",
sample: func() Sample {
s := NewSample()
s.SetLinkIndex(1)
return s
}(),
src: NewProfilesDictionary(),
dst: NewProfilesDictionary(),
wantSample: func() Sample {
s := NewSample()
s.SetLinkIndex(1)
return s
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid link index 1"),
},
{
name: "with a link index equal to the source table length (boundary condition)",
sample: func() Sample {
s := NewSample()
s.SetLinkIndex(2)
return s
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.LinkTable().AppendEmpty()
d.LinkTable().AppendEmpty()
return d
}(),
dst: NewProfilesDictionary(),
wantSample: func() Sample {
s := NewSample()
s.SetLinkIndex(2)
return s
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid link index 2"),
},
{
name: "with an existing stack",
sample: func() Sample {
s := NewSample()
s.SetStackIndex(1)
return s
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.LocationTable().AppendEmpty().SetAddress(1)
d.LocationTable().AppendEmpty().SetAddress(2)
d.StackTable().AppendEmpty()
s := d.StackTable().AppendEmpty()
s.LocationIndices().Append(1)
return d
}(),
dst: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.LocationTable().AppendEmpty()
d.LocationTable().AppendEmpty().SetAddress(2)
d.StackTable().AppendEmpty()
d.StackTable().AppendEmpty()
s := d.StackTable().AppendEmpty()
s.LocationIndices().Append(1)
return d
}(),
wantSample: func() Sample {
s := NewSample()
s.SetStackIndex(2)
return s
}(),
wantDictionary: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.LocationTable().AppendEmpty()
d.LocationTable().AppendEmpty().SetAddress(2)
d.StackTable().AppendEmpty()
d.StackTable().AppendEmpty()
s := d.StackTable().AppendEmpty()
s.LocationIndices().Append(1)
return d
}(),
},
{
name: "with a stack index that does not match anything",
sample: func() Sample {
s := NewSample()
s.SetStackIndex(1)
return s
}(),
src: NewProfilesDictionary(),
dst: NewProfilesDictionary(),
wantSample: func() Sample {
s := NewSample()
s.SetStackIndex(1)
return s
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid stack index 1"),
},
{
name: "with a stack index equal to the source table length (boundary condition)",
sample: func() Sample {
s := NewSample()
s.SetStackIndex(2)
return s
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StackTable().AppendEmpty()
d.StackTable().AppendEmpty()
return d
}(),
dst: NewProfilesDictionary(),
wantSample: func() Sample {
s := NewSample()
s.SetStackIndex(2)
return s
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid stack index 2"),
},
} {
t.Run(tt.name, func(t *testing.T) {
sample := tt.sample
dst := tt.dst
err := sample.switchDictionary(tt.src, dst)
if tt.wantErr == nil {
require.NoError(t, err)
} else {
require.Equal(t, tt.wantErr, err)
}
assert.Equal(t, tt.wantSample, sample)
assert.Equal(t, tt.wantDictionary, dst)
})
}
}
func BenchmarkSampleSwitchDictionary(b *testing.B) {
testutil.SkipMemoryBench(b)
s := NewSample()
s.SetLinkIndex(1)
s.SetStackIndex(1)
src := NewProfilesDictionary()
src.LocationTable().AppendEmpty()
src.LocationTable().AppendEmpty().SetAddress(2)
src.LinkTable().AppendEmpty()
src.LinkTable().AppendEmpty().SetSpanID(pcommon.SpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8}))
src.StackTable().AppendEmpty()
src.StackTable().AppendEmpty().LocationIndices().Append(1)
dst := NewProfilesDictionary()
src.LinkTable().AppendEmpty()
src.LinkTable().AppendEmpty().SetSpanID(pcommon.SpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8}))
b.ReportAllocs()
for b.Loop() {
_ = s.switchDictionary(src, dst)
}
}
================================================
FILE: pdata/pprofile/scopeprofiles.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile // import "go.opentelemetry.io/collector/pdata/pprofile"
import "fmt"
// switchDictionary updates the ScopeProfiles, switching its indices from one
// dictionary to another.
func (ms ScopeProfiles) switchDictionary(src, dst ProfilesDictionary) error {
for i, v := range ms.Profiles().All() {
err := v.switchDictionary(src, dst)
if err != nil {
return fmt.Errorf("error switching dictionary for profile %d: %w", i, err)
}
}
return nil
}
================================================
FILE: pdata/pprofile/scopeprofiles_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/internal/testutil"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestScopeProfilesSwitchDictionary(t *testing.T) {
for _, tt := range []struct {
name string
scopeProfiles ScopeProfiles
src ProfilesDictionary
dst ProfilesDictionary
wantScopeProfiles ScopeProfiles
wantDictionary ProfilesDictionary
wantErr error
}{
{
name: "with an empty scope profile",
scopeProfiles: NewScopeProfiles(),
src: NewProfilesDictionary(),
dst: NewProfilesDictionary(),
wantScopeProfiles: NewScopeProfiles(),
wantDictionary: NewProfilesDictionary(),
},
{
name: "with a scope profiles that has a profile",
scopeProfiles: func() ScopeProfiles {
s := NewScopeProfiles()
profile := s.Profiles().AppendEmpty()
profile.Samples().AppendEmpty().SetLinkIndex(1)
return s
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.LinkTable().AppendEmpty()
l := d.LinkTable().AppendEmpty()
l.SetSpanID(pcommon.SpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8}))
return d
}(),
dst: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.LinkTable().AppendEmpty()
d.LinkTable().AppendEmpty()
return d
}(),
wantScopeProfiles: func() ScopeProfiles {
s := NewScopeProfiles()
profile := s.Profiles().AppendEmpty()
profile.Samples().AppendEmpty().SetLinkIndex(2)
return s
}(),
wantDictionary: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.LinkTable().AppendEmpty()
d.LinkTable().AppendEmpty()
l := d.LinkTable().AppendEmpty()
l.SetSpanID(pcommon.SpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8}))
return d
}(),
},
} {
t.Run(tt.name, func(t *testing.T) {
sp := tt.scopeProfiles
dst := tt.dst
err := sp.switchDictionary(tt.src, dst)
if tt.wantErr == nil {
require.NoError(t, err)
} else {
require.Equal(t, tt.wantErr, err)
}
assert.Equal(t, tt.wantScopeProfiles, sp)
assert.Equal(t, tt.wantDictionary, dst)
})
}
}
func BenchmarkScopeProfilesSwitchDictionary(b *testing.B) {
testutil.SkipMemoryBench(b)
s := NewScopeProfiles()
profile := s.Profiles().AppendEmpty()
profile.Samples().AppendEmpty().SetLinkIndex(1)
src := NewProfilesDictionary()
src.LinkTable().AppendEmpty()
src.LinkTable().AppendEmpty().SetSpanID(pcommon.SpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8}))
dst := NewProfilesDictionary()
b.ReportAllocs()
for b.Loop() {
_ = s.switchDictionary(src, dst)
}
}
================================================
FILE: pdata/pprofile/stack.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile // import "go.opentelemetry.io/collector/pdata/pprofile"
import (
"fmt"
)
// Equal checks equality with another Stack
func (ms Stack) Equal(val Stack) bool {
if ms.LocationIndices().Len() != val.LocationIndices().Len() {
return false
}
for i := range ms.LocationIndices().Len() {
if ms.LocationIndices().At(i) != val.LocationIndices().At(i) {
return false
}
}
return true
}
// switchDictionary updates the Stack, switching its indices from one
// dictionary to another.
func (ms Stack) switchDictionary(src, dst ProfilesDictionary) error {
for i, v := range ms.LocationIndices().All() {
if src.LocationTable().Len() <= int(v) {
return fmt.Errorf("invalid location index %d", v)
}
loc := src.LocationTable().At(int(v))
idx, err := SetLocation(dst.LocationTable(), loc)
if err != nil {
return fmt.Errorf("couldn't set location %d: %w", i, err)
}
ms.LocationIndices().SetAt(i, idx)
}
return nil
}
================================================
FILE: pdata/pprofile/stack_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/internal/testutil"
)
func TestStackEqual(t *testing.T) {
for _, tt := range []struct {
name string
orig Stack
dest Stack
want bool
}{
{
name: "with empty stacks",
orig: NewStack(),
dest: NewStack(),
want: true,
},
{
name: "with non-empty equal stacks",
orig: func() Stack {
s := NewStack()
s.LocationIndices().Append(1)
return s
}(),
dest: func() Stack {
s := NewStack()
s.LocationIndices().Append(1)
return s
}(),
want: true,
},
{
name: "with different location indices lengths",
orig: func() Stack {
s := NewStack()
s.LocationIndices().Append(1)
return s
}(),
dest: NewStack(),
want: false,
},
{
name: "with non-equal location indices",
orig: func() Stack {
s := NewStack()
s.LocationIndices().Append(2)
return s
}(),
dest: func() Stack {
s := NewStack()
s.LocationIndices().Append(1)
return s
}(),
want: false,
},
} {
t.Run(tt.name, func(t *testing.T) {
if tt.want {
assert.True(t, tt.orig.Equal(tt.dest))
} else {
assert.False(t, tt.orig.Equal(tt.dest))
}
})
}
}
func TestStackSwitchDictionary(t *testing.T) {
for _, tt := range []struct {
name string
stack Stack
src ProfilesDictionary
dst ProfilesDictionary
wantStack Stack
wantDictionary ProfilesDictionary
wantErr error
}{
{
name: "with an empty stack",
stack: NewStack(),
src: NewProfilesDictionary(),
dst: NewProfilesDictionary(),
wantStack: NewStack(),
wantDictionary: NewProfilesDictionary(),
},
{
name: "with an existing location",
stack: func() Stack {
s := NewStack()
s.LocationIndices().Append(0)
return s
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
loc := d.LocationTable().AppendEmpty()
loc.SetAddress(42)
return d
}(),
dst: NewProfilesDictionary(),
wantStack: func() Stack {
s := NewStack()
s.LocationIndices().Append(0)
return s
}(),
wantDictionary: func() ProfilesDictionary {
d := NewProfilesDictionary()
loc := d.LocationTable().AppendEmpty()
loc.SetAddress(42)
return d
}(),
},
{
name: "with an existing location that needs a new indice",
stack: func() Stack {
s := NewStack()
s.LocationIndices().Append(0)
return s
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
loc := d.LocationTable().AppendEmpty()
loc.SetAddress(42)
return d
}(),
dst: func() ProfilesDictionary {
d := NewProfilesDictionary()
loc := d.LocationTable().AppendEmpty()
loc.SetAddress(2)
return d
}(),
wantStack: func() Stack {
s := NewStack()
s.LocationIndices().Append(1)
return s
}(),
wantDictionary: func() ProfilesDictionary {
d := NewProfilesDictionary()
loc := d.LocationTable().AppendEmpty()
loc.SetAddress(2)
loc = d.LocationTable().AppendEmpty()
loc.SetAddress(42)
return d
}(),
},
{
name: "with a location index that does not match anything",
stack: func() Stack {
s := NewStack()
s.LocationIndices().Append(2)
return s
}(),
src: NewProfilesDictionary(),
dst: NewProfilesDictionary(),
wantStack: func() Stack {
s := NewStack()
s.LocationIndices().Append(2)
return s
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid location index 2"),
},
{
name: "with a location index equal to the source table length (boundary condition)",
stack: func() Stack {
s := NewStack()
s.LocationIndices().Append(2) // Index 2 with length 2 (indices 0,1 are valid)
return s
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.LocationTable().AppendEmpty() // Index 0
d.LocationTable().AppendEmpty() // Index 1
return d
}(),
dst: NewProfilesDictionary(),
wantStack: func() Stack {
s := NewStack()
s.LocationIndices().Append(2)
return s
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid location index 2"),
},
} {
t.Run(tt.name, func(t *testing.T) {
stack := tt.stack
dst := tt.dst
err := stack.switchDictionary(tt.src, dst)
if tt.wantErr == nil {
require.NoError(t, err)
} else {
require.Equal(t, tt.wantErr, err)
}
assert.Equal(t, tt.wantStack, stack)
assert.Equal(t, tt.wantDictionary, dst)
})
}
}
func BenchmarkStackSwitchDictionary(b *testing.B) {
testutil.SkipMemoryBench(b)
s := NewStack()
s.LocationIndices().Append(1, 2)
src := NewProfilesDictionary()
src.LocationTable().AppendEmpty()
src.LocationTable().AppendEmpty().SetAddress(42)
src.LocationTable().AppendEmpty().SetAddress(43)
b.ReportAllocs()
for b.Loop() {
b.StopTimer()
dst := NewProfilesDictionary()
dst.LocationTable().AppendEmpty()
dst.LocationTable().AppendEmpty().SetAddress(43)
b.StartTimer()
_ = s.switchDictionary(src, dst)
}
}
================================================
FILE: pdata/pprofile/stacks.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile // import "go.opentelemetry.io/collector/pdata/pprofile"
import (
"errors"
"math"
)
var errTooManyStackTableEntries = errors.New("too many entries in StackTable")
// SetStack updates a StackSlice, adding or providing a stack and returns its
// index.
func SetStack(table StackSlice, st Stack) (int32, error) {
for j, l := range table.All() {
if l.Equal(st) {
if j > math.MaxInt32 {
return 0, errTooManyStackTableEntries
}
return int32(j), nil
}
}
if table.Len() >= math.MaxInt32 {
return 0, errTooManyStackTableEntries
}
st.CopyTo(table.AppendEmpty())
return int32(table.Len() - 1), nil
}
================================================
FILE: pdata/pprofile/stacks_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/internal/testutil"
)
func TestSetStack(t *testing.T) {
table := NewStackSlice()
s := NewStack()
s.LocationIndices().Append(1)
s2 := NewStack()
s.LocationIndices().Append(2)
// Put a first stack
idx, err := SetStack(table, s)
require.NoError(t, err)
assert.Equal(t, 1, table.Len())
assert.Equal(t, int32(0), idx)
// Put the same stack
// This should be a no-op.
idx, err = SetStack(table, s)
require.NoError(t, err)
assert.Equal(t, 1, table.Len())
assert.Equal(t, int32(0), idx)
// Set a new stack
// This sets the index and adds to the table.
idx, err = SetStack(table, s2)
require.NoError(t, err)
assert.Equal(t, 2, table.Len())
assert.Equal(t, int32(table.Len()-1), idx)
// Set an existing stack
idx, err = SetStack(table, s)
require.NoError(t, err)
assert.Equal(t, 2, table.Len())
assert.Equal(t, int32(0), idx)
// Set another existing stack
idx, err = SetStack(table, s2)
require.NoError(t, err)
assert.Equal(t, 2, table.Len())
assert.Equal(t, int32(table.Len()-1), idx)
}
func BenchmarkSetStack(b *testing.B) {
testutil.SkipMemoryBench(b)
for _, bb := range []struct {
name string
stack Stack
runBefore func(*testing.B, StackSlice)
}{
{
name: "with a new stack",
stack: NewStack(),
},
{
name: "with an existing stack",
stack: func() Stack {
s := NewStack()
s.LocationIndices().Append(1)
return s
}(),
runBefore: func(_ *testing.B, table StackSlice) {
s := table.AppendEmpty()
s.LocationIndices().Append(1)
},
},
{
name: "with a duplicate stack",
stack: NewStack(),
runBefore: func(_ *testing.B, table StackSlice) {
_, err := SetStack(table, NewStack())
require.NoError(b, err)
},
},
{
name: "with a hundred stacks to loop through",
stack: func() Stack {
s := NewStack()
s.LocationIndices().Append(1)
return s
}(),
runBefore: func(_ *testing.B, table StackSlice) {
for range 100 {
table.AppendEmpty()
}
},
},
} {
b.Run(bb.name, func(b *testing.B) {
table := NewStackSlice()
if bb.runBefore != nil {
bb.runBefore(b, table)
}
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
_, _ = SetStack(table, bb.stack)
}
})
}
}
================================================
FILE: pdata/pprofile/string_table.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile // import "go.opentelemetry.io/collector/pdata/pprofile"
import (
"errors"
"math"
"go.opentelemetry.io/collector/pdata/pcommon"
)
var errTooManyStringTableEntries = errors.New("too many entries in StringTable")
// SetString updates a StringTable, adding or providing a value and returns its index.
func SetString(table pcommon.StringSlice, val string) (int32, error) {
for j, v := range table.All() {
if v == val {
if j > math.MaxInt32 {
return 0, errTooManyStringTableEntries
}
// Return the index of the existing value.
return int32(j), nil
}
}
if table.Len() >= math.MaxInt32 {
return 0, errTooManyMappingTableEntries
}
table.Append(val)
return int32(table.Len() - 1), nil
}
================================================
FILE: pdata/pprofile/string_table_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestSetString(t *testing.T) {
table := pcommon.NewStringSlice()
v := "test"
v2 := "test2"
// Put a first value
idx, err := SetString(table, v)
require.NoError(t, err)
assert.Equal(t, 1, table.Len())
assert.Equal(t, int32(0), idx)
// Put the same string
// This should be a no-op.
idx, err = SetString(table, v)
require.NoError(t, err)
assert.Equal(t, 1, table.Len())
assert.Equal(t, int32(0), idx)
// Set a new value
// This sets the index and adds to the table.
idx, err = SetString(table, v2)
require.NoError(t, err)
assert.Equal(t, 2, table.Len())
assert.Equal(t, int32(table.Len()-1), idx)
// Set an existing value
idx, err = SetString(table, v)
require.NoError(t, err)
assert.Equal(t, 2, table.Len())
assert.Equal(t, int32(0), idx)
// Set another existing value
idx, err = SetString(table, v2)
require.NoError(t, err)
assert.Equal(t, 2, table.Len())
assert.Equal(t, int32(table.Len()-1), idx)
}
func BenchmarkSetString(b *testing.B) {
for _, bb := range []struct {
name string
val string
runBefore func(*testing.B, pcommon.StringSlice)
}{
{
name: "with a new value",
val: "test",
},
{
name: "with an existing value",
val: "test",
runBefore: func(_ *testing.B, table pcommon.StringSlice) {
table.Append("test")
},
},
{
name: "with a duplicate value",
val: "test",
runBefore: func(_ *testing.B, table pcommon.StringSlice) {
_, err := SetString(table, "test")
require.NoError(b, err)
},
},
{
name: "with a hundred values to loop through",
val: "test",
runBefore: func(_ *testing.B, table pcommon.StringSlice) {
for i := range 100 {
table.Append(strconv.Itoa(i))
}
},
},
} {
b.Run(bb.name, func(b *testing.B) {
table := pcommon.NewStringSlice()
if bb.runBefore != nil {
bb.runBefore(b, table)
}
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
_, _ = SetString(table, bb.val)
}
})
}
}
================================================
FILE: pdata/pprofile/valuetype.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile // import "go.opentelemetry.io/collector/pdata/pprofile"
import "fmt"
// switchDictionary updates the ValueType, switching its indices from one
// dictionary to another.
func (ms ValueType) switchDictionary(src, dst ProfilesDictionary) error {
if ms.TypeStrindex() > 0 {
if src.StringTable().Len() <= int(ms.TypeStrindex()) {
return fmt.Errorf("invalid type index %d", ms.TypeStrindex())
}
idx, err := SetString(dst.StringTable(), src.StringTable().At(int(ms.TypeStrindex())))
if err != nil {
return fmt.Errorf("couldn't set type: %w", err)
}
ms.SetTypeStrindex(idx)
}
if ms.UnitStrindex() > 0 {
if src.StringTable().Len() <= int(ms.UnitStrindex()) {
return fmt.Errorf("invalid unit index %d", ms.UnitStrindex())
}
idx, err := SetString(dst.StringTable(), src.StringTable().At(int(ms.UnitStrindex())))
if err != nil {
return fmt.Errorf("couldn't set unit: %w", err)
}
ms.SetUnitStrindex(idx)
}
return nil
}
================================================
FILE: pdata/pprofile/valuetype_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/internal/testutil"
)
func TestValueTypeSwitchDictionary(t *testing.T) {
for _, tt := range []struct {
name string
valueType ValueType
src ProfilesDictionary
dst ProfilesDictionary
wantValueType ValueType
wantDictionary ProfilesDictionary
wantErr error
}{
{
name: "with an empty value type",
valueType: NewValueType(),
src: NewProfilesDictionary(),
dst: NewProfilesDictionary(),
wantValueType: NewValueType(),
wantDictionary: NewProfilesDictionary(),
},
{
name: "with an existing type",
valueType: func() ValueType {
vt := NewValueType()
vt.SetTypeStrindex(1)
return vt
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
return d
}(),
dst: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "foo")
return d
}(),
wantValueType: func() ValueType {
vt := NewValueType()
vt.SetTypeStrindex(2)
return vt
}(),
wantDictionary: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "foo", "test")
return d
}(),
},
{
name: "with a type index that does not match anything",
valueType: func() ValueType {
vt := NewValueType()
vt.SetTypeStrindex(1)
return vt
}(),
src: NewProfilesDictionary(),
dst: NewProfilesDictionary(),
wantValueType: func() ValueType {
vt := NewValueType()
vt.SetTypeStrindex(1)
return vt
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid type index 1"),
},
{
name: "with a type index equal to the source table length (boundary condition)",
valueType: func() ValueType {
vt := NewValueType()
vt.SetTypeStrindex(2)
return vt
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
return d
}(),
dst: NewProfilesDictionary(),
wantValueType: func() ValueType {
vt := NewValueType()
vt.SetTypeStrindex(2)
return vt
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid type index 2"),
},
{
name: "with an existing unit",
valueType: func() ValueType {
vt := NewValueType()
vt.SetUnitStrindex(1)
return vt
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
return d
}(),
dst: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "foo")
return d
}(),
wantValueType: func() ValueType {
vt := NewValueType()
vt.SetUnitStrindex(2)
return vt
}(),
wantDictionary: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "foo", "test")
return d
}(),
},
{
name: "with a unit index that does not match anything",
valueType: func() ValueType {
vt := NewValueType()
vt.SetUnitStrindex(1)
return vt
}(),
src: NewProfilesDictionary(),
dst: NewProfilesDictionary(),
wantValueType: func() ValueType {
vt := NewValueType()
vt.SetUnitStrindex(1)
return vt
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid unit index 1"),
},
{
name: "with a unit index equal to the source table length (boundary condition)",
valueType: func() ValueType {
vt := NewValueType()
vt.SetUnitStrindex(2)
return vt
}(),
src: func() ProfilesDictionary {
d := NewProfilesDictionary()
d.StringTable().Append("", "test")
return d
}(),
dst: NewProfilesDictionary(),
wantValueType: func() ValueType {
vt := NewValueType()
vt.SetUnitStrindex(2)
return vt
}(),
wantDictionary: NewProfilesDictionary(),
wantErr: errors.New("invalid unit index 2"),
},
} {
t.Run(tt.name, func(t *testing.T) {
vt := tt.valueType
dst := tt.dst
err := vt.switchDictionary(tt.src, dst)
if tt.wantErr == nil {
require.NoError(t, err)
} else {
require.Equal(t, tt.wantErr, err)
}
assert.Equal(t, tt.wantValueType, vt)
assert.Equal(t, tt.wantDictionary, dst)
})
}
}
func BenchmarkValueTypeSwitchDictionary(b *testing.B) {
testutil.SkipMemoryBench(b)
vt := NewValueType()
vt.SetTypeStrindex(1)
vt.SetUnitStrindex(2)
src := NewProfilesDictionary()
src.StringTable().Append("", "test", "foo")
b.ReportAllocs()
for b.Loop() {
b.StopTimer()
dst := NewProfilesDictionary()
dst.StringTable().Append("", "foo")
b.StartTimer()
_ = vt.switchDictionary(src, dst)
}
}
================================================
FILE: pdata/ptrace/doc_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package ptrace_test
import (
"fmt"
"strconv"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/ptrace"
)
func ExampleNewTraces() {
traces := ptrace.NewTraces()
resourceSpans := traces.ResourceSpans().AppendEmpty()
resourceSpans.Resource().Attributes().PutStr("service.name", "my-service")
resourceSpans.Resource().Attributes().PutStr("service.version", "1.0.0")
scopeSpans := resourceSpans.ScopeSpans().AppendEmpty()
scopeSpans.Scope().SetName("my-instrumentation-library")
scopeSpans.Scope().SetVersion("1.0.0")
span := scopeSpans.Spans().AppendEmpty()
span.SetName("my-operation")
span.SetKind(ptrace.SpanKindServer)
span.SetStartTimestamp(pcommon.Timestamp(1640995200000000000)) // 2022-01-01 00:00:00 UTC
span.SetEndTimestamp(pcommon.Timestamp(1640995200100000000)) // 2022-01-01 00:00:00.1 UTC
span.Attributes().PutStr("http.method", "GET")
span.Attributes().PutStr("http.url", "/api/v1/users")
span.Attributes().PutInt("http.status_code", 200)
fmt.Printf("Resource spans count: %d\n", traces.ResourceSpans().Len())
fmt.Printf("Spans count: %d\n", scopeSpans.Spans().Len())
fmt.Printf("Span name: %s\n", span.Name())
// Output:
// Resource spans count: 1
// Spans count: 1
// Span name: my-operation
}
func ExampleSpan_SetTraceID() {
traces := ptrace.NewTraces()
resourceSpans := traces.ResourceSpans().AppendEmpty()
scopeSpans := resourceSpans.ScopeSpans().AppendEmpty()
span := scopeSpans.Spans().AppendEmpty()
traceID := pcommon.TraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})
spanID := pcommon.SpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8})
parentSpanID := pcommon.SpanID([8]byte{9, 10, 11, 12, 13, 14, 15, 16})
span.SetTraceID(traceID)
span.SetSpanID(spanID)
span.SetParentSpanID(parentSpanID)
span.SetName("child-operation")
fmt.Printf("TraceID: %s\n", span.TraceID())
fmt.Printf("SpanID: %s\n", span.SpanID())
fmt.Printf("ParentSpanID: %s\n", span.ParentSpanID())
// Output:
// TraceID: 0102030405060708090a0b0c0d0e0f10
// SpanID: 0102030405060708
// ParentSpanID: 090a0b0c0d0e0f10
}
func ExampleSpan_Events() {
traces := ptrace.NewTraces()
resourceSpans := traces.ResourceSpans().AppendEmpty()
scopeSpans := resourceSpans.ScopeSpans().AppendEmpty()
span := scopeSpans.Spans().AppendEmpty()
span.SetName("database-query")
event1 := span.Events().AppendEmpty()
event1.SetName("query.start")
event1.SetTimestamp(pcommon.Timestamp(1640995200000000000))
event1.Attributes().PutStr("query", "SELECT * FROM users")
event2 := span.Events().AppendEmpty()
event2.SetName("query.end")
event2.SetTimestamp(pcommon.Timestamp(1640995200050000000))
event2.Attributes().PutInt("rows_returned", 42)
fmt.Printf("Events count: %d\n", span.Events().Len())
fmt.Printf("First event name: %s\n", span.Events().At(0).Name())
fmt.Printf("Second event name: %s\n", span.Events().At(1).Name())
// Output:
// Events count: 2
// First event name: query.start
// Second event name: query.end
}
func ExampleSpan_Status() {
traces := ptrace.NewTraces()
resourceSpans := traces.ResourceSpans().AppendEmpty()
scopeSpans := resourceSpans.ScopeSpans().AppendEmpty()
span := scopeSpans.Spans().AppendEmpty()
span.SetName("failed-operation")
status := span.Status()
status.SetCode(ptrace.StatusCodeError)
status.SetMessage("Connection timeout")
fmt.Printf("Status code: %s\n", status.Code())
fmt.Printf("Status message: %s\n", status.Message())
// Output:
// Status code: Error
// Status message: Connection timeout
}
func ExampleSpanKind() {
traces := ptrace.NewTraces()
resourceSpans := traces.ResourceSpans().AppendEmpty()
scopeSpans := resourceSpans.ScopeSpans().AppendEmpty()
// Different span kinds
kinds := []ptrace.SpanKind{
ptrace.SpanKindUnspecified,
ptrace.SpanKindInternal,
ptrace.SpanKindServer,
ptrace.SpanKindClient,
ptrace.SpanKindProducer,
ptrace.SpanKindConsumer,
}
for i, kind := range kinds {
span := scopeSpans.Spans().AppendEmpty()
span.SetName("operation-" + strconv.Itoa(i))
span.SetKind(kind)
span.SetStartTimestamp(pcommon.Timestamp(1640995200000000000))
span.SetEndTimestamp(pcommon.Timestamp(1640995200100000000))
}
fmt.Printf("Total spans: %d\n", scopeSpans.Spans().Len())
fmt.Printf("First span kind: %s\n", scopeSpans.Spans().At(0).Kind())
fmt.Printf("Server span kind: %s\n", scopeSpans.Spans().At(2).Kind())
fmt.Printf("Client span kind: %s\n", scopeSpans.Spans().At(3).Kind())
// Output:
// Total spans: 6
// First span kind: Unspecified
// Server span kind: Server
// Client span kind: Client
}
func ExampleSpan_Links() {
traces := ptrace.NewTraces()
resourceSpans := traces.ResourceSpans().AppendEmpty()
scopeSpans := resourceSpans.ScopeSpans().AppendEmpty()
span := scopeSpans.Spans().AppendEmpty()
span.SetName("memlimit-processor")
span.SetKind(ptrace.SpanKindInternal)
// Add links to other spans
link1 := span.Links().AppendEmpty()
link1.SetTraceID(pcommon.TraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}))
link1.SetSpanID(pcommon.SpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8}))
link1.TraceState().FromRaw("rojo=00f067aa0ba902b7,congo=t61rcWkgMzE")
link1.Attributes().PutStr("link.type", "follows_from")
link1.SetFlags(0x01)
link2 := span.Links().AppendEmpty()
link2.SetTraceID(pcommon.TraceID([16]byte{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1}))
link2.SetSpanID(pcommon.SpanID([8]byte{8, 7, 6, 5, 4, 3, 2, 1}))
link2.Attributes().PutStr("link.type", "child_of")
link2.SetDroppedAttributesCount(2)
span.SetDroppedLinksCount(1)
fmt.Printf("Links count: %d\n", span.Links().Len())
fmt.Printf("First link trace state: %s\n", link1.TraceState().AsRaw())
fmt.Printf("First link flags: %d\n", link1.Flags())
fmt.Printf("Dropped links count: %d\n", span.DroppedLinksCount())
// Output:
// Links count: 2
// First link trace state: rojo=00f067aa0ba902b7,congo=t61rcWkgMzE
// First link flags: 1
// Dropped links count: 1
}
func ExampleSpan_TraceState() {
traces := ptrace.NewTraces()
resourceSpans := traces.ResourceSpans().AppendEmpty()
scopeSpans := resourceSpans.ScopeSpans().AppendEmpty()
span := scopeSpans.Spans().AppendEmpty()
span.SetName("traced-operation")
// Set trace state (W3C Trace Context)
span.TraceState().FromRaw("rojo=00f067aa0ba902b7,congo=t61rcWkgMzE")
fmt.Printf("Trace state: %s\n", span.TraceState().AsRaw())
// Output:
// Trace state: rojo=00f067aa0ba902b7,congo=t61rcWkgMzE
}
func ExampleSpan_Flags() {
traces := ptrace.NewTraces()
resourceSpans := traces.ResourceSpans().AppendEmpty()
scopeSpans := resourceSpans.ScopeSpans().AppendEmpty()
span := scopeSpans.Spans().AppendEmpty()
span.SetName("sampled-span")
// Set trace flags (W3C Trace Context)
span.SetFlags(0x01) // Sampled flag
fmt.Printf("Span flags: %d\n", span.Flags())
// Output:
// Span flags: 1
}
func ExampleStatusCode() {
traces := ptrace.NewTraces()
resourceSpans := traces.ResourceSpans().AppendEmpty()
scopeSpans := resourceSpans.ScopeSpans().AppendEmpty()
// Different status codes
statuses := []struct {
code ptrace.StatusCode
msg string
name string
}{
{ptrace.StatusCodeUnset, "", "unset"},
{ptrace.StatusCodeOk, "Success", "ok"},
{ptrace.StatusCodeError, "Internal server error", "error"},
}
for _, s := range statuses {
span := scopeSpans.Spans().AppendEmpty()
span.SetName("operation-" + s.name)
status := span.Status()
status.SetCode(s.code)
status.SetMessage(s.msg)
}
fmt.Printf("Total spans: %d\n", scopeSpans.Spans().Len())
fmt.Printf("Unset status: %s\n", scopeSpans.Spans().At(0).Status().Code())
fmt.Printf("Ok status: %s\n", scopeSpans.Spans().At(1).Status().Code())
fmt.Printf("Error status: %s\n", scopeSpans.Spans().At(2).Status().Code())
fmt.Printf("Error message: %s\n", scopeSpans.Spans().At(2).Status().Message())
// Output:
// Total spans: 3
// Unset status: Unset
// Ok status: Ok
// Error status: Error
// Error message: Internal server error
}
func ExampleSpan_DroppedAttributesCount() {
traces := ptrace.NewTraces()
resourceSpans := traces.ResourceSpans().AppendEmpty()
scopeSpans := resourceSpans.ScopeSpans().AppendEmpty()
span := scopeSpans.Spans().AppendEmpty()
span.SetName("span-with-dropped-data")
// Add some attributes and events
span.Attributes().PutStr("key1", "value1")
span.Attributes().PutStr("key2", "value2")
event := span.Events().AppendEmpty()
event.SetName("event1")
event.SetTimestamp(pcommon.Timestamp(1640995200000000000))
// Set dropped counts
span.SetDroppedAttributesCount(5)
span.SetDroppedEventsCount(3)
span.SetDroppedLinksCount(2)
fmt.Printf("Current attributes: %d\n", span.Attributes().Len())
fmt.Printf("Dropped attributes: %d\n", span.DroppedAttributesCount())
fmt.Printf("Current events: %d\n", span.Events().Len())
fmt.Printf("Dropped events: %d\n", span.DroppedEventsCount())
fmt.Printf("Dropped links: %d\n", span.DroppedLinksCount())
// Output:
// Current attributes: 2
// Dropped attributes: 5
// Current events: 1
// Dropped events: 3
// Dropped links: 2
}
================================================
FILE: pdata/ptrace/encoding.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package ptrace // import "go.opentelemetry.io/collector/pdata/ptrace"
// MarshalSizer is the interface that groups the basic Marshal and Size methods
type MarshalSizer interface {
Marshaler
Sizer
}
// Marshaler marshals Traces into bytes.
type Marshaler interface {
// MarshalTraces the given Traces into bytes.
// If the error is not nil, the returned bytes slice cannot be used.
MarshalTraces(td Traces) ([]byte, error)
}
// Unmarshaler unmarshalls bytes into Traces.
type Unmarshaler interface {
// UnmarshalTraces the given bytes into Traces.
// If the error is not nil, the returned Traces cannot be used.
UnmarshalTraces(buf []byte) (Traces, error)
}
// Sizer is an optional interface implemented by the Marshaler,
// that calculates the size of a marshaled Traces.
type Sizer interface {
// TracesSize returns the size in bytes of a marshaled Traces.
TracesSize(td Traces) int
}
================================================
FILE: pdata/ptrace/fuzz_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package ptrace // import "go.opentelemetry.io/collector/pdata/ptrace"
import (
"bytes"
"testing"
"github.com/stretchr/testify/require"
)
var unexpectedBytes = "expected the same bytes from unmarshaling and marshaling."
func FuzzUnmarshalJSONTraces(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
u1 := &JSONUnmarshaler{}
ld1, err := u1.UnmarshalTraces(data)
if err != nil {
return
}
m1 := &JSONMarshaler{}
b1, err := m1.MarshalTraces(ld1)
require.NoError(t, err, "failed to marshal valid struct")
u2 := &JSONUnmarshaler{}
ld2, err := u2.UnmarshalTraces(b1)
require.NoError(t, err, "failed to unmarshal valid bytes")
m2 := &JSONMarshaler{}
b2, err := m2.MarshalTraces(ld2)
require.NoError(t, err, "failed to marshal valid struct")
require.True(t, bytes.Equal(b1, b2), "%s. \nexpected %d but got %d\n", unexpectedBytes, b1, b2)
})
}
func FuzzUnmarshalPBTraces(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
u1 := &ProtoUnmarshaler{}
ld1, err := u1.UnmarshalTraces(data)
if err != nil {
return
}
m1 := &ProtoMarshaler{}
b1, err := m1.MarshalTraces(ld1)
require.NoError(t, err, "failed to marshal valid struct")
u2 := &ProtoUnmarshaler{}
ld2, err := u2.UnmarshalTraces(b1)
require.NoError(t, err, "failed to unmarshal valid bytes")
m2 := &ProtoMarshaler{}
b2, err := m2.MarshalTraces(ld2)
require.NoError(t, err, "failed to marshal valid struct")
require.True(t, bytes.Equal(b1, b2), "%s. \nexpected %d but got %d\n", unexpectedBytes, b1, b2)
})
}
================================================
FILE: pdata/ptrace/generated_resourcespans.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package ptrace
import (
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// ResourceSpans is a collection of spans from a Resource.
//
// 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 *internal.ResourceSpans
state *internal.State
}
func newResourceSpans(orig *internal.ResourceSpans, state *internal.State) ResourceSpans {
return ResourceSpans{orig: orig, state: state}
}
// NewResourceSpans creates a new empty ResourceSpans.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewResourceSpans() ResourceSpans {
return newResourceSpans(internal.NewResourceSpans(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms ResourceSpans) MoveTo(dest ResourceSpans) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteResourceSpans(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// Resource returns the resource associated with this ResourceSpans.
func (ms ResourceSpans) Resource() pcommon.Resource {
return pcommon.Resource(internal.NewResourceWrapper(&ms.orig.Resource, ms.state))
}
// ScopeSpans returns the ScopeSpans associated with this ResourceSpans.
func (ms ResourceSpans) ScopeSpans() ScopeSpansSlice {
return newScopeSpansSlice(&ms.orig.ScopeSpans, ms.state)
}
// SchemaUrl returns the schemaurl associated with this ResourceSpans.
func (ms ResourceSpans) SchemaUrl() string {
return ms.orig.SchemaUrl
}
// SetSchemaUrl replaces the schemaurl associated with this ResourceSpans.
func (ms ResourceSpans) SetSchemaUrl(v string) {
ms.state.AssertMutable()
ms.orig.SchemaUrl = v
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms ResourceSpans) CopyTo(dest ResourceSpans) {
dest.state.AssertMutable()
internal.CopyResourceSpans(dest.orig, ms.orig)
}
================================================
FILE: pdata/ptrace/generated_resourcespans_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package ptrace
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestResourceSpans_MoveTo(t *testing.T) {
ms := generateTestResourceSpans()
dest := NewResourceSpans()
ms.MoveTo(dest)
assert.Equal(t, NewResourceSpans(), ms)
assert.Equal(t, generateTestResourceSpans(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestResourceSpans(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newResourceSpans(internal.NewResourceSpans(), sharedState)) })
assert.Panics(t, func() { newResourceSpans(internal.NewResourceSpans(), sharedState).MoveTo(dest) })
}
func TestResourceSpans_CopyTo(t *testing.T) {
ms := NewResourceSpans()
orig := NewResourceSpans()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestResourceSpans()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newResourceSpans(internal.NewResourceSpans(), sharedState)) })
}
func TestResourceSpans_Resource(t *testing.T) {
ms := NewResourceSpans()
assert.Equal(t, pcommon.NewResource(), ms.Resource())
ms.orig.Resource = *internal.GenTestResource()
assert.Equal(t, pcommon.Resource(internal.GenTestResourceWrapper()), ms.Resource())
}
func TestResourceSpans_ScopeSpans(t *testing.T) {
ms := NewResourceSpans()
assert.Equal(t, NewScopeSpansSlice(), ms.ScopeSpans())
ms.orig.ScopeSpans = internal.GenTestScopeSpansPtrSlice()
assert.Equal(t, generateTestScopeSpansSlice(), ms.ScopeSpans())
}
func TestResourceSpans_SchemaUrl(t *testing.T) {
ms := NewResourceSpans()
assert.Empty(t, ms.SchemaUrl())
ms.SetSchemaUrl("test_schemaurl")
assert.Equal(t, "test_schemaurl", ms.SchemaUrl())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newResourceSpans(internal.NewResourceSpans(), sharedState).SetSchemaUrl("test_schemaurl") })
}
func generateTestResourceSpans() ResourceSpans {
return newResourceSpans(internal.GenTestResourceSpans(), internal.NewState())
}
================================================
FILE: pdata/ptrace/generated_resourcespansslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package ptrace
import (
"iter"
"sort"
"go.opentelemetry.io/collector/pdata/internal"
)
// 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 *[]*internal.ResourceSpans
state *internal.State
}
func newResourceSpansSlice(orig *[]*internal.ResourceSpans, state *internal.State) ResourceSpansSlice {
return ResourceSpansSlice{orig: orig, state: state}
}
// NewResourceSpansSlice creates a ResourceSpansSliceWrapper with 0 elements.
// Can use "EnsureCapacity" to initialize with a given capacity.
func NewResourceSpansSlice() ResourceSpansSlice {
orig := []*internal.ResourceSpans(nil)
return newResourceSpansSlice(&orig, internal.NewState())
}
// 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(i int) ResourceSpans {
return newResourceSpans((*es.orig)[i], es.state)
}
// All returns an iterator over index-value pairs in the slice.
//
// for i, v := range es.All() {
// ... // Do something with index-value pair
// }
func (es ResourceSpansSlice) All() iter.Seq2[int, ResourceSpans] {
return func(yield func(int, ResourceSpans) bool) {
for i := 0; i < es.Len(); i++ {
if !yield(i, es.At(i)) {
return
}
}
}
}
// EnsureCapacity is an operation that ensures the slice has at least the specified capacity.
// 1. If the newCap <= cap then no change in capacity.
// 2. If the newCap > cap then the slice capacity will be expanded to equal newCap.
//
// Here is how a new ResourceSpansSlice can be initialized:
//
// es := NewResourceSpansSlice()
// es.EnsureCapacity(4)
// for i := 0; i < 4; i++ {
// e := es.AppendEmpty()
// // Here should set all the values for e.
// }
func (es ResourceSpansSlice) EnsureCapacity(newCap int) {
es.state.AssertMutable()
oldCap := cap(*es.orig)
if newCap <= oldCap {
return
}
newOrig := make([]*internal.ResourceSpans, len(*es.orig), newCap)
copy(newOrig, *es.orig)
*es.orig = newOrig
}
// AppendEmpty will append to the end of the slice an empty ResourceSpans.
// It returns the newly added ResourceSpans.
func (es ResourceSpansSlice) AppendEmpty() ResourceSpans {
es.state.AssertMutable()
*es.orig = append(*es.orig, internal.NewResourceSpans())
return es.At(es.Len() - 1)
}
// 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) {
es.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if es.orig == dest.orig {
return
}
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
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (es ResourceSpansSlice) RemoveIf(f func(ResourceSpans) bool) {
es.state.AssertMutable()
newLen := 0
for i := 0; i < len(*es.orig); i++ {
if f(es.At(i)) {
internal.DeleteResourceSpans((*es.orig)[i], true)
(*es.orig)[i] = nil
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*es.orig)[newLen] = (*es.orig)[i]
// Cannot delete here since we just move the data(or pointer to data) to a different position in the slice.
(*es.orig)[i] = nil
newLen++
}
*es.orig = (*es.orig)[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (es ResourceSpansSlice) CopyTo(dest ResourceSpansSlice) {
dest.state.AssertMutable()
if es.orig == dest.orig {
return
}
*dest.orig = internal.CopyResourceSpansPtrSlice(*dest.orig, *es.orig)
}
// Sort sorts the ResourceSpans elements within ResourceSpansSlice given the
// provided less function so that two instances of ResourceSpansSlice
// can be compared.
func (es ResourceSpansSlice) Sort(less func(a, b ResourceSpans) bool) {
es.state.AssertMutable()
sort.SliceStable(*es.orig, func(i, j int) bool { return less(es.At(i), es.At(j)) })
}
================================================
FILE: pdata/ptrace/generated_resourcespansslice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package ptrace
import (
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestResourceSpansSlice(t *testing.T) {
es := NewResourceSpansSlice()
assert.Equal(t, 0, es.Len())
es = newResourceSpansSlice(&[]*internal.ResourceSpans{}, internal.NewState())
assert.Equal(t, 0, es.Len())
emptyVal := NewResourceSpans()
testVal := generateTestResourceSpans()
for i := 0; i < 7; i++ {
es.AppendEmpty()
assert.Equal(t, emptyVal, es.At(i))
(*es.orig)[i] = internal.GenTestResourceSpans()
assert.Equal(t, testVal, es.At(i))
}
assert.Equal(t, 7, es.Len())
}
func TestResourceSpansSliceReadOnly(t *testing.T) {
sharedState := internal.NewState()
sharedState.MarkReadOnly()
es := newResourceSpansSlice(&[]*internal.ResourceSpans{}, sharedState)
assert.Equal(t, 0, es.Len())
assert.Panics(t, func() { es.AppendEmpty() })
assert.Panics(t, func() { es.EnsureCapacity(2) })
es2 := NewResourceSpansSlice()
es.CopyTo(es2)
assert.Panics(t, func() { es2.CopyTo(es) })
assert.Panics(t, func() { es.MoveAndAppendTo(es2) })
assert.Panics(t, func() { es2.MoveAndAppendTo(es) })
}
func TestResourceSpansSlice_CopyTo(t *testing.T) {
dest := NewResourceSpansSlice()
src := generateTestResourceSpansSlice()
src.CopyTo(dest)
assert.Equal(t, generateTestResourceSpansSlice(), dest)
dest.CopyTo(dest)
assert.Equal(t, generateTestResourceSpansSlice(), dest)
}
func TestResourceSpansSlice_EnsureCapacity(t *testing.T) {
es := generateTestResourceSpansSlice()
// Test ensure smaller capacity.
const ensureSmallLen = 4
es.EnsureCapacity(ensureSmallLen)
assert.Less(t, ensureSmallLen, es.Len())
assert.Equal(t, es.Len(), cap(*es.orig))
assert.Equal(t, generateTestResourceSpansSlice(), es)
// Test ensure larger capacity
const ensureLargeLen = 9
es.EnsureCapacity(ensureLargeLen)
assert.Less(t, generateTestResourceSpansSlice().Len(), ensureLargeLen)
assert.Equal(t, ensureLargeLen, cap(*es.orig))
assert.Equal(t, generateTestResourceSpansSlice(), es)
}
func TestResourceSpansSlice_MoveAndAppendTo(t *testing.T) {
// Test MoveAndAppendTo to empty
expectedSlice := generateTestResourceSpansSlice()
dest := NewResourceSpansSlice()
src := generateTestResourceSpansSlice()
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestResourceSpansSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo empty slice
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestResourceSpansSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo not empty slice
generateTestResourceSpansSlice().MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
dest.MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
}
func TestResourceSpansSlice_RemoveIf(t *testing.T) {
// Test RemoveIf on empty slice
emptySlice := NewResourceSpansSlice()
emptySlice.RemoveIf(func(el ResourceSpans) bool {
t.Fail()
return false
})
// Test RemoveIf
filtered := generateTestResourceSpansSlice()
pos := 0
filtered.RemoveIf(func(el ResourceSpans) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, 2, filtered.Len())
}
func TestResourceSpansSlice_RemoveIfAll(t *testing.T) {
got := generateTestResourceSpansSlice()
got.RemoveIf(func(el ResourceSpans) bool {
return true
})
assert.Equal(t, 0, got.Len())
}
func TestResourceSpansSliceAll(t *testing.T) {
ms := generateTestResourceSpansSlice()
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestResourceSpansSlice_Sort(t *testing.T) {
es := generateTestResourceSpansSlice()
es.Sort(func(a, b ResourceSpans) bool {
return uintptr(unsafe.Pointer(a.orig)) < uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Less(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
es.Sort(func(a, b ResourceSpans) bool {
return uintptr(unsafe.Pointer(a.orig)) > uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Greater(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
}
func generateTestResourceSpansSlice() ResourceSpansSlice {
ms := NewResourceSpansSlice()
*ms.orig = internal.GenTestResourceSpansPtrSlice()
return ms
}
================================================
FILE: pdata/ptrace/generated_scopespans.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package ptrace
import (
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// ScopeSpans 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 NewScopeSpans function to create new instances.
// Important: zero-initialized instance is not valid for use.
type ScopeSpans struct {
orig *internal.ScopeSpans
state *internal.State
}
func newScopeSpans(orig *internal.ScopeSpans, state *internal.State) ScopeSpans {
return ScopeSpans{orig: orig, state: state}
}
// NewScopeSpans creates a new empty ScopeSpans.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewScopeSpans() ScopeSpans {
return newScopeSpans(internal.NewScopeSpans(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms ScopeSpans) MoveTo(dest ScopeSpans) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteScopeSpans(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// Scope returns the scope associated with this ScopeSpans.
func (ms ScopeSpans) Scope() pcommon.InstrumentationScope {
return pcommon.InstrumentationScope(internal.NewInstrumentationScopeWrapper(&ms.orig.Scope, ms.state))
}
// Spans returns the Spans associated with this ScopeSpans.
func (ms ScopeSpans) Spans() SpanSlice {
return newSpanSlice(&ms.orig.Spans, ms.state)
}
// SchemaUrl returns the schemaurl associated with this ScopeSpans.
func (ms ScopeSpans) SchemaUrl() string {
return ms.orig.SchemaUrl
}
// SetSchemaUrl replaces the schemaurl associated with this ScopeSpans.
func (ms ScopeSpans) SetSchemaUrl(v string) {
ms.state.AssertMutable()
ms.orig.SchemaUrl = v
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms ScopeSpans) CopyTo(dest ScopeSpans) {
dest.state.AssertMutable()
internal.CopyScopeSpans(dest.orig, ms.orig)
}
================================================
FILE: pdata/ptrace/generated_scopespans_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package ptrace
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestScopeSpans_MoveTo(t *testing.T) {
ms := generateTestScopeSpans()
dest := NewScopeSpans()
ms.MoveTo(dest)
assert.Equal(t, NewScopeSpans(), ms)
assert.Equal(t, generateTestScopeSpans(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestScopeSpans(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newScopeSpans(internal.NewScopeSpans(), sharedState)) })
assert.Panics(t, func() { newScopeSpans(internal.NewScopeSpans(), sharedState).MoveTo(dest) })
}
func TestScopeSpans_CopyTo(t *testing.T) {
ms := NewScopeSpans()
orig := NewScopeSpans()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestScopeSpans()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newScopeSpans(internal.NewScopeSpans(), sharedState)) })
}
func TestScopeSpans_Scope(t *testing.T) {
ms := NewScopeSpans()
assert.Equal(t, pcommon.NewInstrumentationScope(), ms.Scope())
ms.orig.Scope = *internal.GenTestInstrumentationScope()
assert.Equal(t, pcommon.InstrumentationScope(internal.GenTestInstrumentationScopeWrapper()), ms.Scope())
}
func TestScopeSpans_Spans(t *testing.T) {
ms := NewScopeSpans()
assert.Equal(t, NewSpanSlice(), ms.Spans())
ms.orig.Spans = internal.GenTestSpanPtrSlice()
assert.Equal(t, generateTestSpanSlice(), ms.Spans())
}
func TestScopeSpans_SchemaUrl(t *testing.T) {
ms := NewScopeSpans()
assert.Empty(t, ms.SchemaUrl())
ms.SetSchemaUrl("test_schemaurl")
assert.Equal(t, "test_schemaurl", ms.SchemaUrl())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newScopeSpans(internal.NewScopeSpans(), sharedState).SetSchemaUrl("test_schemaurl") })
}
func generateTestScopeSpans() ScopeSpans {
return newScopeSpans(internal.GenTestScopeSpans(), internal.NewState())
}
================================================
FILE: pdata/ptrace/generated_scopespansslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package ptrace
import (
"iter"
"sort"
"go.opentelemetry.io/collector/pdata/internal"
)
// ScopeSpansSlice logically represents a slice of ScopeSpans.
//
// This is a reference type. If passed by value and callee modifies it, the
// caller will see the modification.
//
// Must use NewScopeSpansSlice function to create new instances.
// Important: zero-initialized instance is not valid for use.
type ScopeSpansSlice struct {
orig *[]*internal.ScopeSpans
state *internal.State
}
func newScopeSpansSlice(orig *[]*internal.ScopeSpans, state *internal.State) ScopeSpansSlice {
return ScopeSpansSlice{orig: orig, state: state}
}
// NewScopeSpansSlice creates a ScopeSpansSliceWrapper with 0 elements.
// Can use "EnsureCapacity" to initialize with a given capacity.
func NewScopeSpansSlice() ScopeSpansSlice {
orig := []*internal.ScopeSpans(nil)
return newScopeSpansSlice(&orig, internal.NewState())
}
// Len returns the number of elements in the slice.
//
// Returns "0" for a newly instance created with "NewScopeSpansSlice()".
func (es ScopeSpansSlice) 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 ScopeSpansSlice) At(i int) ScopeSpans {
return newScopeSpans((*es.orig)[i], es.state)
}
// All returns an iterator over index-value pairs in the slice.
//
// for i, v := range es.All() {
// ... // Do something with index-value pair
// }
func (es ScopeSpansSlice) All() iter.Seq2[int, ScopeSpans] {
return func(yield func(int, ScopeSpans) bool) {
for i := 0; i < es.Len(); i++ {
if !yield(i, es.At(i)) {
return
}
}
}
}
// EnsureCapacity is an operation that ensures the slice has at least the specified capacity.
// 1. If the newCap <= cap then no change in capacity.
// 2. If the newCap > cap then the slice capacity will be expanded to equal newCap.
//
// Here is how a new ScopeSpansSlice can be initialized:
//
// es := NewScopeSpansSlice()
// es.EnsureCapacity(4)
// for i := 0; i < 4; i++ {
// e := es.AppendEmpty()
// // Here should set all the values for e.
// }
func (es ScopeSpansSlice) EnsureCapacity(newCap int) {
es.state.AssertMutable()
oldCap := cap(*es.orig)
if newCap <= oldCap {
return
}
newOrig := make([]*internal.ScopeSpans, len(*es.orig), newCap)
copy(newOrig, *es.orig)
*es.orig = newOrig
}
// AppendEmpty will append to the end of the slice an empty ScopeSpans.
// It returns the newly added ScopeSpans.
func (es ScopeSpansSlice) AppendEmpty() ScopeSpans {
es.state.AssertMutable()
*es.orig = append(*es.orig, internal.NewScopeSpans())
return es.At(es.Len() - 1)
}
// MoveAndAppendTo moves all elements from the current slice and appends them to the dest.
// The current slice will be cleared.
func (es ScopeSpansSlice) MoveAndAppendTo(dest ScopeSpansSlice) {
es.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if es.orig == dest.orig {
return
}
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
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (es ScopeSpansSlice) RemoveIf(f func(ScopeSpans) bool) {
es.state.AssertMutable()
newLen := 0
for i := 0; i < len(*es.orig); i++ {
if f(es.At(i)) {
internal.DeleteScopeSpans((*es.orig)[i], true)
(*es.orig)[i] = nil
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*es.orig)[newLen] = (*es.orig)[i]
// Cannot delete here since we just move the data(or pointer to data) to a different position in the slice.
(*es.orig)[i] = nil
newLen++
}
*es.orig = (*es.orig)[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (es ScopeSpansSlice) CopyTo(dest ScopeSpansSlice) {
dest.state.AssertMutable()
if es.orig == dest.orig {
return
}
*dest.orig = internal.CopyScopeSpansPtrSlice(*dest.orig, *es.orig)
}
// Sort sorts the ScopeSpans elements within ScopeSpansSlice given the
// provided less function so that two instances of ScopeSpansSlice
// can be compared.
func (es ScopeSpansSlice) Sort(less func(a, b ScopeSpans) bool) {
es.state.AssertMutable()
sort.SliceStable(*es.orig, func(i, j int) bool { return less(es.At(i), es.At(j)) })
}
================================================
FILE: pdata/ptrace/generated_scopespansslice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package ptrace
import (
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestScopeSpansSlice(t *testing.T) {
es := NewScopeSpansSlice()
assert.Equal(t, 0, es.Len())
es = newScopeSpansSlice(&[]*internal.ScopeSpans{}, internal.NewState())
assert.Equal(t, 0, es.Len())
emptyVal := NewScopeSpans()
testVal := generateTestScopeSpans()
for i := 0; i < 7; i++ {
es.AppendEmpty()
assert.Equal(t, emptyVal, es.At(i))
(*es.orig)[i] = internal.GenTestScopeSpans()
assert.Equal(t, testVal, es.At(i))
}
assert.Equal(t, 7, es.Len())
}
func TestScopeSpansSliceReadOnly(t *testing.T) {
sharedState := internal.NewState()
sharedState.MarkReadOnly()
es := newScopeSpansSlice(&[]*internal.ScopeSpans{}, sharedState)
assert.Equal(t, 0, es.Len())
assert.Panics(t, func() { es.AppendEmpty() })
assert.Panics(t, func() { es.EnsureCapacity(2) })
es2 := NewScopeSpansSlice()
es.CopyTo(es2)
assert.Panics(t, func() { es2.CopyTo(es) })
assert.Panics(t, func() { es.MoveAndAppendTo(es2) })
assert.Panics(t, func() { es2.MoveAndAppendTo(es) })
}
func TestScopeSpansSlice_CopyTo(t *testing.T) {
dest := NewScopeSpansSlice()
src := generateTestScopeSpansSlice()
src.CopyTo(dest)
assert.Equal(t, generateTestScopeSpansSlice(), dest)
dest.CopyTo(dest)
assert.Equal(t, generateTestScopeSpansSlice(), dest)
}
func TestScopeSpansSlice_EnsureCapacity(t *testing.T) {
es := generateTestScopeSpansSlice()
// Test ensure smaller capacity.
const ensureSmallLen = 4
es.EnsureCapacity(ensureSmallLen)
assert.Less(t, ensureSmallLen, es.Len())
assert.Equal(t, es.Len(), cap(*es.orig))
assert.Equal(t, generateTestScopeSpansSlice(), es)
// Test ensure larger capacity
const ensureLargeLen = 9
es.EnsureCapacity(ensureLargeLen)
assert.Less(t, generateTestScopeSpansSlice().Len(), ensureLargeLen)
assert.Equal(t, ensureLargeLen, cap(*es.orig))
assert.Equal(t, generateTestScopeSpansSlice(), es)
}
func TestScopeSpansSlice_MoveAndAppendTo(t *testing.T) {
// Test MoveAndAppendTo to empty
expectedSlice := generateTestScopeSpansSlice()
dest := NewScopeSpansSlice()
src := generateTestScopeSpansSlice()
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestScopeSpansSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo empty slice
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestScopeSpansSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo not empty slice
generateTestScopeSpansSlice().MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
dest.MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
}
func TestScopeSpansSlice_RemoveIf(t *testing.T) {
// Test RemoveIf on empty slice
emptySlice := NewScopeSpansSlice()
emptySlice.RemoveIf(func(el ScopeSpans) bool {
t.Fail()
return false
})
// Test RemoveIf
filtered := generateTestScopeSpansSlice()
pos := 0
filtered.RemoveIf(func(el ScopeSpans) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, 2, filtered.Len())
}
func TestScopeSpansSlice_RemoveIfAll(t *testing.T) {
got := generateTestScopeSpansSlice()
got.RemoveIf(func(el ScopeSpans) bool {
return true
})
assert.Equal(t, 0, got.Len())
}
func TestScopeSpansSliceAll(t *testing.T) {
ms := generateTestScopeSpansSlice()
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestScopeSpansSlice_Sort(t *testing.T) {
es := generateTestScopeSpansSlice()
es.Sort(func(a, b ScopeSpans) bool {
return uintptr(unsafe.Pointer(a.orig)) < uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Less(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
es.Sort(func(a, b ScopeSpans) bool {
return uintptr(unsafe.Pointer(a.orig)) > uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Greater(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
}
func generateTestScopeSpansSlice() ScopeSpansSlice {
ms := NewScopeSpansSlice()
*ms.orig = internal.GenTestScopeSpansPtrSlice()
return ms
}
================================================
FILE: pdata/ptrace/generated_span.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package ptrace
import (
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// Span represents a single operation within a trace.
// See Span definition in OTLP: https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/trace/v1/trace.proto
//
// 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 *internal.Span
state *internal.State
}
func newSpan(orig *internal.Span, state *internal.State) Span {
return Span{orig: orig, state: state}
}
// NewSpan creates a new empty Span.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewSpan() Span {
return newSpan(internal.NewSpan(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms Span) MoveTo(dest Span) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteSpan(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// TraceID returns the traceid associated with this Span.
func (ms Span) TraceID() pcommon.TraceID {
return pcommon.TraceID(ms.orig.TraceId)
}
// SetTraceID replaces the traceid associated with this Span.
func (ms Span) SetTraceID(v pcommon.TraceID) {
ms.state.AssertMutable()
ms.orig.TraceId = internal.TraceID(v)
}
// SpanID returns the spanid associated with this Span.
func (ms Span) SpanID() pcommon.SpanID {
return pcommon.SpanID(ms.orig.SpanId)
}
// SetSpanID replaces the spanid associated with this Span.
func (ms Span) SetSpanID(v pcommon.SpanID) {
ms.state.AssertMutable()
ms.orig.SpanId = internal.SpanID(v)
}
// TraceState returns the tracestate associated with this Span.
func (ms Span) TraceState() pcommon.TraceState {
return pcommon.TraceState(internal.NewTraceStateWrapper(&ms.orig.TraceState, ms.state))
}
// ParentSpanID returns the parentspanid associated with this Span.
func (ms Span) ParentSpanID() pcommon.SpanID {
return pcommon.SpanID(ms.orig.ParentSpanId)
}
// SetParentSpanID replaces the parentspanid associated with this Span.
func (ms Span) SetParentSpanID(v pcommon.SpanID) {
ms.state.AssertMutable()
ms.orig.ParentSpanId = internal.SpanID(v)
}
// Flags returns the flags associated with this Span.
func (ms Span) Flags() uint32 {
return ms.orig.Flags
}
// SetFlags replaces the flags associated with this Span.
func (ms Span) SetFlags(v uint32) {
ms.state.AssertMutable()
ms.orig.Flags = v
}
// Name returns the name associated with this Span.
func (ms Span) Name() string {
return ms.orig.Name
}
// SetName replaces the name associated with this Span.
func (ms Span) SetName(v string) {
ms.state.AssertMutable()
ms.orig.Name = v
}
// Kind returns the kind associated with this Span.
func (ms Span) Kind() SpanKind {
return SpanKind(ms.orig.Kind)
}
// SetKind replaces the kind associated with this Span.
func (ms Span) SetKind(v SpanKind) {
ms.state.AssertMutable()
ms.orig.Kind = internal.SpanKind(v)
}
// StartTimestamp returns the starttimestamp associated with this Span.
func (ms Span) StartTimestamp() pcommon.Timestamp {
return pcommon.Timestamp(ms.orig.StartTimeUnixNano)
}
// SetStartTimestamp replaces the starttimestamp associated with this Span.
func (ms Span) SetStartTimestamp(v pcommon.Timestamp) {
ms.state.AssertMutable()
ms.orig.StartTimeUnixNano = uint64(v)
}
// EndTimestamp returns the endtimestamp associated with this Span.
func (ms Span) EndTimestamp() pcommon.Timestamp {
return pcommon.Timestamp(ms.orig.EndTimeUnixNano)
}
// SetEndTimestamp replaces the endtimestamp associated with this Span.
func (ms Span) SetEndTimestamp(v pcommon.Timestamp) {
ms.state.AssertMutable()
ms.orig.EndTimeUnixNano = uint64(v)
}
// Attributes returns the Attributes associated with this Span.
func (ms Span) Attributes() pcommon.Map {
return pcommon.Map(internal.NewMapWrapper(&ms.orig.Attributes, ms.state))
}
// DroppedAttributesCount returns the droppedattributescount associated with this Span.
func (ms Span) DroppedAttributesCount() uint32 {
return ms.orig.DroppedAttributesCount
}
// SetDroppedAttributesCount replaces the droppedattributescount associated with this Span.
func (ms Span) SetDroppedAttributesCount(v uint32) {
ms.state.AssertMutable()
ms.orig.DroppedAttributesCount = v
}
// Events returns the Events associated with this Span.
func (ms Span) Events() SpanEventSlice {
return newSpanEventSlice(&ms.orig.Events, ms.state)
}
// DroppedEventsCount returns the droppedeventscount associated with this Span.
func (ms Span) DroppedEventsCount() uint32 {
return ms.orig.DroppedEventsCount
}
// SetDroppedEventsCount replaces the droppedeventscount associated with this Span.
func (ms Span) SetDroppedEventsCount(v uint32) {
ms.state.AssertMutable()
ms.orig.DroppedEventsCount = v
}
// Links returns the Links associated with this Span.
func (ms Span) Links() SpanLinkSlice {
return newSpanLinkSlice(&ms.orig.Links, ms.state)
}
// DroppedLinksCount returns the droppedlinkscount associated with this Span.
func (ms Span) DroppedLinksCount() uint32 {
return ms.orig.DroppedLinksCount
}
// SetDroppedLinksCount replaces the droppedlinkscount associated with this Span.
func (ms Span) SetDroppedLinksCount(v uint32) {
ms.state.AssertMutable()
ms.orig.DroppedLinksCount = v
}
// Status returns the status associated with this Span.
func (ms Span) Status() Status {
return newStatus(&ms.orig.Status, ms.state)
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms Span) CopyTo(dest Span) {
dest.state.AssertMutable()
internal.CopySpan(dest.orig, ms.orig)
}
================================================
FILE: pdata/ptrace/generated_span_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package ptrace
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestSpan_MoveTo(t *testing.T) {
ms := generateTestSpan()
dest := NewSpan()
ms.MoveTo(dest)
assert.Equal(t, NewSpan(), ms)
assert.Equal(t, generateTestSpan(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestSpan(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newSpan(internal.NewSpan(), sharedState)) })
assert.Panics(t, func() { newSpan(internal.NewSpan(), sharedState).MoveTo(dest) })
}
func TestSpan_CopyTo(t *testing.T) {
ms := NewSpan()
orig := NewSpan()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestSpan()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newSpan(internal.NewSpan(), sharedState)) })
}
func TestSpan_TraceID(t *testing.T) {
ms := NewSpan()
assert.Equal(t, pcommon.TraceID(internal.TraceID([16]byte{})), ms.TraceID())
testValTraceID := pcommon.TraceID(internal.TraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}))
ms.SetTraceID(testValTraceID)
assert.Equal(t, testValTraceID, ms.TraceID())
}
func TestSpan_SpanID(t *testing.T) {
ms := NewSpan()
assert.Equal(t, pcommon.SpanID(internal.SpanID([8]byte{})), ms.SpanID())
testValSpanID := pcommon.SpanID(internal.SpanID([8]byte{8, 7, 6, 5, 4, 3, 2, 1}))
ms.SetSpanID(testValSpanID)
assert.Equal(t, testValSpanID, ms.SpanID())
}
func TestSpan_TraceState(t *testing.T) {
ms := NewSpan()
assert.Equal(t, pcommon.NewTraceState(), ms.TraceState())
ms.orig.TraceState = *internal.GenTestTraceState()
assert.Equal(t, pcommon.TraceState(internal.GenTestTraceStateWrapper()), ms.TraceState())
}
func TestSpan_ParentSpanID(t *testing.T) {
ms := NewSpan()
assert.Equal(t, pcommon.SpanID(internal.SpanID([8]byte{})), ms.ParentSpanID())
testValParentSpanID := pcommon.SpanID(internal.SpanID([8]byte{8, 7, 6, 5, 4, 3, 2, 1}))
ms.SetParentSpanID(testValParentSpanID)
assert.Equal(t, testValParentSpanID, ms.ParentSpanID())
}
func TestSpan_Flags(t *testing.T) {
ms := NewSpan()
assert.Equal(t, uint32(0), ms.Flags())
ms.SetFlags(uint32(13))
assert.Equal(t, uint32(13), ms.Flags())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newSpan(internal.NewSpan(), sharedState).SetFlags(uint32(13)) })
}
func TestSpan_Name(t *testing.T) {
ms := NewSpan()
assert.Empty(t, ms.Name())
ms.SetName("test_name")
assert.Equal(t, "test_name", ms.Name())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newSpan(internal.NewSpan(), sharedState).SetName("test_name") })
}
func TestSpan_Kind(t *testing.T) {
ms := NewSpan()
assert.Equal(t, SpanKind(internal.SpanKind_SPAN_KIND_UNSPECIFIED), ms.Kind())
testValKind := SpanKind(internal.SpanKind_SPAN_KIND_CLIENT)
ms.SetKind(testValKind)
assert.Equal(t, testValKind, ms.Kind())
}
func TestSpan_StartTimestamp(t *testing.T) {
ms := NewSpan()
assert.Equal(t, pcommon.Timestamp(0), ms.StartTimestamp())
testValStartTimestamp := pcommon.Timestamp(1234567890)
ms.SetStartTimestamp(testValStartTimestamp)
assert.Equal(t, testValStartTimestamp, ms.StartTimestamp())
}
func TestSpan_EndTimestamp(t *testing.T) {
ms := NewSpan()
assert.Equal(t, pcommon.Timestamp(0), ms.EndTimestamp())
testValEndTimestamp := pcommon.Timestamp(1234567890)
ms.SetEndTimestamp(testValEndTimestamp)
assert.Equal(t, testValEndTimestamp, ms.EndTimestamp())
}
func TestSpan_Attributes(t *testing.T) {
ms := NewSpan()
assert.Equal(t, pcommon.NewMap(), ms.Attributes())
ms.orig.Attributes = internal.GenTestKeyValueSlice()
assert.Equal(t, pcommon.Map(internal.GenTestMapWrapper()), ms.Attributes())
}
func TestSpan_DroppedAttributesCount(t *testing.T) {
ms := NewSpan()
assert.Equal(t, uint32(0), ms.DroppedAttributesCount())
ms.SetDroppedAttributesCount(uint32(13))
assert.Equal(t, uint32(13), ms.DroppedAttributesCount())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newSpan(internal.NewSpan(), sharedState).SetDroppedAttributesCount(uint32(13)) })
}
func TestSpan_Events(t *testing.T) {
ms := NewSpan()
assert.Equal(t, NewSpanEventSlice(), ms.Events())
ms.orig.Events = internal.GenTestSpanEventPtrSlice()
assert.Equal(t, generateTestSpanEventSlice(), ms.Events())
}
func TestSpan_DroppedEventsCount(t *testing.T) {
ms := NewSpan()
assert.Equal(t, uint32(0), ms.DroppedEventsCount())
ms.SetDroppedEventsCount(uint32(13))
assert.Equal(t, uint32(13), ms.DroppedEventsCount())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newSpan(internal.NewSpan(), sharedState).SetDroppedEventsCount(uint32(13)) })
}
func TestSpan_Links(t *testing.T) {
ms := NewSpan()
assert.Equal(t, NewSpanLinkSlice(), ms.Links())
ms.orig.Links = internal.GenTestSpanLinkPtrSlice()
assert.Equal(t, generateTestSpanLinkSlice(), ms.Links())
}
func TestSpan_DroppedLinksCount(t *testing.T) {
ms := NewSpan()
assert.Equal(t, uint32(0), ms.DroppedLinksCount())
ms.SetDroppedLinksCount(uint32(13))
assert.Equal(t, uint32(13), ms.DroppedLinksCount())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newSpan(internal.NewSpan(), sharedState).SetDroppedLinksCount(uint32(13)) })
}
func TestSpan_Status(t *testing.T) {
ms := NewSpan()
assert.Equal(t, NewStatus(), ms.Status())
ms.orig.Status = *internal.GenTestStatus()
assert.Equal(t, generateTestStatus(), ms.Status())
}
func generateTestSpan() Span {
return newSpan(internal.GenTestSpan(), internal.NewState())
}
================================================
FILE: pdata/ptrace/generated_spanevent.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package ptrace
import (
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// 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 *internal.SpanEvent
state *internal.State
}
func newSpanEvent(orig *internal.SpanEvent, state *internal.State) SpanEvent {
return SpanEvent{orig: orig, state: state}
}
// NewSpanEvent creates a new empty SpanEvent.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewSpanEvent() SpanEvent {
return newSpanEvent(internal.NewSpanEvent(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms SpanEvent) MoveTo(dest SpanEvent) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteSpanEvent(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// Timestamp returns the timestamp associated with this SpanEvent.
func (ms SpanEvent) Timestamp() pcommon.Timestamp {
return pcommon.Timestamp(ms.orig.TimeUnixNano)
}
// SetTimestamp replaces the timestamp associated with this SpanEvent.
func (ms SpanEvent) SetTimestamp(v pcommon.Timestamp) {
ms.state.AssertMutable()
ms.orig.TimeUnixNano = uint64(v)
}
// Name returns the name associated with this SpanEvent.
func (ms SpanEvent) Name() string {
return ms.orig.Name
}
// SetName replaces the name associated with this SpanEvent.
func (ms SpanEvent) SetName(v string) {
ms.state.AssertMutable()
ms.orig.Name = v
}
// Attributes returns the Attributes associated with this SpanEvent.
func (ms SpanEvent) Attributes() pcommon.Map {
return pcommon.Map(internal.NewMapWrapper(&ms.orig.Attributes, ms.state))
}
// DroppedAttributesCount returns the droppedattributescount associated with this SpanEvent.
func (ms SpanEvent) DroppedAttributesCount() uint32 {
return ms.orig.DroppedAttributesCount
}
// SetDroppedAttributesCount replaces the droppedattributescount associated with this SpanEvent.
func (ms SpanEvent) SetDroppedAttributesCount(v uint32) {
ms.state.AssertMutable()
ms.orig.DroppedAttributesCount = v
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms SpanEvent) CopyTo(dest SpanEvent) {
dest.state.AssertMutable()
internal.CopySpanEvent(dest.orig, ms.orig)
}
================================================
FILE: pdata/ptrace/generated_spanevent_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package ptrace
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestSpanEvent_MoveTo(t *testing.T) {
ms := generateTestSpanEvent()
dest := NewSpanEvent()
ms.MoveTo(dest)
assert.Equal(t, NewSpanEvent(), ms)
assert.Equal(t, generateTestSpanEvent(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestSpanEvent(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newSpanEvent(internal.NewSpanEvent(), sharedState)) })
assert.Panics(t, func() { newSpanEvent(internal.NewSpanEvent(), sharedState).MoveTo(dest) })
}
func TestSpanEvent_CopyTo(t *testing.T) {
ms := NewSpanEvent()
orig := NewSpanEvent()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestSpanEvent()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newSpanEvent(internal.NewSpanEvent(), sharedState)) })
}
func TestSpanEvent_Timestamp(t *testing.T) {
ms := NewSpanEvent()
assert.Equal(t, pcommon.Timestamp(0), ms.Timestamp())
testValTimestamp := pcommon.Timestamp(1234567890)
ms.SetTimestamp(testValTimestamp)
assert.Equal(t, testValTimestamp, ms.Timestamp())
}
func TestSpanEvent_Name(t *testing.T) {
ms := NewSpanEvent()
assert.Empty(t, ms.Name())
ms.SetName("test_name")
assert.Equal(t, "test_name", ms.Name())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newSpanEvent(internal.NewSpanEvent(), sharedState).SetName("test_name") })
}
func TestSpanEvent_Attributes(t *testing.T) {
ms := NewSpanEvent()
assert.Equal(t, pcommon.NewMap(), ms.Attributes())
ms.orig.Attributes = internal.GenTestKeyValueSlice()
assert.Equal(t, pcommon.Map(internal.GenTestMapWrapper()), ms.Attributes())
}
func TestSpanEvent_DroppedAttributesCount(t *testing.T) {
ms := NewSpanEvent()
assert.Equal(t, uint32(0), ms.DroppedAttributesCount())
ms.SetDroppedAttributesCount(uint32(13))
assert.Equal(t, uint32(13), ms.DroppedAttributesCount())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newSpanEvent(internal.NewSpanEvent(), sharedState).SetDroppedAttributesCount(uint32(13)) })
}
func generateTestSpanEvent() SpanEvent {
return newSpanEvent(internal.GenTestSpanEvent(), internal.NewState())
}
================================================
FILE: pdata/ptrace/generated_spaneventslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package ptrace
import (
"iter"
"sort"
"go.opentelemetry.io/collector/pdata/internal"
)
// 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 *[]*internal.SpanEvent
state *internal.State
}
func newSpanEventSlice(orig *[]*internal.SpanEvent, state *internal.State) SpanEventSlice {
return SpanEventSlice{orig: orig, state: state}
}
// NewSpanEventSlice creates a SpanEventSliceWrapper with 0 elements.
// Can use "EnsureCapacity" to initialize with a given capacity.
func NewSpanEventSlice() SpanEventSlice {
orig := []*internal.SpanEvent(nil)
return newSpanEventSlice(&orig, internal.NewState())
}
// 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(i int) SpanEvent {
return newSpanEvent((*es.orig)[i], es.state)
}
// All returns an iterator over index-value pairs in the slice.
//
// for i, v := range es.All() {
// ... // Do something with index-value pair
// }
func (es SpanEventSlice) All() iter.Seq2[int, SpanEvent] {
return func(yield func(int, SpanEvent) bool) {
for i := 0; i < es.Len(); i++ {
if !yield(i, es.At(i)) {
return
}
}
}
}
// EnsureCapacity is an operation that ensures the slice has at least the specified capacity.
// 1. If the newCap <= cap then no change in capacity.
// 2. If the newCap > cap then the slice capacity will be expanded to equal newCap.
//
// Here is how a new SpanEventSlice can be initialized:
//
// es := NewSpanEventSlice()
// es.EnsureCapacity(4)
// for i := 0; i < 4; i++ {
// e := es.AppendEmpty()
// // Here should set all the values for e.
// }
func (es SpanEventSlice) EnsureCapacity(newCap int) {
es.state.AssertMutable()
oldCap := cap(*es.orig)
if newCap <= oldCap {
return
}
newOrig := make([]*internal.SpanEvent, len(*es.orig), newCap)
copy(newOrig, *es.orig)
*es.orig = newOrig
}
// AppendEmpty will append to the end of the slice an empty SpanEvent.
// It returns the newly added SpanEvent.
func (es SpanEventSlice) AppendEmpty() SpanEvent {
es.state.AssertMutable()
*es.orig = append(*es.orig, internal.NewSpanEvent())
return es.At(es.Len() - 1)
}
// 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) {
es.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if es.orig == dest.orig {
return
}
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
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (es SpanEventSlice) RemoveIf(f func(SpanEvent) bool) {
es.state.AssertMutable()
newLen := 0
for i := 0; i < len(*es.orig); i++ {
if f(es.At(i)) {
internal.DeleteSpanEvent((*es.orig)[i], true)
(*es.orig)[i] = nil
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*es.orig)[newLen] = (*es.orig)[i]
// Cannot delete here since we just move the data(or pointer to data) to a different position in the slice.
(*es.orig)[i] = nil
newLen++
}
*es.orig = (*es.orig)[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (es SpanEventSlice) CopyTo(dest SpanEventSlice) {
dest.state.AssertMutable()
if es.orig == dest.orig {
return
}
*dest.orig = internal.CopySpanEventPtrSlice(*dest.orig, *es.orig)
}
// Sort sorts the SpanEvent elements within SpanEventSlice given the
// provided less function so that two instances of SpanEventSlice
// can be compared.
func (es SpanEventSlice) Sort(less func(a, b SpanEvent) bool) {
es.state.AssertMutable()
sort.SliceStable(*es.orig, func(i, j int) bool { return less(es.At(i), es.At(j)) })
}
================================================
FILE: pdata/ptrace/generated_spaneventslice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package ptrace
import (
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestSpanEventSlice(t *testing.T) {
es := NewSpanEventSlice()
assert.Equal(t, 0, es.Len())
es = newSpanEventSlice(&[]*internal.SpanEvent{}, internal.NewState())
assert.Equal(t, 0, es.Len())
emptyVal := NewSpanEvent()
testVal := generateTestSpanEvent()
for i := 0; i < 7; i++ {
es.AppendEmpty()
assert.Equal(t, emptyVal, es.At(i))
(*es.orig)[i] = internal.GenTestSpanEvent()
assert.Equal(t, testVal, es.At(i))
}
assert.Equal(t, 7, es.Len())
}
func TestSpanEventSliceReadOnly(t *testing.T) {
sharedState := internal.NewState()
sharedState.MarkReadOnly()
es := newSpanEventSlice(&[]*internal.SpanEvent{}, sharedState)
assert.Equal(t, 0, es.Len())
assert.Panics(t, func() { es.AppendEmpty() })
assert.Panics(t, func() { es.EnsureCapacity(2) })
es2 := NewSpanEventSlice()
es.CopyTo(es2)
assert.Panics(t, func() { es2.CopyTo(es) })
assert.Panics(t, func() { es.MoveAndAppendTo(es2) })
assert.Panics(t, func() { es2.MoveAndAppendTo(es) })
}
func TestSpanEventSlice_CopyTo(t *testing.T) {
dest := NewSpanEventSlice()
src := generateTestSpanEventSlice()
src.CopyTo(dest)
assert.Equal(t, generateTestSpanEventSlice(), dest)
dest.CopyTo(dest)
assert.Equal(t, generateTestSpanEventSlice(), dest)
}
func TestSpanEventSlice_EnsureCapacity(t *testing.T) {
es := generateTestSpanEventSlice()
// Test ensure smaller capacity.
const ensureSmallLen = 4
es.EnsureCapacity(ensureSmallLen)
assert.Less(t, ensureSmallLen, es.Len())
assert.Equal(t, es.Len(), cap(*es.orig))
assert.Equal(t, generateTestSpanEventSlice(), es)
// Test ensure larger capacity
const ensureLargeLen = 9
es.EnsureCapacity(ensureLargeLen)
assert.Less(t, generateTestSpanEventSlice().Len(), ensureLargeLen)
assert.Equal(t, ensureLargeLen, cap(*es.orig))
assert.Equal(t, generateTestSpanEventSlice(), es)
}
func TestSpanEventSlice_MoveAndAppendTo(t *testing.T) {
// Test MoveAndAppendTo to empty
expectedSlice := generateTestSpanEventSlice()
dest := NewSpanEventSlice()
src := generateTestSpanEventSlice()
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestSpanEventSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo empty slice
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestSpanEventSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo not empty slice
generateTestSpanEventSlice().MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
dest.MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
}
func TestSpanEventSlice_RemoveIf(t *testing.T) {
// Test RemoveIf on empty slice
emptySlice := NewSpanEventSlice()
emptySlice.RemoveIf(func(el SpanEvent) bool {
t.Fail()
return false
})
// Test RemoveIf
filtered := generateTestSpanEventSlice()
pos := 0
filtered.RemoveIf(func(el SpanEvent) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, 2, filtered.Len())
}
func TestSpanEventSlice_RemoveIfAll(t *testing.T) {
got := generateTestSpanEventSlice()
got.RemoveIf(func(el SpanEvent) bool {
return true
})
assert.Equal(t, 0, got.Len())
}
func TestSpanEventSliceAll(t *testing.T) {
ms := generateTestSpanEventSlice()
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestSpanEventSlice_Sort(t *testing.T) {
es := generateTestSpanEventSlice()
es.Sort(func(a, b SpanEvent) bool {
return uintptr(unsafe.Pointer(a.orig)) < uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Less(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
es.Sort(func(a, b SpanEvent) bool {
return uintptr(unsafe.Pointer(a.orig)) > uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Greater(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
}
func generateTestSpanEventSlice() SpanEventSlice {
ms := NewSpanEventSlice()
*ms.orig = internal.GenTestSpanEventPtrSlice()
return ms
}
================================================
FILE: pdata/ptrace/generated_spanlink.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package ptrace
import (
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// SpanLink is a pointer from the current span to another span in the same trace or in a
// different trace.
// See Link definition in OTLP: https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/trace/v1/trace.proto
//
// 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 *internal.SpanLink
state *internal.State
}
func newSpanLink(orig *internal.SpanLink, state *internal.State) SpanLink {
return SpanLink{orig: orig, state: state}
}
// NewSpanLink creates a new empty SpanLink.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewSpanLink() SpanLink {
return newSpanLink(internal.NewSpanLink(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms SpanLink) MoveTo(dest SpanLink) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteSpanLink(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// TraceID returns the traceid associated with this SpanLink.
func (ms SpanLink) TraceID() pcommon.TraceID {
return pcommon.TraceID(ms.orig.TraceId)
}
// SetTraceID replaces the traceid associated with this SpanLink.
func (ms SpanLink) SetTraceID(v pcommon.TraceID) {
ms.state.AssertMutable()
ms.orig.TraceId = internal.TraceID(v)
}
// SpanID returns the spanid associated with this SpanLink.
func (ms SpanLink) SpanID() pcommon.SpanID {
return pcommon.SpanID(ms.orig.SpanId)
}
// SetSpanID replaces the spanid associated with this SpanLink.
func (ms SpanLink) SetSpanID(v pcommon.SpanID) {
ms.state.AssertMutable()
ms.orig.SpanId = internal.SpanID(v)
}
// TraceState returns the tracestate associated with this SpanLink.
func (ms SpanLink) TraceState() pcommon.TraceState {
return pcommon.TraceState(internal.NewTraceStateWrapper(&ms.orig.TraceState, ms.state))
}
// Attributes returns the Attributes associated with this SpanLink.
func (ms SpanLink) Attributes() pcommon.Map {
return pcommon.Map(internal.NewMapWrapper(&ms.orig.Attributes, ms.state))
}
// DroppedAttributesCount returns the droppedattributescount associated with this SpanLink.
func (ms SpanLink) DroppedAttributesCount() uint32 {
return ms.orig.DroppedAttributesCount
}
// SetDroppedAttributesCount replaces the droppedattributescount associated with this SpanLink.
func (ms SpanLink) SetDroppedAttributesCount(v uint32) {
ms.state.AssertMutable()
ms.orig.DroppedAttributesCount = v
}
// Flags returns the flags associated with this SpanLink.
func (ms SpanLink) Flags() uint32 {
return ms.orig.Flags
}
// SetFlags replaces the flags associated with this SpanLink.
func (ms SpanLink) SetFlags(v uint32) {
ms.state.AssertMutable()
ms.orig.Flags = v
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms SpanLink) CopyTo(dest SpanLink) {
dest.state.AssertMutable()
internal.CopySpanLink(dest.orig, ms.orig)
}
================================================
FILE: pdata/ptrace/generated_spanlink_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package ptrace
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestSpanLink_MoveTo(t *testing.T) {
ms := generateTestSpanLink()
dest := NewSpanLink()
ms.MoveTo(dest)
assert.Equal(t, NewSpanLink(), ms)
assert.Equal(t, generateTestSpanLink(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestSpanLink(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newSpanLink(internal.NewSpanLink(), sharedState)) })
assert.Panics(t, func() { newSpanLink(internal.NewSpanLink(), sharedState).MoveTo(dest) })
}
func TestSpanLink_CopyTo(t *testing.T) {
ms := NewSpanLink()
orig := NewSpanLink()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestSpanLink()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newSpanLink(internal.NewSpanLink(), sharedState)) })
}
func TestSpanLink_TraceID(t *testing.T) {
ms := NewSpanLink()
assert.Equal(t, pcommon.TraceID(internal.TraceID([16]byte{})), ms.TraceID())
testValTraceID := pcommon.TraceID(internal.TraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}))
ms.SetTraceID(testValTraceID)
assert.Equal(t, testValTraceID, ms.TraceID())
}
func TestSpanLink_SpanID(t *testing.T) {
ms := NewSpanLink()
assert.Equal(t, pcommon.SpanID(internal.SpanID([8]byte{})), ms.SpanID())
testValSpanID := pcommon.SpanID(internal.SpanID([8]byte{8, 7, 6, 5, 4, 3, 2, 1}))
ms.SetSpanID(testValSpanID)
assert.Equal(t, testValSpanID, ms.SpanID())
}
func TestSpanLink_TraceState(t *testing.T) {
ms := NewSpanLink()
assert.Equal(t, pcommon.NewTraceState(), ms.TraceState())
ms.orig.TraceState = *internal.GenTestTraceState()
assert.Equal(t, pcommon.TraceState(internal.GenTestTraceStateWrapper()), ms.TraceState())
}
func TestSpanLink_Attributes(t *testing.T) {
ms := NewSpanLink()
assert.Equal(t, pcommon.NewMap(), ms.Attributes())
ms.orig.Attributes = internal.GenTestKeyValueSlice()
assert.Equal(t, pcommon.Map(internal.GenTestMapWrapper()), ms.Attributes())
}
func TestSpanLink_DroppedAttributesCount(t *testing.T) {
ms := NewSpanLink()
assert.Equal(t, uint32(0), ms.DroppedAttributesCount())
ms.SetDroppedAttributesCount(uint32(13))
assert.Equal(t, uint32(13), ms.DroppedAttributesCount())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newSpanLink(internal.NewSpanLink(), sharedState).SetDroppedAttributesCount(uint32(13)) })
}
func TestSpanLink_Flags(t *testing.T) {
ms := NewSpanLink()
assert.Equal(t, uint32(0), ms.Flags())
ms.SetFlags(uint32(13))
assert.Equal(t, uint32(13), ms.Flags())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newSpanLink(internal.NewSpanLink(), sharedState).SetFlags(uint32(13)) })
}
func generateTestSpanLink() SpanLink {
return newSpanLink(internal.GenTestSpanLink(), internal.NewState())
}
================================================
FILE: pdata/ptrace/generated_spanlinkslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package ptrace
import (
"iter"
"sort"
"go.opentelemetry.io/collector/pdata/internal"
)
// 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 *[]*internal.SpanLink
state *internal.State
}
func newSpanLinkSlice(orig *[]*internal.SpanLink, state *internal.State) SpanLinkSlice {
return SpanLinkSlice{orig: orig, state: state}
}
// NewSpanLinkSlice creates a SpanLinkSliceWrapper with 0 elements.
// Can use "EnsureCapacity" to initialize with a given capacity.
func NewSpanLinkSlice() SpanLinkSlice {
orig := []*internal.SpanLink(nil)
return newSpanLinkSlice(&orig, internal.NewState())
}
// 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(i int) SpanLink {
return newSpanLink((*es.orig)[i], es.state)
}
// All returns an iterator over index-value pairs in the slice.
//
// for i, v := range es.All() {
// ... // Do something with index-value pair
// }
func (es SpanLinkSlice) All() iter.Seq2[int, SpanLink] {
return func(yield func(int, SpanLink) bool) {
for i := 0; i < es.Len(); i++ {
if !yield(i, es.At(i)) {
return
}
}
}
}
// EnsureCapacity is an operation that ensures the slice has at least the specified capacity.
// 1. If the newCap <= cap then no change in capacity.
// 2. If the newCap > cap then the slice capacity will be expanded to equal newCap.
//
// Here is how a new SpanLinkSlice can be initialized:
//
// es := NewSpanLinkSlice()
// es.EnsureCapacity(4)
// for i := 0; i < 4; i++ {
// e := es.AppendEmpty()
// // Here should set all the values for e.
// }
func (es SpanLinkSlice) EnsureCapacity(newCap int) {
es.state.AssertMutable()
oldCap := cap(*es.orig)
if newCap <= oldCap {
return
}
newOrig := make([]*internal.SpanLink, len(*es.orig), newCap)
copy(newOrig, *es.orig)
*es.orig = newOrig
}
// AppendEmpty will append to the end of the slice an empty SpanLink.
// It returns the newly added SpanLink.
func (es SpanLinkSlice) AppendEmpty() SpanLink {
es.state.AssertMutable()
*es.orig = append(*es.orig, internal.NewSpanLink())
return es.At(es.Len() - 1)
}
// 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) {
es.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if es.orig == dest.orig {
return
}
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
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (es SpanLinkSlice) RemoveIf(f func(SpanLink) bool) {
es.state.AssertMutable()
newLen := 0
for i := 0; i < len(*es.orig); i++ {
if f(es.At(i)) {
internal.DeleteSpanLink((*es.orig)[i], true)
(*es.orig)[i] = nil
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*es.orig)[newLen] = (*es.orig)[i]
// Cannot delete here since we just move the data(or pointer to data) to a different position in the slice.
(*es.orig)[i] = nil
newLen++
}
*es.orig = (*es.orig)[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (es SpanLinkSlice) CopyTo(dest SpanLinkSlice) {
dest.state.AssertMutable()
if es.orig == dest.orig {
return
}
*dest.orig = internal.CopySpanLinkPtrSlice(*dest.orig, *es.orig)
}
// Sort sorts the SpanLink elements within SpanLinkSlice given the
// provided less function so that two instances of SpanLinkSlice
// can be compared.
func (es SpanLinkSlice) Sort(less func(a, b SpanLink) bool) {
es.state.AssertMutable()
sort.SliceStable(*es.orig, func(i, j int) bool { return less(es.At(i), es.At(j)) })
}
================================================
FILE: pdata/ptrace/generated_spanlinkslice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package ptrace
import (
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestSpanLinkSlice(t *testing.T) {
es := NewSpanLinkSlice()
assert.Equal(t, 0, es.Len())
es = newSpanLinkSlice(&[]*internal.SpanLink{}, internal.NewState())
assert.Equal(t, 0, es.Len())
emptyVal := NewSpanLink()
testVal := generateTestSpanLink()
for i := 0; i < 7; i++ {
es.AppendEmpty()
assert.Equal(t, emptyVal, es.At(i))
(*es.orig)[i] = internal.GenTestSpanLink()
assert.Equal(t, testVal, es.At(i))
}
assert.Equal(t, 7, es.Len())
}
func TestSpanLinkSliceReadOnly(t *testing.T) {
sharedState := internal.NewState()
sharedState.MarkReadOnly()
es := newSpanLinkSlice(&[]*internal.SpanLink{}, sharedState)
assert.Equal(t, 0, es.Len())
assert.Panics(t, func() { es.AppendEmpty() })
assert.Panics(t, func() { es.EnsureCapacity(2) })
es2 := NewSpanLinkSlice()
es.CopyTo(es2)
assert.Panics(t, func() { es2.CopyTo(es) })
assert.Panics(t, func() { es.MoveAndAppendTo(es2) })
assert.Panics(t, func() { es2.MoveAndAppendTo(es) })
}
func TestSpanLinkSlice_CopyTo(t *testing.T) {
dest := NewSpanLinkSlice()
src := generateTestSpanLinkSlice()
src.CopyTo(dest)
assert.Equal(t, generateTestSpanLinkSlice(), dest)
dest.CopyTo(dest)
assert.Equal(t, generateTestSpanLinkSlice(), dest)
}
func TestSpanLinkSlice_EnsureCapacity(t *testing.T) {
es := generateTestSpanLinkSlice()
// Test ensure smaller capacity.
const ensureSmallLen = 4
es.EnsureCapacity(ensureSmallLen)
assert.Less(t, ensureSmallLen, es.Len())
assert.Equal(t, es.Len(), cap(*es.orig))
assert.Equal(t, generateTestSpanLinkSlice(), es)
// Test ensure larger capacity
const ensureLargeLen = 9
es.EnsureCapacity(ensureLargeLen)
assert.Less(t, generateTestSpanLinkSlice().Len(), ensureLargeLen)
assert.Equal(t, ensureLargeLen, cap(*es.orig))
assert.Equal(t, generateTestSpanLinkSlice(), es)
}
func TestSpanLinkSlice_MoveAndAppendTo(t *testing.T) {
// Test MoveAndAppendTo to empty
expectedSlice := generateTestSpanLinkSlice()
dest := NewSpanLinkSlice()
src := generateTestSpanLinkSlice()
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestSpanLinkSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo empty slice
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestSpanLinkSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo not empty slice
generateTestSpanLinkSlice().MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
dest.MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
}
func TestSpanLinkSlice_RemoveIf(t *testing.T) {
// Test RemoveIf on empty slice
emptySlice := NewSpanLinkSlice()
emptySlice.RemoveIf(func(el SpanLink) bool {
t.Fail()
return false
})
// Test RemoveIf
filtered := generateTestSpanLinkSlice()
pos := 0
filtered.RemoveIf(func(el SpanLink) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, 2, filtered.Len())
}
func TestSpanLinkSlice_RemoveIfAll(t *testing.T) {
got := generateTestSpanLinkSlice()
got.RemoveIf(func(el SpanLink) bool {
return true
})
assert.Equal(t, 0, got.Len())
}
func TestSpanLinkSliceAll(t *testing.T) {
ms := generateTestSpanLinkSlice()
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestSpanLinkSlice_Sort(t *testing.T) {
es := generateTestSpanLinkSlice()
es.Sort(func(a, b SpanLink) bool {
return uintptr(unsafe.Pointer(a.orig)) < uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Less(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
es.Sort(func(a, b SpanLink) bool {
return uintptr(unsafe.Pointer(a.orig)) > uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Greater(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
}
func generateTestSpanLinkSlice() SpanLinkSlice {
ms := NewSpanLinkSlice()
*ms.orig = internal.GenTestSpanLinkPtrSlice()
return ms
}
================================================
FILE: pdata/ptrace/generated_spanslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package ptrace
import (
"iter"
"sort"
"go.opentelemetry.io/collector/pdata/internal"
)
// 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 *[]*internal.Span
state *internal.State
}
func newSpanSlice(orig *[]*internal.Span, state *internal.State) SpanSlice {
return SpanSlice{orig: orig, state: state}
}
// NewSpanSlice creates a SpanSliceWrapper with 0 elements.
// Can use "EnsureCapacity" to initialize with a given capacity.
func NewSpanSlice() SpanSlice {
orig := []*internal.Span(nil)
return newSpanSlice(&orig, internal.NewState())
}
// 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(i int) Span {
return newSpan((*es.orig)[i], es.state)
}
// All returns an iterator over index-value pairs in the slice.
//
// for i, v := range es.All() {
// ... // Do something with index-value pair
// }
func (es SpanSlice) All() iter.Seq2[int, Span] {
return func(yield func(int, Span) bool) {
for i := 0; i < es.Len(); i++ {
if !yield(i, es.At(i)) {
return
}
}
}
}
// EnsureCapacity is an operation that ensures the slice has at least the specified capacity.
// 1. If the newCap <= cap then no change in capacity.
// 2. If the newCap > cap then the slice capacity will be expanded to equal newCap.
//
// Here is how a new SpanSlice can be initialized:
//
// es := NewSpanSlice()
// es.EnsureCapacity(4)
// for i := 0; i < 4; i++ {
// e := es.AppendEmpty()
// // Here should set all the values for e.
// }
func (es SpanSlice) EnsureCapacity(newCap int) {
es.state.AssertMutable()
oldCap := cap(*es.orig)
if newCap <= oldCap {
return
}
newOrig := make([]*internal.Span, len(*es.orig), newCap)
copy(newOrig, *es.orig)
*es.orig = newOrig
}
// AppendEmpty will append to the end of the slice an empty Span.
// It returns the newly added Span.
func (es SpanSlice) AppendEmpty() Span {
es.state.AssertMutable()
*es.orig = append(*es.orig, internal.NewSpan())
return es.At(es.Len() - 1)
}
// 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) {
es.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if es.orig == dest.orig {
return
}
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
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (es SpanSlice) RemoveIf(f func(Span) bool) {
es.state.AssertMutable()
newLen := 0
for i := 0; i < len(*es.orig); i++ {
if f(es.At(i)) {
internal.DeleteSpan((*es.orig)[i], true)
(*es.orig)[i] = nil
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*es.orig)[newLen] = (*es.orig)[i]
// Cannot delete here since we just move the data(or pointer to data) to a different position in the slice.
(*es.orig)[i] = nil
newLen++
}
*es.orig = (*es.orig)[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (es SpanSlice) CopyTo(dest SpanSlice) {
dest.state.AssertMutable()
if es.orig == dest.orig {
return
}
*dest.orig = internal.CopySpanPtrSlice(*dest.orig, *es.orig)
}
// Sort sorts the Span elements within SpanSlice given the
// provided less function so that two instances of SpanSlice
// can be compared.
func (es SpanSlice) Sort(less func(a, b Span) bool) {
es.state.AssertMutable()
sort.SliceStable(*es.orig, func(i, j int) bool { return less(es.At(i), es.At(j)) })
}
================================================
FILE: pdata/ptrace/generated_spanslice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package ptrace
import (
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestSpanSlice(t *testing.T) {
es := NewSpanSlice()
assert.Equal(t, 0, es.Len())
es = newSpanSlice(&[]*internal.Span{}, internal.NewState())
assert.Equal(t, 0, es.Len())
emptyVal := NewSpan()
testVal := generateTestSpan()
for i := 0; i < 7; i++ {
es.AppendEmpty()
assert.Equal(t, emptyVal, es.At(i))
(*es.orig)[i] = internal.GenTestSpan()
assert.Equal(t, testVal, es.At(i))
}
assert.Equal(t, 7, es.Len())
}
func TestSpanSliceReadOnly(t *testing.T) {
sharedState := internal.NewState()
sharedState.MarkReadOnly()
es := newSpanSlice(&[]*internal.Span{}, sharedState)
assert.Equal(t, 0, es.Len())
assert.Panics(t, func() { es.AppendEmpty() })
assert.Panics(t, func() { es.EnsureCapacity(2) })
es2 := NewSpanSlice()
es.CopyTo(es2)
assert.Panics(t, func() { es2.CopyTo(es) })
assert.Panics(t, func() { es.MoveAndAppendTo(es2) })
assert.Panics(t, func() { es2.MoveAndAppendTo(es) })
}
func TestSpanSlice_CopyTo(t *testing.T) {
dest := NewSpanSlice()
src := generateTestSpanSlice()
src.CopyTo(dest)
assert.Equal(t, generateTestSpanSlice(), dest)
dest.CopyTo(dest)
assert.Equal(t, generateTestSpanSlice(), dest)
}
func TestSpanSlice_EnsureCapacity(t *testing.T) {
es := generateTestSpanSlice()
// Test ensure smaller capacity.
const ensureSmallLen = 4
es.EnsureCapacity(ensureSmallLen)
assert.Less(t, ensureSmallLen, es.Len())
assert.Equal(t, es.Len(), cap(*es.orig))
assert.Equal(t, generateTestSpanSlice(), es)
// Test ensure larger capacity
const ensureLargeLen = 9
es.EnsureCapacity(ensureLargeLen)
assert.Less(t, generateTestSpanSlice().Len(), ensureLargeLen)
assert.Equal(t, ensureLargeLen, cap(*es.orig))
assert.Equal(t, generateTestSpanSlice(), es)
}
func TestSpanSlice_MoveAndAppendTo(t *testing.T) {
// Test MoveAndAppendTo to empty
expectedSlice := generateTestSpanSlice()
dest := NewSpanSlice()
src := generateTestSpanSlice()
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestSpanSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo empty slice
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestSpanSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo not empty slice
generateTestSpanSlice().MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
dest.MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
}
func TestSpanSlice_RemoveIf(t *testing.T) {
// Test RemoveIf on empty slice
emptySlice := NewSpanSlice()
emptySlice.RemoveIf(func(el Span) bool {
t.Fail()
return false
})
// Test RemoveIf
filtered := generateTestSpanSlice()
pos := 0
filtered.RemoveIf(func(el Span) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, 2, filtered.Len())
}
func TestSpanSlice_RemoveIfAll(t *testing.T) {
got := generateTestSpanSlice()
got.RemoveIf(func(el Span) bool {
return true
})
assert.Equal(t, 0, got.Len())
}
func TestSpanSliceAll(t *testing.T) {
ms := generateTestSpanSlice()
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestSpanSlice_Sort(t *testing.T) {
es := generateTestSpanSlice()
es.Sort(func(a, b Span) bool {
return uintptr(unsafe.Pointer(a.orig)) < uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Less(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
es.Sort(func(a, b Span) bool {
return uintptr(unsafe.Pointer(a.orig)) > uintptr(unsafe.Pointer(b.orig))
})
for i := 1; i < es.Len(); i++ {
assert.Greater(t, uintptr(unsafe.Pointer(es.At(i-1).orig)), uintptr(unsafe.Pointer(es.At(i).orig)))
}
}
func generateTestSpanSlice() SpanSlice {
ms := NewSpanSlice()
*ms.orig = internal.GenTestSpanPtrSlice()
return ms
}
================================================
FILE: pdata/ptrace/generated_status.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package ptrace
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// Status is an optional final status for this span. Semantically, when Status was not
// set, that means the span ended without errors and to 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 NewStatus function to create new instances.
// Important: zero-initialized instance is not valid for use.
type Status struct {
orig *internal.Status
state *internal.State
}
func newStatus(orig *internal.Status, state *internal.State) Status {
return Status{orig: orig, state: state}
}
// NewStatus creates a new empty Status.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewStatus() Status {
return newStatus(internal.NewStatus(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms Status) MoveTo(dest Status) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteStatus(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// Message returns the message associated with this Status.
func (ms Status) Message() string {
return ms.orig.Message
}
// SetMessage replaces the message associated with this Status.
func (ms Status) SetMessage(v string) {
ms.state.AssertMutable()
ms.orig.Message = v
}
// Code returns the code associated with this Status.
func (ms Status) Code() StatusCode {
return StatusCode(ms.orig.Code)
}
// SetCode replaces the code associated with this Status.
func (ms Status) SetCode(v StatusCode) {
ms.state.AssertMutable()
ms.orig.Code = internal.StatusCode(v)
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms Status) CopyTo(dest Status) {
dest.state.AssertMutable()
internal.CopyStatus(dest.orig, ms.orig)
}
================================================
FILE: pdata/ptrace/generated_status_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package ptrace
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestStatus_MoveTo(t *testing.T) {
ms := generateTestStatus()
dest := NewStatus()
ms.MoveTo(dest)
assert.Equal(t, NewStatus(), ms)
assert.Equal(t, generateTestStatus(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestStatus(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newStatus(internal.NewStatus(), sharedState)) })
assert.Panics(t, func() { newStatus(internal.NewStatus(), sharedState).MoveTo(dest) })
}
func TestStatus_CopyTo(t *testing.T) {
ms := NewStatus()
orig := NewStatus()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestStatus()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newStatus(internal.NewStatus(), sharedState)) })
}
func TestStatus_Message(t *testing.T) {
ms := NewStatus()
assert.Empty(t, ms.Message())
ms.SetMessage("test_message")
assert.Equal(t, "test_message", ms.Message())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newStatus(internal.NewStatus(), sharedState).SetMessage("test_message") })
}
func TestStatus_Code(t *testing.T) {
ms := NewStatus()
assert.Equal(t, StatusCode(internal.StatusCode_STATUS_CODE_UNSET), ms.Code())
testValCode := StatusCode(internal.StatusCode_STATUS_CODE_OK)
ms.SetCode(testValCode)
assert.Equal(t, testValCode, ms.Code())
}
func generateTestStatus() Status {
return newStatus(internal.GenTestStatus(), internal.NewState())
}
================================================
FILE: pdata/ptrace/generated_traces.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package ptrace
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// Traces is the top-level struct that is propagated through the traces pipeline.
// Use NewTraces to create new instance, zero-initialized instance is not valid for use.
//
// This is a reference type, if passed by value and callee modifies it the
// caller will see the modification.
//
// Must use NewTraces function to create new instances.
// Important: zero-initialized instance is not valid for use.
type Traces internal.TracesWrapper
func newTraces(orig *internal.ExportTraceServiceRequest, state *internal.State) Traces {
return Traces(internal.NewTracesWrapper(orig, state))
}
// NewTraces creates a new empty Traces.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewTraces() Traces {
return newTraces(internal.NewExportTraceServiceRequest(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms Traces) MoveTo(dest Traces) {
ms.getState().AssertMutable()
dest.getState().AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.getOrig() == dest.getOrig() {
return
}
internal.DeleteExportTraceServiceRequest(dest.getOrig(), false)
*dest.getOrig(), *ms.getOrig() = *ms.getOrig(), *dest.getOrig()
}
// ResourceSpans returns the ResourceSpans associated with this Traces.
func (ms Traces) ResourceSpans() ResourceSpansSlice {
return newResourceSpansSlice(&ms.getOrig().ResourceSpans, ms.getState())
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms Traces) CopyTo(dest Traces) {
dest.getState().AssertMutable()
internal.CopyExportTraceServiceRequest(dest.getOrig(), ms.getOrig())
}
func (ms Traces) getOrig() *internal.ExportTraceServiceRequest {
return internal.GetTracesOrig(internal.TracesWrapper(ms))
}
func (ms Traces) getState() *internal.State {
return internal.GetTracesState(internal.TracesWrapper(ms))
}
================================================
FILE: pdata/ptrace/generated_traces_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package ptrace
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestTraces_MoveTo(t *testing.T) {
ms := generateTestTraces()
dest := NewTraces()
ms.MoveTo(dest)
assert.Equal(t, NewTraces(), ms)
assert.Equal(t, generateTestTraces(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestTraces(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newTraces(internal.NewExportTraceServiceRequest(), sharedState)) })
assert.Panics(t, func() { newTraces(internal.NewExportTraceServiceRequest(), sharedState).MoveTo(dest) })
}
func TestTraces_CopyTo(t *testing.T) {
ms := NewTraces()
orig := NewTraces()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestTraces()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newTraces(internal.NewExportTraceServiceRequest(), sharedState)) })
}
func TestTraces_ResourceSpans(t *testing.T) {
ms := NewTraces()
assert.Equal(t, NewResourceSpansSlice(), ms.ResourceSpans())
ms.getOrig().ResourceSpans = internal.GenTestResourceSpansPtrSlice()
assert.Equal(t, generateTestResourceSpansSlice(), ms.ResourceSpans())
}
func generateTestTraces() Traces {
return newTraces(internal.GenTestExportTraceServiceRequest(), internal.NewState())
}
================================================
FILE: pdata/ptrace/json.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package ptrace // import "go.opentelemetry.io/collector/pdata/ptrace"
import (
"slices"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/otlp"
)
// JSONMarshaler marshals Traces to JSON bytes using the OTLP/JSON format.
type JSONMarshaler struct{}
// MarshalTraces to the OTLP/JSON format.
func (*JSONMarshaler) MarshalTraces(td Traces) ([]byte, error) {
dest := json.BorrowStream(nil)
defer json.ReturnStream(dest)
td.getOrig().MarshalJSON(dest)
if dest.Error() != nil {
return nil, dest.Error()
}
return slices.Clone(dest.Buffer()), nil
}
// JSONUnmarshaler unmarshals OTLP/JSON formatted-bytes to Traces.
type JSONUnmarshaler struct{}
// UnmarshalTraces from OTLP/JSON format into Traces.
func (*JSONUnmarshaler) UnmarshalTraces(buf []byte) (Traces, error) {
iter := json.BorrowIterator(buf)
defer json.ReturnIterator(iter)
td := NewTraces()
td.getOrig().UnmarshalJSON(iter)
if iter.Error() != nil {
return Traces{}, iter.Error()
}
otlp.MigrateTraces(td.getOrig().ResourceSpans)
return td, nil
}
================================================
FILE: pdata/ptrace/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package ptrace
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: pdata/ptrace/pb.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package ptrace // import "go.opentelemetry.io/collector/pdata/ptrace"
var _ MarshalSizer = (*ProtoMarshaler)(nil)
type ProtoMarshaler struct{}
func (e *ProtoMarshaler) MarshalTraces(td Traces) ([]byte, error) {
size := td.getOrig().SizeProto()
buf := make([]byte, size)
_ = td.getOrig().MarshalProto(buf)
return buf, nil
}
func (e *ProtoMarshaler) TracesSize(td Traces) int {
return td.getOrig().SizeProto()
}
func (e *ProtoMarshaler) ResourceSpansSize(td ResourceSpans) int {
return td.orig.SizeProto()
}
func (e *ProtoMarshaler) ScopeSpansSize(td ScopeSpans) int {
return td.orig.SizeProto()
}
func (e *ProtoMarshaler) SpanSize(td Span) int {
return td.orig.SizeProto()
}
type ProtoUnmarshaler struct{}
func (d *ProtoUnmarshaler) UnmarshalTraces(buf []byte) (Traces, error) {
td := NewTraces()
err := td.getOrig().UnmarshalProto(buf)
if err != nil {
return Traces{}, err
}
return td, nil
}
================================================
FILE: pdata/ptrace/pb_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package ptrace
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlptrace "go.opentelemetry.io/proto/slim/otlp/trace/v1"
goproto "google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestTracesProtoWireCompatibility(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 Traces as pdata struct.
td := generateTestTraces()
// Marshal its underlying ProtoBuf to wire.
marshaler := &ProtoMarshaler{}
wire1, err := marshaler.MarshalTraces(td)
require.NoError(t, err)
assert.NotNil(t, wire1)
// Unmarshal from the wire to OTLP Protobuf in goproto's representation.
var goprotoMessage gootlptrace.TracesData
err = goproto.Unmarshal(wire1, &goprotoMessage)
require.NoError(t, err)
// Marshal to the wire again.
wire2, err := goproto.Marshal(&goprotoMessage)
require.NoError(t, err)
assert.NotNil(t, wire2)
// Unmarshal from the wire into gogoproto's representation.
var td2 Traces
unmarshaler := &ProtoUnmarshaler{}
td2, err = unmarshaler.UnmarshalTraces(wire2)
require.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.Equal(t, td, td2)
}
func TestProtoTracesUnmarshalerError(t *testing.T) {
p := &ProtoUnmarshaler{}
_, err := p.UnmarshalTraces([]byte("+$%"))
assert.Error(t, err)
}
func TestProtoSizer(t *testing.T) {
marshaler := &ProtoMarshaler{}
td := NewTraces()
rms := td.ResourceSpans()
rms.AppendEmpty().ScopeSpans().AppendEmpty().Spans().AppendEmpty().SetName("foo")
size := marshaler.TracesSize(td)
bytes, err := marshaler.MarshalTraces(td)
require.NoError(t, err)
assert.Equal(t, len(bytes), size)
}
func TestProtoSizerEmptyTraces(t *testing.T) {
sizer := &ProtoMarshaler{}
assert.Equal(t, 0, sizer.TracesSize(NewTraces()))
}
func BenchmarkTracesToProto2k(b *testing.B) {
marshaler := &ProtoMarshaler{}
traces := generateBenchmarkTraces(2_000)
for b.Loop() {
buf, err := marshaler.MarshalTraces(traces)
require.NoError(b, err)
assert.NotEmpty(b, buf)
}
}
func BenchmarkTracesFromProto2k(b *testing.B) {
marshaler := &ProtoMarshaler{}
unmarshaler := &ProtoUnmarshaler{}
baseTraces := generateBenchmarkTraces(2_000)
buf, err := marshaler.MarshalTraces(baseTraces)
require.NoError(b, err)
assert.NotEmpty(b, buf)
b.ReportAllocs()
for b.Loop() {
traces, err := unmarshaler.UnmarshalTraces(buf)
require.NoError(b, err)
assert.Equal(b, baseTraces.ResourceSpans().Len(), traces.ResourceSpans().Len())
}
}
func generateBenchmarkTraces(metricsCount int) Traces {
now := time.Now()
startTime := pcommon.NewTimestampFromTime(now.Add(-10 * time.Second))
endTime := pcommon.NewTimestampFromTime(now)
md := NewTraces()
ilm := md.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty()
ilm.Spans().EnsureCapacity(metricsCount)
for range metricsCount {
im := ilm.Spans().AppendEmpty()
im.SetName("test_name")
im.SetStartTimestamp(startTime)
im.SetEndTimestamp(endTime)
}
return md
}
================================================
FILE: pdata/ptrace/ptraceotlp/fuzz_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package ptraceotlp // import "go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp"
import (
"bytes"
"testing"
"github.com/stretchr/testify/require"
)
var unexpectedBytes = "expected the same bytes from unmarshaling and marshaling."
func FuzzRequestUnmarshalJSON(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
er := NewExportRequest()
err := er.UnmarshalJSON(data)
if err != nil {
return
}
b1, err := er.MarshalJSON()
require.NoError(t, err, "failed to marshal valid struct")
er = NewExportRequest()
require.NoError(t, er.UnmarshalJSON(b1), "failed to unmarshal valid bytes")
b2, err := er.MarshalJSON()
require.NoError(t, err, "failed to marshal valid struct")
require.True(t, bytes.Equal(b1, b2), "%s. \nexpected %d but got %d\n", unexpectedBytes, b1, b2)
})
}
func FuzzResponseUnmarshalJSON(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
er := NewExportResponse()
err := er.UnmarshalJSON(data)
if err != nil {
return
}
b1, err := er.MarshalJSON()
require.NoError(t, err, "failed to marshal valid struct")
er = NewExportResponse()
require.NoError(t, er.UnmarshalJSON(b1), "failed to unmarshal valid bytes")
b2, err := er.MarshalJSON()
require.NoError(t, err, "failed to marshal valid struct")
require.True(t, bytes.Equal(b1, b2), "%s. \nexpected %d but got %d\n", unexpectedBytes, b1, b2)
})
}
func FuzzRequestUnmarshalProto(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
er := NewExportRequest()
err := er.UnmarshalProto(data)
if err != nil {
return
}
b1, err := er.MarshalProto()
require.NoError(t, err, "failed to marshal valid struct")
er = NewExportRequest()
require.NoError(t, er.UnmarshalProto(b1), "failed to unmarshal valid bytes")
b2, err := er.MarshalProto()
require.NoError(t, err, "failed to marshal valid struct")
require.True(t, bytes.Equal(b1, b2), "%s. \nexpected %d but got %d\n", unexpectedBytes, b1, b2)
})
}
func FuzzResponseUnmarshalProto(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
er := NewExportResponse()
err := er.UnmarshalProto(data)
if err != nil {
return
}
b1, err := er.MarshalProto()
require.NoError(t, err, "failed to marshal valid struct")
er = NewExportResponse()
require.NoError(t, er.UnmarshalProto(b1), "failed to unmarshal valid bytes")
b2, err := er.MarshalProto()
require.NoError(t, err, "failed to marshal valid struct")
require.True(t, bytes.Equal(b1, b2), "%s. \nexpected %d but got %d\n", unexpectedBytes, b1, b2)
})
}
================================================
FILE: pdata/ptrace/ptraceotlp/generated_exportpartialsuccess.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package ptraceotlp
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// ExportPartialSuccess represents the details of a partially successful export request.
//
// This is a reference type, if passed by value and callee modifies it the
// caller will see the modification.
//
// Must use NewExportPartialSuccess function to create new instances.
// Important: zero-initialized instance is not valid for use.
type ExportPartialSuccess struct {
orig *internal.ExportTracePartialSuccess
state *internal.State
}
func newExportPartialSuccess(orig *internal.ExportTracePartialSuccess, state *internal.State) ExportPartialSuccess {
return ExportPartialSuccess{orig: orig, state: state}
}
// NewExportPartialSuccess creates a new empty ExportPartialSuccess.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewExportPartialSuccess() ExportPartialSuccess {
return newExportPartialSuccess(internal.NewExportTracePartialSuccess(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms ExportPartialSuccess) MoveTo(dest ExportPartialSuccess) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteExportTracePartialSuccess(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// RejectedSpans returns the rejectedspans associated with this ExportPartialSuccess.
func (ms ExportPartialSuccess) RejectedSpans() int64 {
return ms.orig.RejectedSpans
}
// SetRejectedSpans replaces the rejectedspans associated with this ExportPartialSuccess.
func (ms ExportPartialSuccess) SetRejectedSpans(v int64) {
ms.state.AssertMutable()
ms.orig.RejectedSpans = v
}
// ErrorMessage returns the errormessage associated with this ExportPartialSuccess.
func (ms ExportPartialSuccess) ErrorMessage() string {
return ms.orig.ErrorMessage
}
// SetErrorMessage replaces the errormessage associated with this ExportPartialSuccess.
func (ms ExportPartialSuccess) SetErrorMessage(v string) {
ms.state.AssertMutable()
ms.orig.ErrorMessage = v
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms ExportPartialSuccess) CopyTo(dest ExportPartialSuccess) {
dest.state.AssertMutable()
internal.CopyExportTracePartialSuccess(dest.orig, ms.orig)
}
================================================
FILE: pdata/ptrace/ptraceotlp/generated_exportpartialsuccess_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package ptraceotlp
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestExportPartialSuccess_MoveTo(t *testing.T) {
ms := generateTestExportPartialSuccess()
dest := NewExportPartialSuccess()
ms.MoveTo(dest)
assert.Equal(t, NewExportPartialSuccess(), ms)
assert.Equal(t, generateTestExportPartialSuccess(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestExportPartialSuccess(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newExportPartialSuccess(internal.NewExportTracePartialSuccess(), sharedState)) })
assert.Panics(t, func() { newExportPartialSuccess(internal.NewExportTracePartialSuccess(), sharedState).MoveTo(dest) })
}
func TestExportPartialSuccess_CopyTo(t *testing.T) {
ms := NewExportPartialSuccess()
orig := NewExportPartialSuccess()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestExportPartialSuccess()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newExportPartialSuccess(internal.NewExportTracePartialSuccess(), sharedState)) })
}
func TestExportPartialSuccess_RejectedSpans(t *testing.T) {
ms := NewExportPartialSuccess()
assert.Equal(t, int64(0), ms.RejectedSpans())
ms.SetRejectedSpans(int64(13))
assert.Equal(t, int64(13), ms.RejectedSpans())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() {
newExportPartialSuccess(internal.NewExportTracePartialSuccess(), sharedState).SetRejectedSpans(int64(13))
})
}
func TestExportPartialSuccess_ErrorMessage(t *testing.T) {
ms := NewExportPartialSuccess()
assert.Empty(t, ms.ErrorMessage())
ms.SetErrorMessage("test_errormessage")
assert.Equal(t, "test_errormessage", ms.ErrorMessage())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() {
newExportPartialSuccess(internal.NewExportTracePartialSuccess(), sharedState).SetErrorMessage("test_errormessage")
})
}
func generateTestExportPartialSuccess() ExportPartialSuccess {
return newExportPartialSuccess(internal.GenTestExportTracePartialSuccess(), internal.NewState())
}
================================================
FILE: pdata/ptrace/ptraceotlp/generated_exportresponse.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package ptraceotlp
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// ExportResponse represents the response for gRPC/HTTP client/server.
//
// This is a reference type, if passed by value and callee modifies it the
// caller will see the modification.
//
// Must use NewExportResponse function to create new instances.
// Important: zero-initialized instance is not valid for use.
type ExportResponse struct {
orig *internal.ExportTraceServiceResponse
state *internal.State
}
func newExportResponse(orig *internal.ExportTraceServiceResponse, state *internal.State) ExportResponse {
return ExportResponse{orig: orig, state: state}
}
// NewExportResponse creates a new empty ExportResponse.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewExportResponse() ExportResponse {
return newExportResponse(internal.NewExportTraceServiceResponse(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms ExportResponse) MoveTo(dest ExportResponse) {
ms.state.AssertMutable()
dest.state.AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.orig == dest.orig {
return
}
internal.DeleteExportTraceServiceResponse(dest.orig, false)
*dest.orig, *ms.orig = *ms.orig, *dest.orig
}
// PartialSuccess returns the partialsuccess associated with this ExportResponse.
func (ms ExportResponse) PartialSuccess() ExportPartialSuccess {
return newExportPartialSuccess(&ms.orig.PartialSuccess, ms.state)
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms ExportResponse) CopyTo(dest ExportResponse) {
dest.state.AssertMutable()
internal.CopyExportTraceServiceResponse(dest.orig, ms.orig)
}
================================================
FILE: pdata/ptrace/ptraceotlp/generated_exportresponse_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package ptraceotlp
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestExportResponse_MoveTo(t *testing.T) {
ms := generateTestExportResponse()
dest := NewExportResponse()
ms.MoveTo(dest)
assert.Equal(t, NewExportResponse(), ms)
assert.Equal(t, generateTestExportResponse(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestExportResponse(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newExportResponse(internal.NewExportTraceServiceResponse(), sharedState)) })
assert.Panics(t, func() { newExportResponse(internal.NewExportTraceServiceResponse(), sharedState).MoveTo(dest) })
}
func TestExportResponse_CopyTo(t *testing.T) {
ms := NewExportResponse()
orig := NewExportResponse()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestExportResponse()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newExportResponse(internal.NewExportTraceServiceResponse(), sharedState)) })
}
func TestExportResponse_PartialSuccess(t *testing.T) {
ms := NewExportResponse()
assert.Equal(t, NewExportPartialSuccess(), ms.PartialSuccess())
ms.orig.PartialSuccess = *internal.GenTestExportTracePartialSuccess()
assert.Equal(t, generateTestExportPartialSuccess(), ms.PartialSuccess())
}
func generateTestExportResponse() ExportResponse {
return newExportResponse(internal.GenTestExportTraceServiceResponse(), internal.NewState())
}
================================================
FILE: pdata/ptrace/ptraceotlp/grpc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package ptraceotlp // import "go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp"
import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/internal/otelgrpc"
"go.opentelemetry.io/collector/pdata/internal/otlp"
)
// GRPCClient is the client API for OTLP-GRPC Traces service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type GRPCClient interface {
// Export ptrace.Traces to the server.
//
// For performance reasons, it is recommended to keep this RPC
// alive for the entire life of the application.
Export(ctx context.Context, request ExportRequest, opts ...grpc.CallOption) (ExportResponse, error)
// unexported disallow implementation of the GRPCClient.
unexported()
}
// NewGRPCClient returns a new GRPCClient connected using the given connection.
func NewGRPCClient(cc *grpc.ClientConn) GRPCClient {
return &grpcClient{rawClient: otelgrpc.NewTraceServiceClient(cc)}
}
type grpcClient struct {
rawClient otelgrpc.TraceServiceClient
}
// Export implements the Client interface.
func (c *grpcClient) Export(ctx context.Context, request ExportRequest, opts ...grpc.CallOption) (ExportResponse, error) {
rsp, err := c.rawClient.Export(ctx, request.orig, opts...)
if err != nil {
return ExportResponse{}, err
}
return ExportResponse{orig: rsp, state: internal.NewState()}, err
}
func (c *grpcClient) unexported() {}
// GRPCServer is the server API for OTLP gRPC TracesService service.
// Implementations MUST embed UnimplementedGRPCServer.
type GRPCServer interface {
// Export is called every time a new request is received.
//
// For performance reasons, it is recommended to keep this RPC
// alive for the entire life of the application.
Export(context.Context, ExportRequest) (ExportResponse, error)
// unexported disallow implementation of the GRPCServer.
unexported()
}
var _ GRPCServer = (*UnimplementedGRPCServer)(nil)
// UnimplementedGRPCServer MUST be embedded to have forward compatible implementations.
type UnimplementedGRPCServer struct{}
func (*UnimplementedGRPCServer) Export(context.Context, ExportRequest) (ExportResponse, error) {
return ExportResponse{}, status.Errorf(codes.Unimplemented, "method Export not implemented")
}
func (*UnimplementedGRPCServer) unexported() {}
// RegisterGRPCServer registers the GRPCServer to the grpc.Server.
func RegisterGRPCServer(s *grpc.Server, srv GRPCServer) {
otelgrpc.RegisterTraceServiceServer(s, &rawTracesServer{srv: srv})
}
type rawTracesServer struct {
srv GRPCServer
}
func (s rawTracesServer) Export(ctx context.Context, request *internal.ExportTraceServiceRequest) (*internal.ExportTraceServiceResponse, error) {
otlp.MigrateTraces(request.ResourceSpans)
rsp, err := s.srv.Export(ctx, ExportRequest{orig: request, state: internal.NewState()})
return rsp.orig, err
}
================================================
FILE: pdata/ptrace/ptraceotlp/grpc_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package ptraceotlp
import (
"context"
"errors"
"net"
"sync"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/resolver"
"google.golang.org/grpc/status"
"google.golang.org/grpc/test/bufconn"
"go.opentelemetry.io/collector/pdata/ptrace"
)
func TestGrpc(t *testing.T) {
lis := bufconn.Listen(1024 * 1024)
s := grpc.NewServer()
RegisterGRPCServer(s, &fakeTracesServer{t: t})
wg := sync.WaitGroup{}
wg.Go(func() {
assert.NoError(t, s.Serve(lis))
})
t.Cleanup(func() {
s.Stop()
wg.Wait()
})
resolver.SetDefaultScheme("passthrough")
cc, err := grpc.NewClient("bufnet",
grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
return lis.Dial()
}),
grpc.WithTransportCredentials(insecure.NewCredentials()))
require.NoError(t, err)
t.Cleanup(func() {
assert.NoError(t, cc.Close())
})
logClient := NewGRPCClient(cc)
resp, err := logClient.Export(context.Background(), generateTracesRequest())
require.NoError(t, err)
assert.Equal(t, NewExportResponse(), resp)
}
func TestGrpcError(t *testing.T) {
lis := bufconn.Listen(1024 * 1024)
s := grpc.NewServer()
RegisterGRPCServer(s, &fakeTracesServer{t: t, err: errors.New("my error")})
wg := sync.WaitGroup{}
wg.Go(func() {
assert.NoError(t, s.Serve(lis))
})
t.Cleanup(func() {
s.Stop()
wg.Wait()
})
cc, err := grpc.NewClient("bufnet",
grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
return lis.Dial()
}),
grpc.WithTransportCredentials(insecure.NewCredentials()))
require.NoError(t, err)
t.Cleanup(func() {
assert.NoError(t, cc.Close())
})
logClient := NewGRPCClient(cc)
resp, err := logClient.Export(context.Background(), generateTracesRequest())
require.Error(t, err)
st, okSt := status.FromError(err)
require.True(t, okSt)
assert.Equal(t, "my error", st.Message())
assert.Equal(t, codes.Unknown, st.Code())
assert.Equal(t, ExportResponse{}, resp)
}
type fakeTracesServer struct {
UnimplementedGRPCServer
t *testing.T
err error
}
func (f fakeTracesServer) Export(_ context.Context, request ExportRequest) (ExportResponse, error) {
assert.Equal(f.t, generateTracesRequest(), request)
return NewExportResponse(), f.err
}
func generateTracesRequest() ExportRequest {
td := ptrace.NewTraces()
td.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans().AppendEmpty().SetName("test_span")
return NewExportRequestFromTraces(td)
}
================================================
FILE: pdata/ptrace/ptraceotlp/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package ptraceotlp
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: pdata/ptrace/ptraceotlp/request.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package ptraceotlp // import "go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp"
import (
"slices"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/otlp"
"go.opentelemetry.io/collector/pdata/ptrace"
)
// ExportRequest represents the request for gRPC/HTTP client/server.
// It's a wrapper for ptrace.Traces data.
type ExportRequest struct {
orig *internal.ExportTraceServiceRequest
state *internal.State
}
// NewExportRequest returns an empty ExportRequest.
func NewExportRequest() ExportRequest {
return ExportRequest{
orig: &internal.ExportTraceServiceRequest{},
state: internal.NewState(),
}
}
// NewExportRequestFromTraces returns a ExportRequest from ptrace.Traces.
// Because ExportRequest is a wrapper for ptrace.Traces,
// any changes to the provided Traces struct will be reflected in the ExportRequest and vice versa.
func NewExportRequestFromTraces(td ptrace.Traces) ExportRequest {
return ExportRequest{
orig: internal.GetTracesOrig(internal.TracesWrapper(td)),
state: internal.GetTracesState(internal.TracesWrapper(td)),
}
}
// MarshalProto marshals ExportRequest into proto bytes.
func (ms ExportRequest) MarshalProto() ([]byte, error) {
size := ms.orig.SizeProto()
buf := make([]byte, size)
_ = ms.orig.MarshalProto(buf)
return buf, nil
}
// UnmarshalProto unmarshalls ExportRequest from proto bytes.
func (ms ExportRequest) UnmarshalProto(data []byte) error {
err := ms.orig.UnmarshalProto(data)
if err != nil {
return err
}
otlp.MigrateTraces(ms.orig.ResourceSpans)
return nil
}
// MarshalJSON marshals ExportRequest into JSON bytes.
func (ms ExportRequest) MarshalJSON() ([]byte, error) {
dest := json.BorrowStream(nil)
defer json.ReturnStream(dest)
ms.orig.MarshalJSON(dest)
if dest.Error() != nil {
return nil, dest.Error()
}
return slices.Clone(dest.Buffer()), nil
}
// UnmarshalJSON unmarshalls ExportRequest from JSON bytes.
func (ms ExportRequest) UnmarshalJSON(data []byte) error {
iter := json.BorrowIterator(data)
defer json.ReturnIterator(iter)
ms.orig.UnmarshalJSON(iter)
return iter.Error()
}
func (ms ExportRequest) Traces() ptrace.Traces {
return ptrace.Traces(internal.NewTracesWrapper(ms.orig, ms.state))
}
================================================
FILE: pdata/ptrace/ptraceotlp/request_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package ptraceotlp
import (
"encoding/json"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gootlpcollectortrace "go.opentelemetry.io/proto/slim/otlp/collector/trace/v1"
goproto "google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/internal/otlp"
"go.opentelemetry.io/collector/pdata/ptrace"
)
var (
_ json.Unmarshaler = ExportRequest{}
_ json.Marshaler = ExportRequest{}
)
var tracesRequestJSON = []byte(`
{
"resourceSpans": [
{
"resource": {},
"scopeSpans": [
{
"scope": {},
"spans": [
{
"name": "test_span",
"status": {}
}
]
}
]
}
]
}`)
func TestRequestToPData(t *testing.T) {
tr := NewExportRequest()
assert.Equal(t, 0, tr.Traces().SpanCount())
tr.Traces().ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans().AppendEmpty()
assert.Equal(t, 1, tr.Traces().SpanCount())
}
func TestRequestJSON(t *testing.T) {
tr := NewExportRequest()
require.NoError(t, tr.UnmarshalJSON(tracesRequestJSON))
assert.Equal(t, "test_span", tr.Traces().ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0).Name())
got, err := tr.MarshalJSON()
require.NoError(t, err)
assert.Equal(t, strings.Join(strings.Fields(string(tracesRequestJSON)), ""), string(got))
}
func TestTracesProtoWireCompatibility(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 Traces as pdata struct.
td := NewExportRequestFromTraces(ptrace.Traces(internal.GenTestTracesWrapper()))
// Marshal its underlying ProtoBuf to wire.
wire1, err := td.MarshalProto()
require.NoError(t, err)
assert.NotNil(t, wire1)
// Unmarshal from the wire to OTLP Protobuf in goproto's representation.
var goprotoMessage gootlpcollectortrace.ExportTraceServiceRequest
err = goproto.Unmarshal(wire1, &goprotoMessage)
require.NoError(t, err)
// Marshal to the wire again.
wire2, err := goproto.Marshal(&goprotoMessage)
require.NoError(t, err)
assert.NotNil(t, wire2)
// Unmarshal from the wire into gogoproto's representation.
td2 := NewExportRequest()
err = td2.UnmarshalProto(wire2)
require.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.
// Migration logic will run, so run it on the original message as well.
otlp.MigrateTraces(td.orig.ResourceSpans)
assert.Equal(t, td, td2)
}
================================================
FILE: pdata/ptrace/ptraceotlp/response.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package ptraceotlp // import "go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp"
import (
"slices"
"go.opentelemetry.io/collector/pdata/internal/json"
)
// MarshalProto marshals ExportResponse into proto bytes.
func (ms ExportResponse) MarshalProto() ([]byte, error) {
size := ms.orig.SizeProto()
buf := make([]byte, size)
_ = ms.orig.MarshalProto(buf)
return buf, nil
}
// UnmarshalProto unmarshalls ExportResponse from proto bytes.
func (ms ExportResponse) UnmarshalProto(data []byte) error {
return ms.orig.UnmarshalProto(data)
}
// MarshalJSON marshals ExportResponse into JSON bytes.
func (ms ExportResponse) MarshalJSON() ([]byte, error) {
dest := json.BorrowStream(nil)
defer json.ReturnStream(dest)
ms.orig.MarshalJSON(dest)
return slices.Clone(dest.Buffer()), dest.Error()
}
// UnmarshalJSON unmarshalls ExportResponse from JSON bytes.
func (ms ExportResponse) UnmarshalJSON(data []byte) error {
iter := json.BorrowIterator(data)
defer json.ReturnIterator(iter)
ms.orig.UnmarshalJSON(iter)
return iter.Error()
}
================================================
FILE: pdata/ptrace/ptraceotlp/response_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package ptraceotlp
import (
stdjson "encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var (
_ stdjson.Unmarshaler = ExportResponse{}
_ stdjson.Marshaler = ExportResponse{}
)
func TestExportResponseJSON(t *testing.T) {
jsonStr := `{"partialSuccess": {"rejectedSpans":"1", "errorMessage":"nothing"}}`
val := NewExportResponse()
require.NoError(t, val.UnmarshalJSON([]byte(jsonStr)))
expected := NewExportResponse()
expected.PartialSuccess().SetRejectedSpans(1)
expected.PartialSuccess().SetErrorMessage("nothing")
assert.Equal(t, expected, val)
buf, err := val.MarshalJSON()
require.NoError(t, err)
assert.JSONEq(t, jsonStr, string(buf))
}
func TestUnmarshalJSONExportResponse(t *testing.T) {
jsonStr := `{"extra":"", "partialSuccess": {"extra":""}}`
val := NewExportResponse()
require.NoError(t, val.UnmarshalJSON([]byte(jsonStr)))
assert.Equal(t, NewExportResponse(), val)
}
================================================
FILE: pdata/ptrace/span_kind.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package ptrace // import "go.opentelemetry.io/collector/pdata/ptrace"
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// SpanKind is the type of span. Can be used to specify additional relationships between spans
// in addition to a parent/child relationship.
type SpanKind int32
const (
// SpanKindUnspecified represents that the SpanKind is unspecified, it MUST NOT be used.
SpanKindUnspecified = SpanKind(internal.SpanKind_SPAN_KIND_UNSPECIFIED)
// SpanKindInternal indicates that the span represents an internal operation within an application,
// as opposed to an operation happening at the boundaries. Default value.
SpanKindInternal = SpanKind(internal.SpanKind_SPAN_KIND_INTERNAL)
// SpanKindServer indicates that the span covers server-side handling of an RPC or other
// remote network request.
SpanKindServer = SpanKind(internal.SpanKind_SPAN_KIND_SERVER)
// SpanKindClient indicates that the span describes a request to some remote service.
SpanKindClient = SpanKind(internal.SpanKind_SPAN_KIND_CLIENT)
// SpanKindProducer 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.
SpanKindProducer = SpanKind(internal.SpanKind_SPAN_KIND_PRODUCER)
// SpanKindConsumer 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.
SpanKindConsumer = SpanKind(internal.SpanKind_SPAN_KIND_CONSUMER)
)
// String returns the string representation of the SpanKind.
func (sk SpanKind) String() string {
switch sk {
case SpanKindUnspecified:
return "Unspecified"
case SpanKindInternal:
return "Internal"
case SpanKindServer:
return "Server"
case SpanKindClient:
return "Client"
case SpanKindProducer:
return "Producer"
case SpanKindConsumer:
return "Consumer"
}
return ""
}
================================================
FILE: pdata/ptrace/span_kind_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package ptrace // import "go.opentelemetry.io/collector/pdata/ptrace"
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSpanKindString(t *testing.T) {
assert.Equal(t, "Unspecified", SpanKindUnspecified.String())
assert.Equal(t, "Internal", SpanKindInternal.String())
assert.Equal(t, "Server", SpanKindServer.String())
assert.Equal(t, "Client", SpanKindClient.String())
assert.Equal(t, "Producer", SpanKindProducer.String())
assert.Equal(t, "Consumer", SpanKindConsumer.String())
assert.Empty(t, SpanKind(100).String())
}
================================================
FILE: pdata/ptrace/status_code.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package ptrace // import "go.opentelemetry.io/collector/pdata/ptrace"
import (
"go.opentelemetry.io/collector/pdata/internal"
)
// StatusCode mirrors the codes defined at
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#set-status
type StatusCode int32
const (
StatusCodeUnset = StatusCode(internal.StatusCode_STATUS_CODE_UNSET)
StatusCodeOk = StatusCode(internal.StatusCode_STATUS_CODE_OK)
StatusCodeError = StatusCode(internal.StatusCode_STATUS_CODE_ERROR)
)
// String returns the string representation of the StatusCode.
func (sc StatusCode) String() string {
switch sc {
case StatusCodeUnset:
return "Unset"
case StatusCodeOk:
return "Ok"
case StatusCodeError:
return "Error"
}
return ""
}
================================================
FILE: pdata/ptrace/status_code_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package ptrace // import "go.opentelemetry.io/collector/pdata/ptrace"
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestStatusCodeString(t *testing.T) {
assert.Equal(t, "Unset", StatusCodeUnset.String())
assert.Equal(t, "Ok", StatusCodeOk.String())
assert.Equal(t, "Error", StatusCodeError.String())
assert.Empty(t, StatusCode(100).String())
}
================================================
FILE: pdata/ptrace/traces.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package ptrace // import "go.opentelemetry.io/collector/pdata/ptrace"
// MarkReadOnly marks the Traces as shared so that no further modifications can be done on it.
func (ms Traces) MarkReadOnly() {
ms.getState().MarkReadOnly()
}
// IsReadOnly returns true if this Traces instance is read-only.
func (ms Traces) IsReadOnly() bool {
return ms.getState().IsReadOnly()
}
// SpanCount calculates the total number of spans.
func (ms Traces) SpanCount() int {
spanCount := 0
rss := ms.ResourceSpans()
for i := 0; i < rss.Len(); i++ {
rs := rss.At(i)
ilss := rs.ScopeSpans()
for j := 0; j < ilss.Len(); j++ {
spanCount += ilss.At(j).Spans().Len()
}
}
return spanCount
}
================================================
FILE: pdata/ptrace/traces_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package ptrace
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestSpanCount(t *testing.T) {
traces := NewTraces()
assert.Equal(t, 0, traces.SpanCount())
rs := traces.ResourceSpans().AppendEmpty()
assert.Equal(t, 0, traces.SpanCount())
ils := rs.ScopeSpans().AppendEmpty()
assert.Equal(t, 0, traces.SpanCount())
ils.Spans().AppendEmpty()
assert.Equal(t, 1, traces.SpanCount())
rms := traces.ResourceSpans()
rms.EnsureCapacity(3)
rms.AppendEmpty().ScopeSpans().AppendEmpty()
ilss := rms.AppendEmpty().ScopeSpans().AppendEmpty().Spans()
for range 5 {
ilss.AppendEmpty()
}
// 5 + 1 (from rms.At(0) initialized first)
assert.Equal(t, 6, traces.SpanCount())
}
func TestSpanCountWithEmpty(t *testing.T) {
assert.Equal(t, 0, newTraces(&internal.ExportTraceServiceRequest{
ResourceSpans: []*internal.ResourceSpans{{}},
}, new(internal.State)).SpanCount())
assert.Equal(t, 0, newTraces(&internal.ExportTraceServiceRequest{
ResourceSpans: []*internal.ResourceSpans{
{
ScopeSpans: []*internal.ScopeSpans{{}},
},
},
}, new(internal.State)).SpanCount())
assert.Equal(t, 1, newTraces(&internal.ExportTraceServiceRequest{
ResourceSpans: []*internal.ResourceSpans{
{
ScopeSpans: []*internal.ScopeSpans{
{
Spans: []*internal.Span{{}},
},
},
},
},
}, new(internal.State)).SpanCount())
}
func TestTracesCopyTo(t *testing.T) {
td := generateTestTraces()
tracesCopy := NewTraces()
td.CopyTo(tracesCopy)
assert.Equal(t, td, tracesCopy)
}
func TestReadOnlyTracesInvalidUsage(t *testing.T) {
td := NewTraces()
assert.False(t, td.IsReadOnly())
res := td.ResourceSpans().AppendEmpty().Resource()
res.Attributes().PutStr("k1", "v1")
td.MarkReadOnly()
assert.True(t, td.IsReadOnly())
assert.Panics(t, func() { res.Attributes().PutStr("k2", "v2") })
}
func BenchmarkTracesUsage(b *testing.B) {
td := generateTestTraces()
ts := pcommon.NewTimestampFromTime(time.Now())
b.ReportAllocs()
for b.Loop() {
for i := 0; i < td.ResourceSpans().Len(); i++ {
rs := td.ResourceSpans().At(i)
res := rs.Resource()
res.Attributes().PutStr("foo", "bar")
v, ok := res.Attributes().Get("foo")
assert.True(b, ok)
assert.Equal(b, "bar", v.Str())
v.SetStr("new-bar")
assert.Equal(b, "new-bar", v.Str())
res.Attributes().Remove("foo")
for j := 0; j < rs.ScopeSpans().Len(); j++ {
iss := rs.ScopeSpans().At(j)
iss.Scope().SetName("new_test_name")
assert.Equal(b, "new_test_name", iss.Scope().Name())
for k := 0; k < iss.Spans().Len(); k++ {
s := iss.Spans().At(k)
s.SetName("new_span")
assert.Equal(b, "new_span", s.Name())
s.SetStartTimestamp(ts)
assert.Equal(b, ts, s.StartTimestamp())
s.SetEndTimestamp(ts)
assert.Equal(b, ts, s.EndTimestamp())
s.SetTraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})
assert.Equal(b, pcommon.TraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), s.TraceID())
s.SetSpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8})
assert.Equal(b, pcommon.SpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), s.SpanID())
}
s := iss.Spans().AppendEmpty()
s.SetName("another_span")
s.SetStartTimestamp(ts)
s.SetEndTimestamp(ts)
s.SetTraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})
s.SetParentSpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8})
s.SetSpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8})
s.Attributes().PutStr("foo1", "bar1")
s.Attributes().PutStr("foo2", "bar2")
iss.Spans().RemoveIf(func(lr Span) bool {
return lr.Name() == "another_span"
})
}
}
}
}
func BenchmarkTracesMarshalJSON(b *testing.B) {
td := generateTestTraces()
encoder := &JSONMarshaler{}
b.ReportAllocs()
for b.Loop() {
jsonBuf, err := encoder.MarshalTraces(td)
require.NoError(b, err)
require.NotNil(b, jsonBuf)
}
}
================================================
FILE: pdata/testdata/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: pdata/testdata/common.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package testdata
import (
"go.opentelemetry.io/collector/pdata/pcommon"
)
func initMetricExemplarAttributes(dest pcommon.Map) {
dest.PutStr("exemplar-attachment", "exemplar-attachment-value")
}
func initMetricAttributes1(dest pcommon.Map) {
dest.PutStr("label-1", "label-value-1")
}
func initMetricAttributes2(dest pcommon.Map) {
dest.PutStr("label-2", "label-value-2")
}
func initMetricAttributes12(dest pcommon.Map) {
initMetricAttributes1(dest)
initMetricAttributes2(dest)
}
func initMetricAttributes13(dest pcommon.Map) {
initMetricAttributes1(dest)
dest.PutStr("label-3", "label-value-3")
}
================================================
FILE: pdata/testdata/go.mod
================================================
module go.opentelemetry.io/collector/pdata/testdata
go 1.25.0
require (
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/collector/pdata/pprofile v0.148.0
)
require (
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
)
replace go.opentelemetry.io/collector/pdata => ../
replace go.opentelemetry.io/collector/pdata/pprofile => ../pprofile
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
================================================
FILE: pdata/testdata/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: pdata/testdata/log.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package testdata
import (
"time"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
)
var logTimestamp = pcommon.NewTimestampFromTime(time.Date(2020, 2, 11, 20, 26, 13, 789, time.UTC))
func GenerateLogs(count int) plog.Logs {
ld := plog.NewLogs()
initResource(ld.ResourceLogs().AppendEmpty().Resource())
logs := ld.ResourceLogs().At(0).ScopeLogs().AppendEmpty().LogRecords()
logs.EnsureCapacity(count)
for i := range count {
switch i % 2 {
case 0:
fillLogOne(logs.AppendEmpty())
case 1:
fillLogTwo(logs.AppendEmpty())
}
}
return ld
}
func fillLogOne(log plog.LogRecord) {
log.SetTimestamp(logTimestamp)
log.SetDroppedAttributesCount(1)
log.SetSeverityNumber(plog.SeverityNumberInfo)
log.SetSeverityText("Info")
log.SetSpanID([8]byte{0x01, 0x02, 0x04, 0x08})
log.SetTraceID([16]byte{0x08, 0x04, 0x02, 0x01})
attrs := log.Attributes()
attrs.PutStr("app", "server")
attrs.PutInt("instance_num", 1)
log.Body().SetStr("This is a log message")
}
func fillLogTwo(log plog.LogRecord) {
log.SetTimestamp(logTimestamp)
log.SetDroppedAttributesCount(1)
log.SetSeverityNumber(plog.SeverityNumberInfo)
log.SetSeverityText("Info")
attrs := log.Attributes()
attrs.PutStr("customer", "acme")
attrs.PutStr("env", "dev")
log.Body().SetStr("something happened")
}
================================================
FILE: pdata/testdata/metric.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package testdata
import (
"time"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/pmetric"
)
var (
metricStartTimestamp = pcommon.NewTimestampFromTime(time.Date(2020, 2, 11, 20, 26, 12, 321, time.UTC))
metricExemplarTimestamp = pcommon.NewTimestampFromTime(time.Date(2020, 2, 11, 20, 26, 13, 123, time.UTC))
metricTimestamp = pcommon.NewTimestampFromTime(time.Date(2020, 2, 11, 20, 26, 13, 789, time.UTC))
)
const (
TestGaugeDoubleMetricName = "gauge-double"
TestGaugeIntMetricName = "gauge-int"
TestSumDoubleMetricName = "sum-double"
TestSumIntMetricName = "sum-int"
TestHistogramMetricName = "histogram"
TestExponentialHistogramMetricName = "exponential-histogram"
TestSummaryMetricName = "summary"
)
func generateMetricsOneEmptyInstrumentationScope() pmetric.Metrics {
md := pmetric.NewMetrics()
initResource(md.ResourceMetrics().AppendEmpty().Resource())
md.ResourceMetrics().At(0).ScopeMetrics().AppendEmpty()
return md
}
func GenerateMetricsAllTypesEmpty() pmetric.Metrics {
md := generateMetricsOneEmptyInstrumentationScope()
ms := md.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics()
doubleGauge := ms.AppendEmpty()
initMetric(doubleGauge, TestGaugeDoubleMetricName, pmetric.MetricTypeGauge)
doubleGauge.Gauge().DataPoints().AppendEmpty()
intGauge := ms.AppendEmpty()
initMetric(intGauge, TestGaugeIntMetricName, pmetric.MetricTypeGauge)
intGauge.Gauge().DataPoints().AppendEmpty()
doubleSum := ms.AppendEmpty()
initMetric(doubleSum, TestSumDoubleMetricName, pmetric.MetricTypeSum)
doubleSum.Sum().DataPoints().AppendEmpty()
intSum := ms.AppendEmpty()
initMetric(intSum, TestSumIntMetricName, pmetric.MetricTypeSum)
intSum.Sum().DataPoints().AppendEmpty()
histogram := ms.AppendEmpty()
initMetric(histogram, TestHistogramMetricName, pmetric.MetricTypeHistogram)
histogram.Histogram().DataPoints().AppendEmpty()
summary := ms.AppendEmpty()
initMetric(summary, TestSummaryMetricName, pmetric.MetricTypeSummary)
summary.Summary().DataPoints().AppendEmpty()
return md
}
func GenerateMetricsMetricTypeInvalid() pmetric.Metrics {
md := generateMetricsOneEmptyInstrumentationScope()
initMetric(md.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().AppendEmpty(), TestSumIntMetricName, pmetric.MetricTypeEmpty)
return md
}
func GenerateMetricsAllTypes() pmetric.Metrics {
md := generateMetricsOneEmptyInstrumentationScope()
ms := md.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics()
initGaugeIntMetric(ms.AppendEmpty())
initGaugeDoubleMetric(ms.AppendEmpty())
initSumIntMetric(ms.AppendEmpty())
initSumDoubleMetric(ms.AppendEmpty())
initHistogramMetric(ms.AppendEmpty())
initExponentialHistogramMetric(ms.AppendEmpty())
initSummaryMetric(ms.AppendEmpty())
return md
}
func GenerateMetrics(count int) pmetric.Metrics {
md := generateMetricsOneEmptyInstrumentationScope()
ms := md.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics()
ms.EnsureCapacity(count)
for i := range count {
switch i % 7 {
case 0:
initGaugeIntMetric(ms.AppendEmpty())
case 1:
initGaugeDoubleMetric(ms.AppendEmpty())
case 2:
initSumIntMetric(ms.AppendEmpty())
case 3:
initSumDoubleMetric(ms.AppendEmpty())
case 4:
initHistogramMetric(ms.AppendEmpty())
case 5:
initExponentialHistogramMetric(ms.AppendEmpty())
case 6:
initSummaryMetric(ms.AppendEmpty())
}
}
return md
}
func initGaugeIntMetric(im pmetric.Metric) {
initMetric(im, TestGaugeIntMetricName, pmetric.MetricTypeGauge)
idps := im.Gauge().DataPoints()
idp0 := idps.AppendEmpty()
initMetricAttributes1(idp0.Attributes())
idp0.SetStartTimestamp(metricStartTimestamp)
idp0.SetTimestamp(metricTimestamp)
idp0.SetIntValue(123)
idp1 := idps.AppendEmpty()
initMetricAttributes2(idp1.Attributes())
idp1.SetStartTimestamp(metricStartTimestamp)
idp1.SetTimestamp(metricTimestamp)
idp1.SetIntValue(456)
}
func initGaugeDoubleMetric(im pmetric.Metric) {
initMetric(im, TestGaugeDoubleMetricName, pmetric.MetricTypeGauge)
idps := im.Gauge().DataPoints()
idp0 := idps.AppendEmpty()
initMetricAttributes12(idp0.Attributes())
idp0.SetStartTimestamp(metricStartTimestamp)
idp0.SetTimestamp(metricTimestamp)
idp0.SetDoubleValue(1.23)
idp1 := idps.AppendEmpty()
initMetricAttributes13(idp1.Attributes())
idp1.SetStartTimestamp(metricStartTimestamp)
idp1.SetTimestamp(metricTimestamp)
idp1.SetDoubleValue(4.56)
}
func initSumIntMetric(im pmetric.Metric) {
initMetric(im, TestSumIntMetricName, pmetric.MetricTypeSum)
idps := im.Sum().DataPoints()
idp0 := idps.AppendEmpty()
initMetricAttributes1(idp0.Attributes())
idp0.SetStartTimestamp(metricStartTimestamp)
idp0.SetTimestamp(metricTimestamp)
idp0.SetIntValue(123)
idp1 := idps.AppendEmpty()
initMetricAttributes2(idp1.Attributes())
idp1.SetStartTimestamp(metricStartTimestamp)
idp1.SetTimestamp(metricTimestamp)
idp1.SetIntValue(456)
}
func initSumDoubleMetric(dm pmetric.Metric) {
initMetric(dm, TestSumDoubleMetricName, pmetric.MetricTypeSum)
ddps := dm.Sum().DataPoints()
ddp0 := ddps.AppendEmpty()
initMetricAttributes12(ddp0.Attributes())
ddp0.SetStartTimestamp(metricStartTimestamp)
ddp0.SetTimestamp(metricTimestamp)
ddp0.SetDoubleValue(1.23)
ddp1 := ddps.AppendEmpty()
initMetricAttributes13(ddp1.Attributes())
ddp1.SetStartTimestamp(metricStartTimestamp)
ddp1.SetTimestamp(metricTimestamp)
ddp1.SetDoubleValue(4.56)
}
func initHistogramMetric(hm pmetric.Metric) {
initMetric(hm, TestHistogramMetricName, pmetric.MetricTypeHistogram)
hdps := hm.Histogram().DataPoints()
hdp0 := hdps.AppendEmpty()
initMetricAttributes13(hdp0.Attributes())
hdp0.SetStartTimestamp(metricStartTimestamp)
hdp0.SetTimestamp(metricTimestamp)
hdp0.SetCount(1)
hdp0.SetSum(15)
hdp1 := hdps.AppendEmpty()
initMetricAttributes2(hdp1.Attributes())
hdp1.SetStartTimestamp(metricStartTimestamp)
hdp1.SetTimestamp(metricTimestamp)
hdp1.SetCount(1)
hdp1.SetSum(15)
hdp1.SetMin(15)
hdp1.SetMax(15)
hdp1.BucketCounts().FromRaw([]uint64{0, 1})
exemplar := hdp1.Exemplars().AppendEmpty()
exemplar.SetTraceID([16]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10})
exemplar.SetSpanID([8]byte{0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18})
exemplar.SetTimestamp(metricExemplarTimestamp)
exemplar.SetDoubleValue(15)
initMetricExemplarAttributes(exemplar.FilteredAttributes())
hdp1.ExplicitBounds().FromRaw([]float64{1})
}
func initExponentialHistogramMetric(hm pmetric.Metric) {
initMetric(hm, TestExponentialHistogramMetricName, pmetric.MetricTypeExponentialHistogram)
hdps := hm.ExponentialHistogram().DataPoints()
hdp0 := hdps.AppendEmpty()
initMetricAttributes13(hdp0.Attributes())
hdp0.SetStartTimestamp(metricStartTimestamp)
hdp0.SetTimestamp(metricTimestamp)
hdp0.SetCount(5)
hdp0.SetSum(0.15)
hdp0.SetZeroCount(1)
hdp0.SetScale(1)
// positive index 1 and 2 are values sqrt(2), 2 at scale 1
hdp0.Positive().SetOffset(1)
hdp0.Positive().BucketCounts().FromRaw([]uint64{1, 1})
// negative index -1 and 0 are values -1/sqrt(2), -1 at scale 1
hdp0.Negative().SetOffset(-1)
hdp0.Negative().BucketCounts().FromRaw([]uint64{1, 1})
// The above will print:
// Bucket (-1.414214, -1.000000], Count: 1
// Bucket (-1.000000, -0.707107], Count: 1
// Bucket [0, 0], Count: 1
// Bucket [0.707107, 1.000000), Count: 1
// Bucket [1.000000, 1.414214), Count: 1
hdp1 := hdps.AppendEmpty()
initMetricAttributes2(hdp1.Attributes())
hdp1.SetStartTimestamp(metricStartTimestamp)
hdp1.SetTimestamp(metricTimestamp)
hdp1.SetCount(3)
hdp1.SetSum(1.25)
hdp1.SetMin(0)
hdp1.SetMax(1)
hdp1.SetZeroCount(1)
hdp1.SetScale(-1)
// index -1 and 0 are values 0.25, 1 at scale -1
hdp1.Positive().SetOffset(-1)
hdp1.Positive().BucketCounts().FromRaw([]uint64{1, 1})
// The above will print:
// Bucket [0, 0], Count: 1
// Bucket [0.250000, 1.000000), Count: 1
// Bucket [1.000000, 4.000000), Count: 1
exemplar := hdp1.Exemplars().AppendEmpty()
exemplar.SetTraceID([16]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10})
exemplar.SetSpanID([8]byte{0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18})
exemplar.SetTimestamp(metricExemplarTimestamp)
exemplar.SetIntValue(15)
initMetricExemplarAttributes(exemplar.FilteredAttributes())
}
func initSummaryMetric(sm pmetric.Metric) {
initMetric(sm, TestSummaryMetricName, pmetric.MetricTypeSummary)
sdps := sm.Summary().DataPoints()
sdp0 := sdps.AppendEmpty()
initMetricAttributes13(sdp0.Attributes())
sdp0.SetStartTimestamp(metricStartTimestamp)
sdp0.SetTimestamp(metricTimestamp)
sdp0.SetCount(1)
sdp0.SetSum(15)
sdp1 := sdps.AppendEmpty()
initMetricAttributes2(sdp1.Attributes())
sdp1.SetStartTimestamp(metricStartTimestamp)
sdp1.SetTimestamp(metricTimestamp)
sdp1.SetCount(1)
sdp1.SetSum(15)
quantile := sdp1.QuantileValues().AppendEmpty()
quantile.SetQuantile(0.01)
quantile.SetValue(15)
}
func initMetric(m pmetric.Metric, name string, ty pmetric.MetricType) {
m.SetName(name)
m.SetDescription("")
m.SetUnit("1")
switch ty {
case pmetric.MetricTypeGauge:
m.SetEmptyGauge()
case pmetric.MetricTypeSum:
sum := m.SetEmptySum()
sum.SetIsMonotonic(true)
sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
case pmetric.MetricTypeHistogram:
histo := m.SetEmptyHistogram()
histo.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
case pmetric.MetricTypeExponentialHistogram:
histo := m.SetEmptyExponentialHistogram()
histo.SetAggregationTemporality(pmetric.AggregationTemporalityDelta)
case pmetric.MetricTypeSummary:
m.SetEmptySummary()
}
}
================================================
FILE: pdata/testdata/profile.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package testdata // import "go.opentelemetry.io/collector/pdata/testdata"
import (
"time"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/pprofile"
)
var profileStartTimestamp = pcommon.NewTimestampFromTime(time.Date(2020, 2, 11, 20, 26, 12, 321, time.UTC))
// GenerateProfiles generates dummy profiling data for tests
func GenerateProfiles(profilesCount int) pprofile.Profiles {
td := pprofile.NewProfiles()
initResource(td.ResourceProfiles().AppendEmpty().Resource())
ss := td.ResourceProfiles().At(0).ScopeProfiles().AppendEmpty().Profiles()
dic := td.Dictionary()
// By convention, the first element is empty
dic.StringTable().Append("")
dic.StringTable().Append("key")
// By convention, the first element is empty
dic.AttributeTable().AppendEmpty()
attr := dic.AttributeTable().AppendEmpty()
attr.SetKeyStrindex(1)
attr.Value().SetStr("value-1")
attr2 := dic.AttributeTable().AppendEmpty()
attr2.SetKeyStrindex(1)
attr2.Value().SetStr("value-2")
ss.EnsureCapacity(profilesCount)
for i := range profilesCount {
switch i % 2 {
case 0:
fillProfileOne(dic, ss.AppendEmpty())
case 1:
fillProfileTwo(dic, ss.AppendEmpty())
}
}
return td
}
func fillProfileOne(dic pprofile.ProfilesDictionary, profile pprofile.Profile) {
profile.SetProfileID([16]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10})
profile.SetTime(profileStartTimestamp)
profile.SetDurationNano(uint64(time.Second.Nanoseconds()))
profile.SetDroppedAttributesCount(1)
loc := pprofile.NewLocation()
loc.SetAddress(1)
locID, _ := pprofile.SetLocation(dic.LocationTable(), loc)
stack := pprofile.NewStack()
stack.LocationIndices().Append(locID)
stackID, _ := pprofile.SetStack(dic.StackTable(), stack)
sample := profile.Samples().AppendEmpty()
sample.SetStackIndex(stackID)
sample.Values().Append(4)
sample.AttributeIndices().Append(1)
}
func fillProfileTwo(dic pprofile.ProfilesDictionary, profile pprofile.Profile) {
profile.SetProfileID([16]byte{0x02, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10})
profile.SetTime(profileStartTimestamp)
profile.SetDurationNano(uint64(time.Second.Nanoseconds()))
loc := pprofile.NewLocation()
loc.SetAddress(2)
locID, _ := pprofile.SetLocation(dic.LocationTable(), loc)
stack := pprofile.NewStack()
stack.LocationIndices().Append(locID)
stackID, _ := pprofile.SetStack(dic.StackTable(), stack)
sample := profile.Samples().AppendEmpty()
sample.SetStackIndex(stackID)
sample.Values().Append(9)
sample.AttributeIndices().Append(2)
}
================================================
FILE: pdata/testdata/resource.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package testdata
import "go.opentelemetry.io/collector/pdata/pcommon"
func initResource(r pcommon.Resource) {
r.Attributes().PutStr("resource-attr", "resource-attr-val-1")
}
================================================
FILE: pdata/testdata/trace.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package testdata
import (
"time"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/ptrace"
)
var (
spanStartTimestamp = pcommon.NewTimestampFromTime(time.Date(2020, 2, 11, 20, 26, 12, 321, time.UTC))
spanEventTimestamp = pcommon.NewTimestampFromTime(time.Date(2020, 2, 11, 20, 26, 13, 123, time.UTC))
spanEndTimestamp = pcommon.NewTimestampFromTime(time.Date(2020, 2, 11, 20, 26, 13, 789, time.UTC))
)
func GenerateTraces(spanCount int) ptrace.Traces {
td := ptrace.NewTraces()
initResource(td.ResourceSpans().AppendEmpty().Resource())
ss := td.ResourceSpans().At(0).ScopeSpans().AppendEmpty().Spans()
ss.EnsureCapacity(spanCount)
for i := range spanCount {
switch i % 2 {
case 0:
fillSpanOne(ss.AppendEmpty())
case 1:
fillSpanTwo(ss.AppendEmpty())
}
}
return td
}
func fillSpanOne(span ptrace.Span) {
span.SetName("operationA")
span.SetStartTimestamp(spanStartTimestamp)
span.SetEndTimestamp(spanEndTimestamp)
span.SetDroppedAttributesCount(1)
span.TraceState().FromRaw("ot=th:0") // 100% sampling
span.SetTraceID([16]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10})
span.SetSpanID([8]byte{0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18})
evs := span.Events()
ev0 := evs.AppendEmpty()
ev0.SetTimestamp(spanEventTimestamp)
ev0.SetName("event-with-attr")
ev0.Attributes().PutStr("span-event-attr", "span-event-attr-val")
ev0.SetDroppedAttributesCount(2)
ev1 := evs.AppendEmpty()
ev1.SetTimestamp(spanEventTimestamp)
ev1.SetName("event")
ev1.SetDroppedAttributesCount(2)
span.SetDroppedEventsCount(1)
status := span.Status()
status.SetCode(ptrace.StatusCodeError)
status.SetMessage("status-cancelled")
}
func fillSpanTwo(span ptrace.Span) {
span.SetName("operationB")
span.SetStartTimestamp(spanStartTimestamp)
span.SetEndTimestamp(spanEndTimestamp)
link0 := span.Links().AppendEmpty()
link0.Attributes().PutStr("span-link-attr", "span-link-attr-val")
link0.SetDroppedAttributesCount(4)
link1 := span.Links().AppendEmpty()
link1.SetDroppedAttributesCount(4)
span.SetDroppedLinksCount(3)
}
================================================
FILE: pdata/xpdata/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: pdata/xpdata/documentation.md
================================================
[comment]: <> (Code generated by mdatagen. DO NOT EDIT.)
# xpdata
## Feature Gates
This component has the following feature gates:
| Feature Gate | Stage | Description | From Version | To Version | Reference |
| ------------ | ----- | ----------- | ------------ | ---------- | --------- |
| `pdata.enableRefCounting` | beta | When enabled, enables using ref counting to know when pdata memory can be freed up. This featuregate is here only to protect if unexpected bugs happens because of ref counting logic. | v0.133.0 | N/A | [Link](https://github.com/open-telemetry/opentelemetry-collector/issues/13631) |
For more information about feature gates, see the [Feature Gates](https://github.com/open-telemetry/opentelemetry-collector/blob/main/featuregate/README.md) documentation.
================================================
FILE: pdata/xpdata/entity/entity.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package entity // import "go.opentelemetry.io/collector/pdata/xpdata/entity"
import (
"go.opentelemetry.io/collector/pdata/pcommon"
)
// Entity is a helper struct that represents an entity in a more user-friendly way than the underlying
// EntityRef protobuf message. After adding an entity to a resource, the entity shares the resource's
// attributes map, so modifications to the entity's attributes are immediately reflected in the resource.
// To create an Entity, use the EntityMap's PutEmpty method.
type Entity struct {
ref EntityRef
attributes pcommon.Map
}
func NewEntity(t string) Entity {
ref := NewEntityRef()
ref.SetType(t)
return Entity{
ref: ref,
attributes: pcommon.NewMap(),
}
}
func (e Entity) Type() string {
return e.ref.Type()
}
func (e Entity) SchemaURL() string {
return e.ref.SchemaUrl()
}
func (e Entity) SetSchemaURL(schemaURL string) {
e.ref.SetSchemaUrl(schemaURL)
}
// IdentifyingAttributes returns an EntityAttributeMap for managing the entity's identifying attributes.
func (e Entity) IdentifyingAttributes() EntityAttributeMap {
return EntityAttributeMap{
keys: e.ref.IdKeys(),
attributes: e.attributes,
}
}
// DescriptiveAttributes returns an EntityAttributeMap for managing the entity's descriptive attributes.
func (e Entity) DescriptiveAttributes() EntityAttributeMap {
return EntityAttributeMap{
keys: e.ref.DescriptionKeys(),
attributes: e.attributes,
}
}
// CopyToResource moves the entity to the provided resource by overriding existing entities and attributes.
func (e Entity) CopyToResource(res pcommon.Resource) {
ent := ResourceEntities(res).PutEmpty(e.Type())
for k, v := range e.IdentifyingAttributes().All() {
v.CopyTo(ent.IdentifyingAttributes().PutEmpty(k))
}
for k, v := range e.DescriptiveAttributes().All() {
v.CopyTo(ent.DescriptiveAttributes().PutEmpty(k))
}
}
================================================
FILE: pdata/xpdata/entity/entity_attribute_map.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package entity // import "go.opentelemetry.io/collector/pdata/xpdata/entity"
import (
"iter"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// EntityAttributeMap is a wrapper around pcommon.Map that restricts operations to only the keys
// that belong to a specific set of entity attributes (either ID or Description attributes).
type EntityAttributeMap struct {
keys pcommon.StringSlice
attributes pcommon.Map
}
// Get returns the Value associated with the key and true. Returned
// Value is not a copy, it is a reference to the value stored in this map. It is
// allowed to modify the returned value using Value.Set* functions.
//
// If the key does not exist in the entity's key list or in the underlying map,
// returns an invalid instance and false. Calling any functions on the returned
// invalid instance will cause a panic.
func (m EntityAttributeMap) Get(key string) (pcommon.Value, bool) {
if !m.containsKey(key) {
return pcommon.Value{}, false
}
return m.attributes.Get(key)
}
// CanPut returns true if it's safe to call Put methods on the given key.
// Returns true if:
// - The key is already owned by this entity (in the entity's key list), OR
// - The key doesn't exist in the shared attributes map (available to claim)
//
// Returns false if the key exists in the shared map but belongs to another entity.
//
// Use this method before calling Put* methods to avoid conflicts:
//
// if entity.IdentifyingAttributes().CanPut("service.name") {
// entity.IdentifyingAttributes().PutStr("service.name", "my-service")
// }
func (m EntityAttributeMap) CanPut(key string) bool {
if m.containsKey(key) {
return true
}
_, exists := m.attributes.Get(key)
return !exists
}
// PutEmpty inserts or updates an empty value to the map under given key
// and returns the updated/inserted value.
// The key is also added to the entity's key list if not already present.
//
// WARNING: This method is destructive and will overwrite any existing value in the shared
// attributes map, even if it belongs to another entity. Use CanPut() to check safety first
// if you need to avoid conflicts with other entities.
func (m EntityAttributeMap) PutEmpty(k string) pcommon.Value {
if !m.containsKey(k) {
m.keys.Append(k)
}
return m.attributes.PutEmpty(k)
}
// PutStr performs the Insert or Update action. The Value is
// inserted to the map that did not originally have the key. The key/value is
// updated to the map where the key already existed.
// The key is also added to the entity's key list if not already present.
//
// WARNING: This method is destructive and will overwrite any existing value in the shared
// attributes map, even if it belongs to another entity. Use CanPut() to check safety first
// if you need to avoid conflicts with other entities.
func (m EntityAttributeMap) PutStr(k, v string) {
if !m.containsKey(k) {
m.keys.Append(k)
}
m.attributes.PutStr(k, v)
}
// Remove removes the entry associated with the key and returns true if the key existed.
// The key is also removed from the entity's key list.
func (m EntityAttributeMap) Remove(key string) bool {
var keyFound bool
m.keys.RemoveIf(func(k string) bool {
if k == key {
keyFound = true
return true
}
return false
})
if !keyFound {
return false
}
m.attributes.Remove(key)
return true
}
func (m EntityAttributeMap) containsKey(key string) bool {
for _, k := range m.keys.All() {
if k == key {
return true
}
}
return false
}
// All returns an iterator over the key-value pairs of the attributes belonging to this map's key set.
func (m EntityAttributeMap) All() iter.Seq2[string, pcommon.Value] {
return func(yield func(string, pcommon.Value) bool) {
for _, k := range m.keys.All() {
v, ok := m.attributes.Get(k)
if ok && !yield(k, v) {
return
}
}
}
}
================================================
FILE: pdata/xpdata/entity/entity_attribute_map_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package entity
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestEntityAttributeMap(t *testing.T) {
m := newTestEntityAttributeMap()
val, ok := m.Get("non-existent")
assert.False(t, ok)
assert.Equal(t, pcommon.Value{}, val)
m.PutStr("k1", "v1")
val, ok = m.Get("k1")
assert.True(t, ok)
assert.Equal(t, "v1", val.Str())
assert.True(t, m.Remove("k1"))
_, ok = m.Get("k1")
assert.False(t, ok)
assert.False(t, m.Remove("non-existent"))
}
func TestEntityAttributeMap_Get(t *testing.T) {
m := newTestEntityAttributeMap()
m.attributes.PutStr("owned", "value1")
m.attributes.PutStr("not-owned", "value2")
m.keys.Append("owned")
val, ok := m.Get("owned")
assert.True(t, ok)
assert.Equal(t, "value1", val.Str())
_, ok = m.Get("not-owned")
assert.False(t, ok)
_, ok = m.Get("non-existent")
assert.False(t, ok)
}
func TestEntityAttributeMap_PutStr(t *testing.T) {
m := newTestEntityAttributeMap()
m.PutStr("k1", "v1")
assert.Equal(t, 1, m.keys.Len())
assert.Equal(t, "k1", m.keys.At(0))
val, ok := m.attributes.Get("k1")
assert.True(t, ok)
assert.Equal(t, "v1", val.Str())
m.PutStr("k1", "v2")
assert.Equal(t, 1, m.keys.Len())
val, ok = m.attributes.Get("k1")
assert.True(t, ok)
assert.Equal(t, "v2", val.Str())
}
func TestEntityAttributeMap_PutStr_Overwrites(t *testing.T) {
m := newTestEntityAttributeMap()
m.attributes.PutStr("existing", "old-value")
m.PutStr("existing", "new-value")
assert.Equal(t, 1, m.keys.Len())
assert.Equal(t, "existing", m.keys.At(0))
val, ok := m.attributes.Get("existing")
assert.True(t, ok)
assert.Equal(t, "new-value", val.Str())
}
func TestEntityAttributeMap_PutEmpty(t *testing.T) {
m := newTestEntityAttributeMap()
val := m.PutEmpty("k1")
val.SetStr("v1")
assert.Equal(t, 1, m.keys.Len())
assert.Equal(t, "k1", m.keys.At(0))
resVal, ok := m.attributes.Get("k1")
assert.True(t, ok)
assert.Equal(t, "v1", resVal.Str())
}
func TestEntityAttributeMap_PutEmpty_Overwrites(t *testing.T) {
m := newTestEntityAttributeMap()
m.attributes.PutStr("existing", "old-value")
val := m.PutEmpty("existing")
val.SetStr("new-value")
assert.Equal(t, 1, m.keys.Len())
assert.Equal(t, "existing", m.keys.At(0))
resVal, ok := m.attributes.Get("existing")
assert.True(t, ok)
assert.Equal(t, "new-value", resVal.Str())
}
func TestEntityAttributeMap_Remove(t *testing.T) {
m := newTestEntityAttributeMap()
m.keys.Append("k1")
m.keys.Append("k2")
m.attributes.PutStr("k1", "v1")
m.attributes.PutStr("k2", "v2")
assert.True(t, m.Remove("k1"))
assert.Equal(t, 1, m.keys.Len())
assert.Equal(t, "k2", m.keys.At(0))
_, ok := m.attributes.Get("k1")
assert.False(t, ok)
val, ok := m.attributes.Get("k2")
assert.True(t, ok)
assert.Equal(t, "v2", val.Str())
}
func TestEntityAttributeMap_Remove_NotOwned(t *testing.T) {
m := newTestEntityAttributeMap()
m.attributes.PutStr("not-owned", "value")
assert.False(t, m.Remove("not-owned"))
val, ok := m.attributes.Get("not-owned")
assert.True(t, ok)
assert.Equal(t, "value", val.Str())
}
func TestEntityAttributeMap_Remove_NonExistent(t *testing.T) {
m := newTestEntityAttributeMap()
assert.False(t, m.Remove("non-existent"))
}
func TestEntityAttributeMap_CanPut(t *testing.T) {
m := newTestEntityAttributeMap()
m.keys.Append("owned")
m.attributes.PutStr("owned", "value")
m.attributes.PutStr("other", "value")
assert.True(t, m.CanPut("owned"))
assert.False(t, m.CanPut("other"))
assert.True(t, m.CanPut("new-key"))
}
func TestEntityAttributeMap_All(t *testing.T) {
m := newTestEntityAttributeMap()
m.PutStr("k1", "v1")
m.PutStr("k2", "v2")
// Add an attribute not owned by this map — it should not appear in All().
m.attributes.PutStr("not-owned", "v3")
got := make(map[string]string)
for k, v := range m.All() {
got[k] = v.Str()
}
assert.Equal(t, map[string]string{"k1": "v1", "k2": "v2"}, got)
// Verify early termination: breaking out of the loop stops iteration.
var count int
for range m.All() {
count++
break
}
assert.Equal(t, 1, count)
}
func newTestEntityAttributeMap() EntityAttributeMap {
return EntityAttributeMap{
keys: pcommon.NewStringSlice(),
attributes: pcommon.NewMap(),
}
}
================================================
FILE: pdata/xpdata/entity/entity_map.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package entity // import "go.opentelemetry.io/collector/pdata/xpdata/entity"
import (
"iter"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// EntityMap logically represents a map of Entity keyed by entity type.
//
// This is a reference type. If passed by value and callee modifies it, the
// caller will see the modification.
//
// Must use NewEntityMap function to create new instances.
// Important: zero-initialized instance is not valid for use.
type EntityMap struct {
refs EntityRefSlice
attributes pcommon.Map
}
// NewEntityMap creates a EntityMap with 0 elements.
// Can use "EnsureCapacity" to initialize with a given capacity.
func NewEntityMap() EntityMap {
return EntityMap{
refs: NewEntityRefSlice(),
attributes: pcommon.NewMap(),
}
}
// Len returns the number of elements in the map.
//
// Returns "0" for a newly instance created with "NewEntityMap()".
func (em EntityMap) Len() int {
return em.refs.Len()
}
// EnsureCapacity is an operation that ensures the map has at least the specified capacity.
// 1. If the newCap <= cap then no change in capacity.
// 2. If the newCap > cap then the map capacity will be expanded to equal newCap.
func (em EntityMap) EnsureCapacity(newCap int) {
em.refs.EnsureCapacity(newCap)
}
// Get returns the Entity associated with the entityType and true. The returned
// Entity is not a copy, it is a reference to the entity stored in this map.
// It is allowed to modify the returned entity.
// Such modification will be applied to the entity stored in this map.
//
// If the entityType does not exist, returns a zero-initialized Entity and false.
// Calling any functions on the returned invalid instance may cause a panic.
func (em EntityMap) Get(entityType string) (Entity, bool) {
if entityType == "" {
return Entity{}, false
}
for i := 0; i < em.Len(); i++ {
if em.refs.At(i).Type() == entityType {
return Entity{
ref: em.refs.At(i),
attributes: em.attributes,
}, true
}
}
return Entity{}, false
}
// All returns an iterator over entity type-Entity pairs in the EntityMap.
//
// for entityType, entity := range em.All() {
// ... // Do something with entity type and entity
// }
func (em EntityMap) All() iter.Seq2[string, Entity] {
return func(yield func(string, Entity) bool) {
for i := 0; i < em.Len(); i++ {
ref := em.refs.At(i)
entity := Entity{
ref: ref,
attributes: em.attributes,
}
if !yield(ref.Type(), entity) {
return
}
}
}
}
// Remove removes the entity associated with the entityType and returns true if the entity
// was present in the map, otherwise returns false. All attributes associated with the entity
// are also removed.
func (em EntityMap) Remove(entityType string) bool {
for i := 0; i < em.refs.Len(); i++ {
ref := em.refs.At(i)
if ref.Type() == entityType {
for _, k := range ref.IdKeys().All() {
em.attributes.Remove(k)
}
for _, k := range ref.DescriptionKeys().All() {
em.attributes.Remove(k)
}
em.refs.RemoveIf(func(er EntityRef) bool {
return er.Type() == entityType
})
return true
}
}
return false
}
// PutEmpty inserts or replaces an empty Entity with the specified type and returns it.
// If an entity with the given type already exists, it replaces it and removes all attributes associated with it.
func (em EntityMap) PutEmpty(entityType string) Entity {
em.Remove(entityType)
ref := em.refs.AppendEmpty()
ref.SetType(entityType)
return Entity{
ref: ref,
attributes: em.attributes,
}
}
================================================
FILE: pdata/xpdata/entity/entity_map_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package entity
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestEntityMap(t *testing.T) {
em := NewEntityMap()
assert.Equal(t, 0, em.Len())
e1 := em.PutEmpty("service")
assert.Equal(t, 1, em.Len())
assert.Equal(t, "service", e1.Type())
e2 := em.PutEmpty("host")
assert.Equal(t, 2, em.Len())
assert.Equal(t, "host", e2.Type())
}
func TestEntityMap_PutEmpty(t *testing.T) {
em := NewEntityMap()
e := em.PutEmpty("service")
retrieved, ok := em.Get("service")
assert.True(t, ok)
assert.Equal(t, "service", retrieved.Type())
assert.Equal(t, "service", e.Type())
}
func TestEntityMap_PutEmpty_Override(t *testing.T) {
em := NewEntityMap()
e1 := em.PutEmpty("service")
e1.IdentifyingAttributes().PutStr("service.name", "my-service")
assert.Equal(t, 1, em.Len())
e2 := em.PutEmpty("service")
assert.Equal(t, 1, em.Len())
_, ok := e2.IdentifyingAttributes().Get("service.name")
assert.False(t, ok)
}
func TestEntityMap_EnsureCapacity(t *testing.T) {
em := NewEntityMap()
em.EnsureCapacity(5)
for i := range 5 {
em.PutEmpty(fmt.Sprintf("type%d", i))
}
assert.Equal(t, 5, em.Len())
em.EnsureCapacity(3)
assert.Equal(t, 5, em.Len())
}
func TestEntityMap_AttributesIsolation(t *testing.T) {
em := NewEntityMap()
e1 := em.PutEmpty("service")
e1.IdentifyingAttributes().PutStr("service.name", "my-service")
e2 := em.PutEmpty("host")
e2.IdentifyingAttributes().PutStr("host.name", "my-host")
service, ok := em.Get("service")
assert.True(t, ok)
val, ok := service.IdentifyingAttributes().Get("service.name")
assert.True(t, ok)
assert.Equal(t, "my-service", val.Str())
host, ok := em.Get("host")
assert.True(t, ok)
val, ok = host.IdentifyingAttributes().Get("host.name")
assert.True(t, ok)
assert.Equal(t, "my-host", val.Str())
_, ok = service.IdentifyingAttributes().Get("host.name")
assert.False(t, ok)
_, ok = host.IdentifyingAttributes().Get("service.name")
assert.False(t, ok)
}
func TestEntityMap_Get(t *testing.T) {
em := NewEntityMap()
e1 := em.PutEmpty("service")
e1.IdentifyingAttributes().PutStr("service.name", "my-service")
e2, ok := em.Get("service")
assert.True(t, ok)
assert.Equal(t, "service", e2.Type())
val, ok := e2.IdentifyingAttributes().Get("service.name")
assert.True(t, ok)
assert.Equal(t, "my-service", val.Str())
_, ok = em.Get("host")
assert.False(t, ok)
}
func TestEntityMap_Remove(t *testing.T) {
em := NewEntityMap()
e1 := em.PutEmpty("service")
e1.IdentifyingAttributes().PutStr("service.name", "my-service")
e1.DescriptiveAttributes().PutStr("service.version", "1.0.0")
assert.Equal(t, 1, em.Len())
assert.Equal(t, 2, em.attributes.Len())
removed := em.Remove("service")
assert.True(t, removed)
assert.Empty(t, em.Len())
assert.Empty(t, em.attributes.Len())
assert.False(t, em.Remove("service"))
}
func TestEntityMap_All(t *testing.T) {
em := NewEntityMap()
e1 := em.PutEmpty("service")
e1.IdentifyingAttributes().PutStr("service.name", "my-service")
e2 := em.PutEmpty("host")
e2.IdentifyingAttributes().PutStr("host.name", "my-host")
e3 := em.PutEmpty("process")
e3.IdentifyingAttributes().PutStr("process.pid", "1234")
types := make(map[string]bool)
attributes := make(map[string]string)
for entityType, entity := range em.All() {
types[entityType] = true
switch entityType {
case "service":
val, ok := entity.IdentifyingAttributes().Get("service.name")
assert.True(t, ok)
attributes[entityType] = val.Str()
case "host":
val, ok := entity.IdentifyingAttributes().Get("host.name")
assert.True(t, ok)
attributes[entityType] = val.Str()
case "process":
val, ok := entity.IdentifyingAttributes().Get("process.pid")
assert.True(t, ok)
attributes[entityType] = val.Str()
}
}
assert.Len(t, types, 3)
assert.True(t, types["service"])
assert.True(t, types["host"])
assert.True(t, types["process"])
assert.Equal(t, "my-service", attributes["service"])
assert.Equal(t, "my-host", attributes["host"])
assert.Equal(t, "1234", attributes["process"])
}
func TestEntityMap_All_Empty(t *testing.T) {
em := NewEntityMap()
count := 0
for range em.All() {
count++
}
assert.Equal(t, 0, count)
}
func TestEntityMap_All_EarlyBreak(t *testing.T) {
em := NewEntityMap()
em.PutEmpty("service")
em.PutEmpty("host")
em.PutEmpty("process")
count := 0
for range em.All() {
count++
if count == 2 {
break
}
}
assert.Equal(t, 2, count)
}
================================================
FILE: pdata/xpdata/entity/entity_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package entity
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestNewEntity(t *testing.T) {
e := NewEntity("service")
assert.Equal(t, "service", e.Type())
assert.Empty(t, e.SchemaURL())
}
func TestEntity_CopyToResource(t *testing.T) {
e := NewEntity("service")
e.IdentifyingAttributes().PutStr("service.name", "my-service")
e.DescriptiveAttributes().PutStr("service.version", "1.0.0")
res := pcommon.NewResource()
e.CopyToResource(res)
entities := ResourceEntities(res)
copied, ok := entities.Get("service")
assert.True(t, ok)
idVal, ok := copied.IdentifyingAttributes().Get("service.name")
assert.True(t, ok)
assert.Equal(t, "my-service", idVal.Str())
descVal, ok := copied.DescriptiveAttributes().Get("service.version")
assert.True(t, ok)
assert.Equal(t, "1.0.0", descVal.Str())
}
func TestEntity_Type(t *testing.T) {
em := NewEntityMap()
e := em.PutEmpty("service")
assert.Equal(t, "service", e.Type())
}
func TestEntity_SchemaURL(t *testing.T) {
em := NewEntityMap()
e := em.PutEmpty("service")
assert.Empty(t, e.SchemaURL())
e.SetSchemaURL("https://opentelemetry.io/schemas/1.0.0")
assert.Equal(t, "https://opentelemetry.io/schemas/1.0.0", e.SchemaURL())
e.SetSchemaURL("https://opentelemetry.io/schemas/1.1.0")
assert.Equal(t, "https://opentelemetry.io/schemas/1.1.0", e.SchemaURL())
}
func TestEntity_IdentifyingAttributes(t *testing.T) {
em := NewEntityMap()
e := em.PutEmpty("service")
idAttrs := e.IdentifyingAttributes()
idAttrs.PutStr("key1", "value1")
val, ok := e.IdentifyingAttributes().Get("key1")
assert.True(t, ok)
assert.Equal(t, "value1", val.Str())
}
func TestEntity_DescriptiveAttributes(t *testing.T) {
em := NewEntityMap()
e := em.PutEmpty("service")
descAttrs := e.DescriptiveAttributes()
descAttrs.PutStr("key1", "value1")
val, ok := e.DescriptiveAttributes().Get("key1")
assert.True(t, ok)
assert.Equal(t, "value1", val.Str())
}
func TestEntity_IdAndDescriptionAttributes_Isolated(t *testing.T) {
em := NewEntityMap()
e := em.PutEmpty("service")
e.IdentifyingAttributes().PutStr("id.key", "id-value")
e.DescriptiveAttributes().PutStr("desc.key", "desc-value")
val, ok := e.IdentifyingAttributes().Get("id.key")
assert.True(t, ok)
assert.Equal(t, "id-value", val.Str())
_, ok = e.IdentifyingAttributes().Get("desc.key")
assert.False(t, ok)
val, ok = e.DescriptiveAttributes().Get("desc.key")
assert.True(t, ok)
assert.Equal(t, "desc-value", val.Str())
_, ok = e.DescriptiveAttributes().Get("id.key")
assert.False(t, ok)
}
func TestEntity_IdAndDescriptionAttributes_CanPut(t *testing.T) {
em := NewEntityMap()
e := em.PutEmpty("service")
e.IdentifyingAttributes().PutStr("shared.key", "id-value")
assert.True(t, e.IdentifyingAttributes().CanPut("shared.key"))
assert.False(t, e.DescriptiveAttributes().CanPut("shared.key"))
e.DescriptiveAttributes().PutStr("shared.key", "desc-value")
val, ok := e.IdentifyingAttributes().Get("shared.key")
assert.True(t, ok)
assert.Equal(t, "desc-value", val.Str())
val, ok = e.DescriptiveAttributes().Get("shared.key")
assert.True(t, ok)
assert.Equal(t, "desc-value", val.Str())
}
================================================
FILE: pdata/xpdata/entity/generated_entityref.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package entity
import (
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// This is a reference type, if passed by value and callee modifies it the
// caller will see the modification.
//
// Must use NewEntityRef function to create new instances.
// Important: zero-initialized instance is not valid for use.
type EntityRef internal.EntityRefWrapper
func newEntityRef(orig *internal.EntityRef, state *internal.State) EntityRef {
return EntityRef(internal.NewEntityRefWrapper(orig, state))
}
// NewEntityRef creates a new empty EntityRef.
//
// This must be used only in testing code. Users should use "AppendEmpty" when part of a Slice,
// OR directly access the member if this is embedded in another struct.
func NewEntityRef() EntityRef {
return newEntityRef(internal.NewEntityRef(), internal.NewState())
}
// MoveTo moves all properties from the current struct overriding the destination and
// resetting the current instance to its zero value
func (ms EntityRef) MoveTo(dest EntityRef) {
ms.getState().AssertMutable()
dest.getState().AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if ms.getOrig() == dest.getOrig() {
return
}
internal.DeleteEntityRef(dest.getOrig(), false)
*dest.getOrig(), *ms.getOrig() = *ms.getOrig(), *dest.getOrig()
}
// SchemaUrl returns the schemaurl associated with this EntityRef.
func (ms EntityRef) SchemaUrl() string {
return ms.getOrig().SchemaUrl
}
// SetSchemaUrl replaces the schemaurl associated with this EntityRef.
func (ms EntityRef) SetSchemaUrl(v string) {
ms.getState().AssertMutable()
ms.getOrig().SchemaUrl = v
}
// Type returns the type associated with this EntityRef.
func (ms EntityRef) Type() string {
return ms.getOrig().Type
}
// SetType replaces the type associated with this EntityRef.
func (ms EntityRef) SetType(v string) {
ms.getState().AssertMutable()
ms.getOrig().Type = v
}
// IdKeys returns the IdKeys associated with this EntityRef.
func (ms EntityRef) IdKeys() pcommon.StringSlice {
return pcommon.StringSlice(internal.NewStringSliceWrapper(&ms.getOrig().IdKeys, ms.getState()))
}
// DescriptionKeys returns the DescriptionKeys associated with this EntityRef.
func (ms EntityRef) DescriptionKeys() pcommon.StringSlice {
return pcommon.StringSlice(internal.NewStringSliceWrapper(&ms.getOrig().DescriptionKeys, ms.getState()))
}
// CopyTo copies all properties from the current struct overriding the destination.
func (ms EntityRef) CopyTo(dest EntityRef) {
dest.getState().AssertMutable()
internal.CopyEntityRef(dest.getOrig(), ms.getOrig())
}
func (ms EntityRef) getOrig() *internal.EntityRef {
return internal.GetEntityRefOrig(internal.EntityRefWrapper(ms))
}
func (ms EntityRef) getState() *internal.State {
return internal.GetEntityRefState(internal.EntityRefWrapper(ms))
}
================================================
FILE: pdata/xpdata/entity/generated_entityref_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package entity
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestEntityRef_MoveTo(t *testing.T) {
ms := generateTestEntityRef()
dest := NewEntityRef()
ms.MoveTo(dest)
assert.Equal(t, NewEntityRef(), ms)
assert.Equal(t, generateTestEntityRef(), dest)
dest.MoveTo(dest)
assert.Equal(t, generateTestEntityRef(), dest)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.MoveTo(newEntityRef(internal.NewEntityRef(), sharedState)) })
assert.Panics(t, func() { newEntityRef(internal.NewEntityRef(), sharedState).MoveTo(dest) })
}
func TestEntityRef_CopyTo(t *testing.T) {
ms := NewEntityRef()
orig := NewEntityRef()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
orig = generateTestEntityRef()
orig.CopyTo(ms)
assert.Equal(t, orig, ms)
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { ms.CopyTo(newEntityRef(internal.NewEntityRef(), sharedState)) })
}
func TestEntityRef_SchemaUrl(t *testing.T) {
ms := NewEntityRef()
assert.Empty(t, ms.SchemaUrl())
ms.SetSchemaUrl("test_schemaurl")
assert.Equal(t, "test_schemaurl", ms.SchemaUrl())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newEntityRef(internal.NewEntityRef(), sharedState).SetSchemaUrl("test_schemaurl") })
}
func TestEntityRef_Type(t *testing.T) {
ms := NewEntityRef()
assert.Empty(t, ms.Type())
ms.SetType("test_type")
assert.Equal(t, "test_type", ms.Type())
sharedState := internal.NewState()
sharedState.MarkReadOnly()
assert.Panics(t, func() { newEntityRef(internal.NewEntityRef(), sharedState).SetType("test_type") })
}
func TestEntityRef_IdKeys(t *testing.T) {
ms := NewEntityRef()
assert.Equal(t, pcommon.NewStringSlice(), ms.IdKeys())
ms.getOrig().IdKeys = internal.GenTestStringSlice()
assert.Equal(t, pcommon.StringSlice(internal.GenTestStringSliceWrapper()), ms.IdKeys())
}
func TestEntityRef_DescriptionKeys(t *testing.T) {
ms := NewEntityRef()
assert.Equal(t, pcommon.NewStringSlice(), ms.DescriptionKeys())
ms.getOrig().DescriptionKeys = internal.GenTestStringSlice()
assert.Equal(t, pcommon.StringSlice(internal.GenTestStringSliceWrapper()), ms.DescriptionKeys())
}
func generateTestEntityRef() EntityRef {
return newEntityRef(internal.GenTestEntityRef(), internal.NewState())
}
================================================
FILE: pdata/xpdata/entity/generated_entityrefslice.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package entity
import (
"iter"
"sort"
"go.opentelemetry.io/collector/pdata/internal"
)
// EntityRefSlice logically represents a slice of EntityRef.
//
// This is a reference type. If passed by value and callee modifies it, the
// caller will see the modification.
//
// Must use NewEntityRefSlice function to create new instances.
// Important: zero-initialized instance is not valid for use.
type EntityRefSlice internal.EntityRefSliceWrapper
func newEntityRefSlice(orig *[]*internal.EntityRef, state *internal.State) EntityRefSlice {
return EntityRefSlice(internal.NewEntityRefSliceWrapper(orig, state))
}
// NewEntityRefSlice creates a EntityRefSliceWrapper with 0 elements.
// Can use "EnsureCapacity" to initialize with a given capacity.
func NewEntityRefSlice() EntityRefSlice {
orig := []*internal.EntityRef(nil)
return newEntityRefSlice(&orig, internal.NewState())
}
// Len returns the number of elements in the slice.
//
// Returns "0" for a newly instance created with "NewEntityRefSlice()".
func (es EntityRefSlice) Len() int {
return len(*es.getOrig())
}
// 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 EntityRefSlice) At(i int) EntityRef {
return newEntityRef((*es.getOrig())[i], es.getState())
}
// All returns an iterator over index-value pairs in the slice.
//
// for i, v := range es.All() {
// ... // Do something with index-value pair
// }
func (es EntityRefSlice) All() iter.Seq2[int, EntityRef] {
return func(yield func(int, EntityRef) bool) {
for i := 0; i < es.Len(); i++ {
if !yield(i, es.At(i)) {
return
}
}
}
}
// EnsureCapacity is an operation that ensures the slice has at least the specified capacity.
// 1. If the newCap <= cap then no change in capacity.
// 2. If the newCap > cap then the slice capacity will be expanded to equal newCap.
//
// Here is how a new EntityRefSlice can be initialized:
//
// es := NewEntityRefSlice()
// es.EnsureCapacity(4)
// for i := 0; i < 4; i++ {
// e := es.AppendEmpty()
// // Here should set all the values for e.
// }
func (es EntityRefSlice) EnsureCapacity(newCap int) {
es.getState().AssertMutable()
oldCap := cap(*es.getOrig())
if newCap <= oldCap {
return
}
newOrig := make([]*internal.EntityRef, len(*es.getOrig()), newCap)
copy(newOrig, *es.getOrig())
*es.getOrig() = newOrig
}
// AppendEmpty will append to the end of the slice an empty EntityRef.
// It returns the newly added EntityRef.
func (es EntityRefSlice) AppendEmpty() EntityRef {
es.getState().AssertMutable()
*es.getOrig() = append(*es.getOrig(), internal.NewEntityRef())
return es.At(es.Len() - 1)
}
// MoveAndAppendTo moves all elements from the current slice and appends them to the dest.
// The current slice will be cleared.
func (es EntityRefSlice) MoveAndAppendTo(dest EntityRefSlice) {
es.getState().AssertMutable()
dest.getState().AssertMutable()
// If they point to the same data, they are the same, nothing to do.
if es.getOrig() == dest.getOrig() {
return
}
if *dest.getOrig() == nil {
// We can simply move the entire vector and avoid any allocations.
*dest.getOrig() = *es.getOrig()
} else {
*dest.getOrig() = append(*dest.getOrig(), *es.getOrig()...)
}
*es.getOrig() = nil
}
// RemoveIf calls f sequentially for each element present in the slice.
// If f returns true, the element is removed from the slice.
func (es EntityRefSlice) RemoveIf(f func(EntityRef) bool) {
es.getState().AssertMutable()
newLen := 0
for i := 0; i < len(*es.getOrig()); i++ {
if f(es.At(i)) {
internal.DeleteEntityRef((*es.getOrig())[i], true)
(*es.getOrig())[i] = nil
continue
}
if newLen == i {
// Nothing to move, element is at the right place.
newLen++
continue
}
(*es.getOrig())[newLen] = (*es.getOrig())[i]
// Cannot delete here since we just move the data(or pointer to data) to a different position in the slice.
(*es.getOrig())[i] = nil
newLen++
}
*es.getOrig() = (*es.getOrig())[:newLen]
}
// CopyTo copies all elements from the current slice overriding the destination.
func (es EntityRefSlice) CopyTo(dest EntityRefSlice) {
dest.getState().AssertMutable()
if es.getOrig() == dest.getOrig() {
return
}
*dest.getOrig() = internal.CopyEntityRefPtrSlice(*dest.getOrig(), *es.getOrig())
}
// Sort sorts the EntityRef elements within EntityRefSlice given the
// provided less function so that two instances of EntityRefSlice
// can be compared.
func (es EntityRefSlice) Sort(less func(a, b EntityRef) bool) {
es.getState().AssertMutable()
sort.SliceStable(*es.getOrig(), func(i, j int) bool { return less(es.At(i), es.At(j)) })
}
func (ms EntityRefSlice) getOrig() *[]*internal.EntityRef {
return internal.GetEntityRefSliceOrig(internal.EntityRefSliceWrapper(ms))
}
func (ms EntityRefSlice) getState() *internal.State {
return internal.GetEntityRefSliceState(internal.EntityRefSliceWrapper(ms))
}
================================================
FILE: pdata/xpdata/entity/generated_entityrefslice_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Code generated by "internal/cmd/pdatagen/main.go". DO NOT EDIT.
// To regenerate this file run "make genpdata".
package entity
import (
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestEntityRefSlice(t *testing.T) {
es := NewEntityRefSlice()
assert.Equal(t, 0, es.Len())
es = newEntityRefSlice(&[]*internal.EntityRef{}, internal.NewState())
assert.Equal(t, 0, es.Len())
emptyVal := NewEntityRef()
testVal := EntityRef(internal.GenTestEntityRefWrapper())
for i := 0; i < 7; i++ {
es.AppendEmpty()
assert.Equal(t, emptyVal, es.At(i))
(*es.getOrig())[i] = internal.GenTestEntityRef()
assert.Equal(t, testVal, es.At(i))
}
assert.Equal(t, 7, es.Len())
}
func TestEntityRefSliceReadOnly(t *testing.T) {
sharedState := internal.NewState()
sharedState.MarkReadOnly()
es := newEntityRefSlice(&[]*internal.EntityRef{}, sharedState)
assert.Equal(t, 0, es.Len())
assert.Panics(t, func() { es.AppendEmpty() })
assert.Panics(t, func() { es.EnsureCapacity(2) })
es2 := NewEntityRefSlice()
es.CopyTo(es2)
assert.Panics(t, func() { es2.CopyTo(es) })
assert.Panics(t, func() { es.MoveAndAppendTo(es2) })
assert.Panics(t, func() { es2.MoveAndAppendTo(es) })
}
func TestEntityRefSlice_CopyTo(t *testing.T) {
dest := NewEntityRefSlice()
src := generateTestEntityRefSlice()
src.CopyTo(dest)
assert.Equal(t, generateTestEntityRefSlice(), dest)
dest.CopyTo(dest)
assert.Equal(t, generateTestEntityRefSlice(), dest)
}
func TestEntityRefSlice_EnsureCapacity(t *testing.T) {
es := generateTestEntityRefSlice()
// Test ensure smaller capacity.
const ensureSmallLen = 4
es.EnsureCapacity(ensureSmallLen)
assert.Less(t, ensureSmallLen, es.Len())
assert.Equal(t, es.Len(), cap(*es.getOrig()))
assert.Equal(t, generateTestEntityRefSlice(), es)
// Test ensure larger capacity
const ensureLargeLen = 9
es.EnsureCapacity(ensureLargeLen)
assert.Less(t, generateTestEntityRefSlice().Len(), ensureLargeLen)
assert.Equal(t, ensureLargeLen, cap(*es.getOrig()))
assert.Equal(t, generateTestEntityRefSlice(), es)
}
func TestEntityRefSlice_MoveAndAppendTo(t *testing.T) {
// Test MoveAndAppendTo to empty
expectedSlice := generateTestEntityRefSlice()
dest := NewEntityRefSlice()
src := generateTestEntityRefSlice()
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestEntityRefSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo empty slice
src.MoveAndAppendTo(dest)
assert.Equal(t, generateTestEntityRefSlice(), dest)
assert.Equal(t, 0, src.Len())
assert.Equal(t, expectedSlice.Len(), dest.Len())
// Test MoveAndAppendTo not empty slice
generateTestEntityRefSlice().MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
dest.MoveAndAppendTo(dest)
assert.Equal(t, 2*expectedSlice.Len(), dest.Len())
for i := 0; i < expectedSlice.Len(); i++ {
assert.Equal(t, expectedSlice.At(i), dest.At(i))
assert.Equal(t, expectedSlice.At(i), dest.At(i+expectedSlice.Len()))
}
}
func TestEntityRefSlice_RemoveIf(t *testing.T) {
// Test RemoveIf on empty slice
emptySlice := NewEntityRefSlice()
emptySlice.RemoveIf(func(el EntityRef) bool {
t.Fail()
return false
})
// Test RemoveIf
filtered := generateTestEntityRefSlice()
pos := 0
filtered.RemoveIf(func(el EntityRef) bool {
pos++
return pos%2 == 1
})
assert.Equal(t, 2, filtered.Len())
}
func TestEntityRefSlice_RemoveIfAll(t *testing.T) {
got := generateTestEntityRefSlice()
got.RemoveIf(func(el EntityRef) bool {
return true
})
assert.Equal(t, 0, got.Len())
}
func TestEntityRefSliceAll(t *testing.T) {
ms := generateTestEntityRefSlice()
assert.NotEmpty(t, ms.Len())
var c int
for i, v := range ms.All() {
assert.Equal(t, ms.At(i), v, "element should match")
c++
}
assert.Equal(t, ms.Len(), c, "All elements should have been visited")
}
func TestEntityRefSlice_Sort(t *testing.T) {
es := generateTestEntityRefSlice()
es.Sort(func(a, b EntityRef) bool {
return uintptr(unsafe.Pointer(a.getOrig())) < uintptr(unsafe.Pointer(b.getOrig()))
})
for i := 1; i < es.Len(); i++ {
assert.Less(t, uintptr(unsafe.Pointer(es.At(i-1).getOrig())), uintptr(unsafe.Pointer(es.At(i).getOrig())))
}
es.Sort(func(a, b EntityRef) bool {
return uintptr(unsafe.Pointer(a.getOrig())) > uintptr(unsafe.Pointer(b.getOrig()))
})
for i := 1; i < es.Len(); i++ {
assert.Greater(t, uintptr(unsafe.Pointer(es.At(i-1).getOrig())), uintptr(unsafe.Pointer(es.At(i).getOrig())))
}
}
func generateTestEntityRefSlice() EntityRefSlice {
ms := NewEntityRefSlice()
*ms.getOrig() = internal.GenTestEntityRefPtrSlice()
return ms
}
================================================
FILE: pdata/xpdata/entity/resource_entities.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package entity // import "go.opentelemetry.io/collector/pdata/xpdata/entity"
import (
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// ResourceEntityRefs returns the EntityRefs associated with this Resource.
// Once EntityRefs is stabilized in the proto definition,
// this function will be available in the pcommon package as part of a Resource method.
func ResourceEntityRefs(res pcommon.Resource) EntityRefSlice {
ir := internal.ResourceWrapper(res)
return newEntityRefSlice(&internal.GetResourceOrig(ir).EntityRefs, internal.GetResourceState(ir))
}
// ResourceEntities returns the Entities associated with this Resource.
// The returned EntityMap shares the resource's attributes map, so modifications
// to entity attributes are immediately reflected in the resource.
func ResourceEntities(res pcommon.Resource) EntityMap {
return EntityMap{
refs: ResourceEntityRefs(res),
attributes: res.Attributes(),
}
}
================================================
FILE: pdata/xpdata/entity/resource_entities_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package entity
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestResourceEntityRefs(t *testing.T) {
res := pcommon.NewResource()
refs := ResourceEntityRefs(res)
assert.Equal(t, 0, refs.Len())
ref := refs.AppendEmpty()
ref.SetType("service")
assert.Equal(t, 1, refs.Len())
assert.Equal(t, "service", refs.At(0).Type())
}
func TestResourceEntities(t *testing.T) {
res := pcommon.NewResource()
entities := ResourceEntities(res)
assert.Equal(t, 0, entities.Len())
e := entities.PutEmpty("service")
assert.Equal(t, 1, entities.Len())
retrieved, ok := entities.Get("service")
assert.True(t, ok)
assert.Equal(t, "service", retrieved.Type())
assert.Equal(t, "service", e.Type())
}
================================================
FILE: pdata/xpdata/fuzz_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xpdata
import (
"bytes"
"testing"
"github.com/stretchr/testify/require"
)
var unexpectedBytes = "expected the same bytes from unmarshaling and marshaling."
func FuzzUnmarshalJSONValue(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
u1 := &JSONUnmarshaler{}
ld1, err := u1.UnmarshalValue(data)
if err != nil {
return
}
m1 := &JSONMarshaler{}
b1, err := m1.MarshalValue(ld1)
require.NoError(t, err, "failed to marshal valid struct")
u2 := &JSONUnmarshaler{}
ld2, err := u2.UnmarshalValue(b1)
require.NoError(t, err, "failed to unmarshal valid bytes")
m2 := &JSONMarshaler{}
b2, err := m2.MarshalValue(ld2)
require.NoError(t, err, "failed to marshal valid struct")
require.True(t, bytes.Equal(b1, b2), "%s. \nexpected %d but got %d\n", unexpectedBytes, b1, b2)
})
}
================================================
FILE: pdata/xpdata/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package xpdata
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: pdata/xpdata/go.mod
================================================
module go.opentelemetry.io/collector/pdata/xpdata
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/client v1.54.0
go.opentelemetry.io/collector/featuregate v1.54.0
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/collector/pdata/pprofile v0.148.0
go.opentelemetry.io/collector/pdata/testdata v0.148.0
go.opentelemetry.io/otel/trace v1.42.0
go.uber.org/goleak v1.3.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/pdata => ..
replace go.opentelemetry.io/collector/pdata/pprofile => ../pprofile
replace go.opentelemetry.io/collector/pdata/testdata => ../testdata
replace go.opentelemetry.io/collector/client => ../../client
replace go.opentelemetry.io/collector/consumer => ../../consumer
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
================================================
FILE: pdata/xpdata/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: pdata/xpdata/internal/metadata/generated_feature_gates.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/featuregate"
)
var PdataEnableRefCountingFeatureGate = featuregate.GlobalRegistry().MustRegister(
"pdata.enableRefCounting",
featuregate.StageBeta,
featuregate.WithRegisterDescription("When enabled, enables using ref counting to know when pdata memory can be freed up. This featuregate is here only to protect if unexpected bugs happens because of ref counting logic."),
featuregate.WithRegisterReferenceURL("https://github.com/open-telemetry/opentelemetry-collector/issues/13631"),
featuregate.WithRegisterFromVersion("v0.133.0"),
)
================================================
FILE: pdata/xpdata/json.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xpdata // import "go.opentelemetry.io/collector/pdata/xpdata"
import (
"slices"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/pcommon"
)
type JSONMarshaler struct{}
func (*JSONMarshaler) MarshalValue(value pcommon.Value) ([]byte, error) {
av := internal.GetValueOrig(internal.ValueWrapper(value))
dest := json.BorrowStream(nil)
defer json.ReturnStream(dest)
av.MarshalJSON(dest)
if dest.Error() != nil {
return nil, dest.Error()
}
return slices.Clone(dest.Buffer()), nil
}
type JSONUnmarshaler struct{}
func (*JSONUnmarshaler) UnmarshalValue(buf []byte) (pcommon.Value, error) {
iter := json.BorrowIterator(buf)
defer json.ReturnIterator(iter)
value := &internal.AnyValue{}
value.UnmarshalJSON(iter)
if iter.Error() != nil {
return pcommon.NewValueEmpty(), iter.Error()
}
return pcommon.Value(internal.NewValueWrapper(value, internal.NewState())), nil
}
================================================
FILE: pdata/xpdata/json_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xpdata
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/pcommon"
)
func TestMarshalAndUnmarshalValue(t *testing.T) {
for name, src := range genTestEncodingValues() {
t.Run(name, func(t *testing.T) {
m := &JSONMarshaler{}
b, err := m.MarshalValue(src)
require.NoError(t, err)
u := &JSONUnmarshaler{}
dest, err := u.UnmarshalValue(b)
require.NoError(t, err)
require.Equal(t, src, dest)
})
}
}
func TestUnmarshalValueUnknown(t *testing.T) {
m := &JSONUnmarshaler{}
b, err := m.UnmarshalValue([]byte(`{"unknown": "string"}`))
require.NoError(t, err)
assert.Equal(t, pcommon.NewValueEmpty(), b)
}
func genTestEncodingValues() map[string]pcommon.Value {
return map[string]pcommon.Value{
"empty": pcommon.NewValueEmpty(),
"StringValue/default": pcommon.NewValueStr(""),
"StringValue/test": pcommon.NewValueStr("test_stringvalue"),
"BoolValue/default": pcommon.NewValueBool(false),
"BoolValue/test": pcommon.NewValueBool(true),
"IntValue/default": pcommon.NewValueInt(0),
"IntValue/test": pcommon.NewValueInt(13),
"DoubleValue/default": pcommon.NewValueDouble(0),
"DoubleValue/test": pcommon.NewValueDouble(3.1415926),
"ArrayValue/default": pcommon.NewValueSlice(),
"ArrayValue/test": func() pcommon.Value {
s := pcommon.NewValueSlice()
s.Slice().AppendEmpty().SetStr("test1")
s.Slice().AppendEmpty().SetInt(13)
s.Slice().AppendEmpty().SetBool(true)
s.Slice().AppendEmpty().SetDouble(3.1415926)
s1 := s.Slice().AppendEmpty().SetEmptySlice()
s1.AppendEmpty().SetStr("nested")
m := s.Slice().AppendEmpty().SetEmptyMap()
m.PutStr("key1", "value1")
return s
}(),
"KvlistValue/default": pcommon.NewValueMap(),
"KvlistValue/test": func() pcommon.Value {
m := pcommon.NewValueMap()
m.Map().PutStr("key1", "value1")
m.Map().PutInt("key2", 13)
m.Map().PutBool("key3", true)
m.Map().PutDouble("key4", 3.1415926)
m.Map().PutEmpty("key5").SetStr("value5")
s := m.Map().PutEmptySlice("key6")
s.AppendEmpty().SetStr("nested1")
m1 := m.Map().PutEmptyMap("key6")
m1.PutStr("nestedKey1", "nestedValue1")
return m
}(),
"BytesValue/test": func() pcommon.Value {
v := pcommon.NewValueBytes()
v.Bytes().FromRaw([]byte{1, 2, 3})
return v
}(),
}
}
================================================
FILE: pdata/xpdata/map_builder.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xpdata // import "go.opentelemetry.io/collector/pdata/xpdata"
import (
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// MapBuilder is an experimental struct which can be used to create a pcommon.Map more efficiently
// than by repeated use of the Put family of methods, which check for duplicate keys on every call
// (a linear time operation).
// A zero-initialized MapBuilder is ready for use.
type MapBuilder struct {
state internal.State
pairs []internal.KeyValue
}
// EnsureCapacity increases the capacity of this MapBuilder instance, if necessary,
// to ensure that it can hold at least the number of elements specified by the capacity argument.
func (mb *MapBuilder) EnsureCapacity(capacity int) {
oldValues := mb.pairs
if capacity <= cap(oldValues) {
return
}
mb.pairs = make([]internal.KeyValue, len(oldValues), capacity)
copy(mb.pairs, oldValues)
}
func (mb *MapBuilder) getValue(i int) pcommon.Value {
return pcommon.Value(internal.NewValueWrapper(&mb.pairs[i].Value, &mb.state))
}
// AppendEmpty appends a key/value pair to the MapBuilder and return the inserted value.
// This method does not check for duplicate keys and has an amortized constant time complexity.
func (mb *MapBuilder) AppendEmpty(k string) pcommon.Value {
mb.pairs = append(mb.pairs, internal.KeyValue{Key: k})
return mb.getValue(len(mb.pairs) - 1)
}
// UnsafeIntoMap transfers the contents of a MapBuilder into a MapWrapper, without checking for duplicate keys.
// If the MapBuilder contains duplicate keys, the behavior of the resulting MapWrapper is unspecified.
// This operation has constant time complexity and makes no allocations.
// After this operation, the MapBuilder is reset to an empty state.
func (mb *MapBuilder) UnsafeIntoMap(m pcommon.Map) {
internal.GetMapState(internal.MapWrapper(m)).AssertMutable()
*internal.GetMapOrig(internal.MapWrapper(m)) = mb.pairs
mb.pairs = nil
}
================================================
FILE: pdata/xpdata/map_builder_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xpdata_test
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/xpdata"
)
func TestMapBuilder(t *testing.T) {
var mb xpdata.MapBuilder
mb.EnsureCapacity(3)
mb.AppendEmpty("key1").SetStr("val")
mb.AppendEmpty("key2").SetInt(42)
m := pcommon.NewMap()
mb.UnsafeIntoMap(m)
assert.Equal(t, 2, m.Len())
val, ok := m.Get("key1")
assert.True(t, ok && val.Type() == pcommon.ValueTypeStr && val.Str() == "val")
val, ok = m.Get("key2")
assert.True(t, ok && val.Type() == pcommon.ValueTypeInt && val.Int() == 42)
}
================================================
FILE: pdata/xpdata/metadata.yaml
================================================
type: xpdata
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
stability:
development: [traces, metrics, logs, profiles]
feature_gates:
- id: pdata.enableRefCounting
description: 'When enabled, enables using ref counting to know when pdata memory can be freed up. This featuregate is here only to protect if unexpected bugs happens because of ref counting logic.'
stage: beta
from_version: 'v0.133.0'
reference_url: 'https://github.com/open-telemetry/opentelemetry-collector/issues/13631'
================================================
FILE: pdata/xpdata/pref/gate.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pref // import "go.opentelemetry.io/collector/pdata/xpdata/pref"
import (
"go.opentelemetry.io/collector/pdata/internal/metadata"
)
// UseProtoPooling temporary expose public to allow testing.
var UseProtoPooling = metadata.PdataUseProtoPoolingFeatureGate
================================================
FILE: pdata/xpdata/pref/logs.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pref // import "go.opentelemetry.io/collector/pdata/xpdata/pref"
import (
"reflect"
"go.opentelemetry.io/collector/pdata/internal"
pmetadata "go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/xpdata/internal/metadata"
)
// MarkPipelineOwnedLogs marks the plog.Logs data as owned by the pipeline, returns true if the data were
// previously not owned by the pipeline, otherwise false.
func MarkPipelineOwnedLogs(ld plog.Logs) bool {
return internal.GetLogsState(internal.LogsWrapper(ld)).MarkPipelineOwned()
}
func RefLogs(ld plog.Logs) {
if metadata.PdataEnableRefCountingFeatureGate.IsEnabled() {
internal.GetLogsState(internal.LogsWrapper(ld)).Ref()
}
}
func UnrefLogs(ld plog.Logs) {
if metadata.PdataEnableRefCountingFeatureGate.IsEnabled() {
if !internal.GetLogsState(internal.LogsWrapper(ld)).Unref() {
return
}
// Don't call DeleteExportLogsServiceRequest without the gate because we reset the data and that may still cause issues.
if pmetadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
internal.DeleteExportLogsServiceRequest(internal.GetLogsOrig(internal.LogsWrapper(ld)), true)
}
}
}
// TODO: Generate this in pdata.
func EqualLogs(ld1, ld2 plog.Logs) bool {
return reflect.DeepEqual(internal.GetLogsOrig(internal.LogsWrapper(ld1)), internal.GetLogsOrig(internal.LogsWrapper(ld2)))
}
================================================
FILE: pdata/xpdata/pref/metrics.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pref // import "go.opentelemetry.io/collector/pdata/xpdata/pref"
import (
"reflect"
"go.opentelemetry.io/collector/pdata/internal"
pmetadata "go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/xpdata/internal/metadata"
)
// MarkPipelineOwnedMetrics marks the pmetric.Metrics data as owned by the pipeline, returns true if the data were
// previously not owned by the pipeline, otherwise false.
func MarkPipelineOwnedMetrics(md pmetric.Metrics) bool {
return internal.GetMetricsState(internal.MetricsWrapper(md)).MarkPipelineOwned()
}
func RefMetrics(md pmetric.Metrics) {
if metadata.PdataEnableRefCountingFeatureGate.IsEnabled() {
internal.GetMetricsState(internal.MetricsWrapper(md)).Ref()
}
}
func UnrefMetrics(md pmetric.Metrics) {
if metadata.PdataEnableRefCountingFeatureGate.IsEnabled() {
if !internal.GetMetricsState(internal.MetricsWrapper(md)).Unref() {
return
}
// Don't call DeleteExportLogsServiceRequest without the gate because we reset the data and that may still cause issues.
if pmetadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
internal.DeleteExportMetricsServiceRequest(internal.GetMetricsOrig(internal.MetricsWrapper(md)), true)
}
}
}
// TODO: Generate this in pdata.
func EqualMetrics(md1, md2 pmetric.Metrics) bool {
return reflect.DeepEqual(internal.GetMetricsOrig(internal.MetricsWrapper(md1)), internal.GetMetricsOrig(internal.MetricsWrapper(md2)))
}
================================================
FILE: pdata/xpdata/pref/profiles.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pref // import "go.opentelemetry.io/collector/pdata/xpdata/pref"
import (
"reflect"
"go.opentelemetry.io/collector/pdata/internal"
pmetadata "go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/xpdata/internal/metadata"
)
// MarkPipelineOwnedProfiles marks the pprofile.Profiles data as owned by the pipeline, returns true if the data were
// previously not owned by the pipeline, otherwise false.
func MarkPipelineOwnedProfiles(pd pprofile.Profiles) bool {
return internal.GetProfilesState(internal.ProfilesWrapper(pd)).MarkPipelineOwned()
}
func RefProfiles(pd pprofile.Profiles) {
if metadata.PdataEnableRefCountingFeatureGate.IsEnabled() {
internal.GetProfilesState(internal.ProfilesWrapper(pd)).Ref()
}
}
func UnrefProfiles(pd pprofile.Profiles) {
if metadata.PdataEnableRefCountingFeatureGate.IsEnabled() {
if !internal.GetProfilesState(internal.ProfilesWrapper(pd)).Unref() {
return
}
// Don't call DeleteExportLogsServiceRequest without the gate because we reset the data and that may still cause issues.
if pmetadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
internal.DeleteExportProfilesServiceRequest(internal.GetProfilesOrig(internal.ProfilesWrapper(pd)), true)
}
}
}
// TODO: Generate this in pdata.
func EqualProfiles(pd1, pd2 pprofile.Profiles) bool {
return reflect.DeepEqual(internal.GetProfilesOrig(internal.ProfilesWrapper(pd1)), internal.GetProfilesOrig(internal.ProfilesWrapper(pd2)))
}
================================================
FILE: pdata/xpdata/pref/traces.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pref // import "go.opentelemetry.io/collector/pdata/xpdata/pref"
import (
"reflect"
"go.opentelemetry.io/collector/pdata/internal"
pmetadata "go.opentelemetry.io/collector/pdata/internal/metadata"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/pdata/xpdata/internal/metadata"
)
// MarkPipelineOwnedTraces marks the ptrace.Traces data as owned by the pipeline, returns true if the data were
// previously not owned by the pipeline, otherwise false.
func MarkPipelineOwnedTraces(td ptrace.Traces) bool {
return internal.GetTracesState(internal.TracesWrapper(td)).MarkPipelineOwned()
}
func RefTraces(td ptrace.Traces) {
internal.GetTracesState(internal.TracesWrapper(td)).Ref()
}
func UnrefTraces(td ptrace.Traces) {
if metadata.PdataEnableRefCountingFeatureGate.IsEnabled() {
if !internal.GetTracesState(internal.TracesWrapper(td)).Unref() {
return
}
// Don't call DeleteExportLogsServiceRequest without the gate because we reset the data and that may still cause issues.
if pmetadata.PdataUseProtoPoolingFeatureGate.IsEnabled() {
internal.DeleteExportTraceServiceRequest(internal.GetTracesOrig(internal.TracesWrapper(td)), true)
}
}
}
// TODO: Generate this in pdata.
func EqualTraces(td1, td2 ptrace.Traces) bool {
return reflect.DeepEqual(internal.GetTracesOrig(internal.TracesWrapper(td1)), internal.GetTracesOrig(internal.TracesWrapper(td2)))
}
================================================
FILE: pdata/xpdata/request/context.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package request // import "go.opentelemetry.io/collector/pdata/xpdata/request"
import (
"context"
"net"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/collector/client"
"go.opentelemetry.io/collector/pdata/internal"
)
// encodeContext encodes the context into a map of strings.
func encodeContext(ctx context.Context) *internal.RequestContext {
rc := internal.RequestContext{}
encodeSpanContext(ctx, &rc)
encodeClientMetadata(ctx, &rc)
encodeClientAddress(ctx, &rc)
return &rc
}
func encodeSpanContext(ctx context.Context, rc *internal.RequestContext) {
spanCtx := trace.SpanContextFromContext(ctx)
if !spanCtx.IsValid() {
return
}
rc.SpanContext = &internal.SpanContext{
TraceID: internal.TraceID(spanCtx.TraceID()),
SpanID: internal.SpanID(spanCtx.SpanID()),
TraceFlags: uint32(spanCtx.TraceFlags()),
TraceState: spanCtx.TraceState().String(),
Remote: spanCtx.IsRemote(),
}
}
func encodeClientMetadata(ctx context.Context, rc *internal.RequestContext) {
clientMetadata := client.FromContext(ctx).Metadata
for k := range clientMetadata.Keys() {
vals := clientMetadata.Get(k)
switch len(vals) {
case 1:
rc.ClientMetadata = append(rc.ClientMetadata, internal.KeyValue{
Key: k,
Value: internal.AnyValue{Value: &internal.AnyValue_StringValue{StringValue: vals[0]}},
})
default:
metadataArray := make([]internal.AnyValue, 0, len(vals))
for i := range vals {
metadataArray = append(metadataArray, internal.AnyValue{Value: &internal.AnyValue_StringValue{StringValue: vals[i]}})
}
rc.ClientMetadata = append(rc.ClientMetadata, internal.KeyValue{
Key: k,
Value: internal.AnyValue{Value: &internal.AnyValue_ArrayValue{ArrayValue: &internal.ArrayValue{Values: metadataArray}}},
})
}
}
}
func encodeClientAddress(ctx context.Context, rc *internal.RequestContext) {
switch a := client.FromContext(ctx).Addr.(type) {
case *net.IPAddr:
rc.ClientAddress = &internal.RequestContext_IP{IP: &internal.IPAddr{
IP: a.IP,
Zone: a.Zone,
}}
case *net.TCPAddr:
rc.ClientAddress = &internal.RequestContext_TCP{TCP: &internal.TCPAddr{
IP: a.IP,
Port: int64(a.Port),
Zone: a.Zone,
}}
case *net.UDPAddr:
rc.ClientAddress = &internal.RequestContext_UDP{UDP: &internal.UDPAddr{
IP: a.IP,
Port: int64(a.Port),
Zone: a.Zone,
}}
case *net.UnixAddr:
rc.ClientAddress = &internal.RequestContext_Unix{Unix: &internal.UnixAddr{
Name: a.Name,
Net: a.Net,
}}
}
}
// decodeContext decodes the context from the bytes map.
func decodeContext(ctx context.Context, rc *internal.RequestContext) context.Context {
if rc == nil {
return ctx
}
ctx = decodeSpanContext(ctx, rc.SpanContext)
metadataMap := decodeClientMetadata(rc.ClientMetadata)
clientAddress := decodeClientAddress(rc)
if len(metadataMap) > 0 || clientAddress != nil {
ctx = client.NewContext(ctx, client.Info{
Metadata: client.NewMetadata(metadataMap),
Addr: clientAddress,
})
}
return ctx
}
func decodeSpanContext(ctx context.Context, sc *internal.SpanContext) context.Context {
if sc == nil {
return ctx
}
traceID := trace.TraceID(sc.TraceID)
spanID := trace.SpanID(sc.SpanID)
traceState, _ := trace.ParseTraceState(sc.TraceState)
return trace.ContextWithSpanContext(context.Background(), trace.NewSpanContext(trace.SpanContextConfig{
TraceID: traceID,
SpanID: spanID,
TraceFlags: trace.TraceFlags(sc.TraceFlags),
TraceState: traceState,
Remote: sc.Remote,
}))
}
func decodeClientMetadata(clientMetadata []internal.KeyValue) map[string][]string {
if len(clientMetadata) == 0 {
return nil
}
metadataMap := make(map[string][]string, len(clientMetadata))
for _, kv := range clientMetadata {
switch val := kv.Value.Value.(type) {
case *internal.AnyValue_StringValue:
metadataMap[kv.Key] = make([]string, 1)
metadataMap[kv.Key][0] = val.StringValue
case *internal.AnyValue_ArrayValue:
metadataMap[kv.Key] = make([]string, len(val.ArrayValue.Values))
for i, v := range val.ArrayValue.Values {
metadataMap[kv.Key][i] = v.GetStringValue()
}
}
}
return metadataMap
}
func decodeClientAddress(rc *internal.RequestContext) net.Addr {
switch a := rc.ClientAddress.(type) {
case *internal.RequestContext_IP:
return &net.IPAddr{
IP: a.IP.IP,
Zone: a.IP.Zone,
}
case *internal.RequestContext_TCP:
return &net.TCPAddr{
IP: a.TCP.IP,
Port: int(a.TCP.Port),
Zone: a.TCP.Zone,
}
case *internal.RequestContext_UDP:
return &net.UDPAddr{
IP: a.UDP.IP,
Port: int(a.UDP.Port),
Zone: a.UDP.Zone,
}
case *internal.RequestContext_Unix:
return &net.UnixAddr{
Name: a.Unix.Name,
Net: a.Unix.Net,
}
}
return nil
}
================================================
FILE: pdata/xpdata/request/context_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package request // import "go.opentelemetry.io/collector/pdata/xpdata/request"
import (
"context"
"net"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/collector/client"
"go.opentelemetry.io/collector/pdata/internal"
)
func TestEncodeDecodeContext(t *testing.T) {
spanCtx := fakeSpanContext(t)
clientMetadata := client.NewMetadata(map[string][]string{
"key1": {"value1"},
"key2": {"value2", "value3"},
})
tests := []struct {
name string
clientInfo client.Info
}{
{
name: "without_client_address",
clientInfo: client.Info{Metadata: clientMetadata},
},
{
name: "with_client_IP_address",
clientInfo: client.Info{
Metadata: clientMetadata,
Addr: &net.IPAddr{
IP: net.IPv6loopback,
Zone: "eth0",
},
},
},
{
name: "with_client_TCP_address",
clientInfo: client.Info{
Metadata: clientMetadata,
Addr: &net.TCPAddr{
IP: net.IPv4(127, 0, 0, 1),
Port: 8080,
},
},
},
{
name: "with_client_UDP_address",
clientInfo: client.Info{
Metadata: clientMetadata,
Addr: &net.UDPAddr{
IP: net.IPv4(127, 0, 0, 1),
Port: 8080,
},
},
},
{
name: "with_client_unix_address",
clientInfo: client.Info{
Metadata: clientMetadata,
Addr: &net.UnixAddr{
Name: "/var/run/test.sock",
Net: "unixpacket",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Encode a context with a span and client metadata
ctx := trace.ContextWithSpanContext(context.Background(), spanCtx)
ctx = client.NewContext(ctx, tt.clientInfo)
reqCtx := encodeContext(ctx)
buf := make([]byte, reqCtx.SizeProto())
reqCtx.MarshalProto(buf)
// Decode the context
gotReqCtx := internal.RequestContext{}
require.NoError(t, gotReqCtx.UnmarshalProto(buf))
gotCtx := decodeContext(context.Background(), &gotReqCtx)
assert.Equal(t, spanCtx, trace.SpanContextFromContext(gotCtx))
assert.Equal(t, tt.clientInfo, client.FromContext(gotCtx))
})
}
// Decode a nil context
assert.Equal(t, context.Background(), decodeContext(context.Background(), nil))
}
================================================
FILE: pdata/xpdata/request/internal/request.proto
================================================
syntax = "proto3";
package opentelemetry.collector.pdata.xpdata.internal;
option go_package = "go.opentelemetry.io/collector/pdata/xpdata/internal";
import "gogoproto/gogo.proto";
import "opentelemetry/proto/trace/v1/trace.proto";
import "opentelemetry/proto/metrics/v1/metrics.proto";
import "opentelemetry/proto/logs/v1/logs.proto";
import "opentelemetry/proto/common/v1/common.proto";
import "opentelemetry/proto/profiles/v1development/profiles.proto";
// SpanContext represents a span context encoded associated with a telemetry export request.
message SpanContext {
bytes trace_id = 1;
bytes span_id = 2;
fixed32 trace_flags = 3;
string trace_state = 4;
bool remote = 5;
}
message IPAddr {
bytes ip = 1;
string zone = 2;
}
message TCPAddr {
bytes ip = 1;
int64 port = 2;
string zone = 3;
}
message UDPAddr {
bytes ip = 1;
int64 port = 2;
string zone = 3;
}
message UnixAddr {
string name = 1;
string net = 2;
}
// RequestContext represents metadata associated with a telemetry export request.
message RequestContext {
SpanContext span_context = 1;
// ClientMetadata contains additional metadata about the client making the request.
repeated opentelemetry.proto.common.v1.KeyValue client_metadata = 2 [ (gogoproto.nullable) = false ];
// ClientAddress contains the address of the client making the request.
oneof client_address {
IPAddr ip = 3;
TCPAddr tcp = 4;
UDPAddr udp = 5;
UnixAddr unix = 6;
}
}
// The following messages are wrappers around standard OpenTelemetry data types.
// They embed request-level context and a version discriminator to ensure they are not wire-compatible with
// the canonical OpenTelemetry proto messages.
//
// Each wrapper reserves field tag 1 for a `fixed32` (protobuf wire type 5) format_version field, which makes it
// structurally incompatible with the standard OTLP messages which use tag 1 for the data message field (protobuf wire type 2).
// This ensures old and new formats cannot be confused during decoding.
message TracesRequest {
fixed32 format_version = 1;
RequestContext request_context = 2;
opentelemetry.proto.trace.v1.TracesData traces_data = 3;
}
message MetricsRequest {
fixed32 format_version = 1;
RequestContext request_context = 2;
opentelemetry.proto.metrics.v1.MetricsData metrics_data = 3;
}
message LogsRequest {
fixed32 format_version = 1;
RequestContext request_context = 2;
opentelemetry.proto.logs.v1.LogsData logs_data = 3;
}
message ProfilesRequest {
fixed32 format_version = 1;
RequestContext request_context = 2;
opentelemetry.proto.profiles.v1development.ProfilesData profiles_data = 3;
}
================================================
FILE: pdata/xpdata/request/logs_request.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package request // import "go.opentelemetry.io/collector/pdata/xpdata/request"
import (
"context"
"fmt"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/plog"
)
// MarshalLogs marshals plog.Logs along with the context into a byte slice.
func MarshalLogs(ctx context.Context, ld plog.Logs) ([]byte, error) {
lr := internal.LogsRequest{
FormatVersion: requestFormatVersion,
LogsData: internal.LogsToProto(internal.LogsWrapper(ld)),
RequestContext: encodeContext(ctx),
}
buf := make([]byte, lr.SizeProto())
lr.MarshalProto(buf)
return buf, nil
}
// UnmarshalLogs unmarshals a byte slice into plog.Logs and the context.
func UnmarshalLogs(buf []byte) (context.Context, plog.Logs, error) {
ctx := context.Background()
if !isRequestPayloadV1(buf) {
return ctx, plog.Logs{}, ErrInvalidFormat
}
lr := internal.LogsRequest{}
if err := lr.UnmarshalProto(buf); err != nil {
return ctx, plog.Logs{}, fmt.Errorf("failed to unmarshal logs request: %w", err)
}
return decodeContext(ctx, lr.RequestContext), plog.Logs(internal.LogsFromProto(lr.LogsData)), nil
}
================================================
FILE: pdata/xpdata/request/logs_request_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package request // import "go.opentelemetry.io/collector/pdata/xpdata/request"
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/testdata"
)
func TestMarshalUnmarshalLogsRequest(t *testing.T) {
logs := testdata.GenerateLogs(3)
// unmarshal logs request with a context
spanCtx := fakeSpanContext(t)
buf, err := MarshalLogs(trace.ContextWithSpanContext(context.Background(), spanCtx), logs)
require.NoError(t, err)
gotCtx, gotLogs, err := UnmarshalLogs(buf)
require.NoError(t, err)
assert.Equal(t, spanCtx, trace.SpanContextFromContext(gotCtx))
assert.Equal(t, logs, gotLogs)
// unmarshal logs request with empty context
buf, err = MarshalLogs(context.Background(), logs)
require.NoError(t, err)
gotCtx, gotLogs, err = UnmarshalLogs(buf)
require.NoError(t, err)
assert.Equal(t, context.Background(), gotCtx)
assert.Equal(t, logs, gotLogs)
// unmarshal corrupted data
_, _, err = UnmarshalLogs(buf[:len(buf)-1])
require.ErrorContains(t, err, "failed to unmarshal logs request")
// unmarshal invalid format (bare logs)
buf, err = (&plog.ProtoMarshaler{}).MarshalLogs(logs)
require.NoError(t, err)
_, _, err = UnmarshalLogs(buf)
require.ErrorIs(t, err, ErrInvalidFormat)
}
================================================
FILE: pdata/xpdata/request/metrics_request.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package request // import "go.opentelemetry.io/collector/pdata/xpdata/request"
import (
"context"
"fmt"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pmetric"
)
// MarshalMetrics marshals pmetric.Metrics along with the context into a byte slice.
func MarshalMetrics(ctx context.Context, ld pmetric.Metrics) ([]byte, error) {
mr := internal.MetricsRequest{
FormatVersion: requestFormatVersion,
MetricsData: internal.MetricsToProto(internal.MetricsWrapper(ld)),
RequestContext: encodeContext(ctx),
}
buf := make([]byte, mr.SizeProto())
mr.MarshalProto(buf)
return buf, nil
}
// UnmarshalMetrics unmarshals a byte slice into pmetric.Metrics and the context.
func UnmarshalMetrics(buf []byte) (context.Context, pmetric.Metrics, error) {
ctx := context.Background()
if !isRequestPayloadV1(buf) {
return ctx, pmetric.Metrics{}, ErrInvalidFormat
}
mr := internal.MetricsRequest{}
if err := mr.UnmarshalProto(buf); err != nil {
return ctx, pmetric.Metrics{}, fmt.Errorf("failed to unmarshal metrics request: %w", err)
}
return decodeContext(ctx, mr.RequestContext), pmetric.Metrics(internal.MetricsFromProto(mr.MetricsData)), nil
}
================================================
FILE: pdata/xpdata/request/metrics_request_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package request // import "go.opentelemetry.io/collector/pdata/xpdata/request"
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/testdata"
)
func TestMarshalUnmarshalMetricsRequest(t *testing.T) {
metrics := testdata.GenerateMetrics(3)
// unmarshal metrics request with a context
spanCtx := fakeSpanContext(t)
buf, err := MarshalMetrics(trace.ContextWithSpanContext(context.Background(), spanCtx), metrics)
require.NoError(t, err)
gotCtx, gotMetrics, err := UnmarshalMetrics(buf)
require.NoError(t, err)
assert.Equal(t, spanCtx, trace.SpanContextFromContext(gotCtx))
assert.Equal(t, metrics, gotMetrics)
// unmarshal metrics request with empty context
buf, err = MarshalMetrics(context.Background(), metrics)
require.NoError(t, err)
gotCtx, gotMetrics, err = UnmarshalMetrics(buf)
require.NoError(t, err)
assert.Equal(t, context.Background(), gotCtx)
assert.Equal(t, metrics, gotMetrics)
// unmarshal corrupted data
_, _, err = UnmarshalMetrics(buf[:len(buf)-1])
require.ErrorContains(t, err, "failed to unmarshal metrics request")
// unmarshal invalid format (bare metrics)
buf, err = (&pmetric.ProtoMarshaler{}).MarshalMetrics(metrics)
require.NoError(t, err)
_, _, err = UnmarshalMetrics(buf)
require.ErrorIs(t, err, ErrInvalidFormat)
}
================================================
FILE: pdata/xpdata/request/profiles_request.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package request // import "go.opentelemetry.io/collector/pdata/xpdata/request"
import (
"context"
"fmt"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/pprofile"
)
// MarshalProfiles marshals pprofile.Profiles along with the context into a byte slice.
func MarshalProfiles(ctx context.Context, ld pprofile.Profiles) ([]byte, error) {
pr := internal.ProfilesRequest{
FormatVersion: requestFormatVersion,
ProfilesData: internal.ProfilesToProto(internal.ProfilesWrapper(ld)),
RequestContext: encodeContext(ctx),
}
buf := make([]byte, pr.SizeProto())
pr.MarshalProto(buf)
return buf, nil
}
// UnmarshalProfiles unmarshals a byte slice into pprofile.Profiles and the context.
func UnmarshalProfiles(buf []byte) (context.Context, pprofile.Profiles, error) {
ctx := context.Background()
if !isRequestPayloadV1(buf) {
return ctx, pprofile.Profiles{}, ErrInvalidFormat
}
pr := internal.ProfilesRequest{}
if err := pr.UnmarshalProto(buf); err != nil {
return ctx, pprofile.Profiles{}, fmt.Errorf("failed to unmarshal profiles request: %w", err)
}
return decodeContext(ctx, pr.RequestContext), pprofile.Profiles(internal.ProfilesFromProto(pr.ProfilesData)), nil
}
================================================
FILE: pdata/xpdata/request/profiles_request_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package request // import "go.opentelemetry.io/collector/pdata/xpdata/request"
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/testdata"
)
func TestMarshalUnmarshalProfilesRequest(t *testing.T) {
profiles := testdata.GenerateProfiles(3)
// unmarshal profiles request with a context
spanCtx := fakeSpanContext(t)
buf, err := MarshalProfiles(trace.ContextWithSpanContext(context.Background(), spanCtx), profiles)
require.NoError(t, err)
gotCtx, gotProfiles, err := UnmarshalProfiles(buf)
require.NoError(t, err)
assert.Equal(t, spanCtx, trace.SpanContextFromContext(gotCtx))
assert.Equal(t, profiles, gotProfiles)
// unmarshal profiles request with empty context
buf, err = MarshalProfiles(context.Background(), profiles)
require.NoError(t, err)
gotCtx, gotProfiles, err = UnmarshalProfiles(buf)
require.NoError(t, err)
assert.Equal(t, context.Background(), gotCtx)
assert.Equal(t, profiles, gotProfiles)
// unmarshal corrupted data
_, _, err = UnmarshalProfiles(buf[:len(buf)-1])
require.ErrorContains(t, err, "failed to unmarshal profiles request")
// unmarshal invalid format (bare profiles)
buf, err = (&pprofile.ProtoMarshaler{}).MarshalProfiles(profiles)
require.NoError(t, err)
_, _, err = UnmarshalProfiles(buf)
require.ErrorIs(t, err, ErrInvalidFormat)
}
================================================
FILE: pdata/xpdata/request/requesttest.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package request // import "go.opentelemetry.io/collector/pdata/xpdata/request"
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/trace"
)
func fakeSpanContext(tb testing.TB) trace.SpanContext {
traceID, err := trace.TraceIDFromHex("0102030405060708090a0b0c0d0e0f10")
require.NoError(tb, err)
spanID, err := trace.SpanIDFromHex("0102030405060708")
require.NoError(tb, err)
traceState, err := trace.ParseTraceState("key1=value1,key2=value2,key3=value3")
require.NoError(tb, err)
return trace.NewSpanContext(trace.SpanContextConfig{
TraceID: traceID,
SpanID: spanID,
TraceFlags: 0x01,
TraceState: traceState,
Remote: true,
})
}
================================================
FILE: pdata/xpdata/request/traces_request.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package request // import "go.opentelemetry.io/collector/pdata/xpdata/request"
import (
"context"
"fmt"
"go.opentelemetry.io/collector/pdata/internal"
"go.opentelemetry.io/collector/pdata/ptrace"
)
// MarshalTraces marshals ptrace.Traces along with the context into a byte slice.
func MarshalTraces(ctx context.Context, ld ptrace.Traces) ([]byte, error) {
tr := internal.TracesRequest{
FormatVersion: requestFormatVersion,
TracesData: internal.TracesToProto(internal.TracesWrapper(ld)),
RequestContext: encodeContext(ctx),
}
buf := make([]byte, tr.SizeProto())
tr.MarshalProto(buf)
return buf, nil
}
// UnmarshalTraces unmarshals a byte slice into ptrace.Traces and the context.
func UnmarshalTraces(buf []byte) (context.Context, ptrace.Traces, error) {
ctx := context.Background()
if !isRequestPayloadV1(buf) {
return ctx, ptrace.Traces{}, ErrInvalidFormat
}
tr := internal.TracesRequest{}
if err := tr.UnmarshalProto(buf); err != nil {
return ctx, ptrace.Traces{}, fmt.Errorf("failed to unmarshal traces request: %w", err)
}
return decodeContext(ctx, tr.RequestContext), ptrace.Traces(internal.TracesFromProto(tr.TracesData)), nil
}
================================================
FILE: pdata/xpdata/request/traces_request_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package request // import "go.opentelemetry.io/collector/pdata/xpdata/request"
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/pdata/testdata"
)
func TestMarshalUnmarshalTracesRequest(t *testing.T) {
traces := testdata.GenerateTraces(3)
// unmarshal traces request with a context
spanCtx := fakeSpanContext(t)
buf, err := MarshalTraces(trace.ContextWithSpanContext(context.Background(), spanCtx), traces)
require.NoError(t, err)
gotCtx, gotTraces, err := UnmarshalTraces(buf)
require.NoError(t, err)
assert.Equal(t, spanCtx, trace.SpanContextFromContext(gotCtx))
assert.Equal(t, traces, gotTraces)
// unmarshal traces request with empty context
buf, err = MarshalTraces(context.Background(), traces)
require.NoError(t, err)
gotCtx, gotTraces, err = UnmarshalTraces(buf)
require.NoError(t, err)
assert.Equal(t, context.Background(), gotCtx)
assert.Equal(t, traces, gotTraces)
// unmarshal corrupted data
_, _, err = UnmarshalTraces(buf[:len(buf)-1])
require.ErrorContains(t, err, "failed to unmarshal traces request")
// unmarshal invalid format (bare traces)
buf, err = (&ptrace.ProtoMarshaler{}).MarshalTraces(traces)
require.NoError(t, err)
_, _, err = UnmarshalTraces(buf)
require.ErrorIs(t, err, ErrInvalidFormat)
}
================================================
FILE: pdata/xpdata/request/version_check.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package request // import "go.opentelemetry.io/collector/pdata/xpdata/request"
import (
"encoding/binary"
"errors"
)
const (
// field 1 << 3, wire type 5 (fixed32)
protoTag1TypeByte = 0x0D
// version of the request payload format set in the `format_version` field
requestFormatVersion = uint32(1)
)
var ErrInvalidFormat = errors.New("invalid request payload format")
// isRequestPayloadV1 returns true if the given payload represents one of the wrappers around standard OpenTelemetry
// data types defined in internal/request.go and has the version set to 1.
func isRequestPayloadV1(data []byte) bool {
if len(data) < 5 {
return false
}
if data[0] != protoTag1TypeByte {
return false
}
return binary.LittleEndian.Uint32(data[1:5]) == requestFormatVersion
}
================================================
FILE: pdata/xpdata/request/version_check_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package request
import (
"context"
"encoding/binary"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/ptrace"
)
func TestIsRequestPayloadV1(t *testing.T) {
// too short
assert.False(t, isRequestPayloadV1([]byte{protoTag1TypeByte, 0x00}))
buf := make([]byte, 5)
// wrong type: field 1, wire type 2 (length-delimited)
buf[0] = 0x0A
assert.False(t, isRequestPayloadV1([]byte{protoTag1TypeByte, 0x00}))
// wrong version
buf[0] = protoTag1TypeByte
binary.LittleEndian.PutUint32(buf[1:], 2)
assert.False(t, isRequestPayloadV1(buf))
binary.LittleEndian.PutUint32(buf[1:], requestFormatVersion)
assert.True(t, isRequestPayloadV1(buf))
buf, err := MarshalMetrics(context.Background(), pmetric.NewMetrics())
require.NoError(t, err)
assert.True(t, isRequestPayloadV1(buf))
buf, err = MarshalTraces(context.Background(), ptrace.NewTraces())
require.NoError(t, err)
assert.True(t, isRequestPayloadV1(buf))
buf, err = MarshalLogs(context.Background(), plog.NewLogs())
require.NoError(t, err)
assert.True(t, isRequestPayloadV1(buf))
buf, err = MarshalProfiles(context.Background(), pprofile.NewProfiles())
require.NoError(t, err)
assert.True(t, isRequestPayloadV1(buf))
}
================================================
FILE: pdata/xpdata/xpdata.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:generate mdatagen metadata.yaml
package xpdata // import "go.opentelemetry.io/collector/pdata/xpdata"
================================================
FILE: pipeline/Makefile
================================================
include ../Makefile.Common
================================================
FILE: pipeline/go.mod
================================================
module go.opentelemetry.io/collector/pipeline
go 1.25.0
require github.com/stretchr/testify v1.11.1
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
================================================
FILE: pipeline/go.sum
================================================
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: pipeline/internal/globalsignal/signal.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package globalsignal // import "go.opentelemetry.io/collector/pipeline/internal/globalsignal"
import (
"encoding"
"fmt"
)
var (
SignalProfiles = Signal{name: "profiles"}
SignalTraces = Signal{name: "traces"}
SignalMetrics = Signal{name: "metrics"}
SignalLogs = Signal{name: "logs"}
_ encoding.TextMarshaler = (*Signal)(nil)
_ encoding.TextUnmarshaler = (*Signal)(nil)
)
// Signal represents the signals supported by the collector.
type Signal struct {
name string
}
// String returns the string representation of the signal.
func (s Signal) String() string {
return s.name
}
// MarshalText marshals the Signal.
func (s Signal) MarshalText() ([]byte, error) {
return []byte(s.name), nil
}
// UnmarshalText marshals the Signal.
func (s *Signal) UnmarshalText(text []byte) error {
switch string(text) {
case SignalProfiles.name:
*s = SignalProfiles
case SignalTraces.name:
*s = SignalTraces
case SignalMetrics.name:
*s = SignalMetrics
case SignalLogs.name:
*s = SignalLogs
default:
return fmt.Errorf("unknown pipeline signal: %q", string(text))
}
return nil
}
================================================
FILE: pipeline/internal/globalsignal/signal_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package globalsignal
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSignal_String(t *testing.T) {
assert.Equal(t, "traces", SignalTraces.String())
assert.Equal(t, "metrics", SignalMetrics.String())
assert.Equal(t, "logs", SignalLogs.String())
assert.Equal(t, "profiles", SignalProfiles.String())
}
func TestSignal_MarshalText(t *testing.T) {
b, err := SignalTraces.MarshalText()
require.NoError(t, err)
assert.Equal(t, []byte("traces"), b)
b, err = SignalMetrics.MarshalText()
require.NoError(t, err)
assert.Equal(t, []byte("metrics"), b)
b, err = SignalLogs.MarshalText()
require.NoError(t, err)
assert.Equal(t, []byte("logs"), b)
b, err = SignalProfiles.MarshalText()
require.NoError(t, err)
assert.Equal(t, []byte("profiles"), b)
var s Signal
b, err = s.MarshalText()
require.NoError(t, err)
assert.Equal(t, []byte(""), b)
}
func TestSignal_UnmarshalText(t *testing.T) {
var s Signal
require.NoError(t, s.UnmarshalText([]byte("traces")))
assert.Equal(t, SignalTraces, s)
require.NoError(t, s.UnmarshalText([]byte("metrics")))
assert.Equal(t, SignalMetrics, s)
require.NoError(t, s.UnmarshalText([]byte("logs")))
assert.Equal(t, SignalLogs, s)
require.NoError(t, s.UnmarshalText([]byte("profiles")))
assert.Equal(t, SignalProfiles, s)
require.Error(t, s.UnmarshalText([]byte("unknown")))
}
================================================
FILE: pipeline/metadata.yaml
================================================
type: pipeline
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: pipeline/pipeline.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pipeline // import "go.opentelemetry.io/collector/pipeline"
import (
"errors"
"fmt"
"regexp"
"strings"
)
// typeAndNameSeparator is the separator that is used between type and name in type/name composite keys.
const typeAndNameSeparator = "/"
// ID represents the identity for a pipeline. It combines two values:
// * signal - the Signal of the pipeline.
// * name - the name of that pipeline.
type ID struct {
signal Signal `mapstructure:"-"`
name string `mapstructure:"-"`
}
// NewID returns a new ID with the given Signal and empty name.
func NewID(signal Signal) ID {
return NewIDWithName(signal, "")
}
// NewIDWithName returns a new ID with the given Signal and name.
func NewIDWithName(signal Signal, name string) ID {
return ID{signal: signal, name: name}
}
// Signal returns the Signal of the ID.
func (i ID) Signal() Signal {
return i.signal
}
// Name returns the name of the ID.
func (i ID) Name() string {
return i.name
}
// MarshalText implements the encoding.TextMarshaler interface.
// This marshals the Signal and name as one string in the config.
func (i ID) MarshalText() (text []byte, err error) {
return []byte(i.String()), nil
}
// UnmarshalText implements the encoding.TextUnmarshaler interface.
func (i *ID) UnmarshalText(text []byte) error {
idStr := string(text)
signalStr, nameStr, hasName := strings.Cut(idStr, typeAndNameSeparator)
signalStr = strings.TrimSpace(signalStr)
if signalStr == "" {
if hasName {
return fmt.Errorf("in %q id: the part before %s should not be empty", idStr, typeAndNameSeparator)
}
return errors.New("id must not be empty")
}
if hasName {
// "name" part is present.
nameStr = strings.TrimSpace(nameStr)
if nameStr == "" {
return fmt.Errorf("in %q id: the part after %s should not be empty", idStr, typeAndNameSeparator)
}
if err := validateName(nameStr); err != nil {
return fmt.Errorf("in %q id: %w", nameStr, err)
}
}
if err := i.signal.UnmarshalText([]byte(signalStr)); err != nil {
return fmt.Errorf("in %q id: %w", idStr, err)
}
i.name = nameStr
return nil
}
// String returns the ID string representation as "signal[/name]" format.
func (i ID) String() string {
if i.name == "" {
return i.signal.String()
}
return i.signal.String() + typeAndNameSeparator + i.name
}
// nameRegexp is used to validate the name of an ID. A name can consist of
// 1 to 1024 unicode characters excluding whitespace, control characters, and
// symbols.
var nameRegexp = regexp.MustCompile(`^[^\pZ\pC\pS]+$`)
func validateName(nameStr string) error {
if len(nameStr) > 1024 {
return fmt.Errorf("name %q is longer than 1024 characters (%d characters)", nameStr, len(nameStr))
}
if !nameRegexp.MatchString(nameStr) {
return fmt.Errorf("invalid character(s) in name %q", nameStr)
}
return nil
}
================================================
FILE: pipeline/pipeline_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pipeline
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pipeline/internal/globalsignal"
)
func Test_NewID(t *testing.T) {
id := NewID(SignalTraces)
assert.Equal(t, ID{signal: SignalTraces}, id)
}
func Test_NewIDWithName(t *testing.T) {
id := NewIDWithName(SignalTraces, "test")
assert.Equal(t, ID{signal: SignalTraces, name: "test"}, id)
}
func TestMarshalText(t *testing.T) {
id := NewIDWithName(SignalTraces, "name")
got, err := id.MarshalText()
require.NoError(t, err)
assert.Equal(t, id.String(), string(got))
}
func TestUnmarshalText(t *testing.T) {
testCases := []struct {
idStr string
expectedErr bool
expectedID ID
}{
{
idStr: "traces",
expectedID: ID{signal: globalsignal.SignalTraces, name: ""},
},
{
idStr: "traces/valid_name",
expectedID: ID{signal: globalsignal.SignalTraces, name: "valid_name"},
},
{
idStr: " traces / valid_name ",
expectedID: ID{signal: globalsignal.SignalTraces, name: "valid_name"},
},
{
idStr: "traces/中文好",
expectedID: ID{signal: globalsignal.SignalTraces, name: "中文好"},
},
{
idStr: "traces/name-with-dashes",
expectedID: ID{signal: globalsignal.SignalTraces, name: "name-with-dashes"},
},
// issue 10816
{
idStr: "traces/Linux-Messages-File_01J49HCH3SWFXRVASWFZFRT3J2__processor0__logs",
expectedID: ID{signal: globalsignal.SignalTraces, name: "Linux-Messages-File_01J49HCH3SWFXRVASWFZFRT3J2__processor0__logs"},
},
{
idStr: "traces/1",
expectedID: ID{signal: globalsignal.SignalTraces, name: "1"},
},
{
idStr: "/valid_name",
expectedErr: true,
},
{
idStr: " /valid_name",
expectedErr: true,
},
{
idStr: "traces/",
expectedErr: true,
},
{
idStr: "traces/ ",
expectedErr: true,
},
{
idStr: " ",
expectedErr: true,
},
{
idStr: "traces/invalid name",
expectedErr: true,
},
{
idStr: "traces/" + strings.Repeat("a", 1025),
expectedErr: true,
},
{
idStr: "INVALID",
expectedErr: true,
},
{
idStr: "INVALID/name",
expectedErr: true,
},
}
for _, test := range testCases {
t.Run(test.idStr, func(t *testing.T) {
id := ID{}
err := id.UnmarshalText([]byte(test.idStr))
if test.expectedErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, test.expectedID, id)
assert.Equal(t, test.expectedID.Signal(), id.Signal())
assert.Equal(t, test.expectedID.Name(), id.Name())
assert.Equal(t, test.expectedID.String(), id.String())
})
}
}
================================================
FILE: pipeline/signal.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pipeline // import "go.opentelemetry.io/collector/pipeline"
import (
"errors"
"go.opentelemetry.io/collector/pipeline/internal/globalsignal"
)
// Signal represents the signals supported by the collector. We currently support
// collecting metrics, traces and logs, this can expand in the future.
type Signal = globalsignal.Signal
var ErrSignalNotSupported = errors.New("telemetry type is not supported")
var (
SignalTraces = globalsignal.SignalTraces
SignalMetrics = globalsignal.SignalMetrics
SignalLogs = globalsignal.SignalLogs
)
================================================
FILE: pipeline/xpipeline/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: pipeline/xpipeline/config.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xpipeline // import "go.opentelemetry.io/collector/pipeline/xpipeline"
import "go.opentelemetry.io/collector/pipeline/internal/globalsignal"
var SignalProfiles = globalsignal.SignalProfiles
================================================
FILE: pipeline/xpipeline/go.mod
================================================
module go.opentelemetry.io/collector/pipeline/xpipeline
go 1.25.0
require go.opentelemetry.io/collector/pipeline v1.54.0
replace go.opentelemetry.io/collector/pipeline => ../
================================================
FILE: pipeline/xpipeline/go.sum
================================================
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: pipeline/xpipeline/metadata.yaml
================================================
type: xpipeline
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: processor/Makefile
================================================
include ../Makefile.Common
================================================
FILE: processor/README.md
================================================
# 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).
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)
- [Creating Custom Processor](#creating-custom-processors)
Supported processors (sorted alphabetically):
- [Batch Processor](batchprocessor/README.md)
- [Memory Limiter Processor](memorylimiterprocessor/README.md)
The [contrib repository](https://github.com/open-telemetry/opentelemetry-collector-contrib)
has more processors that can be added to a custom build of the Collector.
## Recommended Processors
By default, no processors are enabled. Depending on the data source, it may be
recommended that multiple processors be enabled. 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.
1. [memory_limiter](memorylimiterprocessor/README.md)
2. Any sampling or initial filtering processors
3. Any processor relying on sending source from `Context` (e.g. `k8sattributes`)
3. [batch](batchprocessor/README.md), although prefer using the exporter's batching capabilities
4. Any other processors
## Data Ownership
The ownership of the `pdata.Traces`, `pdata.Metrics` and `pdata.Logs` data 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 `ConsumeTraces`/`ConsumeMetrics`/`ConsumeLogs`
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 `MutatesData` field of
the struct returned by `Capabilities` 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 `ConsumeTraces`/`ConsumeMetrics`/`ConsumeLogs`
call until the processor calls the next processor's `ConsumeTraces`/`ConsumeMetrics`/`ConsumeLogs`
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 `ConsumeTraces`/`ConsumeMetrics`/`ConsumeLogs` 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
`pdata.Traces`/`pdata.Metrics`/`pdata.Logs` argument. Any approach that does not
mutate the original `pdata.Traces`/`pdata.Metrics`/`pdata.Logs` is allowed.
If the processor uses such technique it should declare that it does not intend
to modify the original data by setting `MutatesData=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.
## Creating Custom Processors
To create a custom processor for the OpenTelemetry Collector, you need to implement the processor interface, define the processor's configuration, and register it with the Collector. The process typically involves creating a factory, implementing the required processing logic, and handling configuration options. For a practical example and guidance, refer to the [`processorhelper`](https://pkg.go.dev/go.opentelemetry.io/collector/processor/processorhelper) package, which provides utilities and patterns to simplify processor development.
================================================
FILE: processor/batchprocessor/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: processor/batchprocessor/README.md
================================================
# Batch Processor
| Status | |
| ------------- |-----------|
| Stability | [beta]: traces, metrics, logs |
| Distributions | [core], [contrib], [k8s] |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Aprocessor%2Fbatch) [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aclosed+is%3Aissue+label%3Aprocessor%2Fbatch) |
[beta]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#beta
[core]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
[k8s]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-k8s
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.
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, metric data points, or log
records after which a batch will be sent regardless of the timeout. `send_batch_size`
acts as a trigger and does not affect the size of the batch. If you need to
enforce batch size limits sent to the next component in the pipeline
see `send_batch_max_size`.
- `timeout` (default = 200ms): Time duration after which a batch will
be sent regardless of size. If set to zero, `send_batch_size` is
ignored as data will be sent immediately, subject to only `send_batch_max_size`.
- `send_batch_max_size` (default = 0): The upper limit of the batch size.
`0` means no upper limit of the batch size.
This property ensures that larger batches are split into smaller units.
It must be greater than or equal to `send_batch_size`.
- `metadata_keys` (default = empty): When set, this processor will
create one batcher instance per distinct combination of values in
the `client.Metadata`.
- `metadata_cardinality_limit` (default = 1000): When `metadata_keys` is
not empty, this setting limits the number of unique combinations of
metadata key values that will be processed over the lifetime of the
process.
See notes about metadata batching below.
Examples:
This configuration contains one default batch processor and a second
with custom settings. The `batch/2` processor will buffer up to 10000
spans, metric data points, or log records for up to 10 seconds without
splitting data items to enforce a maximum batch size.
```yaml
processors:
batch:
batch/2:
send_batch_size: 10000
timeout: 10s
```
This configuration will enforce a maximum batch size limit of 10000
spans, metric data points, or log records without introducing any
artificial delays.
```yaml
processors:
batch:
send_batch_max_size: 10000
timeout: 0s
```
Refer to [config.yaml](./testdata/config.yaml) for detailed
examples on using the processor.
## Batching and client metadata
Batching by metadata enables support for multi-tenant OpenTelemetry
Collector pipelines with batching over groups of data having the same
authorization metadata. For example:
```yaml
processors:
batch:
# batch data by tenant-id
metadata_keys:
- tenant_id
# limit to 10 batcher processes before raising errors
metadata_cardinality_limit: 10
```
Receivers should be configured with `include_metadata: true` so that
metadata keys are available to the processor.
Note that each distinct combination of metadata triggers the
allocation of a new background task in the Collector that runs for the
lifetime of the process, and each background task holds one pending
batch of up to `send_batch_size` records. Batching by metadata can
therefore substantially increase the amount of memory dedicated to
batching.
The maximum number of distinct combinations is limited to the
configured `metadata_cardinality_limit`, which defaults to 1000 to
limit memory impact.
Users of the batching processor configured with metadata keys should
consider use of an Auth extension to validate the relevant
metadata-key values.
The number of batch processors currently in use is exported as the
`otelcol_processor_batch_metadata_cardinality` metric.
================================================
FILE: processor/batchprocessor/batch_processor.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package batchprocessor // import "go.opentelemetry.io/collector/processor/batchprocessor"
import (
"context"
"errors"
"fmt"
"runtime"
"sort"
"strings"
"sync"
"time"
"go.opentelemetry.io/otel/attribute"
"go.uber.org/zap"
"go.opentelemetry.io/collector/client"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/pdata/xpdata/pref"
"go.opentelemetry.io/collector/processor"
)
// errTooManyBatchers is returned when the MetadataCardinalityLimit has been reached.
var errTooManyBatchers = consumererror.NewPermanent(errors.New("too many batcher metadata-value combinations"))
// batch_processor is a component that accepts spans and metrics, places them
// into batches and sends downstream.
//
// batch_processor implements consumer.Traces and consumer.Metrics
//
// 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[T any] struct {
logger *zap.Logger
timeout time.Duration
sendBatchSize int
sendBatchMaxSize int
// batchFunc is a factory for new batch objects corresponding
// with the appropriate signal.
batchFunc func() batch[T]
shutdownC chan struct{}
goroutines sync.WaitGroup
telemetry *batchProcessorTelemetry
// batcher will be either *singletonBatcher or *multiBatcher
batcher batcher[T]
}
// batcher is describes a *singletonBatcher or *multiBatcher.
type batcher[T any] interface {
// start initializes background resources used by this batcher.
start(ctx context.Context) error
// consume incorporates a new item of data into the pending batch.
consume(ctx context.Context, data T) error
// currentMetadataCardinality returns the number of shards.
currentMetadataCardinality() int
}
// shard is a single instance of the batch logic. When metadata
// keys are in use, one of these is created per distinct combination
// of values.
type shard[T any] struct {
// processor refers to this processor, for access to common
// configuration.
processor *batchProcessor[T]
// exportCtx is a context with the metadata key-values
// corresponding with this shard set.
exportCtx context.Context
// timer informs the shard send a batch.
timer *time.Timer
// newItem is used to receive data items from producers.
newItem chan T
// batch is an in-flight data item containing one of the
// underlying data types.
batch batch[T]
}
// batch is an interface generalizing the individual signal types.
type batch[T any] interface {
// export the current batch
export(ctx context.Context, req T) error
// split returns a full request built from pending items.
split(sendBatchMaxSize int) (sentBatchSize int, req T)
// itemCount returns the size of the current batch
itemCount() int
// add item to the current batch
add(item T)
// sizeBytes counts the OTLP encoding size of the batch
sizeBytes(item T) int
}
// newBatchProcessor returns a new batch processor component.
func newBatchProcessor[T any](set processor.Settings, cfg *Config, batchFunc func() batch[T]) (*batchProcessor[T], error) {
// use lower-case, to be consistent with http/2 headers.
mks := make([]string, len(cfg.MetadataKeys))
for i, k := range cfg.MetadataKeys {
mks[i] = strings.ToLower(k)
}
sort.Strings(mks)
bp := &batchProcessor[T]{
logger: set.Logger,
sendBatchSize: int(cfg.SendBatchSize),
sendBatchMaxSize: int(cfg.SendBatchMaxSize),
timeout: cfg.Timeout,
batchFunc: batchFunc,
shutdownC: make(chan struct{}, 1),
}
if len(mks) == 0 {
bp.batcher = &singleShardBatcher[T]{
processor: bp,
single: bp.newShard(nil),
}
} else {
bp.batcher = &multiShardBatcher[T]{
metadataKeys: mks,
metadataLimit: int(cfg.MetadataCardinalityLimit),
processor: bp,
}
}
bpt, err := newBatchProcessorTelemetry(set, bp.batcher.currentMetadataCardinality)
if err != nil {
return nil, fmt.Errorf("error creating batch processor telemetry: %w", err)
}
bp.telemetry = bpt
return bp, nil
}
// newShard gets or creates a batcher corresponding with attrs.
func (bp *batchProcessor[T]) newShard(md map[string][]string) *shard[T] {
exportCtx := client.NewContext(context.Background(), client.Info{
Metadata: client.NewMetadata(md),
})
b := &shard[T]{
processor: bp,
newItem: make(chan T, runtime.NumCPU()),
exportCtx: exportCtx,
batch: bp.batchFunc(),
}
return b
}
func (bp *batchProcessor[T]) Capabilities() consumer.Capabilities {
return consumer.Capabilities{MutatesData: true}
}
// Start is invoked during service startup.
func (bp *batchProcessor[T]) Start(ctx context.Context, _ component.Host) error {
return bp.batcher.start(ctx)
}
// Shutdown is invoked during service shutdown.
func (bp *batchProcessor[T]) Shutdown(context.Context) error {
close(bp.shutdownC)
// Wait until all goroutines are done.
bp.goroutines.Wait()
return nil
}
func (b *shard[T]) start() {
b.processor.goroutines.Go(b.startLoop)
}
func (b *shard[T]) startLoop() {
// timerCh ensures we only block when there is a
// timer, since <- from a nil channel is blocking.
var timerCh <-chan time.Time
if b.processor.timeout != 0 && b.processor.sendBatchSize != 0 {
b.timer = time.NewTimer(b.processor.timeout)
timerCh = b.timer.C
}
for {
select {
case <-b.processor.shutdownC:
DONE:
for {
select {
case item := <-b.newItem:
b.processItem(item)
default:
break DONE
}
}
// This is the close of the channel
if b.batch.itemCount() > 0 {
// TODO: Set a timeout on sendTraces or
// make it cancellable using the context that Shutdown gets as a parameter
b.sendItems(triggerTimeout)
}
return
case item := <-b.newItem:
b.processItem(item)
case <-timerCh:
if b.batch.itemCount() > 0 {
b.sendItems(triggerTimeout)
}
b.resetTimer()
}
}
}
func (b *shard[T]) processItem(item T) {
b.batch.add(item)
sent := false
for b.batch.itemCount() > 0 && (!b.hasTimer() || b.batch.itemCount() >= b.processor.sendBatchSize) {
sent = true
b.sendItems(triggerBatchSize)
}
if sent {
b.stopTimer()
b.resetTimer()
}
}
func (b *shard[T]) hasTimer() bool {
return b.timer != nil
}
func (b *shard[T]) stopTimer() {
if b.hasTimer() && !b.timer.Stop() {
<-b.timer.C
}
}
func (b *shard[T]) resetTimer() {
if b.hasTimer() {
b.timer.Reset(b.processor.timeout)
}
}
func (b *shard[T]) sendItems(trigger trigger) {
sent, req := b.batch.split(b.processor.sendBatchMaxSize)
bpt := b.processor.telemetry
var bytes int
// Check if the instrument is enabled to calculate the size of the batch in bytes.
// See https://pkg.go.dev/go.opentelemetry.io/otel/sdk/metric/internal/x#readme-instrument-enabled
batchSendSizeBytes := bpt.telemetryBuilder.ProcessorBatchBatchSendSizeBytes
instr, ok := batchSendSizeBytes.(interface{ Enabled(context.Context) bool })
if !ok || instr.Enabled(bpt.exportCtx) {
bytes = b.batch.sizeBytes(req)
}
err := b.batch.export(b.exportCtx, req)
if err != nil {
b.processor.logger.Warn("Sender failed", zap.Error(err))
return
}
bpt.record(trigger, int64(sent), int64(bytes))
}
// singleShardBatcher is used when metadataKeys is empty, to avoid the
// additional lock and map operations used in multiBatcher.
type singleShardBatcher[T any] struct {
processor *batchProcessor[T]
single *shard[T]
}
func (sb *singleShardBatcher[T]) start(context.Context) error {
sb.single.start()
return nil
}
func (sb *singleShardBatcher[T]) consume(_ context.Context, data T) error {
sb.single.newItem <- data
return nil
}
func (sb *singleShardBatcher[T]) currentMetadataCardinality() int {
return 1
}
// multiShardBatcher is used when metadataKeys is not empty.
type multiShardBatcher[T any] struct {
// metadataKeys is the configured list of metadata keys. When
// empty, the `singleton` batcher is used. When non-empty,
// each distinct combination of metadata keys and values
// triggers a new batcher, counted in `goroutines`.
metadataKeys []string
// metadataLimit is the limiting size of the batchers map.
metadataLimit int
processor *batchProcessor[T]
batchers sync.Map
// Guards the size and the storing logic to ensure no more than limit items are stored.
// If we are willing to allow "some" extra items than the limit this can be removed and size can be made atomic.
lock sync.Mutex
size int
}
func (mb *multiShardBatcher[T]) start(context.Context) error {
return nil
}
func (mb *multiShardBatcher[T]) consume(ctx context.Context, data T) error {
// Get each metadata key value, form the corresponding
// attribute set for use as a map lookup key.
info := client.FromContext(ctx)
attrs := make([]attribute.KeyValue, 0, len(mb.metadataKeys))
for _, k := range mb.metadataKeys {
// Lookup the value in the incoming metadata, copy it
// into the outgoing metadata, and create a unique
// value for the attributeSet.
vs := info.Metadata.Get(k)
if len(vs) == 1 {
attrs = append(attrs, attribute.String(k, vs[0]))
} else {
attrs = append(attrs, attribute.StringSlice(k, vs))
}
}
aset := attribute.NewSet(attrs...)
b, ok := mb.batchers.Load(aset)
if !ok {
mb.lock.Lock()
if mb.metadataLimit != 0 && mb.size >= mb.metadataLimit {
mb.lock.Unlock()
return errTooManyBatchers
}
// aset.ToSlice() returns the sorted, deduplicated,
// and name-lowercased list of attributes.
var loaded bool
md := make(map[string][]string, len(mb.metadataKeys))
for _, k := range mb.metadataKeys {
md[k] = info.Metadata.Get(k)
}
b, loaded = mb.batchers.LoadOrStore(aset, mb.processor.newShard(md))
if !loaded {
// Start the goroutine only if we added the object to the map, otherwise is already started.
b.(*shard[T]).start()
mb.size++
}
mb.lock.Unlock()
}
b.(*shard[T]).newItem <- data
return nil
}
func (mb *multiShardBatcher[T]) currentMetadataCardinality() int {
mb.lock.Lock()
defer mb.lock.Unlock()
return mb.size
}
type tracesBatchProcessor struct {
*batchProcessor[ptrace.Traces]
}
// newTracesBatchProcessor creates a new batch processor that batches traces by size or with timeout
func newTracesBatchProcessor(set processor.Settings, next consumer.Traces, cfg *Config) (processor.Traces, error) {
bp, err := newBatchProcessor(set, cfg, func() batch[ptrace.Traces] { return newBatchTraces(next) })
if err != nil {
return nil, err
}
return &tracesBatchProcessor{batchProcessor: bp}, nil
}
func (t *tracesBatchProcessor) ConsumeTraces(ctx context.Context, td ptrace.Traces) error {
pref.RefTraces(td)
return t.batcher.consume(ctx, td)
}
type metricsBatchProcessor struct {
*batchProcessor[pmetric.Metrics]
}
// newMetricsBatchProcessor creates a new batch processor that batches metrics by size or with timeout
func newMetricsBatchProcessor(set processor.Settings, next consumer.Metrics, cfg *Config) (processor.Metrics, error) {
bp, err := newBatchProcessor(set, cfg, func() batch[pmetric.Metrics] { return newMetricsBatch(next) })
if err != nil {
return nil, err
}
return &metricsBatchProcessor{batchProcessor: bp}, nil
}
// ConsumeMetrics implements processor.Metrics
func (m *metricsBatchProcessor) ConsumeMetrics(ctx context.Context, md pmetric.Metrics) error {
pref.RefMetrics(md)
return m.batcher.consume(ctx, md)
}
type logsBatchProcessor struct {
*batchProcessor[plog.Logs]
}
// newLogsBatchProcessor creates a new batch processor that batches logs by size or with timeout
func newLogsBatchProcessor(set processor.Settings, next consumer.Logs, cfg *Config) (processor.Logs, error) {
bp, err := newBatchProcessor(set, cfg, func() batch[plog.Logs] { return newBatchLogs(next) })
if err != nil {
return nil, err
}
return &logsBatchProcessor{batchProcessor: bp}, nil
}
// ConsumeLogs implements processor.Logs
func (l *logsBatchProcessor) ConsumeLogs(ctx context.Context, ld plog.Logs) error {
pref.RefLogs(ld)
return l.batcher.consume(ctx, ld)
}
type batchTraces struct {
nextConsumer consumer.Traces
traceData ptrace.Traces
spanCount int
sizer ptrace.Sizer
}
func newBatchTraces(nextConsumer consumer.Traces) *batchTraces {
return &batchTraces{nextConsumer: nextConsumer, traceData: ptrace.NewTraces(), sizer: &ptrace.ProtoMarshaler{}}
}
// add updates current batchTraces by adding new TraceData object
func (bt *batchTraces) add(td ptrace.Traces) {
defer pref.UnrefTraces(td)
newSpanCount := td.SpanCount()
if newSpanCount == 0 {
return
}
bt.spanCount += newSpanCount
td.ResourceSpans().MoveAndAppendTo(bt.traceData.ResourceSpans())
}
func (bt *batchTraces) sizeBytes(td ptrace.Traces) int {
return bt.sizer.TracesSize(td)
}
func (bt *batchTraces) export(ctx context.Context, td ptrace.Traces) error {
return bt.nextConsumer.ConsumeTraces(ctx, td)
}
func (bt *batchTraces) split(sendBatchMaxSize int) (int, ptrace.Traces) {
var td ptrace.Traces
var sent int
if sendBatchMaxSize > 0 && bt.itemCount() > sendBatchMaxSize {
td = splitTraces(sendBatchMaxSize, bt.traceData)
bt.spanCount -= sendBatchMaxSize
sent = sendBatchMaxSize
} else {
td = bt.traceData
sent = bt.spanCount
bt.traceData = ptrace.NewTraces()
bt.spanCount = 0
}
return sent, td
}
func (bt *batchTraces) itemCount() int {
return bt.spanCount
}
type batchMetrics struct {
nextConsumer consumer.Metrics
metricData pmetric.Metrics
dataPointCount int
sizer pmetric.Sizer
}
func newMetricsBatch(nextConsumer consumer.Metrics) *batchMetrics {
return &batchMetrics{nextConsumer: nextConsumer, metricData: pmetric.NewMetrics(), sizer: &pmetric.ProtoMarshaler{}}
}
func (bm *batchMetrics) sizeBytes(md pmetric.Metrics) int {
return bm.sizer.MetricsSize(md)
}
func (bm *batchMetrics) export(ctx context.Context, md pmetric.Metrics) error {
return bm.nextConsumer.ConsumeMetrics(ctx, md)
}
func (bm *batchMetrics) split(sendBatchMaxSize int) (int, pmetric.Metrics) {
var md pmetric.Metrics
var sent int
if sendBatchMaxSize > 0 && bm.dataPointCount > sendBatchMaxSize {
md = splitMetrics(sendBatchMaxSize, bm.metricData)
bm.dataPointCount -= sendBatchMaxSize
sent = sendBatchMaxSize
} else {
md = bm.metricData
sent = bm.dataPointCount
bm.metricData = pmetric.NewMetrics()
bm.dataPointCount = 0
}
return sent, md
}
func (bm *batchMetrics) itemCount() int {
return bm.dataPointCount
}
func (bm *batchMetrics) add(md pmetric.Metrics) {
defer pref.UnrefMetrics(md)
newDataPointCount := md.DataPointCount()
if newDataPointCount == 0 {
return
}
bm.dataPointCount += newDataPointCount
md.ResourceMetrics().MoveAndAppendTo(bm.metricData.ResourceMetrics())
}
type batchLogs struct {
nextConsumer consumer.Logs
logData plog.Logs
logCount int
sizer plog.Sizer
}
func newBatchLogs(nextConsumer consumer.Logs) *batchLogs {
return &batchLogs{nextConsumer: nextConsumer, logData: plog.NewLogs(), sizer: &plog.ProtoMarshaler{}}
}
func (bl *batchLogs) sizeBytes(ld plog.Logs) int {
return bl.sizer.LogsSize(ld)
}
func (bl *batchLogs) export(ctx context.Context, ld plog.Logs) error {
return bl.nextConsumer.ConsumeLogs(ctx, ld)
}
func (bl *batchLogs) split(sendBatchMaxSize int) (int, plog.Logs) {
var ld plog.Logs
var sent int
if sendBatchMaxSize > 0 && bl.logCount > sendBatchMaxSize {
ld = splitLogs(sendBatchMaxSize, bl.logData)
bl.logCount -= sendBatchMaxSize
sent = sendBatchMaxSize
} else {
ld = bl.logData
sent = bl.logCount
bl.logData = plog.NewLogs()
bl.logCount = 0
}
return sent, ld
}
func (bl *batchLogs) itemCount() int {
return bl.logCount
}
func (bl *batchLogs) add(ld plog.Logs) {
defer pref.UnrefLogs(ld)
newLogsCount := ld.LogRecordCount()
if newLogsCount == 0 {
return
}
bl.logCount += newLogsCount
ld.ResourceLogs().MoveAndAppendTo(bl.logData.ResourceLogs())
}
================================================
FILE: processor/batchprocessor/batch_processor_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package batchprocessor
import (
"context"
"fmt"
"math"
"strconv"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"go.opentelemetry.io/collector/client"
"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/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/pdata/testdata"
"go.opentelemetry.io/collector/processor/batchprocessor/internal/metadata"
"go.opentelemetry.io/collector/processor/batchprocessor/internal/metadatatest"
"go.opentelemetry.io/collector/processor/processortest"
)
func TestProcessorShutdown(t *testing.T) {
factory := NewFactory()
ctx := context.Background()
processorCreationSet := processortest.NewNopSettings(metadata.Type)
for range 5 {
require.NotPanics(t, func() {
tProc, err := factory.CreateTraces(ctx, processorCreationSet, factory.CreateDefaultConfig(), consumertest.NewNop())
require.NoError(t, err)
_ = tProc.Shutdown(ctx)
})
require.NotPanics(t, func() {
mProc, err := factory.CreateMetrics(ctx, processorCreationSet, factory.CreateDefaultConfig(), consumertest.NewNop())
require.NoError(t, err)
_ = mProc.Shutdown(ctx)
})
require.NotPanics(t, func() {
lProc, err := factory.CreateLogs(ctx, processorCreationSet, factory.CreateDefaultConfig(), consumertest.NewNop())
require.NoError(t, err)
_ = lProc.Shutdown(ctx)
})
}
}
func TestProcessorLifecycle(t *testing.T) {
factory := NewFactory()
ctx := context.Background()
processorCreationSet := processortest.NewNopSettings(metadata.Type)
for range 5 {
tProc, err := factory.CreateTraces(ctx, processorCreationSet, factory.CreateDefaultConfig(), consumertest.NewNop())
require.NoError(t, err)
require.NoError(t, tProc.Start(ctx, componenttest.NewNopHost()))
require.NoError(t, tProc.Shutdown(ctx))
mProc, err := factory.CreateMetrics(ctx, processorCreationSet, factory.CreateDefaultConfig(), consumertest.NewNop())
require.NoError(t, err)
require.NoError(t, mProc.Start(ctx, componenttest.NewNopHost()))
require.NoError(t, mProc.Shutdown(ctx))
lProc, err := factory.CreateLogs(ctx, processorCreationSet, factory.CreateDefaultConfig(), consumertest.NewNop())
require.NoError(t, err)
require.NoError(t, lProc.Start(ctx, componenttest.NewNopHost()))
require.NoError(t, lProc.Shutdown(ctx))
}
}
func TestBatchProcessorSpansDelivered(t *testing.T) {
sink := new(consumertest.TracesSink)
cfg := createDefaultConfig().(*Config)
cfg.SendBatchSize = 128
traces, err := NewFactory().CreateTraces(context.Background(), processortest.NewNopSettings(metadata.Type), cfg, sink)
require.NoError(t, err)
require.NoError(t, traces.Start(context.Background(), componenttest.NewNopHost()))
requestCount := 1000
spansPerRequest := 100
sentResourceSpans := ptrace.NewTraces().ResourceSpans()
for requestNum := range requestCount {
td := testdata.GenerateTraces(spansPerRequest)
spans := td.ResourceSpans().At(0).ScopeSpans().At(0).Spans()
for spanIndex := range spansPerRequest {
spans.At(spanIndex).SetName(getTestSpanName(requestNum, spanIndex))
}
td.ResourceSpans().At(0).CopyTo(sentResourceSpans.AppendEmpty())
require.NoError(t, traces.ConsumeTraces(context.Background(), td))
}
// Added to test logic that check for empty resources.
td := ptrace.NewTraces()
assert.NoError(t, traces.ConsumeTraces(context.Background(), td))
require.NoError(t, traces.Shutdown(context.Background()))
require.Equal(t, requestCount*spansPerRequest, sink.SpanCount())
receivedTraces := sink.AllTraces()
spansReceivedByName := spansReceivedByName(receivedTraces)
for requestNum := range requestCount {
spans := sentResourceSpans.At(requestNum).ScopeSpans().At(0).Spans()
for spanIndex := range spansPerRequest {
require.Equal(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 = 130
traces, err := NewFactory().CreateTraces(context.Background(), processortest.NewNopSettings(metadata.Type), cfg, sink)
require.NoError(t, err)
require.NoError(t, traces.Start(context.Background(), componenttest.NewNopHost()))
requestCount := 1000
spansPerRequest := 150
for requestNum := range requestCount {
td := testdata.GenerateTraces(spansPerRequest)
spans := td.ResourceSpans().At(0).ScopeSpans().At(0).Spans()
for spanIndex := range spansPerRequest {
spans.At(spanIndex).SetName(getTestSpanName(requestNum, spanIndex))
}
require.NoError(t, traces.ConsumeTraces(context.Background(), td))
}
// Added to test logic that check for empty resources.
td := ptrace.NewTraces()
require.NoError(t, traces.ConsumeTraces(context.Background(), td))
// wait for all spans to be reported
for sink.SpanCount() != requestCount*spansPerRequest {
<-time.After(cfg.Timeout)
}
require.NoError(t, traces.Shutdown(context.Background()))
require.Equal(t, requestCount*spansPerRequest, sink.SpanCount())
for i := 0; i < len(sink.AllTraces())-1; i++ {
assert.Equal(t, int(cfg.SendBatchMaxSize), sink.AllTraces()[i].SpanCount())
}
// the last batch has the remaining size
assert.Equal(t, (requestCount*spansPerRequest)%int(cfg.SendBatchMaxSize), sink.AllTraces()[len(sink.AllTraces())-1].SpanCount())
}
func TestBatchProcessorSentBySize(t *testing.T) {
const (
sendBatchSize = 20
requestCount = 100
spansPerRequest = 5
expectedBatchesNum = requestCount * spansPerRequest / sendBatchSize
expectedBatchingFactor = sendBatchSize / spansPerRequest
)
tel := componenttest.NewTelemetry()
sizer := &ptrace.ProtoMarshaler{}
sink := new(consumertest.TracesSink)
cfg := createDefaultConfig().(*Config)
cfg.SendBatchSize = sendBatchSize
cfg.Timeout = 500 * time.Millisecond
traces, err := NewFactory().CreateTraces(context.Background(), metadatatest.NewSettings(tel), cfg, sink)
require.NoError(t, err)
require.NoError(t, traces.Start(context.Background(), componenttest.NewNopHost()))
start := time.Now()
sizeSum := 0
for range requestCount {
td := testdata.GenerateTraces(spansPerRequest)
require.NoError(t, traces.ConsumeTraces(context.Background(), td))
}
require.NoError(t, traces.Shutdown(context.Background()))
elapsed := time.Since(start)
require.LessOrEqual(t, elapsed.Nanoseconds(), cfg.Timeout.Nanoseconds())
require.Equal(t, requestCount*spansPerRequest, sink.SpanCount())
receivedTraces := sink.AllTraces()
require.Len(t, receivedTraces, expectedBatchesNum)
for _, td := range receivedTraces {
sizeSum += sizer.TracesSize(td)
rss := td.ResourceSpans()
require.Equal(t, expectedBatchingFactor, rss.Len())
for i := range expectedBatchingFactor {
require.Equal(t, spansPerRequest, rss.At(i).ScopeSpans().At(0).Spans().Len())
}
}
metadatatest.AssertEqualProcessorBatchBatchSendSizeBytes(t, tel,
[]metricdata.HistogramDataPoint[int64]{
{
Attributes: attribute.NewSet(attribute.String("processor", "batch")),
Count: uint64(expectedBatchesNum),
Bounds: []float64{
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_000, 900_000,
1000_000, 2000_000, 3000_000, 4000_000, 5000_000, 6000_000, 7000_000, 8000_000, 9000_000,
},
BucketCounts: []uint64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, uint64(expectedBatchesNum), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
Sum: int64(sizeSum),
Min: metricdata.NewExtrema(int64(sizeSum / expectedBatchesNum)),
Max: metricdata.NewExtrema(int64(sizeSum / expectedBatchesNum)),
},
}, metricdatatest.IgnoreTimestamp())
metadatatest.AssertEqualProcessorBatchBatchSendSize(t, tel,
[]metricdata.HistogramDataPoint[int64]{
{
Attributes: attribute.NewSet(attribute.String("processor", "batch")),
Count: uint64(expectedBatchesNum),
Bounds: []float64{10, 25, 50, 75, 100, 250, 500, 750, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000, 20000, 30000, 50000, 100000},
BucketCounts: []uint64{0, uint64(expectedBatchesNum), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
Sum: int64(sink.SpanCount()),
Min: metricdata.NewExtrema(int64(sendBatchSize)),
Max: metricdata.NewExtrema(int64(sendBatchSize)),
},
}, metricdatatest.IgnoreTimestamp())
metadatatest.AssertEqualProcessorBatchBatchSizeTriggerSend(t, tel,
[]metricdata.DataPoint[int64]{
{
Value: int64(expectedBatchesNum),
Attributes: attribute.NewSet(attribute.String("processor", "batch")),
},
}, metricdatatest.IgnoreTimestamp())
metadatatest.AssertEqualProcessorBatchMetadataCardinality(t, tel,
[]metricdata.DataPoint[int64]{
{
Value: 1,
Attributes: attribute.NewSet(attribute.String("processor", "batch")),
},
}, metricdatatest.IgnoreTimestamp())
require.NoError(t, tel.Shutdown(context.Background()))
}
func TestBatchProcessorSentBySizeWithMaxSize(t *testing.T) {
const (
sendBatchSize = 20
sendBatchMaxSize = 37
requestCount = 1
spansPerRequest = 500
totalSpans = requestCount * spansPerRequest
)
tel := componenttest.NewTelemetry()
sizer := &ptrace.ProtoMarshaler{}
sink := new(consumertest.TracesSink)
cfg := createDefaultConfig().(*Config)
cfg.SendBatchSize = uint32(sendBatchSize)
cfg.SendBatchMaxSize = uint32(sendBatchMaxSize)
cfg.Timeout = 500 * time.Millisecond
traces, err := NewFactory().CreateTraces(context.Background(), metadatatest.NewSettings(tel), cfg, sink)
require.NoError(t, err)
require.NoError(t, traces.Start(context.Background(), componenttest.NewNopHost()))
start := time.Now()
sizeSum := 0
for range requestCount {
td := testdata.GenerateTraces(spansPerRequest)
require.NoError(t, traces.ConsumeTraces(context.Background(), td))
}
require.NoError(t, traces.Shutdown(context.Background()))
elapsed := time.Since(start)
require.LessOrEqual(t, elapsed.Nanoseconds(), cfg.Timeout.Nanoseconds())
// The max batch size is not a divisor of the total number of spans
expectedBatchesNum := math.Ceil(float64(totalSpans) / float64(sendBatchMaxSize))
require.Equal(t, totalSpans, sink.SpanCount())
receivedTraces := sink.AllTraces()
require.Len(t, receivedTraces, int(expectedBatchesNum))
// we have to count the size after it was processed since splitTraces will cause some
// repeated ResourceSpan data to be sent through the processor
minSize := math.MaxInt
maxSize := math.MinInt
for _, td := range receivedTraces {
minSize = min(minSize, sizer.TracesSize(td))
maxSize = max(maxSize, sizer.TracesSize(td))
sizeSum += sizer.TracesSize(td)
}
metadatatest.AssertEqualProcessorBatchBatchSendSizeBytes(t, tel,
[]metricdata.HistogramDataPoint[int64]{
{
Attributes: attribute.NewSet(attribute.String("processor", "batch")),
Count: uint64(expectedBatchesNum),
Bounds: []float64{
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_000, 900_000,
1000_000, 2000_000, 3000_000, 4000_000, 5000_000, 6000_000, 7000_000, 8000_000, 9000_000,
},
BucketCounts: []uint64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, uint64(expectedBatchesNum - 1), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
Sum: int64(sizeSum),
Min: metricdata.NewExtrema(int64(minSize)),
Max: metricdata.NewExtrema(int64(maxSize)),
},
}, metricdatatest.IgnoreTimestamp())
metadatatest.AssertEqualProcessorBatchBatchSendSize(t, tel,
[]metricdata.HistogramDataPoint[int64]{
{
Attributes: attribute.NewSet(attribute.String("processor", "batch")),
Count: uint64(expectedBatchesNum),
Bounds: []float64{10, 25, 50, 75, 100, 250, 500, 750, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000, 20000, 30000, 50000, 100000},
BucketCounts: []uint64{0, 1, uint64(expectedBatchesNum - 1), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
Sum: int64(sink.SpanCount()),
Min: metricdata.NewExtrema(int64(sendBatchSize - 1)),
Max: metricdata.NewExtrema(int64(cfg.SendBatchMaxSize)),
},
}, metricdatatest.IgnoreTimestamp())
metadatatest.AssertEqualProcessorBatchBatchSizeTriggerSend(t, tel,
[]metricdata.DataPoint[int64]{
{
Value: int64(expectedBatchesNum - 1),
Attributes: attribute.NewSet(attribute.String("processor", "batch")),
},
}, metricdatatest.IgnoreTimestamp())
metadatatest.AssertEqualProcessorBatchMetadataCardinality(t, tel,
[]metricdata.DataPoint[int64]{
{
Value: 1,
Attributes: attribute.NewSet(attribute.String("processor", "batch")),
},
}, metricdatatest.IgnoreTimestamp())
require.NoError(t, tel.Shutdown(context.Background()))
}
func TestBatchProcessorSentByTimeout(t *testing.T) {
sink := new(consumertest.TracesSink)
cfg := createDefaultConfig().(*Config)
sendBatchSize := 100
cfg.SendBatchSize = uint32(sendBatchSize)
cfg.Timeout = 100 * time.Millisecond
requestCount := 5
spansPerRequest := 10
start := time.Now()
traces, err := NewFactory().CreateTraces(context.Background(), processortest.NewNopSettings(metadata.Type), cfg, sink)
require.NoError(t, err)
require.NoError(t, traces.Start(context.Background(), componenttest.NewNopHost()))
for range requestCount {
td := testdata.GenerateTraces(spansPerRequest)
require.NoError(t, traces.ConsumeTraces(context.Background(), td))
}
// Wait for at least one batch to be sent.
for sink.SpanCount() == 0 {
<-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, traces.Shutdown(context.Background()))
expectedBatchesNum := 1
expectedBatchingFactor := 5
require.Equal(t, requestCount*spansPerRequest, sink.SpanCount())
receivedTraces := sink.AllTraces()
require.Len(t, receivedTraces, expectedBatchesNum)
for _, td := range receivedTraces {
rss := td.ResourceSpans()
require.Equal(t, expectedBatchingFactor, rss.Len())
for i := range expectedBatchingFactor {
require.Equal(t, spansPerRequest, rss.At(i).ScopeSpans().At(0).Spans().Len())
}
}
}
func TestBatchProcessorTraceSendWhenClosing(t *testing.T) {
cfg := &Config{
Timeout: 3 * time.Second,
SendBatchSize: 1000,
}
sink := new(consumertest.TracesSink)
traces, err := NewFactory().CreateTraces(context.Background(), processortest.NewNopSettings(metadata.Type), cfg, sink)
require.NoError(t, err)
require.NoError(t, traces.Start(context.Background(), componenttest.NewNopHost()))
requestCount := 10
spansPerRequest := 10
for range requestCount {
td := testdata.GenerateTraces(spansPerRequest)
require.NoError(t, traces.ConsumeTraces(context.Background(), td))
}
require.NoError(t, traces.Shutdown(context.Background()))
require.Equal(t, requestCount*spansPerRequest, sink.SpanCount())
require.Len(t, sink.AllTraces(), 1)
}
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)
metrics, err := NewFactory().CreateMetrics(context.Background(), processortest.NewNopSettings(metadata.Type), cfg, sink)
require.NoError(t, err)
require.NoError(t, metrics.Start(context.Background(), componenttest.NewNopHost()))
sentResourceMetrics := pmetric.NewMetrics().ResourceMetrics()
for requestNum := range requestCount {
md := testdata.GenerateMetrics(metricsPerRequest)
ms := md.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics()
for metricIndex := range metricsPerRequest {
ms.At(metricIndex).SetName(getTestMetricName(requestNum, metricIndex))
}
md.ResourceMetrics().At(0).CopyTo(sentResourceMetrics.AppendEmpty())
require.NoError(t, metrics.ConsumeMetrics(context.Background(), md))
}
// Added to test case with empty resources sent.
md := pmetric.NewMetrics()
assert.NoError(t, metrics.ConsumeMetrics(context.Background(), md))
require.NoError(t, metrics.Shutdown(context.Background()))
require.Equal(t, requestCount*2*metricsPerRequest, sink.DataPointCount())
receivedMds := sink.AllMetrics()
metricsReceivedByName := metricsReceivedByName(receivedMds)
for requestNum := range requestCount {
ms := sentResourceMetrics.At(requestNum).ScopeMetrics().At(0).Metrics()
for metricIndex := range metricsPerRequest {
require.Equal(t,
ms.At(metricIndex),
metricsReceivedByName[getTestMetricName(requestNum, metricIndex)])
}
}
}
func TestBatchMetricProcessorBatchSize(t *testing.T) {
tel := componenttest.NewTelemetry()
sizer := &pmetric.ProtoMarshaler{}
// Instantiate the batch processor with low config values to test data
// gets sent through the processor.
cfg := &Config{
Timeout: 100 * time.Millisecond,
SendBatchSize: 50,
}
const (
requestCount = 100
metricsPerRequest = 5
dataPointsPerMetric = 2 // Since the int counter uses two datapoints.
dataPointsPerRequest = metricsPerRequest * dataPointsPerMetric
)
sink := new(consumertest.MetricsSink)
metrics, err := NewFactory().CreateMetrics(context.Background(), metadatatest.NewSettings(tel), cfg, sink)
require.NoError(t, err)
require.NoError(t, metrics.Start(context.Background(), componenttest.NewNopHost()))
start := time.Now()
size := 0
for range requestCount {
md := testdata.GenerateMetrics(metricsPerRequest)
size += sizer.MetricsSize(md)
require.NoError(t, metrics.ConsumeMetrics(context.Background(), md))
}
require.NoError(t, metrics.Shutdown(context.Background()))
elapsed := time.Since(start)
require.LessOrEqual(t, elapsed.Nanoseconds(), cfg.Timeout.Nanoseconds())
expectedBatchesNum := requestCount * dataPointsPerRequest / cfg.SendBatchSize
expectedBatchingFactor := int(cfg.SendBatchSize) / dataPointsPerRequest
require.Equal(t, requestCount*2*metricsPerRequest, sink.DataPointCount())
receivedMds := sink.AllMetrics()
require.Len(t, receivedMds, int(expectedBatchesNum))
for _, md := range receivedMds {
require.Equal(t, expectedBatchingFactor, md.ResourceMetrics().Len())
for i := range expectedBatchingFactor {
require.Equal(t, metricsPerRequest, md.ResourceMetrics().At(i).ScopeMetrics().At(0).Metrics().Len())
}
}
metadatatest.AssertEqualProcessorBatchBatchSendSizeBytes(t, tel,
[]metricdata.HistogramDataPoint[int64]{
{
Attributes: attribute.NewSet(attribute.String("processor", "batch")),
Count: uint64(expectedBatchesNum),
Bounds: []float64{
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_000, 900_000,
1000_000, 2000_000, 3000_000, 4000_000, 5000_000, 6000_000, 7000_000, 8000_000, 9000_000,
},
BucketCounts: []uint64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, uint64(expectedBatchesNum), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
Sum: int64(size),
Min: metricdata.NewExtrema(int64(size / int(expectedBatchesNum))),
Max: metricdata.NewExtrema(int64(size / int(expectedBatchesNum))),
},
}, metricdatatest.IgnoreTimestamp())
metadatatest.AssertEqualProcessorBatchBatchSendSize(t, tel,
[]metricdata.HistogramDataPoint[int64]{
{
Attributes: attribute.NewSet(attribute.String("processor", "batch")),
Count: uint64(expectedBatchesNum),
Bounds: []float64{10, 25, 50, 75, 100, 250, 500, 750, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000, 20000, 30000, 50000, 100000},
BucketCounts: []uint64{0, 0, uint64(expectedBatchesNum), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
Sum: int64(sink.DataPointCount()),
Min: metricdata.NewExtrema(int64(cfg.SendBatchSize)),
Max: metricdata.NewExtrema(int64(cfg.SendBatchSize)),
},
}, metricdatatest.IgnoreTimestamp())
metadatatest.AssertEqualProcessorBatchBatchSizeTriggerSend(t, tel,
[]metricdata.DataPoint[int64]{
{
Value: int64(expectedBatchesNum),
Attributes: attribute.NewSet(attribute.String("processor", "batch")),
},
}, metricdatatest.IgnoreTimestamp())
metadatatest.AssertEqualProcessorBatchMetadataCardinality(t, tel,
[]metricdata.DataPoint[int64]{
{
Value: 1,
Attributes: attribute.NewSet(attribute.String("processor", "batch")),
},
}, metricdatatest.IgnoreTimestamp())
require.NoError(t, tel.Shutdown(context.Background()))
}
func TestBatchMetrics_UnevenBatchMaxSize(t *testing.T) {
ctx := context.Background()
sink := new(metricsSink)
metricsCount := 50
dataPointsPerMetric := 2
sendBatchMaxSize := 99
batchMetrics := newMetricsBatch(sink)
md := testdata.GenerateMetrics(metricsCount)
batchMetrics.add(md)
require.Equal(t, dataPointsPerMetric*metricsCount, batchMetrics.dataPointCount)
sent, req := batchMetrics.split(sendBatchMaxSize)
sendErr := batchMetrics.export(ctx, req)
require.NoError(t, sendErr)
require.Equal(t, sendBatchMaxSize, sent)
remainingDataPointCount := metricsCount*dataPointsPerMetric - sendBatchMaxSize
require.Equal(t, remainingDataPointCount, batchMetrics.dataPointCount)
}
func TestBatchMetricsProcessor_Timeout(t *testing.T) {
cfg := &Config{
Timeout: 100 * time.Millisecond,
SendBatchSize: 101,
}
requestCount := 5
metricsPerRequest := 10
sink := new(consumertest.MetricsSink)
metrics, err := NewFactory().CreateMetrics(context.Background(), processortest.NewNopSettings(metadata.Type), cfg, sink)
require.NoError(t, err)
require.NoError(t, metrics.Start(context.Background(), componenttest.NewNopHost()))
start := time.Now()
for range requestCount {
md := testdata.GenerateMetrics(metricsPerRequest)
require.NoError(t, metrics.ConsumeMetrics(context.Background(), md))
}
// Wait for at least one batch to be sent.
for sink.DataPointCount() == 0 {
<-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, metrics.Shutdown(context.Background()))
expectedBatchesNum := 1
expectedBatchingFactor := 5
require.Equal(t, requestCount*2*metricsPerRequest, sink.DataPointCount())
receivedMds := sink.AllMetrics()
require.Len(t, receivedMds, expectedBatchesNum)
for _, md := range receivedMds {
require.Equal(t, expectedBatchingFactor, md.ResourceMetrics().Len())
for i := range expectedBatchingFactor {
require.Equal(t, metricsPerRequest, md.ResourceMetrics().At(i).ScopeMetrics().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)
metrics, err := NewFactory().CreateMetrics(context.Background(), processortest.NewNopSettings(metadata.Type), cfg, sink)
require.NoError(t, err)
require.NoError(t, metrics.Start(context.Background(), componenttest.NewNopHost()))
for range requestCount {
md := testdata.GenerateMetrics(metricsPerRequest)
require.NoError(t, metrics.ConsumeMetrics(context.Background(), md))
}
require.NoError(t, metrics.Shutdown(context.Background()))
require.Equal(t, requestCount*2*metricsPerRequest, sink.DataPointCount())
require.Len(t, sink.AllMetrics(), 1)
}
func getTestSpanName(requestNum, index int) string {
return fmt.Sprintf("test-span-%d-%d", requestNum, index)
}
func spansReceivedByName(tds []ptrace.Traces) map[string]ptrace.Span {
spansReceivedByName := map[string]ptrace.Span{}
for i := range tds {
rss := tds[i].ResourceSpans()
for i := 0; i < rss.Len(); i++ {
ilss := rss.At(i).ScopeSpans()
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 []pmetric.Metrics) map[string]pmetric.Metric {
metricsReceivedByName := map[string]pmetric.Metric{}
for _, md := range mds {
rms := md.ResourceMetrics()
for i := 0; i < rms.Len(); i++ {
ilms := rms.At(i).ScopeMetrics()
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) {
sizer := &ptrace.ProtoMarshaler{}
td := testdata.GenerateTraces(8192)
for b.Loop() {
fmt.Println(sizer.TracesSize(td))
}
}
func BenchmarkTraceSizeSpanCount(b *testing.B) {
td := testdata.GenerateTraces(8192)
for b.Loop() {
td.SpanCount()
}
}
func BenchmarkBatchMetricProcessor2k(b *testing.B) {
b.StopTimer()
cfg := &Config{
Timeout: 100 * time.Millisecond,
SendBatchSize: 2000,
}
runMetricsProcessorBenchmark(b, cfg)
}
func BenchmarkMultiBatchMetricProcessor2k(b *testing.B) {
b.StopTimer()
cfg := &Config{
Timeout: 100 * time.Millisecond,
SendBatchSize: 2000,
MetadataKeys: []string{"test", "test2"},
}
runMetricsProcessorBenchmark(b, cfg)
}
func runMetricsProcessorBenchmark(b *testing.B, cfg *Config) {
ctx := context.Background()
sink := new(metricsSink)
metrics, err := NewFactory().CreateMetrics(context.Background(), processortest.NewNopSettings(metadata.Type), cfg, sink)
require.NoError(b, err)
require.NoError(b, metrics.Start(ctx, componenttest.NewNopHost()))
const metricsPerRequest = 150_000
b.StartTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
require.NoError(b, metrics.ConsumeMetrics(ctx, testdata.GenerateMetrics(metricsPerRequest)))
}
})
b.StopTimer()
require.NoError(b, metrics.Shutdown(ctx))
require.Equal(b, b.N*metricsPerRequest, sink.metricsCount)
}
type metricsSink struct {
mu sync.Mutex
metricsCount int
}
func (sme *metricsSink) Capabilities() consumer.Capabilities {
return consumer.Capabilities{
MutatesData: false,
}
}
func (sme *metricsSink) ConsumeMetrics(_ context.Context, md pmetric.Metrics) error {
sme.mu.Lock()
defer sme.mu.Unlock()
sme.metricsCount += md.MetricCount()
return nil
}
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)
logs, err := NewFactory().CreateLogs(context.Background(), processortest.NewNopSettings(metadata.Type), cfg, sink)
require.NoError(t, err)
require.NoError(t, logs.Start(context.Background(), componenttest.NewNopHost()))
sentResourceLogs := plog.NewLogs().ResourceLogs()
for requestNum := range requestCount {
ld := testdata.GenerateLogs(logsPerRequest)
lrs := ld.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords()
for logIndex := range logsPerRequest {
lrs.At(logIndex).SetSeverityText(getTestLogSeverityText(requestNum, logIndex))
}
ld.ResourceLogs().At(0).CopyTo(sentResourceLogs.AppendEmpty())
require.NoError(t, logs.ConsumeLogs(context.Background(), ld))
}
// Added to test case with empty resources sent.
ld := plog.NewLogs()
assert.NoError(t, logs.ConsumeLogs(context.Background(), ld))
require.NoError(t, logs.Shutdown(context.Background()))
require.Equal(t, requestCount*logsPerRequest, sink.LogRecordCount())
receivedMds := sink.AllLogs()
logsReceivedBySeverityText := logsReceivedBySeverityText(receivedMds)
for requestNum := range requestCount {
lrs := sentResourceLogs.At(requestNum).ScopeLogs().At(0).LogRecords()
for logIndex := range logsPerRequest {
require.Equal(t,
lrs.At(logIndex),
logsReceivedBySeverityText[getTestLogSeverityText(requestNum, logIndex)])
}
}
}
func TestBatchLogProcessor_BatchSize(t *testing.T) {
tel := componenttest.NewTelemetry()
sizer := &plog.ProtoMarshaler{}
// Instantiate the batch processor with low config values to test data
// gets sent through the processor.
cfg := &Config{
Timeout: 100 * time.Millisecond,
SendBatchSize: 50,
}
const (
requestCount = 100
logsPerRequest = 5
)
sink := new(consumertest.LogsSink)
logs, err := NewFactory().CreateLogs(context.Background(), metadatatest.NewSettings(tel), cfg, sink)
require.NoError(t, err)
require.NoError(t, logs.Start(context.Background(), componenttest.NewNopHost()))
start := time.Now()
size := 0
for range requestCount {
ld := testdata.GenerateLogs(logsPerRequest)
size += sizer.LogsSize(ld)
require.NoError(t, logs.ConsumeLogs(context.Background(), ld))
}
require.NoError(t, logs.Shutdown(context.Background()))
elapsed := time.Since(start)
require.LessOrEqual(t, elapsed.Nanoseconds(), cfg.Timeout.Nanoseconds())
expectedBatchesNum := requestCount * logsPerRequest / cfg.SendBatchSize
expectedBatchingFactor := int(cfg.SendBatchSize) / logsPerRequest
require.Equal(t, requestCount*logsPerRequest, sink.LogRecordCount())
receivedMds := sink.AllLogs()
require.Len(t, receivedMds, int(expectedBatchesNum))
for _, ld := range receivedMds {
require.Equal(t, expectedBatchingFactor, ld.ResourceLogs().Len())
for i := range expectedBatchingFactor {
require.Equal(t, logsPerRequest, ld.ResourceLogs().At(i).ScopeLogs().At(0).LogRecords().Len())
}
}
metadatatest.AssertEqualProcessorBatchBatchSendSizeBytes(t, tel,
[]metricdata.HistogramDataPoint[int64]{
{
Attributes: attribute.NewSet(attribute.String("processor", "batch")),
Count: uint64(expectedBatchesNum),
Bounds: []float64{
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_000, 900_000,
1000_000, 2000_000, 3000_000, 4000_000, 5000_000, 6000_000, 7000_000, 8000_000, 9000_000,
},
BucketCounts: []uint64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, uint64(expectedBatchesNum), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
Sum: int64(size),
Min: metricdata.NewExtrema(int64(size / int(expectedBatchesNum))),
Max: metricdata.NewExtrema(int64(size / int(expectedBatchesNum))),
},
}, metricdatatest.IgnoreTimestamp())
metadatatest.AssertEqualProcessorBatchBatchSendSize(t, tel,
[]metricdata.HistogramDataPoint[int64]{
{
Attributes: attribute.NewSet(attribute.String("processor", "batch")),
Count: uint64(expectedBatchesNum),
Bounds: []float64{10, 25, 50, 75, 100, 250, 500, 750, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000, 20000, 30000, 50000, 100000},
BucketCounts: []uint64{0, 0, uint64(expectedBatchesNum), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
Sum: int64(sink.LogRecordCount()),
Min: metricdata.NewExtrema(int64(cfg.SendBatchSize)),
Max: metricdata.NewExtrema(int64(cfg.SendBatchSize)),
},
}, metricdatatest.IgnoreTimestamp())
metadatatest.AssertEqualProcessorBatchBatchSizeTriggerSend(t, tel,
[]metricdata.DataPoint[int64]{
{
Value: int64(expectedBatchesNum),
Attributes: attribute.NewSet(attribute.String("processor", "batch")),
},
}, metricdatatest.IgnoreTimestamp())
metadatatest.AssertEqualProcessorBatchMetadataCardinality(t, tel,
[]metricdata.DataPoint[int64]{
{
Value: 1,
Attributes: attribute.NewSet(attribute.String("processor", "batch")),
},
}, metricdatatest.IgnoreTimestamp())
require.NoError(t, tel.Shutdown(context.Background()))
}
func TestBatchLogsProcessor_Timeout(t *testing.T) {
cfg := &Config{
Timeout: 100 * time.Millisecond,
SendBatchSize: 100,
}
requestCount := 5
logsPerRequest := 10
sink := new(consumertest.LogsSink)
logs, err := NewFactory().CreateLogs(context.Background(), processortest.NewNopSettings(metadata.Type), cfg, sink)
require.NoError(t, err)
require.NoError(t, logs.Start(context.Background(), componenttest.NewNopHost()))
start := time.Now()
for range requestCount {
ld := testdata.GenerateLogs(logsPerRequest)
require.NoError(t, logs.ConsumeLogs(context.Background(), ld))
}
// Wait for at least one batch to be sent.
for sink.LogRecordCount() == 0 {
<-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, logs.Shutdown(context.Background()))
expectedBatchesNum := 1
expectedBatchingFactor := 5
require.Equal(t, requestCount*logsPerRequest, sink.LogRecordCount())
receivedMds := sink.AllLogs()
require.Len(t, receivedMds, expectedBatchesNum)
for _, ld := range receivedMds {
require.Equal(t, expectedBatchingFactor, ld.ResourceLogs().Len())
for i := range expectedBatchingFactor {
require.Equal(t, logsPerRequest, ld.ResourceLogs().At(i).ScopeLogs().At(0).LogRecords().Len())
}
}
}
func TestBatchLogProcessor_Shutdown(t *testing.T) {
cfg := &Config{
Timeout: 3 * time.Second,
SendBatchSize: 1000,
}
requestCount := 5
logsPerRequest := 10
sink := new(consumertest.LogsSink)
logs, err := NewFactory().CreateLogs(context.Background(), processortest.NewNopSettings(metadata.Type), cfg, sink)
require.NoError(t, err)
require.NoError(t, logs.Start(context.Background(), componenttest.NewNopHost()))
for range requestCount {
ld := testdata.GenerateLogs(logsPerRequest)
require.NoError(t, logs.ConsumeLogs(context.Background(), ld))
}
require.NoError(t, logs.Shutdown(context.Background()))
require.Equal(t, requestCount*logsPerRequest, sink.LogRecordCount())
require.Len(t, sink.AllLogs(), 1)
}
func getTestLogSeverityText(requestNum, index int) string {
return fmt.Sprintf("test-log-int-%d-%d", requestNum, index)
}
func logsReceivedBySeverityText(lds []plog.Logs) map[string]plog.LogRecord {
logsReceivedBySeverityText := map[string]plog.LogRecord{}
for i := range lds {
ld := lds[i]
rms := ld.ResourceLogs()
for i := 0; i < rms.Len(); i++ {
ilms := rms.At(i).ScopeLogs()
for j := 0; j < ilms.Len(); j++ {
logs := ilms.At(j).LogRecords()
for k := 0; k < logs.Len(); k++ {
log := logs.At(k)
logsReceivedBySeverityText[log.SeverityText()] = log
}
}
}
}
return logsReceivedBySeverityText
}
func TestShutdown(t *testing.T) {
factory := NewFactory()
processortest.VerifyShutdown(t, factory, factory.CreateDefaultConfig())
}
type metadataTracesSink struct {
*consumertest.TracesSink
lock sync.Mutex
spanCountByToken12 map[string]int
}
func formatTwo(first, second []string) string {
return fmt.Sprintf("%s;%s", first, second)
}
func (mts *metadataTracesSink) ConsumeTraces(ctx context.Context, td ptrace.Traces) error {
info := client.FromContext(ctx)
token1 := info.Metadata.Get("token1")
token2 := info.Metadata.Get("token2")
mts.lock.Lock()
defer mts.lock.Unlock()
mts.spanCountByToken12[formatTwo(
token1,
token2,
)] += td.SpanCount()
return mts.TracesSink.ConsumeTraces(ctx, td)
}
func TestBatchProcessorSpansBatchedByMetadata(t *testing.T) {
sink := &metadataTracesSink{
TracesSink: &consumertest.TracesSink{},
spanCountByToken12: map[string]int{},
}
cfg := createDefaultConfig().(*Config)
cfg.SendBatchSize = 1000
cfg.Timeout = 10 * time.Minute
cfg.MetadataKeys = []string{"token1", "token2"}
traces, err := NewFactory().CreateTraces(context.Background(), processortest.NewNopSettings(metadata.Type), cfg, sink)
require.NoError(t, err)
require.NoError(t, traces.Start(context.Background(), componenttest.NewNopHost()))
bg := context.Background()
callCtxs := []context.Context{
client.NewContext(bg, client.Info{
Metadata: client.NewMetadata(map[string][]string{
"token1": {"single"},
"token3": {"n/a"},
}),
}),
client.NewContext(bg, client.Info{
Metadata: client.NewMetadata(map[string][]string{
"token1": {"single"},
"token2": {"one", "two"},
"token4": {"n/a"},
}),
}),
client.NewContext(bg, client.Info{
Metadata: client.NewMetadata(map[string][]string{
"token1": nil,
"token2": {"single"},
}),
}),
client.NewContext(bg, client.Info{
Metadata: client.NewMetadata(map[string][]string{
"token1": {"one", "two", "three"},
"token2": {"single"},
"token3": {"n/a"},
"token4": {"n/a", "d/c"},
}),
}),
}
expectByContext := make([]int, len(callCtxs))
requestCount := 1000
spansPerRequest := 33
sentResourceSpans := ptrace.NewTraces().ResourceSpans()
for requestNum := range requestCount {
td := testdata.GenerateTraces(spansPerRequest)
spans := td.ResourceSpans().At(0).ScopeSpans().At(0).Spans()
for spanIndex := range spansPerRequest {
spans.At(spanIndex).SetName(getTestSpanName(requestNum, spanIndex))
}
td.ResourceSpans().At(0).CopyTo(sentResourceSpans.AppendEmpty())
// use round-robin to assign context.
num := requestNum % len(callCtxs)
expectByContext[num] += spansPerRequest
require.NoError(t, traces.ConsumeTraces(callCtxs[num], td))
}
require.NoError(t, traces.Shutdown(context.Background()))
// The following tests are the same as TestBatchProcessorSpansDelivered().
require.Equal(t, requestCount*spansPerRequest, sink.SpanCount())
receivedTraces := sink.AllTraces()
spansReceivedByName := spansReceivedByName(receivedTraces)
for requestNum := range requestCount {
spans := sentResourceSpans.At(requestNum).ScopeSpans().At(0).Spans()
for spanIndex := range spansPerRequest {
require.Equal(t,
spans.At(spanIndex),
spansReceivedByName[getTestSpanName(requestNum, spanIndex)])
}
}
// This test ensures each context had the expected number of spans.
require.Len(t, sink.spanCountByToken12, len(callCtxs))
for idx, ctx := range callCtxs {
md := client.FromContext(ctx).Metadata
exp := formatTwo(md.Get("token1"), md.Get("token2"))
require.Equal(t, expectByContext[idx], sink.spanCountByToken12[exp])
}
}
func TestBatchProcessorDuplicateMetadataKeys(t *testing.T) {
cfg := createDefaultConfig().(*Config)
cfg.MetadataKeys = []string{"myTOKEN", "mytoken"}
err := cfg.Validate()
require.ErrorContains(t, err, "duplicate")
require.ErrorContains(t, err, "mytoken")
}
func TestBatchProcessorMetadataCardinalityLimit(t *testing.T) {
const cardLimit = 10
sink := new(consumertest.TracesSink)
cfg := createDefaultConfig().(*Config)
cfg.MetadataKeys = []string{"token"}
cfg.MetadataCardinalityLimit = cardLimit
traces, err := NewFactory().CreateTraces(context.Background(), processortest.NewNopSettings(metadata.Type), cfg, sink)
require.NoError(t, err)
require.NoError(t, traces.Start(context.Background(), componenttest.NewNopHost()))
bg := context.Background()
for requestNum := range cardLimit {
td := testdata.GenerateTraces(1)
ctx := client.NewContext(bg, client.Info{
Metadata: client.NewMetadata(map[string][]string{
"token": {strconv.Itoa(requestNum)},
}),
})
require.NoError(t, traces.ConsumeTraces(ctx, td))
}
td := testdata.GenerateTraces(1)
ctx := client.NewContext(bg, client.Info{
Metadata: client.NewMetadata(map[string][]string{
"token": {"limit_exceeded"},
}),
})
err = traces.ConsumeTraces(ctx, td)
require.Error(t, err)
assert.True(t, consumererror.IsPermanent(err))
require.ErrorContains(t, err, "too many")
require.NoError(t, traces.Shutdown(context.Background()))
}
func TestBatchZeroConfig(t *testing.T) {
// This is a no-op configuration. No need for a timer, no
// minimum, no maximum, just a pass through.
cfg := &Config{}
require.NoError(t, cfg.Validate())
const requestCount = 5
const logsPerRequest = 10
sink := new(consumertest.LogsSink)
logs, err := NewFactory().CreateLogs(context.Background(), processortest.NewNopSettings(metadata.Type), cfg, sink)
require.NoError(t, err)
require.NoError(t, logs.Start(context.Background(), componenttest.NewNopHost()))
defer func() { require.NoError(t, logs.Shutdown(context.Background())) }()
expect := 0
for requestNum := range requestCount {
cnt := logsPerRequest + requestNum
expect += cnt
ld := testdata.GenerateLogs(cnt)
require.NoError(t, logs.ConsumeLogs(context.Background(), ld))
}
// Wait for all batches.
require.Eventually(t, func() bool {
return sink.LogRecordCount() == expect
}, time.Second, 5*time.Millisecond)
// Expect them to be the original sizes.
receivedMds := sink.AllLogs()
require.Len(t, receivedMds, requestCount)
for i, ld := range receivedMds {
require.Equal(t, 1, ld.ResourceLogs().Len())
require.Equal(t, logsPerRequest+i, ld.LogRecordCount())
}
}
func TestBatchSplitOnly(t *testing.T) {
const maxBatch = 10
const requestCount = 5
const logsPerRequest = 100
cfg := &Config{
SendBatchMaxSize: maxBatch,
}
require.NoError(t, cfg.Validate())
sink := new(consumertest.LogsSink)
logs, err := NewFactory().CreateLogs(context.Background(), processortest.NewNopSettings(metadata.Type), cfg, sink)
require.NoError(t, err)
require.NoError(t, logs.Start(context.Background(), componenttest.NewNopHost()))
defer func() { require.NoError(t, logs.Shutdown(context.Background())) }()
for range requestCount {
ld := testdata.GenerateLogs(logsPerRequest)
require.NoError(t, logs.ConsumeLogs(context.Background(), ld))
}
// Wait for all batches.
require.Eventually(t, func() bool {
return sink.LogRecordCount() == logsPerRequest*requestCount
}, time.Second, 5*time.Millisecond)
// Expect them to be the limited by maxBatch.
receivedMds := sink.AllLogs()
require.Len(t, receivedMds, requestCount*logsPerRequest/maxBatch)
for _, ld := range receivedMds {
require.Equal(t, maxBatch, ld.LogRecordCount())
}
}
================================================
FILE: processor/batchprocessor/config.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package batchprocessor // import "go.opentelemetry.io/collector/processor/batchprocessor"
import (
"errors"
"fmt"
"strings"
"time"
"go.opentelemetry.io/collector/component"
)
// Config defines configuration for batch processor.
type Config struct {
// Timeout sets the time after which a batch will be sent regardless of size.
// When this is set to zero, batched data will be sent immediately.
Timeout time.Duration `mapstructure:"timeout"`
// SendBatchSize is the size of a batch which after hit, will trigger it to be sent.
// When this is set to zero, the batch size is ignored and data will be sent immediately
// subject to only send_batch_max_size.
SendBatchSize uint32 `mapstructure:"send_batch_size"`
// SendBatchMaxSize is the maximum size of a batch. It must be larger than SendBatchSize.
// Larger batches are split into smaller units.
// Default value is 0, that means no maximum size.
SendBatchMaxSize uint32 `mapstructure:"send_batch_max_size"`
// MetadataKeys is a list of client.Metadata keys that will be
// used to form distinct batchers. If this setting is empty,
// a single batcher instance will be used. When this setting
// is not empty, one batcher will be used per distinct
// combination of values for the listed metadata keys.
//
// Empty value and unset metadata are treated as distinct cases.
//
// Entries are case-insensitive. Duplicated entries will
// trigger a validation error.
MetadataKeys []string `mapstructure:"metadata_keys"`
// MetadataCardinalityLimit indicates the maximum number of
// batcher instances that will be created through a distinct
// combination of MetadataKeys.
MetadataCardinalityLimit uint32 `mapstructure:"metadata_cardinality_limit"`
// prevent unkeyed literal initialization
_ struct{}
}
var _ component.Config = (*Config)(nil)
// Validate checks if the processor configuration is valid
func (cfg *Config) Validate() error {
if cfg.SendBatchMaxSize > 0 && cfg.SendBatchMaxSize < cfg.SendBatchSize {
return errors.New("send_batch_max_size must be greater or equal to send_batch_size")
}
uniq := map[string]bool{}
for _, k := range cfg.MetadataKeys {
l := strings.ToLower(k)
if _, has := uniq[l]; has {
return fmt.Errorf("duplicate entry in metadata_keys: %q (case-insensitive)", l)
}
uniq[l] = true
}
if cfg.Timeout < 0 {
return errors.New("timeout must be greater or equal to 0")
}
return nil
}
================================================
FILE: processor/batchprocessor/config_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package batchprocessor
import (
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/confmaptest"
)
func TestUnmarshalDefaultConfig(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
require.NoError(t, confmap.New().Unmarshal(&cfg))
assert.Equal(t, factory.CreateDefaultConfig(), cfg)
}
func TestUnmarshalConfig(t *testing.T) {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
require.NoError(t, err)
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
require.NoError(t, cm.Unmarshal(&cfg))
assert.Equal(t,
&Config{
SendBatchSize: uint32(10000),
SendBatchMaxSize: uint32(11000),
Timeout: time.Second * 10,
MetadataCardinalityLimit: 1000,
}, cfg)
}
func TestValidateConfig_DefaultBatchMaxSize(t *testing.T) {
cfg := &Config{
SendBatchSize: 100,
SendBatchMaxSize: 0,
}
assert.NoError(t, cfg.Validate())
}
func TestValidateConfig_ValidBatchSizes(t *testing.T) {
cfg := &Config{
SendBatchSize: 100,
SendBatchMaxSize: 1000,
}
assert.NoError(t, cfg.Validate())
}
func TestValidateConfig_InvalidBatchSize(t *testing.T) {
cfg := &Config{
SendBatchSize: 1000,
SendBatchMaxSize: 100,
}
assert.Error(t, cfg.Validate())
}
func TestValidateConfig_InvalidTimeout(t *testing.T) {
cfg := &Config{
Timeout: -time.Second,
}
assert.Error(t, cfg.Validate())
}
func TestValidateConfig_ValidZero(t *testing.T) {
cfg := &Config{}
assert.NoError(t, cfg.Validate())
}
================================================
FILE: processor/batchprocessor/documentation.md
================================================
[comment]: <> (Code generated by mdatagen. DO NOT EDIT.)
# batch
## Internal Telemetry
The following telemetry is emitted by this component.
### otelcol_processor_batch_batch_send_size
Number of units in the batch
| Unit | Metric Type | Value Type | Stability |
| ---- | ----------- | ---------- | --------- |
| {unit} | Histogram | Int | Development |
### otelcol_processor_batch_batch_send_size_bytes
Number of bytes in batch that was sent. Only available on detailed level.
| Unit | Metric Type | Value Type | Stability |
| ---- | ----------- | ---------- | --------- |
| By | Histogram | Int | Development |
### otelcol_processor_batch_batch_size_trigger_send
Number of times the batch was sent due to a size trigger
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {time} | Sum | Int | true | Development |
### otelcol_processor_batch_metadata_cardinality
Number of distinct metadata value combinations being processed
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {combination} | Sum | Int | false | Development |
### otelcol_processor_batch_timeout_trigger_send
Number of times the batch was sent due to a timeout trigger
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {time} | Sum | Int | true | Development |
================================================
FILE: processor/batchprocessor/factory.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:generate mdatagen metadata.yaml
package batchprocessor // import "go.opentelemetry.io/collector/processor/batchprocessor"
import (
"context"
"time"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/batchprocessor/internal/metadata"
)
const (
defaultSendBatchSize = uint32(8192)
defaultTimeout = 200 * time.Millisecond
// defaultMetadataCardinalityLimit should be set to the number
// of metadata configurations the user expects to submit to
// the collector.
defaultMetadataCardinalityLimit = 1000
)
// NewFactory returns a new factory for the Batch processor.
func NewFactory() processor.Factory {
return processor.NewFactory(
metadata.Type,
createDefaultConfig,
processor.WithTraces(createTraces, metadata.TracesStability),
processor.WithMetrics(createMetrics, metadata.MetricsStability),
processor.WithLogs(createLogs, metadata.LogsStability))
}
func createDefaultConfig() component.Config {
return &Config{
SendBatchSize: defaultSendBatchSize,
Timeout: defaultTimeout,
MetadataCardinalityLimit: defaultMetadataCardinalityLimit,
}
}
func createTraces(
_ context.Context,
set processor.Settings,
cfg component.Config,
nextConsumer consumer.Traces,
) (processor.Traces, error) {
return newTracesBatchProcessor(set, nextConsumer, cfg.(*Config))
}
func createMetrics(
_ context.Context,
set processor.Settings,
cfg component.Config,
nextConsumer consumer.Metrics,
) (processor.Metrics, error) {
return newMetricsBatchProcessor(set, nextConsumer, cfg.(*Config))
}
func createLogs(
_ context.Context,
set processor.Settings,
cfg component.Config,
nextConsumer consumer.Logs,
) (processor.Logs, error) {
return newLogsBatchProcessor(set, nextConsumer, cfg.(*Config))
}
================================================
FILE: processor/batchprocessor/factory_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package batchprocessor
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/processor/processortest"
)
func TestCreateDefaultConfig(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
assert.NotNil(t, cfg, "failed to create default config")
assert.NoError(t, componenttest.CheckConfigStruct(cfg))
}
func TestCreateProcessor(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
creationSet := processortest.NewNopSettings(factory.Type())
tp, err := factory.CreateTraces(context.Background(), creationSet, cfg, nil)
assert.NotNil(t, tp)
assert.NoError(t, err, "cannot create trace processor")
assert.NoError(t, tp.Shutdown(context.Background()))
mp, err := factory.CreateMetrics(context.Background(), creationSet, cfg, nil)
assert.NotNil(t, mp)
assert.NoError(t, err, "cannot create metric processor")
assert.NoError(t, mp.Shutdown(context.Background()))
lp, err := factory.CreateLogs(context.Background(), creationSet, cfg, nil)
assert.NotNil(t, lp)
assert.NoError(t, err, "cannot create logs processor")
assert.NoError(t, lp.Shutdown(context.Background()))
}
================================================
FILE: processor/batchprocessor/generated_component_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package batchprocessor
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/confmap/confmaptest"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/processortest"
)
var typ = component.MustNewType("batch")
func TestComponentFactoryType(t *testing.T) {
require.Equal(t, typ, NewFactory().Type())
}
func TestComponentConfigStruct(t *testing.T) {
require.NoError(t, componenttest.CheckConfigStruct(NewFactory().CreateDefaultConfig()))
}
func TestComponentLifecycle(t *testing.T) {
factory := NewFactory()
tests := []struct {
createFn func(ctx context.Context, set processor.Settings, cfg component.Config) (component.Component, error)
name string
}{
{
name: "logs",
createFn: func(ctx context.Context, set processor.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateLogs(ctx, set, cfg, consumertest.NewNop())
},
},
{
name: "metrics",
createFn: func(ctx context.Context, set processor.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateMetrics(ctx, set, cfg, consumertest.NewNop())
},
},
{
name: "traces",
createFn: func(ctx context.Context, set processor.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateTraces(ctx, set, cfg, consumertest.NewNop())
},
},
}
cm, err := confmaptest.LoadConf("metadata.yaml")
require.NoError(t, err)
cfg := factory.CreateDefaultConfig()
sub, err := cm.Sub("tests::config")
require.NoError(t, err)
require.NoError(t, sub.Unmarshal(&cfg))
for _, tt := range tests {
t.Run(tt.name+"-shutdown", func(t *testing.T) {
c, err := tt.createFn(context.Background(), processortest.NewNopSettings(typ), cfg)
require.NoError(t, err)
err = c.Shutdown(context.Background())
require.NoError(t, err)
})
t.Run(tt.name+"-lifecycle", func(t *testing.T) {
c, err := tt.createFn(context.Background(), processortest.NewNopSettings(typ), cfg)
require.NoError(t, err)
host := newMdatagenNopHost()
err = c.Start(context.Background(), host)
require.NoError(t, err)
require.NotPanics(t, func() {
switch tt.name {
case "logs":
e, ok := c.(processor.Logs)
require.True(t, ok)
logs := generateLifecycleTestLogs()
if !e.Capabilities().MutatesData {
logs.MarkReadOnly()
}
err = e.ConsumeLogs(context.Background(), logs)
case "metrics":
e, ok := c.(processor.Metrics)
require.True(t, ok)
metrics := generateLifecycleTestMetrics()
if !e.Capabilities().MutatesData {
metrics.MarkReadOnly()
}
err = e.ConsumeMetrics(context.Background(), metrics)
case "traces":
e, ok := c.(processor.Traces)
require.True(t, ok)
traces := generateLifecycleTestTraces()
if !e.Capabilities().MutatesData {
traces.MarkReadOnly()
}
err = e.ConsumeTraces(context.Background(), traces)
}
})
require.NoError(t, err)
err = c.Shutdown(context.Background())
require.NoError(t, err)
})
}
}
func generateLifecycleTestLogs() plog.Logs {
logs := plog.NewLogs()
rl := logs.ResourceLogs().AppendEmpty()
rl.Resource().Attributes().PutStr("resource", "R1")
l := rl.ScopeLogs().AppendEmpty().LogRecords().AppendEmpty()
l.Body().SetStr("test log message")
l.SetTimestamp(pcommon.NewTimestampFromTime(time.Now()))
return logs
}
func generateLifecycleTestMetrics() pmetric.Metrics {
metrics := pmetric.NewMetrics()
rm := metrics.ResourceMetrics().AppendEmpty()
rm.Resource().Attributes().PutStr("resource", "R1")
m := rm.ScopeMetrics().AppendEmpty().Metrics().AppendEmpty()
m.SetName("test_metric")
dp := m.SetEmptyGauge().DataPoints().AppendEmpty()
dp.Attributes().PutStr("test_attr", "value_1")
dp.SetIntValue(123)
dp.SetTimestamp(pcommon.NewTimestampFromTime(time.Now()))
return metrics
}
func generateLifecycleTestTraces() ptrace.Traces {
traces := ptrace.NewTraces()
rs := traces.ResourceSpans().AppendEmpty()
rs.Resource().Attributes().PutStr("resource", "R1")
span := rs.ScopeSpans().AppendEmpty().Spans().AppendEmpty()
span.Attributes().PutStr("test_attr", "value_1")
span.SetName("test_span")
span.SetStartTimestamp(pcommon.NewTimestampFromTime(time.Now().Add(-1 * time.Second)))
span.SetEndTimestamp(pcommon.NewTimestampFromTime(time.Now()))
return traces
}
var _ component.Host = (*mdatagenNopHost)(nil)
type mdatagenNopHost struct{}
func newMdatagenNopHost() component.Host {
return &mdatagenNopHost{}
}
func (mnh *mdatagenNopHost) GetExtensions() map[component.ID]component.Component {
return nil
}
func (mnh *mdatagenNopHost) GetFactory(_ component.Kind, _ component.Type) component.Factory {
return nil
}
================================================
FILE: processor/batchprocessor/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package batchprocessor
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: processor/batchprocessor/go.mod
================================================
module go.opentelemetry.io/collector/processor/batchprocessor
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/client v1.54.0
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.opentelemetry.io/collector/confmap v1.54.0
go.opentelemetry.io/collector/consumer v1.54.0
go.opentelemetry.io/collector/consumer/consumererror v0.148.0
go.opentelemetry.io/collector/consumer/consumertest v0.148.0
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/collector/pdata/testdata v0.148.0
go.opentelemetry.io/collector/pdata/xpdata v0.148.0
go.opentelemetry.io/collector/processor v1.54.0
go.opentelemetry.io/collector/processor/processortest v0.148.0
go.opentelemetry.io/otel v1.42.0
go.opentelemetry.io/otel/metric v1.42.0
go.opentelemetry.io/otel/sdk/metric v1.42.0
go.opentelemetry.io/otel/trace v1.42.0
go.uber.org/goleak v1.3.0
go.uber.org/zap v1.27.1
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/component/componentstatus v0.148.0 // indirect
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
go.opentelemetry.io/collector/pdata/pprofile v0.148.0 // indirect
go.opentelemetry.io/collector/pipeline v1.54.0 // indirect
go.opentelemetry.io/collector/processor/xprocessor v0.148.0 // indirect
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/sys v0.41.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/processor => ../
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest
replace go.opentelemetry.io/collector/client => ../../client
replace go.opentelemetry.io/collector/confmap => ../../confmap
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/pdata/testdata => ../../pdata/testdata
replace go.opentelemetry.io/collector/pdata/xpdata => ../../pdata/xpdata
replace go.opentelemetry.io/collector/consumer => ../../consumer
retract (
v0.76.0 // Depends on retracted pdata v1.0.0-rc10 module, use v0.76.1
v0.69.0 // Release failed, use v0.69.1
)
replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile
replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer
replace go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest
replace go.opentelemetry.io/collector/component/componentstatus => ../../component/componentstatus
replace go.opentelemetry.io/collector/processor/xprocessor => ../xprocessor
replace go.opentelemetry.io/collector/pipeline => ../../pipeline
replace go.opentelemetry.io/collector/processor/processortest => ../processortest
replace go.opentelemetry.io/collector/consumer/consumererror => ../../consumer/consumererror
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
================================================
FILE: processor/batchprocessor/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: processor/batchprocessor/internal/metadata/generated_status.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/component"
)
var (
Type = component.MustNewType("batch")
ScopeName = "go.opentelemetry.io/collector/processor/batchprocessor"
)
const (
TracesStability = component.StabilityLevelBeta
MetricsStability = component.StabilityLevelBeta
LogsStability = component.StabilityLevelBeta
)
================================================
FILE: processor/batchprocessor/internal/metadata/generated_telemetry.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"context"
"errors"
"sync"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/metric/embedded"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/collector/component"
)
func Meter(settings component.TelemetrySettings) metric.Meter {
return settings.MeterProvider.Meter("go.opentelemetry.io/collector/processor/batchprocessor")
}
func Tracer(settings component.TelemetrySettings) trace.Tracer {
return settings.TracerProvider.Tracer("go.opentelemetry.io/collector/processor/batchprocessor")
}
// TelemetryBuilder provides an interface for components to report telemetry
// as defined in metadata and user config.
type TelemetryBuilder struct {
meter metric.Meter
mu sync.Mutex
registrations []metric.Registration
ProcessorBatchBatchSendSize metric.Int64Histogram
ProcessorBatchBatchSendSizeBytes metric.Int64Histogram
ProcessorBatchBatchSizeTriggerSend metric.Int64Counter
ProcessorBatchMetadataCardinality metric.Int64ObservableUpDownCounter
ProcessorBatchTimeoutTriggerSend metric.Int64Counter
}
// TelemetryBuilderOption applies changes to default builder.
type TelemetryBuilderOption interface {
apply(*TelemetryBuilder)
}
type telemetryBuilderOptionFunc func(mb *TelemetryBuilder)
func (tbof telemetryBuilderOptionFunc) apply(mb *TelemetryBuilder) {
tbof(mb)
}
// RegisterProcessorBatchMetadataCardinalityCallback sets callback for observable ProcessorBatchMetadataCardinality metric.
func (builder *TelemetryBuilder) RegisterProcessorBatchMetadataCardinalityCallback(cb metric.Int64Callback) error {
reg, err := builder.meter.RegisterCallback(func(ctx context.Context, o metric.Observer) error {
cb(ctx, &observerInt64{inst: builder.ProcessorBatchMetadataCardinality, obs: o})
return nil
}, builder.ProcessorBatchMetadataCardinality)
if err != nil {
return err
}
builder.mu.Lock()
defer builder.mu.Unlock()
builder.registrations = append(builder.registrations, reg)
return nil
}
type observerInt64 struct {
embedded.Int64Observer
inst metric.Int64Observable
obs metric.Observer
}
func (oi *observerInt64) Observe(value int64, opts ...metric.ObserveOption) {
oi.obs.ObserveInt64(oi.inst, value, opts...)
}
// Shutdown unregister all registered callbacks for async instruments.
func (builder *TelemetryBuilder) Shutdown() {
builder.mu.Lock()
defer builder.mu.Unlock()
for _, reg := range builder.registrations {
reg.Unregister()
}
}
// NewTelemetryBuilder provides a struct with methods to update all internal telemetry
// for a component
func NewTelemetryBuilder(settings component.TelemetrySettings, options ...TelemetryBuilderOption) (*TelemetryBuilder, error) {
builder := TelemetryBuilder{}
for _, op := range options {
op.apply(&builder)
}
builder.meter = Meter(settings)
var err, errs error
builder.ProcessorBatchBatchSendSize, err = builder.meter.Int64Histogram(
"otelcol_processor_batch_batch_send_size",
metric.WithDescription("Number of units in the batch [Development]"),
metric.WithUnit("{unit}"),
metric.WithExplicitBucketBoundaries([]float64{10, 25, 50, 75, 100, 250, 500, 750, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000, 20000, 30000, 50000, 100000}...),
)
errs = errors.Join(errs, err)
builder.ProcessorBatchBatchSendSizeBytes, err = builder.meter.Int64Histogram(
"otelcol_processor_batch_batch_send_size_bytes",
metric.WithDescription("Number of bytes in batch that was sent. Only available on detailed level. [Development]"),
metric.WithUnit("By"),
metric.WithExplicitBucketBoundaries([]float64{10, 25, 50, 75, 100, 250, 500, 750, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000, 20000, 30000, 50000, 100000, 200000, 300000, 400000, 500000, 600000, 700000, 800000, 900000, 1e+06, 2e+06, 3e+06, 4e+06, 5e+06, 6e+06, 7e+06, 8e+06, 9e+06}...),
)
errs = errors.Join(errs, err)
builder.ProcessorBatchBatchSizeTriggerSend, err = builder.meter.Int64Counter(
"otelcol_processor_batch_batch_size_trigger_send",
metric.WithDescription("Number of times the batch was sent due to a size trigger [Development]"),
metric.WithUnit("{time}"),
)
errs = errors.Join(errs, err)
builder.ProcessorBatchMetadataCardinality, err = builder.meter.Int64ObservableUpDownCounter(
"otelcol_processor_batch_metadata_cardinality",
metric.WithDescription("Number of distinct metadata value combinations being processed [Development]"),
metric.WithUnit("{combination}"),
)
errs = errors.Join(errs, err)
builder.ProcessorBatchTimeoutTriggerSend, err = builder.meter.Int64Counter(
"otelcol_processor_batch_timeout_trigger_send",
metric.WithDescription("Number of times the batch was sent due to a timeout trigger [Development]"),
metric.WithUnit("{time}"),
)
errs = errors.Join(errs, err)
return &builder, errs
}
================================================
FILE: processor/batchprocessor/internal/metadata/generated_telemetry_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/metric"
embeddedmetric "go.opentelemetry.io/otel/metric/embedded"
noopmetric "go.opentelemetry.io/otel/metric/noop"
"go.opentelemetry.io/otel/trace"
embeddedtrace "go.opentelemetry.io/otel/trace/embedded"
nooptrace "go.opentelemetry.io/otel/trace/noop"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
)
type mockMeter struct {
noopmetric.Meter
name string
}
type mockMeterProvider struct {
embeddedmetric.MeterProvider
}
func (m mockMeterProvider) Meter(name string, opts ...metric.MeterOption) metric.Meter {
return mockMeter{name: name}
}
type mockTracer struct {
nooptrace.Tracer
name string
}
type mockTracerProvider struct {
embeddedtrace.TracerProvider
}
func (m mockTracerProvider) Tracer(name string, opts ...trace.TracerOption) trace.Tracer {
return mockTracer{name: name}
}
func TestProviders(t *testing.T) {
set := component.TelemetrySettings{
MeterProvider: mockMeterProvider{},
TracerProvider: mockTracerProvider{},
}
meter := Meter(set)
if m, ok := meter.(mockMeter); ok {
require.Equal(t, "go.opentelemetry.io/collector/processor/batchprocessor", m.name)
} else {
require.Fail(t, "returned Meter not mockMeter")
}
tracer := Tracer(set)
if m, ok := tracer.(mockTracer); ok {
require.Equal(t, "go.opentelemetry.io/collector/processor/batchprocessor", m.name)
} else {
require.Fail(t, "returned Meter not mockTracer")
}
}
func TestNewTelemetryBuilder(t *testing.T) {
set := componenttest.NewNopTelemetrySettings()
applied := false
_, err := NewTelemetryBuilder(set, telemetryBuilderOptionFunc(func(b *TelemetryBuilder) {
applied = true
}))
require.NoError(t, err)
require.True(t, applied)
}
================================================
FILE: processor/batchprocessor/internal/metadatatest/generated_telemetrytest.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadatatest
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/processortest"
)
func NewSettings(tt *componenttest.Telemetry) processor.Settings {
set := processortest.NewNopSettings(processortest.NopType)
set.ID = component.NewID(component.MustNewType("batch"))
set.TelemetrySettings = tt.NewTelemetrySettings()
return set
}
func AssertEqualProcessorBatchBatchSendSize(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.HistogramDataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_processor_batch_batch_send_size",
Description: "Number of units in the batch [Development]",
Unit: "{unit}",
Data: metricdata.Histogram[int64]{
Temporality: metricdata.CumulativeTemporality,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_processor_batch_batch_send_size")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualProcessorBatchBatchSendSizeBytes(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.HistogramDataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_processor_batch_batch_send_size_bytes",
Description: "Number of bytes in batch that was sent. Only available on detailed level. [Development]",
Unit: "By",
Data: metricdata.Histogram[int64]{
Temporality: metricdata.CumulativeTemporality,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_processor_batch_batch_send_size_bytes")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualProcessorBatchBatchSizeTriggerSend(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_processor_batch_batch_size_trigger_send",
Description: "Number of times the batch was sent due to a size trigger [Development]",
Unit: "{time}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_processor_batch_batch_size_trigger_send")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualProcessorBatchMetadataCardinality(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_processor_batch_metadata_cardinality",
Description: "Number of distinct metadata value combinations being processed [Development]",
Unit: "{combination}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: false,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_processor_batch_metadata_cardinality")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualProcessorBatchTimeoutTriggerSend(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_processor_batch_timeout_trigger_send",
Description: "Number of times the batch was sent due to a timeout trigger [Development]",
Unit: "{time}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_processor_batch_timeout_trigger_send")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
================================================
FILE: processor/batchprocessor/internal/metadatatest/generated_telemetrytest_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadatatest
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/processor/batchprocessor/internal/metadata"
)
func TestSetupTelemetry(t *testing.T) {
testTel := componenttest.NewTelemetry()
tb, err := metadata.NewTelemetryBuilder(testTel.NewTelemetrySettings())
require.NoError(t, err)
defer tb.Shutdown()
require.NoError(t, tb.RegisterProcessorBatchMetadataCardinalityCallback(func(_ context.Context, observer metric.Int64Observer) error {
observer.Observe(1)
return nil
}))
tb.ProcessorBatchBatchSendSize.Record(context.Background(), 1)
tb.ProcessorBatchBatchSendSizeBytes.Record(context.Background(), 1)
tb.ProcessorBatchBatchSizeTriggerSend.Add(context.Background(), 1)
tb.ProcessorBatchTimeoutTriggerSend.Add(context.Background(), 1)
AssertEqualProcessorBatchBatchSendSize(t, testTel,
[]metricdata.HistogramDataPoint[int64]{{}}, metricdatatest.IgnoreValue(),
metricdatatest.IgnoreTimestamp())
AssertEqualProcessorBatchBatchSendSizeBytes(t, testTel,
[]metricdata.HistogramDataPoint[int64]{{}}, metricdatatest.IgnoreValue(),
metricdatatest.IgnoreTimestamp())
AssertEqualProcessorBatchBatchSizeTriggerSend(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualProcessorBatchMetadataCardinality(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualProcessorBatchTimeoutTriggerSend(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
require.NoError(t, testTel.Shutdown(context.Background()))
}
================================================
FILE: processor/batchprocessor/metadata.yaml
================================================
display_name: Batch Processor
type: batch
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: processor
stability:
beta: [traces, metrics, logs]
distributions: [core, contrib, k8s]
tests:
telemetry:
metrics:
processor_batch_batch_send_size:
enabled: true
stability: development
description: Number of units in the batch
unit: "{unit}"
histogram:
value_type: int
bucket_boundaries:
[
10,
25,
50,
75,
100,
250,
500,
750,
1000,
2000,
3000,
4000,
5000,
6000,
7000,
8000,
9000,
10000,
20000,
30000,
50000,
100000,
]
processor_batch_batch_send_size_bytes:
enabled: true
stability: development
description: Number of bytes in batch that was sent. Only available on detailed level.
unit: By
histogram:
value_type: int
bucket_boundaries:
[
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_000,
900_000,
1000_000,
2000_000,
3000_000,
4000_000,
5000_000,
6000_000,
7000_000,
8000_000,
9000_000,
]
processor_batch_batch_size_trigger_send:
enabled: true
stability: development
description: Number of times the batch was sent due to a size trigger
unit: "{time}"
sum:
value_type: int
monotonic: true
processor_batch_metadata_cardinality:
enabled: true
stability: development
description: Number of distinct metadata value combinations being processed
unit: "{combination}"
sum:
value_type: int
async: true
processor_batch_timeout_trigger_send:
enabled: true
stability: development
description: Number of times the batch was sent due to a timeout trigger
unit: "{time}"
sum:
value_type: int
monotonic: true
================================================
FILE: processor/batchprocessor/metrics.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package batchprocessor // import "go.opentelemetry.io/collector/processor/batchprocessor"
import (
"context"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/batchprocessor/internal/metadata"
"go.opentelemetry.io/collector/processor/internal"
)
type trigger int
const (
triggerTimeout trigger = iota
triggerBatchSize
)
type batchProcessorTelemetry struct {
exportCtx context.Context
processorAttr metric.MeasurementOption
telemetryBuilder *metadata.TelemetryBuilder
}
func newBatchProcessorTelemetry(set processor.Settings, currentMetadataCardinality func() int) (*batchProcessorTelemetry, error) {
attrs := metric.WithAttributeSet(attribute.NewSet(attribute.String(internal.ProcessorKey, set.ID.String())))
telemetryBuilder, err := metadata.NewTelemetryBuilder(set.TelemetrySettings)
if err != nil {
return nil, err
}
err = telemetryBuilder.RegisterProcessorBatchMetadataCardinalityCallback(func(_ context.Context, observer metric.Int64Observer) error {
observer.Observe(int64(currentMetadataCardinality()), attrs)
return nil
})
if err != nil {
return nil, err
}
return &batchProcessorTelemetry{
exportCtx: context.Background(),
telemetryBuilder: telemetryBuilder,
processorAttr: attrs,
}, nil
}
func (bpt *batchProcessorTelemetry) record(trigger trigger, sent, bytes int64) {
switch trigger {
case triggerBatchSize:
bpt.telemetryBuilder.ProcessorBatchBatchSizeTriggerSend.Add(bpt.exportCtx, 1, bpt.processorAttr)
case triggerTimeout:
bpt.telemetryBuilder.ProcessorBatchTimeoutTriggerSend.Add(bpt.exportCtx, 1, bpt.processorAttr)
}
bpt.telemetryBuilder.ProcessorBatchBatchSendSize.Record(bpt.exportCtx, sent, bpt.processorAttr)
bpt.telemetryBuilder.ProcessorBatchBatchSendSizeBytes.Record(bpt.exportCtx, bytes, bpt.processorAttr)
}
================================================
FILE: processor/batchprocessor/splitlogs.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package batchprocessor // import "go.opentelemetry.io/collector/processor/batchprocessor"
import (
"go.opentelemetry.io/collector/pdata/plog"
)
// splitLogs removes logrecords from the input data and returns a new data of the specified size.
func splitLogs(size int, src plog.Logs) plog.Logs {
if src.LogRecordCount() <= size {
return src
}
totalCopiedLogRecords := 0
dest := plog.NewLogs()
src.ResourceLogs().RemoveIf(func(srcRl plog.ResourceLogs) bool {
// If we are done skip everything else.
if totalCopiedLogRecords == size {
return false
}
// If it fully fits
srcRlLRC := resourceLRC(srcRl)
if (totalCopiedLogRecords + srcRlLRC) <= size {
totalCopiedLogRecords += srcRlLRC
srcRl.MoveTo(dest.ResourceLogs().AppendEmpty())
return true
}
destRl := dest.ResourceLogs().AppendEmpty()
srcRl.Resource().CopyTo(destRl.Resource())
destRl.SetSchemaUrl(srcRl.SchemaUrl())
srcRl.ScopeLogs().RemoveIf(func(srcIll plog.ScopeLogs) bool {
// If we are done skip everything else.
if totalCopiedLogRecords == size {
return false
}
// If possible to move all metrics do that.
srcIllLRC := srcIll.LogRecords().Len()
if size >= srcIllLRC+totalCopiedLogRecords {
totalCopiedLogRecords += srcIllLRC
srcIll.MoveTo(destRl.ScopeLogs().AppendEmpty())
return true
}
destIll := destRl.ScopeLogs().AppendEmpty()
srcIll.Scope().CopyTo(destIll.Scope())
destIll.SetSchemaUrl(srcIll.SchemaUrl())
srcIll.LogRecords().RemoveIf(func(srcMetric plog.LogRecord) bool {
// If we are done skip everything else.
if totalCopiedLogRecords == size {
return false
}
srcMetric.MoveTo(destIll.LogRecords().AppendEmpty())
totalCopiedLogRecords++
return true
})
return false
})
return srcRl.ScopeLogs().Len() == 0
})
return dest
}
// resourceLRC calculates the total number of log records in the plog.ResourceLogs.
func resourceLRC(rs plog.ResourceLogs) (count int) {
for k := 0; k < rs.ScopeLogs().Len(); k++ {
count += rs.ScopeLogs().At(k).LogRecords().Len()
}
return count
}
================================================
FILE: processor/batchprocessor/splitlogs_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package batchprocessor
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/testdata"
)
func TestSplitLogs_noop(t *testing.T) {
td := testdata.GenerateLogs(20)
splitSize := 40
split := splitLogs(splitSize, td)
assert.Equal(t, td, split)
i := 0
td.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().RemoveIf(func(plog.LogRecord) bool {
i++
return i > 5
})
assert.Equal(t, td, split)
}
func TestSplitLogs(t *testing.T) {
ld := testdata.GenerateLogs(20)
logs := ld.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords()
for i := 0; i < logs.Len(); i++ {
logs.At(i).SetSeverityText(getTestLogSeverityText(0, i))
}
cp := plog.NewLogs()
cpLogs := cp.ResourceLogs().AppendEmpty().ScopeLogs().AppendEmpty().LogRecords()
cpLogs.EnsureCapacity(5)
ld.ResourceLogs().At(0).Resource().CopyTo(
cp.ResourceLogs().At(0).Resource())
ld.ResourceLogs().At(0).ScopeLogs().At(0).Scope().CopyTo(
cp.ResourceLogs().At(0).ScopeLogs().At(0).Scope())
logs.At(0).CopyTo(cpLogs.AppendEmpty())
logs.At(1).CopyTo(cpLogs.AppendEmpty())
logs.At(2).CopyTo(cpLogs.AppendEmpty())
logs.At(3).CopyTo(cpLogs.AppendEmpty())
logs.At(4).CopyTo(cpLogs.AppendEmpty())
splitSize := 5
split := splitLogs(splitSize, ld)
assert.Equal(t, splitSize, split.LogRecordCount())
assert.Equal(t, cp, split)
assert.Equal(t, 15, ld.LogRecordCount())
assert.Equal(t, "test-log-int-0-0", split.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().At(0).SeverityText())
assert.Equal(t, "test-log-int-0-4", split.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().At(4).SeverityText())
split = splitLogs(splitSize, ld)
assert.Equal(t, 10, ld.LogRecordCount())
assert.Equal(t, "test-log-int-0-5", split.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().At(0).SeverityText())
assert.Equal(t, "test-log-int-0-9", split.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().At(4).SeverityText())
split = splitLogs(splitSize, ld)
assert.Equal(t, 5, ld.LogRecordCount())
assert.Equal(t, "test-log-int-0-10", split.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().At(0).SeverityText())
assert.Equal(t, "test-log-int-0-14", split.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().At(4).SeverityText())
split = splitLogs(splitSize, ld)
assert.Equal(t, 5, ld.LogRecordCount())
assert.Equal(t, "test-log-int-0-15", split.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().At(0).SeverityText())
assert.Equal(t, "test-log-int-0-19", split.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().At(4).SeverityText())
}
func TestSplitLogsMultipleResourceLogs(t *testing.T) {
td := testdata.GenerateLogs(20)
logs := td.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords()
for i := 0; i < logs.Len(); i++ {
logs.At(i).SetSeverityText(getTestLogSeverityText(0, i))
}
// add second index to resource logs
testdata.GenerateLogs(20).
ResourceLogs().At(0).CopyTo(td.ResourceLogs().AppendEmpty())
logs = td.ResourceLogs().At(1).ScopeLogs().At(0).LogRecords()
for i := 0; i < logs.Len(); i++ {
logs.At(i).SetSeverityText(getTestLogSeverityText(1, i))
}
splitSize := 5
split := splitLogs(splitSize, td)
assert.Equal(t, splitSize, split.LogRecordCount())
assert.Equal(t, 35, td.LogRecordCount())
assert.Equal(t, "test-log-int-0-0", split.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().At(0).SeverityText())
assert.Equal(t, "test-log-int-0-4", split.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().At(4).SeverityText())
}
func TestSplitLogsMultipleResourceLogs_split_size_greater_than_log_size(t *testing.T) {
td := testdata.GenerateLogs(20)
logs := td.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords()
for i := 0; i < logs.Len(); i++ {
logs.At(i).SetSeverityText(getTestLogSeverityText(0, i))
}
// add second index to resource logs
testdata.GenerateLogs(20).
ResourceLogs().At(0).CopyTo(td.ResourceLogs().AppendEmpty())
logs = td.ResourceLogs().At(1).ScopeLogs().At(0).LogRecords()
for i := 0; i < logs.Len(); i++ {
logs.At(i).SetSeverityText(getTestLogSeverityText(1, i))
}
splitSize := 25
split := splitLogs(splitSize, td)
assert.Equal(t, splitSize, split.LogRecordCount())
assert.Equal(t, 40-splitSize, td.LogRecordCount())
assert.Equal(t, 1, td.ResourceLogs().Len())
assert.Equal(t, "test-log-int-0-0", split.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().At(0).SeverityText())
assert.Equal(t, "test-log-int-0-19", split.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().At(19).SeverityText())
assert.Equal(t, "test-log-int-1-0", split.ResourceLogs().At(1).ScopeLogs().At(0).LogRecords().At(0).SeverityText())
assert.Equal(t, "test-log-int-1-4", split.ResourceLogs().At(1).ScopeLogs().At(0).LogRecords().At(4).SeverityText())
}
func TestSplitLogsMultipleILL(t *testing.T) {
td := testdata.GenerateLogs(20)
logs := td.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords()
for i := 0; i < logs.Len(); i++ {
logs.At(i).SetSeverityText(getTestLogSeverityText(0, i))
}
// add second index to ILL
td.ResourceLogs().At(0).ScopeLogs().At(0).
CopyTo(td.ResourceLogs().At(0).ScopeLogs().AppendEmpty())
logs = td.ResourceLogs().At(0).ScopeLogs().At(1).LogRecords()
for i := 0; i < logs.Len(); i++ {
logs.At(i).SetSeverityText(getTestLogSeverityText(1, i))
}
// add third index to ILL
td.ResourceLogs().At(0).ScopeLogs().At(0).
CopyTo(td.ResourceLogs().At(0).ScopeLogs().AppendEmpty())
logs = td.ResourceLogs().At(0).ScopeLogs().At(2).LogRecords()
for i := 0; i < logs.Len(); i++ {
logs.At(i).SetSeverityText(getTestLogSeverityText(2, i))
}
splitSize := 40
split := splitLogs(splitSize, td)
assert.Equal(t, splitSize, split.LogRecordCount())
assert.Equal(t, 20, td.LogRecordCount())
assert.Equal(t, "test-log-int-0-0", split.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().At(0).SeverityText())
assert.Equal(t, "test-log-int-0-4", split.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().At(4).SeverityText())
}
func TestSplitLogsPreserveSchemaURLOnPartialSplit(t *testing.T) {
resourceSchemaURL := "https://test-resource-schema-url.com/"
scopeSchemaURL := "https://test-scope-schema-url.com/"
td := testdata.GenerateLogs(2)
td.ResourceLogs().At(0).SetSchemaUrl(resourceSchemaURL)
td.ResourceLogs().At(0).ScopeLogs().At(0).SetSchemaUrl(scopeSchemaURL)
splitSize := 1
split := splitLogs(splitSize, td)
assert.Equal(t, resourceSchemaURL, split.ResourceLogs().At(0).SchemaUrl())
assert.Equal(t, scopeSchemaURL, split.ResourceLogs().At(0).ScopeLogs().At(0).SchemaUrl())
}
================================================
FILE: processor/batchprocessor/splitmetrics.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package batchprocessor // import "go.opentelemetry.io/collector/processor/batchprocessor"
import (
"go.opentelemetry.io/collector/pdata/pmetric"
)
// splitMetrics removes metrics from the input data and returns a new data of the specified size.
func splitMetrics(size int, src pmetric.Metrics) pmetric.Metrics {
dataPoints := src.DataPointCount()
if dataPoints <= size {
return src
}
totalCopiedDataPoints := 0
dest := pmetric.NewMetrics()
src.ResourceMetrics().RemoveIf(func(srcRs pmetric.ResourceMetrics) bool {
// If we are done skip everything else.
if totalCopiedDataPoints == size {
return false
}
// If it fully fits
srcRsDataPointCount := resourceMetricsDPC(srcRs)
if (totalCopiedDataPoints + srcRsDataPointCount) <= size {
totalCopiedDataPoints += srcRsDataPointCount
srcRs.MoveTo(dest.ResourceMetrics().AppendEmpty())
return true
}
destRs := dest.ResourceMetrics().AppendEmpty()
srcRs.Resource().CopyTo(destRs.Resource())
destRs.SetSchemaUrl(srcRs.SchemaUrl())
srcRs.ScopeMetrics().RemoveIf(func(srcIlm pmetric.ScopeMetrics) bool {
// If we are done skip everything else.
if totalCopiedDataPoints == size {
return false
}
// If possible to move all metrics do that.
srcIlmDataPointCount := scopeMetricsDPC(srcIlm)
if srcIlmDataPointCount+totalCopiedDataPoints <= size {
totalCopiedDataPoints += srcIlmDataPointCount
srcIlm.MoveTo(destRs.ScopeMetrics().AppendEmpty())
return true
}
destIlm := destRs.ScopeMetrics().AppendEmpty()
srcIlm.Scope().CopyTo(destIlm.Scope())
destIlm.SetSchemaUrl(srcIlm.SchemaUrl())
srcIlm.Metrics().RemoveIf(func(srcMetric pmetric.Metric) bool {
// If we are done skip everything else.
if totalCopiedDataPoints == size {
return false
}
// If possible to move all points do that.
srcMetricPointCount := metricDPC(srcMetric)
if srcMetricPointCount+totalCopiedDataPoints <= size {
totalCopiedDataPoints += srcMetricPointCount
srcMetric.MoveTo(destIlm.Metrics().AppendEmpty())
return true
}
// If the metric has more data points than free slots we should split it.
copiedDataPoints, remove := splitMetric(srcMetric, destIlm.Metrics().AppendEmpty(), size-totalCopiedDataPoints)
totalCopiedDataPoints += copiedDataPoints
return remove
})
return false
})
return srcRs.ScopeMetrics().Len() == 0
})
return dest
}
// resourceMetricsDPC calculates the total number of data points in the pmetric.ResourceMetrics.
func resourceMetricsDPC(rs pmetric.ResourceMetrics) int {
dataPointCount := 0
ilms := rs.ScopeMetrics()
for k := 0; k < ilms.Len(); k++ {
dataPointCount += scopeMetricsDPC(ilms.At(k))
}
return dataPointCount
}
// scopeMetricsDPC calculates the total number of data points in the pmetric.ScopeMetrics.
func scopeMetricsDPC(ilm pmetric.ScopeMetrics) int {
dataPointCount := 0
ms := ilm.Metrics()
for k := 0; k < ms.Len(); k++ {
dataPointCount += metricDPC(ms.At(k))
}
return dataPointCount
}
// metricDPC calculates the total number of data points in the pmetric.Metric.
func metricDPC(ms pmetric.Metric) int {
switch ms.Type() {
case pmetric.MetricTypeGauge:
return ms.Gauge().DataPoints().Len()
case pmetric.MetricTypeSum:
return ms.Sum().DataPoints().Len()
case pmetric.MetricTypeHistogram:
return ms.Histogram().DataPoints().Len()
case pmetric.MetricTypeExponentialHistogram:
return ms.ExponentialHistogram().DataPoints().Len()
case pmetric.MetricTypeSummary:
return ms.Summary().DataPoints().Len()
}
return 0
}
// splitMetric removes metric points from the input data and moves data of the specified size to destination.
// Returns size of moved data and boolean describing, whether the metric should be removed from original slice.
func splitMetric(ms, dest pmetric.Metric, size int) (int, bool) {
dest.SetName(ms.Name())
dest.SetDescription(ms.Description())
dest.SetUnit(ms.Unit())
switch ms.Type() {
case pmetric.MetricTypeGauge:
return splitNumberDataPoints(ms.Gauge().DataPoints(), dest.SetEmptyGauge().DataPoints(), size)
case pmetric.MetricTypeSum:
destSum := dest.SetEmptySum()
destSum.SetAggregationTemporality(ms.Sum().AggregationTemporality())
destSum.SetIsMonotonic(ms.Sum().IsMonotonic())
return splitNumberDataPoints(ms.Sum().DataPoints(), destSum.DataPoints(), size)
case pmetric.MetricTypeHistogram:
destHistogram := dest.SetEmptyHistogram()
destHistogram.SetAggregationTemporality(ms.Histogram().AggregationTemporality())
return splitHistogramDataPoints(ms.Histogram().DataPoints(), destHistogram.DataPoints(), size)
case pmetric.MetricTypeExponentialHistogram:
destHistogram := dest.SetEmptyExponentialHistogram()
destHistogram.SetAggregationTemporality(ms.ExponentialHistogram().AggregationTemporality())
return splitExponentialHistogramDataPoints(ms.ExponentialHistogram().DataPoints(), destHistogram.DataPoints(), size)
case pmetric.MetricTypeSummary:
return splitSummaryDataPoints(ms.Summary().DataPoints(), dest.SetEmptySummary().DataPoints(), size)
}
return size, false
}
func splitNumberDataPoints(src, dst pmetric.NumberDataPointSlice, size int) (int, bool) {
dst.EnsureCapacity(size)
i := 0
src.RemoveIf(func(dp pmetric.NumberDataPoint) bool {
if i < size {
dp.MoveTo(dst.AppendEmpty())
i++
return true
}
return false
})
return size, false
}
func splitHistogramDataPoints(src, dst pmetric.HistogramDataPointSlice, size int) (int, bool) {
dst.EnsureCapacity(size)
i := 0
src.RemoveIf(func(dp pmetric.HistogramDataPoint) bool {
if i < size {
dp.MoveTo(dst.AppendEmpty())
i++
return true
}
return false
})
return size, false
}
func splitExponentialHistogramDataPoints(src, dst pmetric.ExponentialHistogramDataPointSlice, size int) (int, bool) {
dst.EnsureCapacity(size)
i := 0
src.RemoveIf(func(dp pmetric.ExponentialHistogramDataPoint) bool {
if i < size {
dp.MoveTo(dst.AppendEmpty())
i++
return true
}
return false
})
return size, false
}
func splitSummaryDataPoints(src, dst pmetric.SummaryDataPointSlice, size int) (int, bool) {
dst.EnsureCapacity(size)
i := 0
src.RemoveIf(func(dp pmetric.SummaryDataPoint) bool {
if i < size {
dp.MoveTo(dst.AppendEmpty())
i++
return true
}
return false
})
return size, false
}
================================================
FILE: processor/batchprocessor/splitmetrics_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package batchprocessor
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/testdata"
)
func TestSplitMetrics_noop(t *testing.T) {
td := testdata.GenerateMetrics(20)
splitSize := 40
split := splitMetrics(splitSize, td)
assert.Equal(t, td, split)
i := 0
td.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().RemoveIf(func(pmetric.Metric) bool {
i++
return i > 5
})
assert.Equal(t, td, split)
}
func TestSplitMetrics(t *testing.T) {
md := testdata.GenerateMetrics(20)
metrics := md.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics()
dataPointCount := metricDPC(metrics.At(0))
for i := 0; i < metrics.Len(); i++ {
metrics.At(i).SetName(getTestMetricName(0, i))
assert.Equal(t, dataPointCount, metricDPC(metrics.At(i)))
}
cp := pmetric.NewMetrics()
cpMetrics := cp.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics()
cpMetrics.EnsureCapacity(5)
md.ResourceMetrics().At(0).ScopeMetrics().At(0).Scope().CopyTo(
cp.ResourceMetrics().At(0).ScopeMetrics().At(0).Scope())
md.ResourceMetrics().At(0).Resource().CopyTo(
cp.ResourceMetrics().At(0).Resource())
metrics.At(0).CopyTo(cpMetrics.AppendEmpty())
metrics.At(1).CopyTo(cpMetrics.AppendEmpty())
metrics.At(2).CopyTo(cpMetrics.AppendEmpty())
metrics.At(3).CopyTo(cpMetrics.AppendEmpty())
metrics.At(4).CopyTo(cpMetrics.AppendEmpty())
splitMetricCount := 5
splitSize := splitMetricCount * dataPointCount
split := splitMetrics(splitSize, md)
assert.Equal(t, splitMetricCount, split.MetricCount())
assert.Equal(t, cp, split)
assert.Equal(t, 15, md.MetricCount())
assert.Equal(t, "test-metric-int-0-0", split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0).Name())
assert.Equal(t, "test-metric-int-0-4", split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(4).Name())
split = splitMetrics(splitSize, md)
assert.Equal(t, 10, md.MetricCount())
assert.Equal(t, "test-metric-int-0-5", split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0).Name())
assert.Equal(t, "test-metric-int-0-9", split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(4).Name())
split = splitMetrics(splitSize, md)
assert.Equal(t, 5, md.MetricCount())
assert.Equal(t, "test-metric-int-0-10", split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0).Name())
assert.Equal(t, "test-metric-int-0-14", split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(4).Name())
split = splitMetrics(splitSize, md)
assert.Equal(t, 5, md.MetricCount())
assert.Equal(t, "test-metric-int-0-15", split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0).Name())
assert.Equal(t, "test-metric-int-0-19", split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(4).Name())
}
func TestSplitMetricsMultipleResourceSpans(t *testing.T) {
md := testdata.GenerateMetrics(20)
metrics := md.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics()
dataPointCount := metricDPC(metrics.At(0))
for i := 0; i < metrics.Len(); i++ {
metrics.At(i).SetName(getTestMetricName(0, i))
assert.Equal(t, dataPointCount, metricDPC(metrics.At(i)))
}
// add second index to resource metrics
testdata.GenerateMetrics(20).
ResourceMetrics().At(0).CopyTo(md.ResourceMetrics().AppendEmpty())
metrics = md.ResourceMetrics().At(1).ScopeMetrics().At(0).Metrics()
for i := 0; i < metrics.Len(); i++ {
metrics.At(i).SetName(getTestMetricName(1, i))
}
splitMetricCount := 5
splitSize := splitMetricCount * dataPointCount
split := splitMetrics(splitSize, md)
assert.Equal(t, splitMetricCount, split.MetricCount())
assert.Equal(t, 35, md.MetricCount())
assert.Equal(t, "test-metric-int-0-0", split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0).Name())
assert.Equal(t, "test-metric-int-0-4", split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(4).Name())
}
func TestSplitMetricsMultipleResourceSpans_SplitSizeGreaterThanMetricSize(t *testing.T) {
td := testdata.GenerateMetrics(20)
metrics := td.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics()
dataPointCount := metricDPC(metrics.At(0))
for i := 0; i < metrics.Len(); i++ {
metrics.At(i).SetName(getTestMetricName(0, i))
assert.Equal(t, dataPointCount, metricDPC(metrics.At(i)))
}
// add second index to resource metrics
testdata.GenerateMetrics(20).
ResourceMetrics().At(0).CopyTo(td.ResourceMetrics().AppendEmpty())
metrics = td.ResourceMetrics().At(1).ScopeMetrics().At(0).Metrics()
for i := 0; i < metrics.Len(); i++ {
metrics.At(i).SetName(getTestMetricName(1, i))
}
splitMetricCount := 25
splitSize := splitMetricCount * dataPointCount
split := splitMetrics(splitSize, td)
assert.Equal(t, splitMetricCount, split.MetricCount())
assert.Equal(t, 40-splitMetricCount, td.MetricCount())
assert.Equal(t, 1, td.ResourceMetrics().Len())
assert.Equal(t, "test-metric-int-0-0", split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0).Name())
assert.Equal(t, "test-metric-int-0-19", split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(19).Name())
assert.Equal(t, "test-metric-int-1-0", split.ResourceMetrics().At(1).ScopeMetrics().At(0).Metrics().At(0).Name())
assert.Equal(t, "test-metric-int-1-4", split.ResourceMetrics().At(1).ScopeMetrics().At(0).Metrics().At(4).Name())
}
func TestSplitMetricsUneven(t *testing.T) {
md := testdata.GenerateMetrics(10)
metrics := md.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics()
dataPointCount := 2
for i := 0; i < metrics.Len(); i++ {
metrics.At(i).SetName(getTestMetricName(0, i))
assert.Equal(t, dataPointCount, metricDPC(metrics.At(i)))
}
splitSize := 9
split := splitMetrics(splitSize, md)
assert.Equal(t, 5, split.MetricCount())
assert.Equal(t, 6, md.MetricCount())
assert.Equal(t, "test-metric-int-0-0", split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0).Name())
assert.Equal(t, "test-metric-int-0-4", split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(4).Name())
split = splitMetrics(splitSize, md)
assert.Equal(t, 5, split.MetricCount())
assert.Equal(t, 1, md.MetricCount())
assert.Equal(t, "test-metric-int-0-4", split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0).Name())
assert.Equal(t, "test-metric-int-0-8", split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(4).Name())
split = splitMetrics(splitSize, md)
assert.Equal(t, 1, split.MetricCount())
assert.Equal(t, "test-metric-int-0-9", split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0).Name())
}
func TestSplitMetricsAllTypes(t *testing.T) {
md := testdata.GenerateMetricsAllTypes()
dataPointCount := 2
metrics := md.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics()
for i := 0; i < metrics.Len(); i++ {
metrics.At(i).SetName(getTestMetricName(0, i))
assert.Equal(t, dataPointCount, metricDPC(metrics.At(i)))
}
splitSize := 2
// Start with 7 metric types, and 2 points per-metric. Split out the first,
// and then split by 2 for the rest so that each metric is split in half.
// Verify that descriptors are preserved for all data types across splits.
split := splitMetrics(1, md)
assert.Equal(t, 1, split.MetricCount())
assert.Equal(t, 7, md.MetricCount())
gaugeInt := split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0)
assert.Equal(t, 1, gaugeInt.Gauge().DataPoints().Len())
assert.Equal(t, "test-metric-int-0-0", gaugeInt.Name())
split = splitMetrics(splitSize, md)
assert.Equal(t, 2, split.MetricCount())
assert.Equal(t, 6, md.MetricCount())
gaugeInt = split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0)
gaugeDouble := split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(1)
assert.Equal(t, 1, gaugeInt.Gauge().DataPoints().Len())
assert.Equal(t, "test-metric-int-0-0", gaugeInt.Name())
assert.Equal(t, 1, gaugeDouble.Gauge().DataPoints().Len())
assert.Equal(t, "test-metric-int-0-1", gaugeDouble.Name())
split = splitMetrics(splitSize, md)
assert.Equal(t, 2, split.MetricCount())
assert.Equal(t, 5, md.MetricCount())
gaugeDouble = split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0)
sumInt := split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(1)
assert.Equal(t, 1, gaugeDouble.Gauge().DataPoints().Len())
assert.Equal(t, "test-metric-int-0-1", gaugeDouble.Name())
assert.Equal(t, 1, sumInt.Sum().DataPoints().Len())
assert.Equal(t, pmetric.AggregationTemporalityCumulative, sumInt.Sum().AggregationTemporality())
assert.True(t, sumInt.Sum().IsMonotonic())
assert.Equal(t, "test-metric-int-0-2", sumInt.Name())
split = splitMetrics(splitSize, md)
assert.Equal(t, 2, split.MetricCount())
assert.Equal(t, 4, md.MetricCount())
sumInt = split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0)
sumDouble := split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(1)
assert.Equal(t, 1, sumInt.Sum().DataPoints().Len())
assert.Equal(t, pmetric.AggregationTemporalityCumulative, sumInt.Sum().AggregationTemporality())
assert.True(t, sumInt.Sum().IsMonotonic())
assert.Equal(t, "test-metric-int-0-2", sumInt.Name())
assert.Equal(t, 1, sumDouble.Sum().DataPoints().Len())
assert.Equal(t, pmetric.AggregationTemporalityCumulative, sumDouble.Sum().AggregationTemporality())
assert.True(t, sumDouble.Sum().IsMonotonic())
assert.Equal(t, "test-metric-int-0-3", sumDouble.Name())
split = splitMetrics(splitSize, md)
assert.Equal(t, 2, split.MetricCount())
assert.Equal(t, 3, md.MetricCount())
sumDouble = split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0)
histogram := split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(1)
assert.Equal(t, 1, sumDouble.Sum().DataPoints().Len())
assert.Equal(t, pmetric.AggregationTemporalityCumulative, sumDouble.Sum().AggregationTemporality())
assert.True(t, sumDouble.Sum().IsMonotonic())
assert.Equal(t, "test-metric-int-0-3", sumDouble.Name())
assert.Equal(t, 1, histogram.Histogram().DataPoints().Len())
assert.Equal(t, pmetric.AggregationTemporalityCumulative, histogram.Histogram().AggregationTemporality())
assert.Equal(t, "test-metric-int-0-4", histogram.Name())
split = splitMetrics(splitSize, md)
assert.Equal(t, 2, split.MetricCount())
assert.Equal(t, 2, md.MetricCount())
histogram = split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0)
exponentialHistogram := split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(1)
assert.Equal(t, 1, histogram.Histogram().DataPoints().Len())
assert.Equal(t, pmetric.AggregationTemporalityCumulative, histogram.Histogram().AggregationTemporality())
assert.Equal(t, "test-metric-int-0-4", histogram.Name())
assert.Equal(t, 1, exponentialHistogram.ExponentialHistogram().DataPoints().Len())
assert.Equal(t, pmetric.AggregationTemporalityDelta, exponentialHistogram.ExponentialHistogram().AggregationTemporality())
assert.Equal(t, "test-metric-int-0-5", exponentialHistogram.Name())
split = splitMetrics(splitSize, md)
assert.Equal(t, 2, split.MetricCount())
assert.Equal(t, 1, md.MetricCount())
exponentialHistogram = split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0)
summary := split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(1)
assert.Equal(t, 1, exponentialHistogram.ExponentialHistogram().DataPoints().Len())
assert.Equal(t, pmetric.AggregationTemporalityDelta, exponentialHistogram.ExponentialHistogram().AggregationTemporality())
assert.Equal(t, "test-metric-int-0-5", exponentialHistogram.Name())
assert.Equal(t, 1, summary.Summary().DataPoints().Len())
assert.Equal(t, "test-metric-int-0-6", summary.Name())
split = splitMetrics(splitSize, md)
summary = split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0)
assert.Equal(t, 1, summary.Summary().DataPoints().Len())
assert.Equal(t, "test-metric-int-0-6", summary.Name())
}
func TestSplitMetricsBatchSizeSmallerThanDataPointCount(t *testing.T) {
md := testdata.GenerateMetrics(2)
metrics := md.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics()
dataPointCount := 2
for i := 0; i < metrics.Len(); i++ {
metrics.At(i).SetName(getTestMetricName(0, i))
assert.Equal(t, dataPointCount, metricDPC(metrics.At(i)))
}
splitSize := 1
split := splitMetrics(splitSize, md)
splitMetric := split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0)
assert.Equal(t, 1, split.MetricCount())
assert.Equal(t, 2, md.MetricCount())
assert.Equal(t, "test-metric-int-0-0", splitMetric.Name())
split = splitMetrics(splitSize, md)
splitMetric = split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0)
assert.Equal(t, 1, split.MetricCount())
assert.Equal(t, 1, md.MetricCount())
assert.Equal(t, "test-metric-int-0-0", splitMetric.Name())
split = splitMetrics(splitSize, md)
splitMetric = split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0)
assert.Equal(t, 1, split.MetricCount())
assert.Equal(t, 1, md.MetricCount())
assert.Equal(t, "test-metric-int-0-1", splitMetric.Name())
split = splitMetrics(splitSize, md)
splitMetric = split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0)
assert.Equal(t, 1, split.MetricCount())
assert.Equal(t, 1, md.MetricCount())
assert.Equal(t, "test-metric-int-0-1", splitMetric.Name())
}
func TestSplitMetricsMultipleILM(t *testing.T) {
md := testdata.GenerateMetrics(20)
metrics := md.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics()
dataPointCount := metricDPC(metrics.At(0))
for i := 0; i < metrics.Len(); i++ {
metrics.At(i).SetName(getTestMetricName(0, i))
assert.Equal(t, dataPointCount, metricDPC(metrics.At(i)))
}
// add second index to ilm
md.ResourceMetrics().At(0).ScopeMetrics().At(0).
CopyTo(md.ResourceMetrics().At(0).ScopeMetrics().AppendEmpty())
// add a third index to ilm
md.ResourceMetrics().At(0).ScopeMetrics().At(0).
CopyTo(md.ResourceMetrics().At(0).ScopeMetrics().AppendEmpty())
metrics = md.ResourceMetrics().At(0).ScopeMetrics().At(2).Metrics()
for i := 0; i < metrics.Len(); i++ {
metrics.At(i).SetName(getTestMetricName(2, i))
}
splitMetricCount := 40
splitSize := splitMetricCount * dataPointCount
split := splitMetrics(splitSize, md)
assert.Equal(t, splitMetricCount, split.MetricCount())
assert.Equal(t, 20, md.MetricCount())
assert.Equal(t, "test-metric-int-0-0", split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0).Name())
assert.Equal(t, "test-metric-int-0-4", split.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(4).Name())
}
func TestSplitMetricsPreserveSchemaURLOnPartialSplit(t *testing.T) {
resourceSchemaURL := "https://test-resource-schema-url.com/"
scopeSchemaURL := "https://test-scope-schema-url.com/"
md := testdata.GenerateMetrics(2)
md.ResourceMetrics().At(0).SetSchemaUrl(resourceSchemaURL)
md.ResourceMetrics().At(0).ScopeMetrics().At(0).SetSchemaUrl(scopeSchemaURL)
splitSize := 1
split := splitMetrics(splitSize, md)
assert.Equal(t, resourceSchemaURL, split.ResourceMetrics().At(0).SchemaUrl())
assert.Equal(t, scopeSchemaURL, split.ResourceMetrics().At(0).ScopeMetrics().At(0).SchemaUrl())
}
================================================
FILE: processor/batchprocessor/splittraces.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package batchprocessor // import "go.opentelemetry.io/collector/processor/batchprocessor"
import (
"go.opentelemetry.io/collector/pdata/ptrace"
)
// splitTraces removes spans from the input trace and returns a new trace of the specified size.
func splitTraces(size int, src ptrace.Traces) ptrace.Traces {
if src.SpanCount() <= size {
return src
}
totalCopiedSpans := 0
dest := ptrace.NewTraces()
src.ResourceSpans().RemoveIf(func(srcRs ptrace.ResourceSpans) bool {
// If we are done skip everything else.
if totalCopiedSpans == size {
return false
}
// If it fully fits
srcRsSC := resourceSC(srcRs)
if (totalCopiedSpans + srcRsSC) <= size {
totalCopiedSpans += srcRsSC
srcRs.MoveTo(dest.ResourceSpans().AppendEmpty())
return true
}
destRs := dest.ResourceSpans().AppendEmpty()
srcRs.Resource().CopyTo(destRs.Resource())
destRs.SetSchemaUrl(srcRs.SchemaUrl())
srcRs.ScopeSpans().RemoveIf(func(srcIls ptrace.ScopeSpans) bool {
// If we are done skip everything else.
if totalCopiedSpans == size {
return false
}
// If possible to move all metrics do that.
srcIlsSC := srcIls.Spans().Len()
if size-totalCopiedSpans >= srcIlsSC {
totalCopiedSpans += srcIlsSC
srcIls.MoveTo(destRs.ScopeSpans().AppendEmpty())
return true
}
destIls := destRs.ScopeSpans().AppendEmpty()
srcIls.Scope().CopyTo(destIls.Scope())
destIls.SetSchemaUrl(srcIls.SchemaUrl())
srcIls.Spans().RemoveIf(func(srcSpan ptrace.Span) bool {
// If we are done skip everything else.
if totalCopiedSpans == size {
return false
}
srcSpan.MoveTo(destIls.Spans().AppendEmpty())
totalCopiedSpans++
return true
})
return false
})
return srcRs.ScopeSpans().Len() == 0
})
return dest
}
// resourceSC calculates the total number of spans in the ptrace.ResourceSpans.
func resourceSC(rs ptrace.ResourceSpans) (count int) {
for k := 0; k < rs.ScopeSpans().Len(); k++ {
count += rs.ScopeSpans().At(k).Spans().Len()
}
return count
}
================================================
FILE: processor/batchprocessor/splittraces_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package batchprocessor
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/pdata/testdata"
)
func TestSplitTraces_noop(t *testing.T) {
td := testdata.GenerateTraces(20)
splitSize := 40
split := splitTraces(splitSize, td)
assert.Equal(t, td, split)
i := 0
td.ResourceSpans().At(0).ScopeSpans().At(0).Spans().RemoveIf(func(ptrace.Span) bool {
i++
return i > 5
})
assert.Equal(t, td, split)
}
func TestSplitTraces(t *testing.T) {
td := testdata.GenerateTraces(20)
spans := td.ResourceSpans().At(0).ScopeSpans().At(0).Spans()
for i := 0; i < spans.Len(); i++ {
spans.At(i).SetName(getTestSpanName(0, i))
}
cp := ptrace.NewTraces()
cpSpans := cp.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans()
cpSpans.EnsureCapacity(5)
td.ResourceSpans().At(0).Resource().CopyTo(
cp.ResourceSpans().At(0).Resource())
td.ResourceSpans().At(0).ScopeSpans().At(0).Scope().CopyTo(
cp.ResourceSpans().At(0).ScopeSpans().At(0).Scope())
spans.At(0).CopyTo(cpSpans.AppendEmpty())
spans.At(1).CopyTo(cpSpans.AppendEmpty())
spans.At(2).CopyTo(cpSpans.AppendEmpty())
spans.At(3).CopyTo(cpSpans.AppendEmpty())
spans.At(4).CopyTo(cpSpans.AppendEmpty())
splitSize := 5
split := splitTraces(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-0", split.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0).Name())
assert.Equal(t, "test-span-0-4", split.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(4).Name())
split = splitTraces(splitSize, td)
assert.Equal(t, 10, td.SpanCount())
assert.Equal(t, "test-span-0-5", split.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0).Name())
assert.Equal(t, "test-span-0-9", split.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(4).Name())
split = splitTraces(splitSize, td)
assert.Equal(t, 5, td.SpanCount())
assert.Equal(t, "test-span-0-10", split.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0).Name())
assert.Equal(t, "test-span-0-14", split.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(4).Name())
split = splitTraces(splitSize, td)
assert.Equal(t, 5, td.SpanCount())
assert.Equal(t, "test-span-0-15", split.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0).Name())
assert.Equal(t, "test-span-0-19", split.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(4).Name())
}
func TestSplitTracesMultipleResourceSpans(t *testing.T) {
td := testdata.GenerateTraces(20)
spans := td.ResourceSpans().At(0).ScopeSpans().At(0).Spans()
for i := 0; i < spans.Len(); i++ {
spans.At(i).SetName(getTestSpanName(0, i))
}
// add second index to resource spans
testdata.GenerateTraces(20).
ResourceSpans().At(0).CopyTo(td.ResourceSpans().AppendEmpty())
spans = td.ResourceSpans().At(1).ScopeSpans().At(0).Spans()
for i := 0; i < spans.Len(); i++ {
spans.At(i).SetName(getTestSpanName(1, i))
}
splitSize := 5
split := splitTraces(splitSize, td)
assert.Equal(t, splitSize, split.SpanCount())
assert.Equal(t, 35, td.SpanCount())
assert.Equal(t, "test-span-0-0", split.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0).Name())
assert.Equal(t, "test-span-0-4", split.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(4).Name())
}
func TestSplitTracesMultipleResourceSpans_SplitSizeGreaterThanSpanSize(t *testing.T) {
td := testdata.GenerateTraces(20)
spans := td.ResourceSpans().At(0).ScopeSpans().At(0).Spans()
for i := 0; i < spans.Len(); i++ {
spans.At(i).SetName(getTestSpanName(0, i))
}
// add second index to resource spans
testdata.GenerateTraces(20).
ResourceSpans().At(0).CopyTo(td.ResourceSpans().AppendEmpty())
spans = td.ResourceSpans().At(1).ScopeSpans().At(0).Spans()
for i := 0; i < spans.Len(); i++ {
spans.At(i).SetName(getTestSpanName(1, i))
}
splitSize := 25
split := splitTraces(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-0-0", split.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0).Name())
assert.Equal(t, "test-span-0-19", split.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(19).Name())
assert.Equal(t, "test-span-1-0", split.ResourceSpans().At(1).ScopeSpans().At(0).Spans().At(0).Name())
assert.Equal(t, "test-span-1-4", split.ResourceSpans().At(1).ScopeSpans().At(0).Spans().At(4).Name())
}
func TestSplitTracesMultipleILS(t *testing.T) {
td := testdata.GenerateTraces(20)
spans := td.ResourceSpans().At(0).ScopeSpans().At(0).Spans()
for i := 0; i < spans.Len(); i++ {
spans.At(i).SetName(getTestSpanName(0, i))
}
// add second index to ILS
td.ResourceSpans().At(0).ScopeSpans().At(0).
CopyTo(td.ResourceSpans().At(0).ScopeSpans().AppendEmpty())
spans = td.ResourceSpans().At(0).ScopeSpans().At(1).Spans()
for i := 0; i < spans.Len(); i++ {
spans.At(i).SetName(getTestSpanName(1, i))
}
// add third index to ILS
td.ResourceSpans().At(0).ScopeSpans().At(0).
CopyTo(td.ResourceSpans().At(0).ScopeSpans().AppendEmpty())
spans = td.ResourceSpans().At(0).ScopeSpans().At(2).Spans()
for i := 0; i < spans.Len(); i++ {
spans.At(i).SetName(getTestSpanName(2, i))
}
splitSize := 40
split := splitTraces(splitSize, td)
assert.Equal(t, splitSize, split.SpanCount())
assert.Equal(t, 20, td.SpanCount())
assert.Equal(t, "test-span-0-0", split.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0).Name())
assert.Equal(t, "test-span-0-4", split.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(4).Name())
}
func TestSplitTracesPreserveSchemaURLOnPartialSplit(t *testing.T) {
resourceSchemaURL := "https://test-resource-schema-url.com/"
scopeSchemaURL := "https://test-scope-schema-url.com/"
td := testdata.GenerateTraces(2)
td.ResourceSpans().At(0).SetSchemaUrl(resourceSchemaURL)
td.ResourceSpans().At(0).ScopeSpans().At(0).SetSchemaUrl(scopeSchemaURL)
splitSize := 1
split := splitTraces(splitSize, td)
assert.Equal(t, resourceSchemaURL, split.ResourceSpans().At(0).SchemaUrl())
assert.Equal(t, scopeSchemaURL, split.ResourceSpans().At(0).ScopeSpans().At(0).SchemaUrl())
}
================================================
FILE: processor/batchprocessor/testdata/config.yaml
================================================
timeout: 10s
send_batch_size: 10000
send_batch_max_size: 11000
================================================
FILE: processor/go.mod
================================================
module go.opentelemetry.io/collector/processor
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/consumer v1.54.0
go.opentelemetry.io/collector/consumer/consumertest v0.148.0
go.opentelemetry.io/collector/internal/componentalias v0.148.0
go.opentelemetry.io/collector/pipeline v1.54.0
go.uber.org/goleak v1.3.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/pdata v1.54.0 // indirect
go.opentelemetry.io/collector/pdata/pprofile v0.148.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/component => ../component
replace go.opentelemetry.io/collector/consumer => ../consumer
replace go.opentelemetry.io/collector/pdata => ../pdata
replace go.opentelemetry.io/collector/pdata/testdata => ../pdata/testdata
replace go.opentelemetry.io/collector/pdata/pprofile => ../pdata/pprofile
replace go.opentelemetry.io/collector/consumer/xconsumer => ../consumer/xconsumer
replace go.opentelemetry.io/collector/consumer/consumertest => ../consumer/consumertest
replace go.opentelemetry.io/collector/pipeline => ../pipeline
replace go.opentelemetry.io/collector/featuregate => ../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../internal/componentalias
================================================
FILE: processor/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: processor/internal/err.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/processor/internal"
import (
"fmt"
"go.opentelemetry.io/collector/component"
)
func ErrIDMismatch(id component.ID, typ component.Type) error {
return fmt.Errorf("component type mismatch: component ID %q does not have type %q", id, typ)
}
================================================
FILE: processor/internal/obsmetrics.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/processor/internal"
const (
MetricNameSep = "_"
// ProcessorKey is the key used to identify processors in metrics and traces.
ProcessorKey = "processor"
ProcessorMetricPrefix = ProcessorKey + MetricNameSep
)
================================================
FILE: processor/memorylimiterprocessor/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: processor/memorylimiterprocessor/README.md
================================================
# Memory Limiter Processor
| Status | |
| ------------- |-----------|
| Stability | [alpha]: profiles |
| | [beta]: traces, metrics, logs |
| Distributions | [core], [contrib], [k8s] |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Aprocessor%2Fmemorylimiter) [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aclosed+is%3Aissue+label%3Aprocessor%2Fmemorylimiter) |
[alpha]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#alpha
[beta]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#beta
[core]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
[k8s]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-k8s
## Overview
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.
## Functionality
The memory limiter processor performs periodic checks of memory
usage and will begin refusing data and forcing GC to reduce
memory consumption when defined limits have been exceeded.
The processor uses soft and hard memory limits. The hard limit is defined via the
`limit_mib` configuration option, and is always above or equal
to the soft limit. The difference between the soft limit and hard limit is defined via
the `spike_limit_mib` configuration option.
The processor will enter memory limited mode and will start refusing the data when
memory usage exceeds the soft limit. This is done by returning errors to the preceding component
in the pipeline that made the ConsumeLogs/Trace/Metrics function call.
> Warning: Incoming data can consume additional memory in a Collector before the
> memory limiter processor is able to reject it. Be sure to consider this when
> setting your limits, particularly for non-OTLP receivers.
>
> See for
> more.
In memory limited mode the error returned by ConsumeLogs/Trace/Metrics function is a
non-permanent error. When receivers see this error they are expected to retry sending
the same data. The receivers may also apply backpressure to their own data sources
in order to slow the inflow of data into the Collector, and to allow memory usage
to go below the set limits.
> Warning: Data will be permanently lost if the component preceding the memory limiter
> in the telemetry pipeline does not correctly retry sending data after it has
> been refused by the memory limiter.
> We consider such components to be incorrectly implemented.
When the memory usage is above the hard limit the processor will additionally
force garbage collection to be performed.
Normal operation is resumed when memory usage drops below the soft limit, meaning data
will no longer be refused and the processor won't force garbage collection to
be performed.
## Best Practices
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 may
eventually result in dropped data since the receivers may not be able to
retry the data indefinitely.
It is highly recommended to configure the `GOMEMLIMIT`
[environment variable](https://pkg.go.dev/runtime#hdr-Environment_Variables) as well
as the `memory_limiter` processor on every collector. `GOMEMLIMIT` should be set to
80% of the hard memory limit of your collector. For the `memory_limiter` processor, the
best practice is to add it as the first processor in a pipeline. 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.
The value of the `spike_limit_mib` configuration option should be selected in a way
that ensures that memory usage cannot increase by more than this value within a single
memory check interval. 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.
It's recommended to coordinate the hard memory limit with the host environment
of the Collector. As always, you know your environment best, so use your own
judgement to determine the best configuration for your situation. However, the
following points are worth considering when configuring the processor:
For containerized environments or other similar environments that support
setting memory restrictions on the Collector, `limit_percentage` should
generally be used, which allows the host environment to determine the
Collector's max memory allocation without tying the Collector's own config to
its deployment config. The percentage should be large enough to use the majority
of the Collector's allocated memory, but not so much that it risks running out
of memory.
For bare metal or virtualized environments where the Collector's memory is not
constrained by the host environment, it is recommended to use `limit_mib` for
environments where you know the rough data throughput and memory consumption you
expect for a given Collector, regardless of the memory capacity of the machine
it runs on. If the Collector's memory consumption is expected to scale with its
host machine's memory capacity, `limit_percentage` may be more appropriate.
## Configuration
Please refer to [config.go](../../internal/memorylimiter/config.go) for the config spec.
The following configuration options are available. Note that one of `limit_mib`
or `limit_percentage` must be set.
- `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 = 20% of `limit_percentage`): 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`.
Examples:
```yaml
processors:
memory_limiter:
check_interval: 1s
limit_mib: 4000
spike_limit_mib: 800
```
- Hard limit will be set to **4000 MiB**.
- Soft limit will be set to 4000 - 800 = **3200 MiB**.
```yaml
processors:
memory_limiter:
check_interval: 1s
limit_percentage: 80
spike_limit_percentage: 15
```
On a machine with 1000 MiB total memory available:
- Hard limit will be set to 1000 * 0.80 = **800 MiB**.
- Soft limit will be set to 1000 * 0.80 - 1000 * 0.15 = 1000 * 0.65 = **650 MiB**.
================================================
FILE: processor/memorylimiterprocessor/config.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package memorylimiterprocessor // import "go.opentelemetry.io/collector/processor/memorylimiterprocessor"
import "go.opentelemetry.io/collector/internal/memorylimiter"
type Config = memorylimiter.Config
================================================
FILE: processor/memorylimiterprocessor/documentation.md
================================================
[comment]: <> (Code generated by mdatagen. DO NOT EDIT.)
# memory_limiter
## Internal Telemetry
The following telemetry is emitted by this component.
### otelcol_processor_accepted_log_records
Number of log records successfully pushed into the next component in the pipeline.
> **Deprecated since 0.110.0**
> This metric is deprecated
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {record} | Sum | Int | true | Deprecated since 0.110.0 |
**Deprecation note**: This metric is deprecated
### otelcol_processor_accepted_metric_points
Number of metric points successfully pushed into the next component in the pipeline.
> **Deprecated since 0.110.0**
> This metric is deprecated
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {datapoint} | Sum | Int | true | Deprecated since 0.110.0 |
**Deprecation note**: This metric is deprecated
### otelcol_processor_accepted_spans
Number of spans successfully pushed into the next component in the pipeline.
> **Deprecated since 0.110.0**
> This metric is deprecated
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {span} | Sum | Int | true | Deprecated since 0.110.0 |
**Deprecation note**: This metric is deprecated
### otelcol_processor_refused_log_records
Number of log records that were rejected by the next component in the pipeline.
> **Deprecated since 0.110.0**
> This metric is deprecated
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {record} | Sum | Int | true | Deprecated since 0.110.0 |
**Deprecation note**: This metric is deprecated
### otelcol_processor_refused_metric_points
Number of metric points that were rejected by the next component in the pipeline.
> **Deprecated since 0.110.0**
> This metric is deprecated
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {datapoint} | Sum | Int | true | Deprecated since 0.110.0 |
**Deprecation note**: This metric is deprecated
### otelcol_processor_refused_spans
Number of spans that were rejected by the next component in the pipeline.
> **Deprecated since 0.110.0**
> This metric is deprecated
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {span} | Sum | Int | true | Deprecated since 0.110.0 |
**Deprecation note**: This metric is deprecated
================================================
FILE: processor/memorylimiterprocessor/factory.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:generate mdatagen metadata.yaml
package memorylimiterprocessor // import "go.opentelemetry.io/collector/processor/memorylimiterprocessor"
import (
"context"
"sync"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/internal/memorylimiter"
"go.opentelemetry.io/collector/internal/telemetry"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/memorylimiterprocessor/internal/metadata"
"go.opentelemetry.io/collector/processor/processorhelper"
"go.opentelemetry.io/collector/processor/processorhelper/xprocessorhelper"
"go.opentelemetry.io/collector/processor/xprocessor"
)
var processorCapabilities = consumer.Capabilities{MutatesData: false}
type factory struct {
// memoryLimiters stores memoryLimiter instances with unique configs that multiple processors can reuse.
// This avoids running multiple memory checks (ie: GC) for every processor using the same processor config.
memoryLimiters map[component.Config]*memoryLimiterProcessor
lock sync.Mutex
}
// NewFactory returns a new factory for the Memory Limiter processor.
func NewFactory() xprocessor.Factory {
f := &factory{
memoryLimiters: map[component.Config]*memoryLimiterProcessor{},
}
return xprocessor.NewFactory(
metadata.Type,
createDefaultConfig,
xprocessor.WithTraces(f.createTraces, metadata.TracesStability),
xprocessor.WithMetrics(f.createMetrics, metadata.MetricsStability),
xprocessor.WithLogs(f.createLogs, metadata.LogsStability),
xprocessor.WithProfiles(f.createProfiles, metadata.ProfilesStability))
}
// CreateDefaultConfig creates the default configuration for processor. Notice
// that the default configuration is expected to fail for this processor.
func createDefaultConfig() component.Config {
return memorylimiter.NewDefaultConfig()
}
func (f *factory) createTraces(
ctx context.Context,
set processor.Settings,
cfg component.Config,
nextConsumer consumer.Traces,
) (processor.Traces, error) {
memLimiter, err := f.getMemoryLimiter(set, cfg)
if err != nil {
return nil, err
}
return processorhelper.NewTraces(ctx, set, cfg, nextConsumer,
memLimiter.processTraces,
processorhelper.WithCapabilities(processorCapabilities),
processorhelper.WithStart(memLimiter.start),
processorhelper.WithShutdown(memLimiter.shutdown))
}
func (f *factory) createMetrics(
ctx context.Context,
set processor.Settings,
cfg component.Config,
nextConsumer consumer.Metrics,
) (processor.Metrics, error) {
memLimiter, err := f.getMemoryLimiter(set, cfg)
if err != nil {
return nil, err
}
return processorhelper.NewMetrics(ctx, set, cfg, nextConsumer,
memLimiter.processMetrics,
processorhelper.WithCapabilities(processorCapabilities),
processorhelper.WithStart(memLimiter.start),
processorhelper.WithShutdown(memLimiter.shutdown))
}
func (f *factory) createLogs(
ctx context.Context,
set processor.Settings,
cfg component.Config,
nextConsumer consumer.Logs,
) (processor.Logs, error) {
memLimiter, err := f.getMemoryLimiter(set, cfg)
if err != nil {
return nil, err
}
return processorhelper.NewLogs(ctx, set, cfg, nextConsumer,
memLimiter.processLogs,
processorhelper.WithCapabilities(processorCapabilities),
processorhelper.WithStart(memLimiter.start),
processorhelper.WithShutdown(memLimiter.shutdown))
}
func (f *factory) createProfiles(
ctx context.Context,
set processor.Settings,
cfg component.Config,
nextConsumer xconsumer.Profiles,
) (xprocessor.Profiles, error) {
memLimiter, err := f.getMemoryLimiter(set, cfg)
if err != nil {
return nil, err
}
return xprocessorhelper.NewProfiles(
ctx,
set,
cfg,
nextConsumer,
memLimiter.processProfiles,
xprocessorhelper.WithCapabilities(processorCapabilities),
xprocessorhelper.WithStart(memLimiter.start),
xprocessorhelper.WithShutdown(memLimiter.shutdown),
)
}
// getMemoryLimiter checks if we have a cached memoryLimiter with a specific config,
// otherwise initialize and add one to the store.
func (f *factory) getMemoryLimiter(set processor.Settings, cfg component.Config) (*memoryLimiterProcessor, error) {
f.lock.Lock()
defer f.lock.Unlock()
if memLimiter, ok := f.memoryLimiters[cfg]; ok {
return memLimiter, nil
}
set.TelemetrySettings = telemetry.DropInjectedAttributes(
set.TelemetrySettings,
telemetry.SignalKey,
telemetry.PipelineIDKey,
telemetry.ComponentIDKey,
)
set.Logger.Debug("created singleton logger")
memLimiter, err := newMemoryLimiterProcessor(set, cfg.(*Config))
if err != nil {
return nil, err
}
f.memoryLimiters[cfg] = memLimiter
return memLimiter, nil
}
================================================
FILE: processor/memorylimiterprocessor/factory_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package memorylimiterprocessor
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/internal/telemetry"
"go.opentelemetry.io/collector/internal/telemetry/telemetrytest"
"go.opentelemetry.io/collector/processor/processortest"
)
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, componenttest.CheckConfigStruct(cfg))
}
func TestCreateProcessor(t *testing.T) {
factory := NewFactory()
require.NotNil(t, factory)
cfg := factory.CreateDefaultConfig()
// Create processor with a valid config.
pCfg := cfg.(*Config)
pCfg.MemoryLimitMiB = 5722
pCfg.MemorySpikeLimitMiB = 1907
pCfg.CheckInterval = 100 * time.Millisecond
set := processortest.NewNopSettings(factory.Type())
var droppedAttrs []string
set.Logger = telemetrytest.MockInjectorLogger(set.Logger, &droppedAttrs)
tp, err := factory.CreateTraces(context.Background(), set, cfg, consumertest.NewNop())
require.NoError(t, err)
assert.NotNil(t, tp)
// test if we can shutdown a monitoring routine that has not started
require.NoError(t, tp.Shutdown(context.Background()))
require.NoError(t, tp.Start(context.Background(), componenttest.NewNopHost()))
mp, err := factory.CreateMetrics(context.Background(), set, cfg, consumertest.NewNop())
require.NoError(t, err)
assert.NotNil(t, mp)
require.NoError(t, mp.Start(context.Background(), componenttest.NewNopHost()))
lp, err := factory.CreateLogs(context.Background(), set, cfg, consumertest.NewNop())
require.NoError(t, err)
assert.NotNil(t, lp)
require.NoError(t, lp.Start(context.Background(), componenttest.NewNopHost()))
pp, err := factory.CreateProfiles(context.Background(), set, cfg, consumertest.NewNop())
require.NoError(t, err)
assert.NotNil(t, pp)
require.NoError(t, pp.Start(context.Background(), componenttest.NewNopHost()))
// Test that we've dropped the relevant injected attributes exactly once
assert.ElementsMatch(t, droppedAttrs, []string{
telemetry.SignalKey, telemetry.ComponentIDKey, telemetry.PipelineIDKey,
})
assert.NoError(t, lp.Shutdown(context.Background()))
assert.NoError(t, tp.Shutdown(context.Background()))
assert.NoError(t, mp.Shutdown(context.Background()))
assert.NoError(t, pp.Shutdown(context.Background()))
// verify that no monitoring routine is running
require.NoError(t, tp.Shutdown(context.Background()))
// start and shutdown a new monitoring routine
assert.NoError(t, lp.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, lp.Shutdown(context.Background()))
// calling it again should throw no error
require.NoError(t, lp.Shutdown(context.Background()))
}
================================================
FILE: processor/memorylimiterprocessor/generated_component_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package memorylimiterprocessor
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/confmap/confmaptest"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/processortest"
"go.opentelemetry.io/collector/processor/xprocessor"
)
var typ = component.MustNewType("memory_limiter")
func TestComponentFactoryType(t *testing.T) {
require.Equal(t, typ, NewFactory().Type())
}
func TestComponentConfigStruct(t *testing.T) {
require.NoError(t, componenttest.CheckConfigStruct(NewFactory().CreateDefaultConfig()))
}
func TestComponentLifecycle(t *testing.T) {
factory := NewFactory()
tests := []struct {
createFn func(ctx context.Context, set processor.Settings, cfg component.Config) (component.Component, error)
name string
}{
{
name: "logs",
createFn: func(ctx context.Context, set processor.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateLogs(ctx, set, cfg, consumertest.NewNop())
},
},
{
name: "metrics",
createFn: func(ctx context.Context, set processor.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateMetrics(ctx, set, cfg, consumertest.NewNop())
},
},
{
name: "traces",
createFn: func(ctx context.Context, set processor.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateTraces(ctx, set, cfg, consumertest.NewNop())
},
},
{
name: "profiles",
createFn: func(ctx context.Context, set processor.Settings, cfg component.Config) (component.Component, error) {
return factory.(xprocessor.Factory).CreateProfiles(ctx, set, cfg, consumertest.NewNop())
},
},
}
cm, err := confmaptest.LoadConf("metadata.yaml")
require.NoError(t, err)
cfg := factory.CreateDefaultConfig()
sub, err := cm.Sub("tests::config")
require.NoError(t, err)
require.NoError(t, sub.Unmarshal(&cfg))
for _, tt := range tests {
t.Run(tt.name+"-shutdown", func(t *testing.T) {
c, err := tt.createFn(context.Background(), processortest.NewNopSettings(typ), cfg)
require.NoError(t, err)
err = c.Shutdown(context.Background())
require.NoError(t, err)
})
t.Run(tt.name+"-lifecycle", func(t *testing.T) {
c, err := tt.createFn(context.Background(), processortest.NewNopSettings(typ), cfg)
require.NoError(t, err)
host := newMdatagenNopHost()
err = c.Start(context.Background(), host)
require.NoError(t, err)
require.NotPanics(t, func() {
switch tt.name {
case "logs":
e, ok := c.(processor.Logs)
require.True(t, ok)
logs := generateLifecycleTestLogs()
if !e.Capabilities().MutatesData {
logs.MarkReadOnly()
}
err = e.ConsumeLogs(context.Background(), logs)
case "metrics":
e, ok := c.(processor.Metrics)
require.True(t, ok)
metrics := generateLifecycleTestMetrics()
if !e.Capabilities().MutatesData {
metrics.MarkReadOnly()
}
err = e.ConsumeMetrics(context.Background(), metrics)
case "traces":
e, ok := c.(processor.Traces)
require.True(t, ok)
traces := generateLifecycleTestTraces()
if !e.Capabilities().MutatesData {
traces.MarkReadOnly()
}
err = e.ConsumeTraces(context.Background(), traces)
}
})
require.NoError(t, err)
err = c.Shutdown(context.Background())
require.NoError(t, err)
})
}
}
func generateLifecycleTestLogs() plog.Logs {
logs := plog.NewLogs()
rl := logs.ResourceLogs().AppendEmpty()
rl.Resource().Attributes().PutStr("resource", "R1")
l := rl.ScopeLogs().AppendEmpty().LogRecords().AppendEmpty()
l.Body().SetStr("test log message")
l.SetTimestamp(pcommon.NewTimestampFromTime(time.Now()))
return logs
}
func generateLifecycleTestMetrics() pmetric.Metrics {
metrics := pmetric.NewMetrics()
rm := metrics.ResourceMetrics().AppendEmpty()
rm.Resource().Attributes().PutStr("resource", "R1")
m := rm.ScopeMetrics().AppendEmpty().Metrics().AppendEmpty()
m.SetName("test_metric")
dp := m.SetEmptyGauge().DataPoints().AppendEmpty()
dp.Attributes().PutStr("test_attr", "value_1")
dp.SetIntValue(123)
dp.SetTimestamp(pcommon.NewTimestampFromTime(time.Now()))
return metrics
}
func generateLifecycleTestTraces() ptrace.Traces {
traces := ptrace.NewTraces()
rs := traces.ResourceSpans().AppendEmpty()
rs.Resource().Attributes().PutStr("resource", "R1")
span := rs.ScopeSpans().AppendEmpty().Spans().AppendEmpty()
span.Attributes().PutStr("test_attr", "value_1")
span.SetName("test_span")
span.SetStartTimestamp(pcommon.NewTimestampFromTime(time.Now().Add(-1 * time.Second)))
span.SetEndTimestamp(pcommon.NewTimestampFromTime(time.Now()))
return traces
}
var _ component.Host = (*mdatagenNopHost)(nil)
type mdatagenNopHost struct{}
func newMdatagenNopHost() component.Host {
return &mdatagenNopHost{}
}
func (mnh *mdatagenNopHost) GetExtensions() map[component.ID]component.Component {
return nil
}
func (mnh *mdatagenNopHost) GetFactory(_ component.Kind, _ component.Type) component.Factory {
return nil
}
================================================
FILE: processor/memorylimiterprocessor/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package memorylimiterprocessor
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: processor/memorylimiterprocessor/go.mod
================================================
module go.opentelemetry.io/collector/processor/memorylimiterprocessor
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.opentelemetry.io/collector/confmap v1.54.0
go.opentelemetry.io/collector/consumer v1.54.0
go.opentelemetry.io/collector/consumer/consumererror v0.148.0
go.opentelemetry.io/collector/consumer/consumertest v0.148.0
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0
go.opentelemetry.io/collector/internal/memorylimiter v0.148.0
go.opentelemetry.io/collector/internal/telemetry v0.148.0
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/collector/pdata/pprofile v0.148.0
go.opentelemetry.io/collector/pipeline v1.54.0
go.opentelemetry.io/collector/pipeline/xpipeline v0.148.0
go.opentelemetry.io/collector/processor v1.54.0
go.opentelemetry.io/collector/processor/processorhelper v0.148.0
go.opentelemetry.io/collector/processor/processorhelper/xprocessorhelper v0.148.0
go.opentelemetry.io/collector/processor/processortest v0.148.0
go.opentelemetry.io/collector/processor/xprocessor v0.148.0
go.opentelemetry.io/otel v1.42.0
go.opentelemetry.io/otel/metric v1.42.0
go.opentelemetry.io/otel/sdk/metric v1.42.0
go.opentelemetry.io/otel/trace v1.42.0
go.uber.org/goleak v1.3.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/ebitengine/purego v0.10.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/shirou/gopsutil/v4 v4.26.2 // indirect
github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.11.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/component/componentstatus v0.148.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
go.opentelemetry.io/collector/pdata/testdata v0.148.0 // indirect
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/sys v0.41.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/processor => ../
replace go.opentelemetry.io/collector/processor/processortest => ../processortest
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest
replace go.opentelemetry.io/collector/confmap => ../../confmap
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/pdata/testdata => ../../pdata/testdata
replace go.opentelemetry.io/collector/consumer => ../../consumer
retract (
v0.76.0 // Depends on retracted pdata v1.0.0-rc10 module, use v0.76.1
v0.69.0 // Release failed, use v0.69.1
)
replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile
replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer
replace go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest
replace go.opentelemetry.io/collector/component/componentstatus => ../../component/componentstatus
replace go.opentelemetry.io/collector/processor/xprocessor => ../xprocessor
replace go.opentelemetry.io/collector/pipeline => ../../pipeline
replace go.opentelemetry.io/collector/internal/memorylimiter => ../../internal/memorylimiter
replace go.opentelemetry.io/collector/internal/telemetry => ../../internal/telemetry
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/consumer/consumererror => ../../consumer/consumererror
replace go.opentelemetry.io/collector/processor/processorhelper/xprocessorhelper => ../processorhelper/xprocessorhelper
replace go.opentelemetry.io/collector/processor/processorhelper => ../processorhelper
replace go.opentelemetry.io/collector/pipeline/xpipeline => ../../pipeline/xpipeline
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
================================================
FILE: processor/memorylimiterprocessor/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/shirou/gopsutil/v4 v4.26.2 h1:X8i6sicvUFih4BmYIGT1m2wwgw2VG9YgrDTi7cIRGUI=
github.com/shirou/gopsutil/v4 v4.26.2/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: processor/memorylimiterprocessor/internal/metadata/generated_status.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/component"
)
var (
Type = component.MustNewType("memory_limiter")
ScopeName = "go.opentelemetry.io/collector/processor/memorylimiterprocessor"
)
const (
ProfilesStability = component.StabilityLevelAlpha
TracesStability = component.StabilityLevelBeta
MetricsStability = component.StabilityLevelBeta
LogsStability = component.StabilityLevelBeta
)
================================================
FILE: processor/memorylimiterprocessor/internal/metadata/generated_telemetry.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"errors"
"sync"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/collector/component"
)
func Meter(settings component.TelemetrySettings) metric.Meter {
return settings.MeterProvider.Meter("go.opentelemetry.io/collector/processor/memorylimiterprocessor")
}
func Tracer(settings component.TelemetrySettings) trace.Tracer {
return settings.TracerProvider.Tracer("go.opentelemetry.io/collector/processor/memorylimiterprocessor")
}
// TelemetryBuilder provides an interface for components to report telemetry
// as defined in metadata and user config.
type TelemetryBuilder struct {
meter metric.Meter
mu sync.Mutex
registrations []metric.Registration
ProcessorAcceptedLogRecords metric.Int64Counter
ProcessorAcceptedMetricPoints metric.Int64Counter
ProcessorAcceptedSpans metric.Int64Counter
ProcessorRefusedLogRecords metric.Int64Counter
ProcessorRefusedMetricPoints metric.Int64Counter
ProcessorRefusedSpans metric.Int64Counter
}
// TelemetryBuilderOption applies changes to default builder.
type TelemetryBuilderOption interface {
apply(*TelemetryBuilder)
}
type telemetryBuilderOptionFunc func(mb *TelemetryBuilder)
func (tbof telemetryBuilderOptionFunc) apply(mb *TelemetryBuilder) {
tbof(mb)
}
// Shutdown unregister all registered callbacks for async instruments.
func (builder *TelemetryBuilder) Shutdown() {
builder.mu.Lock()
defer builder.mu.Unlock()
for _, reg := range builder.registrations {
reg.Unregister()
}
}
// NewTelemetryBuilder provides a struct with methods to update all internal telemetry
// for a component
func NewTelemetryBuilder(settings component.TelemetrySettings, options ...TelemetryBuilderOption) (*TelemetryBuilder, error) {
builder := TelemetryBuilder{}
for _, op := range options {
op.apply(&builder)
}
builder.meter = Meter(settings)
var err, errs error
builder.ProcessorAcceptedLogRecords, err = builder.meter.Int64Counter(
"otelcol_processor_accepted_log_records",
metric.WithDescription("Number of log records successfully pushed into the next component in the pipeline. [Deprecated]"),
metric.WithUnit("{record}"),
)
errs = errors.Join(errs, err)
builder.ProcessorAcceptedMetricPoints, err = builder.meter.Int64Counter(
"otelcol_processor_accepted_metric_points",
metric.WithDescription("Number of metric points successfully pushed into the next component in the pipeline. [Deprecated]"),
metric.WithUnit("{datapoint}"),
)
errs = errors.Join(errs, err)
builder.ProcessorAcceptedSpans, err = builder.meter.Int64Counter(
"otelcol_processor_accepted_spans",
metric.WithDescription("Number of spans successfully pushed into the next component in the pipeline. [Deprecated]"),
metric.WithUnit("{span}"),
)
errs = errors.Join(errs, err)
builder.ProcessorRefusedLogRecords, err = builder.meter.Int64Counter(
"otelcol_processor_refused_log_records",
metric.WithDescription("Number of log records that were rejected by the next component in the pipeline. [Deprecated]"),
metric.WithUnit("{record}"),
)
errs = errors.Join(errs, err)
builder.ProcessorRefusedMetricPoints, err = builder.meter.Int64Counter(
"otelcol_processor_refused_metric_points",
metric.WithDescription("Number of metric points that were rejected by the next component in the pipeline. [Deprecated]"),
metric.WithUnit("{datapoint}"),
)
errs = errors.Join(errs, err)
builder.ProcessorRefusedSpans, err = builder.meter.Int64Counter(
"otelcol_processor_refused_spans",
metric.WithDescription("Number of spans that were rejected by the next component in the pipeline. [Deprecated]"),
metric.WithUnit("{span}"),
)
errs = errors.Join(errs, err)
return &builder, errs
}
================================================
FILE: processor/memorylimiterprocessor/internal/metadata/generated_telemetry_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/metric"
embeddedmetric "go.opentelemetry.io/otel/metric/embedded"
noopmetric "go.opentelemetry.io/otel/metric/noop"
"go.opentelemetry.io/otel/trace"
embeddedtrace "go.opentelemetry.io/otel/trace/embedded"
nooptrace "go.opentelemetry.io/otel/trace/noop"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
)
type mockMeter struct {
noopmetric.Meter
name string
}
type mockMeterProvider struct {
embeddedmetric.MeterProvider
}
func (m mockMeterProvider) Meter(name string, opts ...metric.MeterOption) metric.Meter {
return mockMeter{name: name}
}
type mockTracer struct {
nooptrace.Tracer
name string
}
type mockTracerProvider struct {
embeddedtrace.TracerProvider
}
func (m mockTracerProvider) Tracer(name string, opts ...trace.TracerOption) trace.Tracer {
return mockTracer{name: name}
}
func TestProviders(t *testing.T) {
set := component.TelemetrySettings{
MeterProvider: mockMeterProvider{},
TracerProvider: mockTracerProvider{},
}
meter := Meter(set)
if m, ok := meter.(mockMeter); ok {
require.Equal(t, "go.opentelemetry.io/collector/processor/memorylimiterprocessor", m.name)
} else {
require.Fail(t, "returned Meter not mockMeter")
}
tracer := Tracer(set)
if m, ok := tracer.(mockTracer); ok {
require.Equal(t, "go.opentelemetry.io/collector/processor/memorylimiterprocessor", m.name)
} else {
require.Fail(t, "returned Meter not mockTracer")
}
}
func TestNewTelemetryBuilder(t *testing.T) {
set := componenttest.NewNopTelemetrySettings()
applied := false
_, err := NewTelemetryBuilder(set, telemetryBuilderOptionFunc(func(b *TelemetryBuilder) {
applied = true
}))
require.NoError(t, err)
require.True(t, applied)
}
================================================
FILE: processor/memorylimiterprocessor/internal/metadatatest/generated_telemetrytest.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadatatest
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/processortest"
)
func NewSettings(tt *componenttest.Telemetry) processor.Settings {
set := processortest.NewNopSettings(processortest.NopType)
set.ID = component.NewID(component.MustNewType("memory_limiter"))
set.TelemetrySettings = tt.NewTelemetrySettings()
return set
}
func AssertEqualProcessorAcceptedLogRecords(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_processor_accepted_log_records",
Description: "Number of log records successfully pushed into the next component in the pipeline. [Deprecated]",
Unit: "{record}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_processor_accepted_log_records")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualProcessorAcceptedMetricPoints(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_processor_accepted_metric_points",
Description: "Number of metric points successfully pushed into the next component in the pipeline. [Deprecated]",
Unit: "{datapoint}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_processor_accepted_metric_points")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualProcessorAcceptedSpans(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_processor_accepted_spans",
Description: "Number of spans successfully pushed into the next component in the pipeline. [Deprecated]",
Unit: "{span}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_processor_accepted_spans")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualProcessorRefusedLogRecords(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_processor_refused_log_records",
Description: "Number of log records that were rejected by the next component in the pipeline. [Deprecated]",
Unit: "{record}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_processor_refused_log_records")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualProcessorRefusedMetricPoints(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_processor_refused_metric_points",
Description: "Number of metric points that were rejected by the next component in the pipeline. [Deprecated]",
Unit: "{datapoint}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_processor_refused_metric_points")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualProcessorRefusedSpans(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_processor_refused_spans",
Description: "Number of spans that were rejected by the next component in the pipeline. [Deprecated]",
Unit: "{span}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_processor_refused_spans")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
================================================
FILE: processor/memorylimiterprocessor/internal/metadatatest/generated_telemetrytest_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadatatest
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/processor/memorylimiterprocessor/internal/metadata"
)
func TestSetupTelemetry(t *testing.T) {
testTel := componenttest.NewTelemetry()
tb, err := metadata.NewTelemetryBuilder(testTel.NewTelemetrySettings())
require.NoError(t, err)
defer tb.Shutdown()
tb.ProcessorAcceptedLogRecords.Add(context.Background(), 1)
tb.ProcessorAcceptedMetricPoints.Add(context.Background(), 1)
tb.ProcessorAcceptedSpans.Add(context.Background(), 1)
tb.ProcessorRefusedLogRecords.Add(context.Background(), 1)
tb.ProcessorRefusedMetricPoints.Add(context.Background(), 1)
tb.ProcessorRefusedSpans.Add(context.Background(), 1)
AssertEqualProcessorAcceptedLogRecords(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualProcessorAcceptedMetricPoints(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualProcessorAcceptedSpans(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualProcessorRefusedLogRecords(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualProcessorRefusedMetricPoints(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualProcessorRefusedSpans(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
require.NoError(t, testTel.Shutdown(context.Background()))
}
================================================
FILE: processor/memorylimiterprocessor/internal/mock_exporter.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/processor/memorylimiterprocessor/internal"
import (
"context"
"sync/atomic"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/pdata/plog"
)
type MockExporter struct {
destAvailable atomic.Bool
acceptedLogCount atomic.Int64
deliveredLogCount atomic.Int64
Logs consumertest.LogsSink
}
var _ consumer.Logs = (*MockExporter)(nil)
func (e *MockExporter) Capabilities() consumer.Capabilities {
return consumer.Capabilities{}
}
func (e *MockExporter) ConsumeLogs(ctx context.Context, ld plog.Logs) error {
e.acceptedLogCount.Add(int64(ld.LogRecordCount()))
if !e.destAvailable.Load() {
// Destination is not available. Queue the logs in the exporter.
return e.Logs.ConsumeLogs(ctx, ld)
}
// Destination is available, immediately deliver.
e.deliveredLogCount.Add(int64(ld.LogRecordCount()))
return nil
}
func (e *MockExporter) SetDestAvailable(available bool) {
if available {
// Pretend we delivered all queued accepted logs.
e.deliveredLogCount.Add(int64(e.Logs.LogRecordCount()))
// Get rid of the delivered logs so that memory can be collected.
e.Logs.Reset()
// Now mark destination available so that subsequent ConsumeLogs
// don't queue the logs anymore.
e.destAvailable.Store(true)
} else {
e.destAvailable.Store(false)
}
}
func (e *MockExporter) AcceptedLogCount() int {
return int(e.acceptedLogCount.Load())
}
func (e *MockExporter) DeliveredLogCount() int {
return int(e.deliveredLogCount.Load())
}
func NewMockExporter() *MockExporter {
return &MockExporter{
destAvailable: atomic.Bool{},
acceptedLogCount: atomic.Int64{},
deliveredLogCount: atomic.Int64{},
Logs: consumertest.LogsSink{},
}
}
================================================
FILE: processor/memorylimiterprocessor/internal/mock_receiver.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/processor/memorylimiterprocessor/internal"
import (
"context"
"strings"
"sync"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/pdata/plog"
)
type MockReceiver struct {
ProduceCount int
NextConsumer consumer.Logs
lastConsumeResult error
mux sync.Mutex
}
func (m *MockReceiver) Start() {
go m.produce()
}
// This function demonstrates how the receivers should behave when the ConsumeLogs/Traces/Metrics
// call returns an error.
func (m *MockReceiver) produce() {
for i := 0; i < m.ProduceCount; i++ {
// Create a large log to consume some memory.
ld := plog.NewLogs()
lr := ld.ResourceLogs().AppendEmpty().ScopeLogs().AppendEmpty().LogRecords().AppendEmpty()
kiloStr := strings.Repeat("x", 10*1024)
lr.SetSeverityText(kiloStr)
retry:
// Send to the pipeline.
err := m.NextConsumer.ConsumeLogs(context.Background(), ld)
// Remember the result to be used in the tests.
m.mux.Lock()
m.lastConsumeResult = err
m.mux.Unlock()
if err != nil {
// Sending to the pipeline failed.
if !consumererror.IsPermanent(err) {
// Retryable error. Try the same data again.
goto retry
}
// Permanent error. Drop it.
}
}
}
func (m *MockReceiver) LastConsumeResult() error {
m.mux.Lock()
defer m.mux.Unlock()
return m.lastConsumeResult
}
================================================
FILE: processor/memorylimiterprocessor/memorylimiter.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package memorylimiterprocessor // import "go.opentelemetry.io/collector/processor/memorylimiterprocessor"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/internal/memorylimiter"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/pipeline/xpipeline"
"go.opentelemetry.io/collector/processor"
)
type memoryLimiterProcessor struct {
memlimiter *memorylimiter.MemoryLimiter
obsrep *obsReport
}
// newMemoryLimiter returns a new memorylimiter processor.
func newMemoryLimiterProcessor(set processor.Settings, cfg *Config) (*memoryLimiterProcessor, error) {
ml, err := memorylimiter.NewMemoryLimiter(cfg, set.Logger)
if err != nil {
return nil, err
}
obsrep, err := newObsReport(set)
if err != nil {
return nil, err
}
p := &memoryLimiterProcessor{
memlimiter: ml,
obsrep: obsrep,
}
return p, nil
}
func (p *memoryLimiterProcessor) start(ctx context.Context, host component.Host) error {
return p.memlimiter.Start(ctx, host)
}
func (p *memoryLimiterProcessor) shutdown(ctx context.Context) error {
return p.memlimiter.Shutdown(ctx)
}
func (p *memoryLimiterProcessor) processTraces(ctx context.Context, td ptrace.Traces) (ptrace.Traces, error) {
numSpans := td.SpanCount()
if p.memlimiter.MustRefuse() {
// TODO:
// https://github.com/open-telemetry/opentelemetry-collector/issues/12463
p.obsrep.refused(ctx, numSpans, pipeline.SignalTraces)
return td, memorylimiter.ErrDataRefused
}
// Even if the next consumer returns error record the data as accepted by
// this processor.
p.obsrep.accepted(ctx, numSpans, pipeline.SignalTraces)
return td, nil
}
func (p *memoryLimiterProcessor) processMetrics(ctx context.Context, md pmetric.Metrics) (pmetric.Metrics, error) {
numDataPoints := md.DataPointCount()
if p.memlimiter.MustRefuse() {
// TODO:
// https://github.com/open-telemetry/opentelemetry-collector/issues/12463
p.obsrep.refused(ctx, numDataPoints, pipeline.SignalMetrics)
return md, memorylimiter.ErrDataRefused
}
// Even if the next consumer returns error record the data as accepted by
// this processor.
p.obsrep.accepted(ctx, numDataPoints, pipeline.SignalMetrics)
return md, nil
}
func (p *memoryLimiterProcessor) processLogs(ctx context.Context, ld plog.Logs) (plog.Logs, error) {
numRecords := ld.LogRecordCount()
if p.memlimiter.MustRefuse() {
// TODO:
// https://github.com/open-telemetry/opentelemetry-collector/issues/12463
p.obsrep.refused(ctx, numRecords, pipeline.SignalLogs)
return ld, memorylimiter.ErrDataRefused
}
// Even if the next consumer returns error record the data as accepted by
// this processor.
p.obsrep.accepted(ctx, numRecords, pipeline.SignalLogs)
return ld, nil
}
func (p *memoryLimiterProcessor) processProfiles(ctx context.Context, td pprofile.Profiles) (pprofile.Profiles, error) {
numProfiles := td.SampleCount()
if p.memlimiter.MustRefuse() {
// TODO:
// https://github.com/open-telemetry/opentelemetry-collector/issues/12463
p.obsrep.refused(ctx, numProfiles, xpipeline.SignalProfiles)
return td, memorylimiter.ErrDataRefused
}
// Even if the next consumer returns error record the data as accepted by
// this processor.
p.obsrep.accepted(ctx, numProfiles, xpipeline.SignalProfiles)
return td, nil
}
================================================
FILE: processor/memorylimiterprocessor/memorylimiter_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package memorylimiterprocessor
import (
"context"
"runtime"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/internal/memorylimiter"
"go.opentelemetry.io/collector/internal/memorylimiter/iruntime"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/memorylimiterprocessor/internal"
"go.opentelemetry.io/collector/processor/memorylimiterprocessor/internal/metadata"
"go.opentelemetry.io/collector/processor/memorylimiterprocessor/internal/metadatatest"
"go.opentelemetry.io/collector/processor/processorhelper"
"go.opentelemetry.io/collector/processor/processorhelper/xprocessorhelper"
"go.opentelemetry.io/collector/processor/processortest"
)
func TestNoDataLoss(t *testing.T) {
// Create an exporter.
exporter := internal.NewMockExporter()
// Mark exporter's destination unavailable. The exporter will accept data and will queue it,
// thus increasing the memory usage of the Collector.
exporter.SetDestAvailable(false)
// Create a memory limiter processor.
cfg := createDefaultConfig().(*Config)
// Check frequently to make the test quick.
cfg.CheckInterval = time.Millisecond * 10
// By how much we expect memory usage to increase because of queuing up of produced data.
const expectedMemoryIncreaseMiB = 10
var ms runtime.MemStats
runtime.ReadMemStats(&ms)
// Set the limit to current usage plus expected increase. This means initially we will not be limited.
cfg.MemoryLimitMiB = uint32(ms.Alloc/(1024*1024) + expectedMemoryIncreaseMiB)
cfg.MemorySpikeLimitMiB = 1
set := processortest.NewNopSettings(metadata.Type)
limiter, err := newMemoryLimiterProcessor(set, cfg)
require.NoError(t, err)
processor, err := processorhelper.NewLogs(context.Background(), processor.Settings{
ID: component.MustNewID("nop"),
TelemetrySettings: componenttest.NewNopTelemetrySettings(),
}, cfg, exporter,
limiter.processLogs,
processorhelper.WithStart(limiter.start),
processorhelper.WithShutdown(limiter.shutdown))
require.NoError(t, err)
// Create a receiver.
receiver := &internal.MockReceiver{
ProduceCount: 1e5, // Must produce enough logs to make sure memory increases by at least expectedMemoryIncreaseMiB
NextConsumer: processor,
}
err = processor.Start(context.Background(), componenttest.NewNopHost())
require.NoError(t, err)
// Start producing data.
receiver.Start()
// The exporter was created such that its destination is not available.
// This will result in queuing of produced data inside the exporter and memory usage
// will increase.
// We must eventually hit the memory limit and the receiver must see an error from memory limiter.
require.Eventually(t, func() bool {
// Did last ConsumeLogs call return an error?
return receiver.LastConsumeResult() != nil
}, 5*time.Second, 1*time.Millisecond)
// We are now memory limited and receiver can't produce data anymore.
// Now make the exporter's destination available.
exporter.SetDestAvailable(true)
// We should now see that exporter's queue is purged and memory usage goes down.
// Eventually we must see that receiver's ConsumeLog call returns success again.
require.Eventually(t, func() bool {
return receiver.LastConsumeResult() == nil
}, 5*time.Second, 1*time.Millisecond)
// And eventually the exporter must confirm that it delivered exact number of produced logs.
require.Eventually(t, func() bool {
d := exporter.DeliveredLogCount()
t.Logf("received: %d, expected: %d\n", d, receiver.ProduceCount)
return receiver.ProduceCount == d
}, 5*time.Second, 100*time.Millisecond)
// Double check that the number of logs accepted by exporter matches the number of produced by receiver.
assert.Equal(t, receiver.ProduceCount, exporter.AcceptedLogCount())
err = processor.Shutdown(context.Background())
require.NoError(t, err)
}
// TestMetricsMemoryPressureResponse manipulates results from querying memory and
// check expected side effects.
func TestMetricsMemoryPressureResponse(t *testing.T) {
md := pmetric.NewMetrics()
tests := []struct {
name string
mlCfg *Config
memAlloc uint64
expectError bool
}{
{
name: "Below memAllocLimit",
mlCfg: &Config{
CheckInterval: time.Second,
MemoryLimitPercentage: 50,
MemorySpikePercentage: 1,
},
memAlloc: 800,
expectError: false,
},
{
name: "Above memAllocLimit",
mlCfg: &Config{
CheckInterval: time.Second,
MemoryLimitPercentage: 50,
MemorySpikePercentage: 1,
},
memAlloc: 1800,
expectError: true,
},
{
name: "Below memSpikeLimit",
mlCfg: &Config{
CheckInterval: time.Second,
MemoryLimitPercentage: 50,
MemorySpikePercentage: 10,
},
memAlloc: 800,
expectError: false,
},
{
name: "Above memSpikeLimit",
mlCfg: &Config{
CheckInterval: time.Second,
MemoryLimitPercentage: 50,
MemorySpikePercentage: 11,
},
memAlloc: 800,
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
memorylimiter.GetMemoryFn = totalMemory
memorylimiter.ReadMemStatsFn = func(ms *runtime.MemStats) {
ms.Alloc = tt.memAlloc
}
ml, err := newMemoryLimiterProcessor(processortest.NewNopSettings(metadata.Type), tt.mlCfg)
require.NoError(t, err)
mp, err := processorhelper.NewMetrics(
ctx,
processortest.NewNopSettings(metadata.Type),
tt.mlCfg,
consumertest.NewNop(),
ml.processMetrics,
processorhelper.WithCapabilities(processorCapabilities),
processorhelper.WithStart(ml.start),
processorhelper.WithShutdown(ml.shutdown))
require.NoError(t, err)
assert.NoError(t, mp.Start(ctx, &host{}))
ml.memlimiter.CheckMemLimits()
err = mp.ConsumeMetrics(ctx, md)
if tt.expectError {
assert.Equal(t, memorylimiter.ErrDataRefused, err)
} else {
require.NoError(t, err)
}
assert.NoError(t, mp.Shutdown(ctx))
})
}
t.Cleanup(func() {
memorylimiter.GetMemoryFn = iruntime.TotalMemory
memorylimiter.ReadMemStatsFn = runtime.ReadMemStats
})
}
func TestMetricsTelemetry(t *testing.T) {
tel := componenttest.NewTelemetry()
cfg := &Config{
CheckInterval: time.Second,
MemoryLimitPercentage: 50,
MemorySpikePercentage: 10,
}
metrics, err := NewFactory().CreateMetrics(context.Background(), metadatatest.NewSettings(tel), cfg, consumertest.NewNop())
require.NoError(t, err)
require.NoError(t, metrics.Start(context.Background(), componenttest.NewNopHost()))
md := pmetric.NewMetrics()
md.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty().SetEmptySum().DataPoints().AppendEmpty()
for range 10 {
require.NoError(t, metrics.ConsumeMetrics(context.Background(), md))
}
require.NoError(t, metrics.Shutdown(context.Background()))
metadatatest.AssertEqualProcessorAcceptedMetricPoints(t, tel,
[]metricdata.DataPoint[int64]{
{
Value: 10,
Attributes: attribute.NewSet(attribute.String("processor", "memory_limiter")),
},
}, metricdatatest.IgnoreTimestamp())
require.NoError(t, tel.Shutdown(context.Background()))
}
// TestTraceMemoryPressureResponse manipulates results from querying memory and
// check expected side effects.
func TestTraceMemoryPressureResponse(t *testing.T) {
td := ptrace.NewTraces()
tests := []struct {
name string
mlCfg *Config
memAlloc uint64
expectError bool
}{
{
name: "Below memAllocLimit",
mlCfg: &Config{
CheckInterval: time.Second,
MemoryLimitPercentage: 50,
MemorySpikePercentage: 1,
},
memAlloc: 800,
expectError: false,
},
{
name: "Above memAllocLimit",
mlCfg: &Config{
CheckInterval: time.Second,
MemoryLimitPercentage: 50,
MemorySpikePercentage: 1,
},
memAlloc: 1800,
expectError: true,
},
{
name: "Below memSpikeLimit",
mlCfg: &Config{
CheckInterval: time.Second,
MemoryLimitPercentage: 50,
MemorySpikePercentage: 10,
},
memAlloc: 800,
expectError: false,
},
{
name: "Above memSpikeLimit",
mlCfg: &Config{
CheckInterval: time.Second,
MemoryLimitPercentage: 50,
MemorySpikePercentage: 11,
},
memAlloc: 800,
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
memorylimiter.GetMemoryFn = totalMemory
memorylimiter.ReadMemStatsFn = func(ms *runtime.MemStats) {
ms.Alloc = tt.memAlloc
}
ml, err := newMemoryLimiterProcessor(processortest.NewNopSettings(metadata.Type), tt.mlCfg)
require.NoError(t, err)
tp, err := processorhelper.NewTraces(
ctx,
processortest.NewNopSettings(metadata.Type),
tt.mlCfg,
consumertest.NewNop(),
ml.processTraces,
processorhelper.WithCapabilities(processorCapabilities),
processorhelper.WithStart(ml.start),
processorhelper.WithShutdown(ml.shutdown))
require.NoError(t, err)
assert.NoError(t, tp.Start(ctx, &host{}))
ml.memlimiter.CheckMemLimits()
err = tp.ConsumeTraces(ctx, td)
if tt.expectError {
assert.Equal(t, memorylimiter.ErrDataRefused, err)
} else {
require.NoError(t, err)
}
assert.NoError(t, tp.Shutdown(ctx))
})
}
t.Cleanup(func() {
memorylimiter.GetMemoryFn = iruntime.TotalMemory
memorylimiter.ReadMemStatsFn = runtime.ReadMemStats
})
}
// TestLogMemoryPressureResponse manipulates results from querying memory and
// check expected side effects.
func TestLogMemoryPressureResponse(t *testing.T) {
ld := plog.NewLogs()
tests := []struct {
name string
mlCfg *Config
memAlloc uint64
expectError bool
}{
{
name: "Below memAllocLimit",
mlCfg: &Config{
CheckInterval: time.Second,
MemoryLimitPercentage: 50,
MemorySpikePercentage: 1,
},
memAlloc: 800,
expectError: false,
},
{
name: "Above memAllocLimit",
mlCfg: &Config{
CheckInterval: time.Second,
MemoryLimitPercentage: 50,
MemorySpikePercentage: 1,
},
memAlloc: 1800,
expectError: true,
},
{
name: "Below memSpikeLimit",
mlCfg: &Config{
CheckInterval: time.Second,
MemoryLimitPercentage: 50,
MemorySpikePercentage: 10,
},
memAlloc: 800,
expectError: false,
},
{
name: "Above memSpikeLimit",
mlCfg: &Config{
CheckInterval: time.Second,
MemoryLimitPercentage: 50,
MemorySpikePercentage: 11,
},
memAlloc: 800,
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
memorylimiter.GetMemoryFn = totalMemory
memorylimiter.ReadMemStatsFn = func(ms *runtime.MemStats) {
ms.Alloc = tt.memAlloc
}
ml, err := newMemoryLimiterProcessor(processortest.NewNopSettings(metadata.Type), tt.mlCfg)
require.NoError(t, err)
tp, err := processorhelper.NewLogs(
ctx,
processortest.NewNopSettings(metadata.Type),
tt.mlCfg,
consumertest.NewNop(),
ml.processLogs,
processorhelper.WithCapabilities(processorCapabilities),
processorhelper.WithStart(ml.start),
processorhelper.WithShutdown(ml.shutdown))
require.NoError(t, err)
assert.NoError(t, tp.Start(ctx, &host{}))
ml.memlimiter.CheckMemLimits()
err = tp.ConsumeLogs(ctx, ld)
if tt.expectError {
assert.Equal(t, memorylimiter.ErrDataRefused, err)
} else {
require.NoError(t, err)
}
assert.NoError(t, tp.Shutdown(ctx))
})
}
t.Cleanup(func() {
memorylimiter.GetMemoryFn = iruntime.TotalMemory
memorylimiter.ReadMemStatsFn = runtime.ReadMemStats
})
}
// TestProfileMemoryPressureResponse manipulates results from querying memory and
// check expected side effects.
func TestProfileMemoryPressureResponse(t *testing.T) {
pd := pprofile.NewProfiles()
tests := []struct {
name string
mlCfg *Config
memAlloc uint64
expectError bool
}{
{
name: "Below memAllocLimit",
mlCfg: &Config{
CheckInterval: time.Second,
MemoryLimitPercentage: 50,
MemorySpikePercentage: 1,
},
memAlloc: 800,
expectError: false,
},
{
name: "Above memAllocLimit",
mlCfg: &Config{
CheckInterval: time.Second,
MemoryLimitPercentage: 50,
MemorySpikePercentage: 1,
},
memAlloc: 1800,
expectError: true,
},
{
name: "Below memSpikeLimit",
mlCfg: &Config{
CheckInterval: time.Second,
MemoryLimitPercentage: 50,
MemorySpikePercentage: 10,
},
memAlloc: 800,
expectError: false,
},
{
name: "Above memSpikeLimit",
mlCfg: &Config{
CheckInterval: time.Second,
MemoryLimitPercentage: 50,
MemorySpikePercentage: 11,
},
memAlloc: 800,
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
memorylimiter.GetMemoryFn = totalMemory
memorylimiter.ReadMemStatsFn = func(ms *runtime.MemStats) {
ms.Alloc = tt.memAlloc
}
ml, err := newMemoryLimiterProcessor(processortest.NewNopSettings(metadata.Type), tt.mlCfg)
require.NoError(t, err)
tp, err := xprocessorhelper.NewProfiles(
ctx,
processortest.NewNopSettings(metadata.Type),
tt.mlCfg,
consumertest.NewNop(),
ml.processProfiles,
xprocessorhelper.WithCapabilities(processorCapabilities),
xprocessorhelper.WithStart(ml.start),
xprocessorhelper.WithShutdown(ml.shutdown))
require.NoError(t, err)
assert.NoError(t, tp.Start(ctx, &host{}))
ml.memlimiter.CheckMemLimits()
err = tp.ConsumeProfiles(ctx, pd)
if tt.expectError {
assert.Equal(t, memorylimiter.ErrDataRefused, err)
} else {
require.NoError(t, err)
}
assert.NoError(t, tp.Shutdown(ctx))
})
}
t.Cleanup(func() {
memorylimiter.GetMemoryFn = iruntime.TotalMemory
memorylimiter.ReadMemStatsFn = runtime.ReadMemStats
})
}
type host struct {
component.Host
}
func (h *host) GetExtensions() map[component.ID]component.Component {
ret := make(map[component.ID]component.Component)
return ret
}
func totalMemory() (uint64, error) {
return uint64(2048), nil
}
================================================
FILE: processor/memorylimiterprocessor/metadata.yaml
================================================
display_name: Memory Limiter Processor
type: memory_limiter
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: processor
stability:
alpha: [profiles]
beta: [traces, metrics, logs]
distributions: [core, contrib, k8s]
tests:
config:
check_interval: 5s
limit_mib: 400
spike_limit_mib: 50
telemetry:
metrics:
processor_accepted_log_records:
enabled: true
description: Number of log records successfully pushed into the next component in the pipeline.
stability: deprecated
deprecated:
since: "0.110.0"
note: "This metric is deprecated"
unit: "{record}"
sum:
value_type: int
monotonic: true
processor_accepted_metric_points:
enabled: true
description: Number of metric points successfully pushed into the next component in the pipeline.
stability: deprecated
deprecated:
since: "0.110.0"
note: "This metric is deprecated"
unit: "{datapoint}"
sum:
value_type: int
monotonic: true
processor_accepted_spans:
enabled: true
description: Number of spans successfully pushed into the next component in the pipeline.
stability: deprecated
deprecated:
since: "0.110.0"
note: "This metric is deprecated"
unit: "{span}"
sum:
value_type: int
monotonic: true
processor_refused_log_records:
enabled: true
description: Number of log records that were rejected by the next component in the pipeline.
stability: deprecated
deprecated:
since: "0.110.0"
note: "This metric is deprecated"
unit: "{record}"
sum:
value_type: int
monotonic: true
processor_refused_metric_points:
enabled: true
description: Number of metric points that were rejected by the next component in the pipeline.
stability: deprecated
deprecated:
since: "0.110.0"
note: "This metric is deprecated"
unit: "{datapoint}"
sum:
value_type: int
monotonic: true
processor_refused_spans:
enabled: true
description: Number of spans that were rejected by the next component in the pipeline.
stability: deprecated
deprecated:
since: "0.110.0"
note: "This metric is deprecated"
unit: "{span}"
sum:
value_type: int
monotonic: true
================================================
FILE: processor/memorylimiterprocessor/obsreport.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package memorylimiterprocessor // import "go.opentelemetry.io/collector/processor/memorylimiterprocessor"
import (
"context"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/internal"
"go.opentelemetry.io/collector/processor/memorylimiterprocessor/internal/metadata"
)
type obsReport struct {
otelAttrs metric.MeasurementOption
telemetryBuilder *metadata.TelemetryBuilder
}
func newObsReport(set processor.Settings) (*obsReport, error) {
telemetryBuilder, err := metadata.NewTelemetryBuilder(set.TelemetrySettings)
if err != nil {
return nil, err
}
return &obsReport{
otelAttrs: metric.WithAttributeSet(attribute.NewSet(attribute.String(internal.ProcessorKey, set.ID.String()))),
telemetryBuilder: telemetryBuilder,
}, nil
}
// accepted reports that the num data was accepted.
func (or *obsReport) accepted(ctx context.Context, num int, signal pipeline.Signal) {
switch signal {
case pipeline.SignalTraces:
or.telemetryBuilder.ProcessorAcceptedSpans.Add(ctx, int64(num), or.otelAttrs)
case pipeline.SignalMetrics:
or.telemetryBuilder.ProcessorAcceptedMetricPoints.Add(ctx, int64(num), or.otelAttrs)
case pipeline.SignalLogs:
or.telemetryBuilder.ProcessorAcceptedLogRecords.Add(ctx, int64(num), or.otelAttrs)
}
}
// refused reports that the num data was refused.
func (or *obsReport) refused(ctx context.Context, num int, signal pipeline.Signal) {
switch signal {
case pipeline.SignalTraces:
or.telemetryBuilder.ProcessorRefusedSpans.Add(ctx, int64(num), or.otelAttrs)
case pipeline.SignalMetrics:
or.telemetryBuilder.ProcessorRefusedMetricPoints.Add(ctx, int64(num), or.otelAttrs)
case pipeline.SignalLogs:
or.telemetryBuilder.ProcessorRefusedLogRecords.Add(ctx, int64(num), or.otelAttrs)
}
}
================================================
FILE: processor/metadata.yaml
================================================
type: processor
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: processor/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package processor
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: processor/processor.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package processor // import "go.opentelemetry.io/collector/processor"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/internal/componentalias"
"go.opentelemetry.io/collector/pipeline"
)
// Traces is a processor that can consume traces.
type Traces interface {
component.Component
consumer.Traces
}
// Metrics is a processor that can consume metrics.
type Metrics interface {
component.Component
consumer.Metrics
}
// Logs is a processor that can consume logs.
type Logs interface {
component.Component
consumer.Logs
}
// Settings is passed to Create* functions in Factory.
type Settings struct {
// ID returns the ID of the component that will be created.
ID component.ID
component.TelemetrySettings
// BuildInfo can be used by components for informational purposes
BuildInfo component.BuildInfo
// prevent unkeyed literal initialization
_ struct{}
}
// Factory is Factory interface for processors.
//
// This interface cannot be directly implemented. Implementations must
// use the NewFactory to implement it.
type Factory interface {
component.Factory
// CreateTraces creates a Traces processor based on this config.
// If the processor type does not support traces,
// this function returns the error [pipeline.ErrSignalNotSupported].
// Implementers can assume `next` is never nil.
CreateTraces(ctx context.Context, set Settings, cfg component.Config, next consumer.Traces) (Traces, error)
// TracesStability gets the stability level of the Traces processor.
TracesStability() component.StabilityLevel
// CreateMetrics creates a Metrics processor based on this config.
// If the processor type does not support metrics,
// this function returns the error [pipeline.ErrSignalNotSupported].
// Implementers can assume `next` is never nil.
CreateMetrics(ctx context.Context, set Settings, cfg component.Config, next consumer.Metrics) (Metrics, error)
// MetricsStability gets the stability level of the Metrics processor.
MetricsStability() component.StabilityLevel
// CreateLogs creates a Logs processor based on the config.
// If the processor type does not support logs,
// this function returns the error [pipeline.ErrSignalNotSupported].
// Implementers can assume `next` is never nil.
CreateLogs(ctx context.Context, set Settings, cfg component.Config, next consumer.Logs) (Logs, error)
// LogsStability gets the stability level of the Logs processor.
LogsStability() component.StabilityLevel
unexportedFactoryFunc()
}
// FactoryOption apply changes to Options.
type FactoryOption interface {
// applyOption applies the option.
applyOption(o *factory)
}
var _ FactoryOption = (*factoryOptionFunc)(nil)
// factoryOptionFunc is a FactoryOption created through a function.
type factoryOptionFunc func(*factory)
func (f factoryOptionFunc) applyOption(o *factory) {
f(o)
}
type factory struct {
cfgType component.Type
component.CreateDefaultConfigFunc
componentalias.TypeAliasHolder
createTracesFunc CreateTracesFunc
tracesStabilityLevel component.StabilityLevel
createMetricsFunc CreateMetricsFunc
metricsStabilityLevel component.StabilityLevel
createLogsFunc CreateLogsFunc
logsStabilityLevel component.StabilityLevel
}
func (f *factory) Type() component.Type {
return f.cfgType
}
func (f *factory) unexportedFactoryFunc() {}
func (f *factory) TracesStability() component.StabilityLevel {
return f.tracesStabilityLevel
}
func (f *factory) MetricsStability() component.StabilityLevel {
return f.metricsStabilityLevel
}
func (f *factory) LogsStability() component.StabilityLevel {
return f.logsStabilityLevel
}
func (f *factory) CreateTraces(ctx context.Context, set Settings, cfg component.Config, next consumer.Traces) (Traces, error) {
if f.createTracesFunc == nil {
return nil, pipeline.ErrSignalNotSupported
}
if err := componentalias.ValidateComponentType(f, set.ID); err != nil {
return nil, err
}
return f.createTracesFunc(ctx, set, cfg, next)
}
func (f *factory) CreateMetrics(ctx context.Context, set Settings, cfg component.Config, next consumer.Metrics) (Metrics, error) {
if f.createMetricsFunc == nil {
return nil, pipeline.ErrSignalNotSupported
}
if err := componentalias.ValidateComponentType(f, set.ID); err != nil {
return nil, err
}
return f.createMetricsFunc(ctx, set, cfg, next)
}
func (f *factory) CreateLogs(ctx context.Context, set Settings, cfg component.Config, next consumer.Logs) (Logs, error) {
if f.createLogsFunc == nil {
return nil, pipeline.ErrSignalNotSupported
}
if err := componentalias.ValidateComponentType(f, set.ID); err != nil {
return nil, err
}
return f.createLogsFunc(ctx, set, cfg, next)
}
// CreateTracesFunc is the equivalent of Factory.CreateTraces().
type CreateTracesFunc func(context.Context, Settings, component.Config, consumer.Traces) (Traces, error)
// CreateMetricsFunc is the equivalent of Factory.CreateMetrics().
type CreateMetricsFunc func(context.Context, Settings, component.Config, consumer.Metrics) (Metrics, error)
// CreateLogsFunc is the equivalent of Factory.CreateLogs.
type CreateLogsFunc func(context.Context, Settings, component.Config, consumer.Logs) (Logs, error)
// WithTraces overrides the default "error not supported" implementation for CreateTraces and the default "undefined" stability level.
func WithTraces(createTraces CreateTracesFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.tracesStabilityLevel = sl
o.createTracesFunc = createTraces
})
}
// WithMetrics overrides the default "error not supported" implementation for CreateMetrics and the default "undefined" stability level.
func WithMetrics(createMetrics CreateMetricsFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.metricsStabilityLevel = sl
o.createMetricsFunc = createMetrics
})
}
// WithLogs overrides the default "error not supported" implementation for CreateLogs and the default "undefined" stability level.
func WithLogs(createLogs CreateLogsFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.logsStabilityLevel = sl
o.createLogsFunc = createLogs
})
}
// NewFactory returns a Factory.
func NewFactory(cfgType component.Type, createDefaultConfig component.CreateDefaultConfigFunc, options ...FactoryOption) Factory {
f := &factory{
cfgType: cfgType,
CreateDefaultConfigFunc: createDefaultConfig,
TypeAliasHolder: componentalias.NewTypeAliasHolder(),
}
for _, opt := range options {
opt.applyOption(f)
}
return f
}
================================================
FILE: processor/processor_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package processor
import (
"context"
"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/pipeline"
"go.opentelemetry.io/collector/processor/internal"
)
var (
testType = component.MustNewType("test")
testID = component.NewID(testType)
)
func TestNewFactory(t *testing.T) {
defaultCfg := struct{}{}
f := NewFactory(
testType,
func() component.Config { return &defaultCfg })
assert.Equal(t, testType, f.Type())
assert.EqualValues(t, &defaultCfg, f.CreateDefaultConfig())
_, err := f.CreateTraces(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
require.ErrorIs(t, err, pipeline.ErrSignalNotSupported)
_, err = f.CreateMetrics(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
require.ErrorIs(t, err, pipeline.ErrSignalNotSupported)
_, err = f.CreateLogs(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
require.ErrorIs(t, err, pipeline.ErrSignalNotSupported)
}
func TestNewFactoryWithOptions(t *testing.T) {
defaultCfg := struct{}{}
f := NewFactory(
testType,
func() component.Config { return &defaultCfg },
WithTraces(createTraces, component.StabilityLevelAlpha),
WithMetrics(createMetrics, component.StabilityLevelBeta),
WithLogs(createLogs, component.StabilityLevelUnmaintained))
assert.Equal(t, testType, f.Type())
assert.EqualValues(t, &defaultCfg, f.CreateDefaultConfig())
wrongID := component.MustNewID("wrong")
wrongIDErrStr := internal.ErrIDMismatch(wrongID, testType).Error()
assert.Equal(t, component.StabilityLevelAlpha, f.TracesStability())
_, err := f.CreateTraces(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
require.NoError(t, err)
_, err = f.CreateTraces(context.Background(), Settings{ID: wrongID}, &defaultCfg, consumertest.NewNop())
require.EqualError(t, err, wrongIDErrStr)
assert.Equal(t, component.StabilityLevelBeta, f.MetricsStability())
_, err = f.CreateMetrics(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
require.NoError(t, err)
_, err = f.CreateMetrics(context.Background(), Settings{ID: wrongID}, &defaultCfg, consumertest.NewNop())
require.EqualError(t, err, wrongIDErrStr)
assert.Equal(t, component.StabilityLevelUnmaintained, f.LogsStability())
_, err = f.CreateLogs(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
require.NoError(t, err)
_, err = f.CreateLogs(context.Background(), Settings{ID: wrongID}, &defaultCfg, consumertest.NewNop())
require.EqualError(t, err, wrongIDErrStr)
}
var nopInstance = &nopProcessor{
Consumer: consumertest.NewNop(),
}
// nopProcessor stores consumed traces and metrics for testing purposes.
type nopProcessor struct {
component.StartFunc
component.ShutdownFunc
consumertest.Consumer
}
func createTraces(context.Context, Settings, component.Config, consumer.Traces) (Traces, error) {
return nopInstance, nil
}
func createMetrics(context.Context, Settings, component.Config, consumer.Metrics) (Metrics, error) {
return nopInstance, nil
}
func createLogs(context.Context, Settings, component.Config, consumer.Logs) (Logs, error) {
return nopInstance, nil
}
================================================
FILE: processor/processorhelper/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: processor/processorhelper/documentation.md
================================================
[comment]: <> (Code generated by mdatagen. DO NOT EDIT.)
# processorhelper
## Internal Telemetry
The following telemetry is emitted by this component.
### otelcol_processor_incoming_items
Number of items passed to the processor.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {item} | Sum | Int | true | Alpha |
### otelcol_processor_internal_duration
Duration of time taken to process a batch of telemetry data through the processor.
| Unit | Metric Type | Value Type | Stability |
| ---- | ----------- | ---------- | --------- |
| s | Histogram | Double | Alpha |
### otelcol_processor_outgoing_items
Number of items emitted from the processor.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {item} | Sum | Int | true | Alpha |
================================================
FILE: processor/processorhelper/example_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package processorhelper_test
import (
"context"
"fmt"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/processorhelper"
)
// typeStr defines the unique type identifier for the processor.
var typeStr = component.MustNewType("example")
// exampleConfig holds configuration settings for the processor.
type exampleConfig struct{}
// exampleProcessor implements the OpenTelemetry processor interface.
type exampleProcessor struct {
cancel context.CancelFunc
config exampleConfig
}
// Example demonstrates the usage of the processor factory.
func Example() {
// Instantiate the processor factory and print its type.
exampleProcessor := NewFactory()
fmt.Println(exampleProcessor.Type())
// Output:
// example
}
// NewFactory creates a new processor factory.
func NewFactory() processor.Factory {
return processor.NewFactory(
typeStr,
createDefaultConfig,
processor.WithMetrics(createExampleProcessor, component.StabilityLevelAlpha),
)
}
// createDefaultConfig returns the default configuration for the processor.
func createDefaultConfig() component.Config {
return &exampleConfig{}
}
// createExampleProcessor initializes an instance of the example processor.
func createExampleProcessor(ctx context.Context, params processor.Settings, baseCfg component.Config, next consumer.Metrics) (processor.Metrics, error) {
// Convert baseCfg to the correct type.
cfg := baseCfg.(*exampleConfig)
// Create a new processor instance.
pcsr := newExampleProcessor(ctx, cfg)
// Wrap the processor with the helper utilities.
return processorhelper.NewMetrics(
ctx,
params,
cfg,
next,
pcsr.consumeMetrics,
processorhelper.WithCapabilities(consumer.Capabilities{MutatesData: true}),
processorhelper.WithShutdown(pcsr.shutdown),
)
}
// newExampleProcessor constructs a new instance of the example processor.
func newExampleProcessor(ctx context.Context, cfg *exampleConfig) *exampleProcessor {
pcsr := &exampleProcessor{
config: *cfg,
}
// Create a cancelable context.
_, pcsr.cancel = context.WithCancel(ctx)
return pcsr
}
// ConsumeMetrics modify metrics adding one attribute to resource.
func (pcsr *exampleProcessor) consumeMetrics(_ context.Context, md pmetric.Metrics) (pmetric.Metrics, error) {
rm := md.ResourceMetrics()
for i := 0; i < rm.Len(); i++ {
rm.At(i).Resource().Attributes().PutStr("processed_by", "exampleProcessor")
}
return md, nil
}
// Shutdown properly stops the processor and releases resources.
func (pcsr *exampleProcessor) shutdown(_ context.Context) error {
pcsr.cancel()
return nil
}
================================================
FILE: processor/processorhelper/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package processorhelper
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: processor/processorhelper/go.mod
================================================
module go.opentelemetry.io/collector/processor/processorhelper
go 1.25.0
replace go.opentelemetry.io/collector/processor => ../
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.opentelemetry.io/collector/consumer v1.54.0
go.opentelemetry.io/collector/consumer/consumertest v0.148.0
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/collector/pipeline v1.54.0
go.opentelemetry.io/collector/processor v1.54.0
go.opentelemetry.io/collector/processor/processortest v0.148.0
go.opentelemetry.io/otel v1.42.0
go.opentelemetry.io/otel/metric v1.42.0
go.opentelemetry.io/otel/sdk/metric v1.42.0
go.opentelemetry.io/otel/trace v1.42.0
go.uber.org/goleak v1.3.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/component/componentstatus v0.148.0 // indirect
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
go.opentelemetry.io/collector/pdata/pprofile v0.148.0 // indirect
go.opentelemetry.io/collector/pdata/testdata v0.148.0 // indirect
go.opentelemetry.io/collector/processor/xprocessor v0.148.0 // indirect
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
golang.org/x/sys v0.41.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest
replace go.opentelemetry.io/collector/consumer => ../../consumer
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/processor/xprocessor => ../xprocessor
replace go.opentelemetry.io/collector/pdata/testdata => ../../pdata/testdata
replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile
replace go.opentelemetry.io/collector/processor/processortest => ../processortest
replace go.opentelemetry.io/collector/pipeline => ../../pipeline
replace go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest
replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer
replace go.opentelemetry.io/collector/component/componentstatus => ../../component/componentstatus
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
================================================
FILE: processor/processorhelper/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: processor/processorhelper/internal/metadata/generated_telemetry.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"errors"
"sync"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/collector/component"
)
func Meter(settings component.TelemetrySettings) metric.Meter {
return settings.MeterProvider.Meter("go.opentelemetry.io/collector/processor/processorhelper")
}
func Tracer(settings component.TelemetrySettings) trace.Tracer {
return settings.TracerProvider.Tracer("go.opentelemetry.io/collector/processor/processorhelper")
}
// TelemetryBuilder provides an interface for components to report telemetry
// as defined in metadata and user config.
type TelemetryBuilder struct {
meter metric.Meter
mu sync.Mutex
registrations []metric.Registration
ProcessorIncomingItems metric.Int64Counter
ProcessorInternalDuration metric.Float64Histogram
ProcessorOutgoingItems metric.Int64Counter
}
// TelemetryBuilderOption applies changes to default builder.
type TelemetryBuilderOption interface {
apply(*TelemetryBuilder)
}
type telemetryBuilderOptionFunc func(mb *TelemetryBuilder)
func (tbof telemetryBuilderOptionFunc) apply(mb *TelemetryBuilder) {
tbof(mb)
}
// Shutdown unregister all registered callbacks for async instruments.
func (builder *TelemetryBuilder) Shutdown() {
builder.mu.Lock()
defer builder.mu.Unlock()
for _, reg := range builder.registrations {
reg.Unregister()
}
}
// NewTelemetryBuilder provides a struct with methods to update all internal telemetry
// for a component
func NewTelemetryBuilder(settings component.TelemetrySettings, options ...TelemetryBuilderOption) (*TelemetryBuilder, error) {
builder := TelemetryBuilder{}
for _, op := range options {
op.apply(&builder)
}
builder.meter = Meter(settings)
var err, errs error
builder.ProcessorIncomingItems, err = builder.meter.Int64Counter(
"otelcol_processor_incoming_items",
metric.WithDescription("Number of items passed to the processor. [Alpha]"),
metric.WithUnit("{item}"),
)
errs = errors.Join(errs, err)
builder.ProcessorInternalDuration, err = builder.meter.Float64Histogram(
"otelcol_processor_internal_duration",
metric.WithDescription("Duration of time taken to process a batch of telemetry data through the processor. [Alpha]"),
metric.WithUnit("s"),
)
errs = errors.Join(errs, err)
builder.ProcessorOutgoingItems, err = builder.meter.Int64Counter(
"otelcol_processor_outgoing_items",
metric.WithDescription("Number of items emitted from the processor. [Alpha]"),
metric.WithUnit("{item}"),
)
errs = errors.Join(errs, err)
return &builder, errs
}
================================================
FILE: processor/processorhelper/internal/metadata/generated_telemetry_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/metric"
embeddedmetric "go.opentelemetry.io/otel/metric/embedded"
noopmetric "go.opentelemetry.io/otel/metric/noop"
"go.opentelemetry.io/otel/trace"
embeddedtrace "go.opentelemetry.io/otel/trace/embedded"
nooptrace "go.opentelemetry.io/otel/trace/noop"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
)
type mockMeter struct {
noopmetric.Meter
name string
}
type mockMeterProvider struct {
embeddedmetric.MeterProvider
}
func (m mockMeterProvider) Meter(name string, opts ...metric.MeterOption) metric.Meter {
return mockMeter{name: name}
}
type mockTracer struct {
nooptrace.Tracer
name string
}
type mockTracerProvider struct {
embeddedtrace.TracerProvider
}
func (m mockTracerProvider) Tracer(name string, opts ...trace.TracerOption) trace.Tracer {
return mockTracer{name: name}
}
func TestProviders(t *testing.T) {
set := component.TelemetrySettings{
MeterProvider: mockMeterProvider{},
TracerProvider: mockTracerProvider{},
}
meter := Meter(set)
if m, ok := meter.(mockMeter); ok {
require.Equal(t, "go.opentelemetry.io/collector/processor/processorhelper", m.name)
} else {
require.Fail(t, "returned Meter not mockMeter")
}
tracer := Tracer(set)
if m, ok := tracer.(mockTracer); ok {
require.Equal(t, "go.opentelemetry.io/collector/processor/processorhelper", m.name)
} else {
require.Fail(t, "returned Meter not mockTracer")
}
}
func TestNewTelemetryBuilder(t *testing.T) {
set := componenttest.NewNopTelemetrySettings()
applied := false
_, err := NewTelemetryBuilder(set, telemetryBuilderOptionFunc(func(b *TelemetryBuilder) {
applied = true
}))
require.NoError(t, err)
require.True(t, applied)
}
================================================
FILE: processor/processorhelper/internal/metadatatest/generated_telemetrytest.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadatatest
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"go.opentelemetry.io/collector/component/componenttest"
)
func AssertEqualProcessorIncomingItems(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_processor_incoming_items",
Description: "Number of items passed to the processor. [Alpha]",
Unit: "{item}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_processor_incoming_items")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualProcessorInternalDuration(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.HistogramDataPoint[float64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_processor_internal_duration",
Description: "Duration of time taken to process a batch of telemetry data through the processor. [Alpha]",
Unit: "s",
Data: metricdata.Histogram[float64]{
Temporality: metricdata.CumulativeTemporality,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_processor_internal_duration")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualProcessorOutgoingItems(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_processor_outgoing_items",
Description: "Number of items emitted from the processor. [Alpha]",
Unit: "{item}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_processor_outgoing_items")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
================================================
FILE: processor/processorhelper/internal/metadatatest/generated_telemetrytest_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadatatest
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/processor/processorhelper/internal/metadata"
)
func TestSetupTelemetry(t *testing.T) {
testTel := componenttest.NewTelemetry()
tb, err := metadata.NewTelemetryBuilder(testTel.NewTelemetrySettings())
require.NoError(t, err)
defer tb.Shutdown()
tb.ProcessorIncomingItems.Add(context.Background(), 1)
tb.ProcessorInternalDuration.Record(context.Background(), 1)
tb.ProcessorOutgoingItems.Add(context.Background(), 1)
AssertEqualProcessorIncomingItems(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualProcessorInternalDuration(t, testTel,
[]metricdata.HistogramDataPoint[float64]{{}}, metricdatatest.IgnoreValue(),
metricdatatest.IgnoreTimestamp())
AssertEqualProcessorOutgoingItems(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
require.NoError(t, testTel.Shutdown(context.Background()))
}
================================================
FILE: processor/processorhelper/logs.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package processorhelper // import "go.opentelemetry.io/collector/processor/processorhelper"
import (
"context"
"errors"
"time"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/processor"
)
// ProcessLogsFunc 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.
type ProcessLogsFunc func(context.Context, plog.Logs) (plog.Logs, error)
type logs struct {
component.StartFunc
component.ShutdownFunc
consumer.Logs
}
// NewLogs creates a processor.Logs that ensure context propagation and the right tags are set.
func NewLogs(
_ context.Context,
set processor.Settings,
_ component.Config,
nextConsumer consumer.Logs,
logsFunc ProcessLogsFunc,
options ...Option,
) (processor.Logs, error) {
if logsFunc == nil {
return nil, errors.New("nil logsFunc")
}
obs, err := newObsReport(set, pipeline.SignalLogs)
if err != nil {
return nil, err
}
eventOptions := spanAttributes(set.ID)
bs := fromOptions(options)
logsConsumer, err := consumer.NewLogs(func(ctx context.Context, ld plog.Logs) error {
span := trace.SpanFromContext(ctx)
span.AddEvent("Start processing.", eventOptions)
startTime := time.Now()
recordsIn := ld.LogRecordCount()
var errFunc error
ld, errFunc = logsFunc(ctx, ld)
obs.recordInternalDuration(ctx, startTime)
span.AddEvent("End processing.", eventOptions)
if errFunc != nil {
obs.recordInOut(ctx, recordsIn, 0)
if errors.Is(errFunc, ErrSkipProcessingData) {
return nil
}
return errFunc
}
recordsOut := ld.LogRecordCount()
obs.recordInOut(ctx, recordsIn, recordsOut)
return nextConsumer.ConsumeLogs(ctx, ld)
}, bs.consumerOptions...)
if err != nil {
return nil, err
}
return &logs{
StartFunc: bs.StartFunc,
ShutdownFunc: bs.ShutdownFunc,
Logs: logsConsumer,
}, nil
}
================================================
FILE: processor/processorhelper/logs_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package processorhelper
import (
"context"
"errors"
"sync"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"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/pdata/plog"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/processorhelper/internal/metadatatest"
"go.opentelemetry.io/collector/processor/processortest"
)
var testLogsCfg = struct{}{}
func TestNewLogs(t *testing.T) {
lp, err := NewLogs(context.Background(), processortest.NewNopSettings(processortest.NopType), &testLogsCfg, consumertest.NewNop(), newTestLProcessor(nil))
require.NoError(t, err)
assert.True(t, lp.Capabilities().MutatesData)
assert.NoError(t, lp.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, lp.ConsumeLogs(context.Background(), plog.NewLogs()))
assert.NoError(t, lp.Shutdown(context.Background()))
}
func TestNewLogs_WithOptions(t *testing.T) {
want := errors.New("my_error")
lp, err := NewLogs(context.Background(), processortest.NewNopSettings(processortest.NopType), &testLogsCfg, consumertest.NewNop(), newTestLProcessor(nil),
WithStart(func(context.Context, component.Host) error { return want }),
WithShutdown(func(context.Context) error { return want }),
WithCapabilities(consumer.Capabilities{MutatesData: false}))
require.NoError(t, err)
assert.Equal(t, want, lp.Start(context.Background(), componenttest.NewNopHost()))
assert.Equal(t, want, lp.Shutdown(context.Background()))
assert.False(t, lp.Capabilities().MutatesData)
}
func TestNewLogs_NilRequiredFields(t *testing.T) {
_, err := NewLogs(context.Background(), processortest.NewNopSettings(processortest.NopType), &testLogsCfg, consumertest.NewNop(), nil)
assert.Error(t, err)
}
func TestNewLogs_ProcessLogError(t *testing.T) {
want := errors.New("my_error")
lp, err := NewLogs(context.Background(), processortest.NewNopSettings(processortest.NopType), &testLogsCfg, consumertest.NewNop(), newTestLProcessor(want))
require.NoError(t, err)
assert.Equal(t, want, lp.ConsumeLogs(context.Background(), plog.NewLogs()))
}
func TestNewLogs_ProcessLogsErrSkipProcessingData(t *testing.T) {
lp, err := NewLogs(context.Background(), processortest.NewNopSettings(processortest.NopType), &testLogsCfg, consumertest.NewNop(), newTestLProcessor(ErrSkipProcessingData))
require.NoError(t, err)
assert.NoError(t, lp.ConsumeLogs(context.Background(), plog.NewLogs()))
}
func newTestLProcessor(retError error) ProcessLogsFunc {
return func(_ context.Context, ld plog.Logs) (plog.Logs, error) {
return ld, retError
}
}
func TestLogsConcurrency(t *testing.T) {
logsFunc := func(_ context.Context, ld plog.Logs) (plog.Logs, error) {
return ld, nil
}
incomingLogs := plog.NewLogs()
incomingLogRecords := incomingLogs.ResourceLogs().AppendEmpty().ScopeLogs().AppendEmpty().LogRecords()
// Add 3 records to the incoming
incomingLogRecords.AppendEmpty()
incomingLogRecords.AppendEmpty()
incomingLogRecords.AppendEmpty()
lp, err := NewLogs(context.Background(), processortest.NewNopSettings(processortest.NopType), &testLogsCfg, consumertest.NewNop(), logsFunc)
require.NoError(t, err)
assert.NoError(t, lp.Start(context.Background(), componenttest.NewNopHost()))
var wg sync.WaitGroup
for range 10 {
wg.Go(func() {
for range 10000 {
assert.NoError(t, lp.ConsumeLogs(context.Background(), incomingLogs))
}
})
}
wg.Wait()
assert.NoError(t, lp.Shutdown(context.Background()))
}
func TestLogs_RecordInOut(t *testing.T) {
// Regardless of how many logs are ingested, emit just one
mockAggregate := func(_ context.Context, _ plog.Logs) (plog.Logs, error) {
ld := plog.NewLogs()
ld.ResourceLogs().AppendEmpty().ScopeLogs().AppendEmpty().LogRecords().AppendEmpty()
return ld, nil
}
incomingLogs := plog.NewLogs()
incomingLogRecords := incomingLogs.ResourceLogs().AppendEmpty().ScopeLogs().AppendEmpty().LogRecords()
// Add 3 records to the incoming
incomingLogRecords.AppendEmpty()
incomingLogRecords.AppendEmpty()
incomingLogRecords.AppendEmpty()
tel := componenttest.NewTelemetry()
lp, err := NewLogs(context.Background(), newSettings(tel), &testLogsCfg, consumertest.NewNop(), mockAggregate)
require.NoError(t, err)
assert.NoError(t, lp.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, lp.ConsumeLogs(context.Background(), incomingLogs))
assert.NoError(t, lp.Shutdown(context.Background()))
metadatatest.AssertEqualProcessorIncomingItems(t, tel,
[]metricdata.DataPoint[int64]{
{
Value: 3,
Attributes: attribute.NewSet(attribute.String("processor", "processorhelper"), attribute.String("otel.signal", "logs")),
},
}, metricdatatest.IgnoreTimestamp())
metadatatest.AssertEqualProcessorOutgoingItems(t, tel,
[]metricdata.DataPoint[int64]{
{
Value: 1,
Attributes: attribute.NewSet(attribute.String("processor", "processorhelper"), attribute.String("otel.signal", "logs")),
},
}, metricdatatest.IgnoreTimestamp())
}
func TestLogs_RecordIn_ErrorOut(t *testing.T) {
// Regardless of input, return error
mockErr := func(_ context.Context, _ plog.Logs) (plog.Logs, error) {
return plog.NewLogs(), errors.New("fake")
}
incomingLogs := plog.NewLogs()
incomingLogRecords := incomingLogs.ResourceLogs().AppendEmpty().ScopeLogs().AppendEmpty().LogRecords()
// Add 3 records to the incoming
incomingLogRecords.AppendEmpty()
incomingLogRecords.AppendEmpty()
incomingLogRecords.AppendEmpty()
tel := componenttest.NewTelemetry()
lp, err := NewLogs(context.Background(), newSettings(tel), &testLogsCfg, consumertest.NewNop(), mockErr)
require.NoError(t, err)
require.NoError(t, lp.Start(context.Background(), componenttest.NewNopHost()))
require.Error(t, lp.ConsumeLogs(context.Background(), incomingLogs))
require.NoError(t, lp.Shutdown(context.Background()))
metadatatest.AssertEqualProcessorIncomingItems(t, tel,
[]metricdata.DataPoint[int64]{
{
Value: 3,
Attributes: attribute.NewSet(attribute.String("processor", "processorhelper"), attribute.String("otel.signal", "logs")),
},
}, metricdatatest.IgnoreTimestamp())
metadatatest.AssertEqualProcessorOutgoingItems(t, tel,
[]metricdata.DataPoint[int64]{
{
Value: 0,
Attributes: attribute.NewSet(attribute.String("processor", "processorhelper"), attribute.String("otel.signal", "logs")),
},
}, metricdatatest.IgnoreTimestamp())
}
func TestLogs_ProcessInternalDuration(t *testing.T) {
mockAggregate := func(_ context.Context, _ plog.Logs) (plog.Logs, error) {
ld := plog.NewLogs()
ld.ResourceLogs().AppendEmpty().ScopeLogs().AppendEmpty().LogRecords().AppendEmpty()
return ld, nil
}
incomingLogs := plog.NewLogs()
tel := componenttest.NewTelemetry()
lp, err := NewLogs(context.Background(), newSettings(tel), &testLogsCfg, consumertest.NewNop(), mockAggregate)
require.NoError(t, err)
assert.NoError(t, lp.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, lp.ConsumeLogs(context.Background(), incomingLogs))
assert.NoError(t, lp.Shutdown(context.Background()))
metadatatest.AssertEqualProcessorInternalDuration(t, tel,
[]metricdata.HistogramDataPoint[float64]{
{
Count: 1,
BucketCounts: []uint64{1},
Attributes: attribute.NewSet(attribute.String("processor", "processorhelper"), attribute.String("otel.signal", "logs")),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue())
}
func newSettings(tel *componenttest.Telemetry) processor.Settings {
set := processortest.NewNopSettings(component.MustNewType("processorhelper"))
set.TelemetrySettings = tel.NewTelemetrySettings()
return set
}
================================================
FILE: processor/processorhelper/metadata.yaml
================================================
type: processorhelper
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
stability:
beta: [traces, metrics, logs]
telemetry:
metrics:
processor_incoming_items:
enabled: true
stability: alpha
description: Number of items passed to the processor.
unit: "{item}"
sum:
value_type: int
monotonic: true
processor_internal_duration:
enabled: true
stability: alpha
description: Duration of time taken to process a batch of telemetry data through the processor.
unit: s
histogram:
async: false
value_type: double
processor_outgoing_items:
enabled: true
stability: alpha
description: Number of items emitted from the processor.
unit: "{item}"
sum:
value_type: int
monotonic: true
================================================
FILE: processor/processorhelper/metrics.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package processorhelper // import "go.opentelemetry.io/collector/processor/processorhelper"
import (
"context"
"errors"
"time"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/processor"
)
// ProcessMetricsFunc 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.
type ProcessMetricsFunc func(context.Context, pmetric.Metrics) (pmetric.Metrics, error)
type metrics struct {
component.StartFunc
component.ShutdownFunc
consumer.Metrics
}
// NewMetrics creates a processor.Metrics that ensure context propagation and the right tags are set.
func NewMetrics(
_ context.Context,
set processor.Settings,
_ component.Config,
nextConsumer consumer.Metrics,
metricsFunc ProcessMetricsFunc,
options ...Option,
) (processor.Metrics, error) {
if metricsFunc == nil {
return nil, errors.New("nil metricsFunc")
}
obs, err := newObsReport(set, pipeline.SignalMetrics)
if err != nil {
return nil, err
}
eventOptions := spanAttributes(set.ID)
bs := fromOptions(options)
metricsConsumer, err := consumer.NewMetrics(func(ctx context.Context, md pmetric.Metrics) error {
span := trace.SpanFromContext(ctx)
span.AddEvent("Start processing.", eventOptions)
startTime := time.Now()
pointsIn := md.DataPointCount()
var errFunc error
md, errFunc = metricsFunc(ctx, md)
obs.recordInternalDuration(ctx, startTime)
span.AddEvent("End processing.", eventOptions)
if errFunc != nil {
obs.recordInOut(ctx, pointsIn, 0)
if errors.Is(errFunc, ErrSkipProcessingData) {
return nil
}
return errFunc
}
pointsOut := md.DataPointCount()
obs.recordInOut(ctx, pointsIn, pointsOut)
return nextConsumer.ConsumeMetrics(ctx, md)
}, bs.consumerOptions...)
if err != nil {
return nil, err
}
return &metrics{
StartFunc: bs.StartFunc,
ShutdownFunc: bs.ShutdownFunc,
Metrics: metricsConsumer,
}, nil
}
================================================
FILE: processor/processorhelper/metrics_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package processorhelper
import (
"context"
"errors"
"sync"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"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/pdata/pmetric"
"go.opentelemetry.io/collector/processor/processorhelper/internal/metadatatest"
"go.opentelemetry.io/collector/processor/processortest"
)
var testMetricsCfg = struct{}{}
func TestNewMetrics(t *testing.T) {
mp, err := NewMetrics(context.Background(), processortest.NewNopSettings(processortest.NopType), &testMetricsCfg, consumertest.NewNop(), newTestMProcessor(nil))
require.NoError(t, err)
assert.True(t, mp.Capabilities().MutatesData)
assert.NoError(t, mp.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, mp.ConsumeMetrics(context.Background(), pmetric.NewMetrics()))
assert.NoError(t, mp.Shutdown(context.Background()))
}
func TestNewMetrics_WithOptions(t *testing.T) {
want := errors.New("my_error")
mp, err := NewMetrics(context.Background(), processortest.NewNopSettings(processortest.NopType), &testMetricsCfg, consumertest.NewNop(), newTestMProcessor(nil),
WithStart(func(context.Context, component.Host) error { return want }),
WithShutdown(func(context.Context) error { return want }),
WithCapabilities(consumer.Capabilities{MutatesData: false}))
require.NoError(t, err)
assert.Equal(t, want, mp.Start(context.Background(), componenttest.NewNopHost()))
assert.Equal(t, want, mp.Shutdown(context.Background()))
assert.False(t, mp.Capabilities().MutatesData)
}
func TestNewMetrics_NilRequiredFields(t *testing.T) {
_, err := NewMetrics(context.Background(), processortest.NewNopSettings(processortest.NopType), &testMetricsCfg, consumertest.NewNop(), nil)
assert.Error(t, err)
}
func TestNewMetrics_ProcessMetricsError(t *testing.T) {
want := errors.New("my_error")
mp, err := NewMetrics(context.Background(), processortest.NewNopSettings(processortest.NopType), &testMetricsCfg, consumertest.NewNop(), newTestMProcessor(want))
require.NoError(t, err)
assert.Equal(t, want, mp.ConsumeMetrics(context.Background(), pmetric.NewMetrics()))
}
func TestNewMetrics_ProcessMetricsErrSkipProcessingData(t *testing.T) {
mp, err := NewMetrics(context.Background(), processortest.NewNopSettings(processortest.NopType), &testMetricsCfg, consumertest.NewNop(), newTestMProcessor(ErrSkipProcessingData))
require.NoError(t, err)
assert.NoError(t, mp.ConsumeMetrics(context.Background(), pmetric.NewMetrics()))
}
func newTestMProcessor(retError error) ProcessMetricsFunc {
return func(_ context.Context, md pmetric.Metrics) (pmetric.Metrics, error) {
return md, retError
}
}
func TestMetricsConcurrency(t *testing.T) {
metricsFunc := func(_ context.Context, md pmetric.Metrics) (pmetric.Metrics, error) {
return md, nil
}
incomingMetrics := pmetric.NewMetrics()
dps := incomingMetrics.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty().SetEmptySum().DataPoints()
// Add 2 data points to the incoming
dps.AppendEmpty()
dps.AppendEmpty()
mp, err := NewMetrics(context.Background(), processortest.NewNopSettings(processortest.NopType), &testLogsCfg, consumertest.NewNop(), metricsFunc)
require.NoError(t, err)
assert.NoError(t, mp.Start(context.Background(), componenttest.NewNopHost()))
var wg sync.WaitGroup
for range 10 {
wg.Go(func() {
for range 10000 {
assert.NoError(t, mp.ConsumeMetrics(context.Background(), incomingMetrics))
}
})
}
wg.Wait()
assert.NoError(t, mp.Shutdown(context.Background()))
}
func TestMetrics_RecordInOut(t *testing.T) {
// Regardless of how many data points are ingested, emit 3
mockAggregate := func(_ context.Context, _ pmetric.Metrics) (pmetric.Metrics, error) {
md := pmetric.NewMetrics()
md.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty().SetEmptySum().DataPoints().AppendEmpty()
md.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty().SetEmptySum().DataPoints().AppendEmpty()
md.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty().SetEmptySum().DataPoints().AppendEmpty()
return md, nil
}
incomingMetrics := pmetric.NewMetrics()
dps := incomingMetrics.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty().SetEmptySum().DataPoints()
// Add 2 data points to the incoming
dps.AppendEmpty()
dps.AppendEmpty()
tel := componenttest.NewTelemetry()
mp, err := NewMetrics(context.Background(), newSettings(tel), &testMetricsCfg, consumertest.NewNop(), mockAggregate)
require.NoError(t, err)
assert.NoError(t, mp.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, mp.ConsumeMetrics(context.Background(), incomingMetrics))
assert.NoError(t, mp.Shutdown(context.Background()))
metadatatest.AssertEqualProcessorIncomingItems(t, tel,
[]metricdata.DataPoint[int64]{
{
Value: 2,
Attributes: attribute.NewSet(attribute.String("processor", "processorhelper"), attribute.String("otel.signal", "metrics")),
},
}, metricdatatest.IgnoreTimestamp())
metadatatest.AssertEqualProcessorOutgoingItems(t, tel,
[]metricdata.DataPoint[int64]{
{
Value: 3,
Attributes: attribute.NewSet(attribute.String("processor", "processorhelper"), attribute.String("otel.signal", "metrics")),
},
}, metricdatatest.IgnoreTimestamp())
}
func TestMetrics_RecordIn_ErrorOut(t *testing.T) {
/// Regardless of input, return error
mockErr := func(_ context.Context, _ pmetric.Metrics) (pmetric.Metrics, error) {
return pmetric.NewMetrics(), errors.New("fake")
}
incomingMetrics := pmetric.NewMetrics()
dps := incomingMetrics.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty().SetEmptySum().DataPoints()
// Add 2 data points to the incoming
dps.AppendEmpty()
dps.AppendEmpty()
tel := componenttest.NewTelemetry()
mp, err := NewMetrics(context.Background(), newSettings(tel), &testMetricsCfg, consumertest.NewNop(), mockErr)
require.NoError(t, err)
require.NoError(t, mp.Start(context.Background(), componenttest.NewNopHost()))
require.Error(t, mp.ConsumeMetrics(context.Background(), incomingMetrics))
require.NoError(t, mp.Shutdown(context.Background()))
metadatatest.AssertEqualProcessorIncomingItems(t, tel,
[]metricdata.DataPoint[int64]{
{
Value: 2,
Attributes: attribute.NewSet(attribute.String("processor", "processorhelper"), attribute.String("otel.signal", "metrics")),
},
}, metricdatatest.IgnoreTimestamp())
metadatatest.AssertEqualProcessorOutgoingItems(t, tel,
[]metricdata.DataPoint[int64]{
{
Value: 0,
Attributes: attribute.NewSet(attribute.String("processor", "processorhelper"), attribute.String("otel.signal", "metrics")),
},
}, metricdatatest.IgnoreTimestamp())
}
func TestMetrics_ProcessInternalDuration(t *testing.T) {
mockAggregate := func(_ context.Context, _ pmetric.Metrics) (pmetric.Metrics, error) {
md := pmetric.NewMetrics()
md.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty().SetEmptySum().DataPoints().AppendEmpty()
md.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty().SetEmptySum().DataPoints().AppendEmpty()
md.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty().SetEmptySum().DataPoints().AppendEmpty()
return md, nil
}
incomingMetrics := pmetric.NewMetrics()
tel := componenttest.NewTelemetry()
mp, err := NewMetrics(context.Background(), newSettings(tel), &testMetricsCfg, consumertest.NewNop(), mockAggregate)
require.NoError(t, err)
assert.NoError(t, mp.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, mp.ConsumeMetrics(context.Background(), incomingMetrics))
assert.NoError(t, mp.Shutdown(context.Background()))
metadatatest.AssertEqualProcessorInternalDuration(t, tel,
[]metricdata.HistogramDataPoint[float64]{
{
Count: 1,
BucketCounts: []uint64{1},
Attributes: attribute.NewSet(attribute.String("processor", "processorhelper"), attribute.String("otel.signal", "metrics")),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue())
}
================================================
FILE: processor/processorhelper/obsreport.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package processorhelper // import "go.opentelemetry.io/collector/processor/processorhelper"
import (
"context"
"time"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/internal"
"go.opentelemetry.io/collector/processor/processorhelper/internal/metadata"
)
const signalKey = "otel.signal"
type obsReport struct {
otelAttrs metric.MeasurementOption
telemetryBuilder *metadata.TelemetryBuilder
}
func newObsReport(set processor.Settings, signal pipeline.Signal) (*obsReport, error) {
telemetryBuilder, err := metadata.NewTelemetryBuilder(set.TelemetrySettings)
if err != nil {
return nil, err
}
return &obsReport{
otelAttrs: metric.WithAttributeSet(attribute.NewSet(
attribute.String(internal.ProcessorKey, set.ID.String()),
attribute.String(signalKey, signal.String()),
)),
telemetryBuilder: telemetryBuilder,
}, nil
}
func (or *obsReport) recordInOut(ctx context.Context, incoming, outgoing int) {
or.telemetryBuilder.ProcessorIncomingItems.Add(ctx, int64(incoming), or.otelAttrs)
or.telemetryBuilder.ProcessorOutgoingItems.Add(ctx, int64(outgoing), or.otelAttrs)
}
func (or *obsReport) recordInternalDuration(ctx context.Context, startTime time.Time) {
duration := time.Since(startTime)
or.telemetryBuilder.ProcessorInternalDuration.Record(ctx, duration.Seconds(), or.otelAttrs)
}
================================================
FILE: processor/processorhelper/processor.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:generate mdatagen metadata.yaml
package processorhelper // import "go.opentelemetry.io/collector/processor/processorhelper"
import (
"errors"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/processor/internal"
)
// 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")
// Option apply changes to internalOptions.
type Option interface {
apply(*baseSettings)
}
type optionFunc func(*baseSettings)
func (of optionFunc) apply(e *baseSettings) {
of(e)
}
// WithStart overrides the default Start function for an processor.
// The default shutdown function does nothing and always returns nil.
func WithStart(start component.StartFunc) Option {
return optionFunc(func(o *baseSettings) {
o.StartFunc = start
})
}
// WithShutdown overrides the default Shutdown function for an processor.
// The default shutdown function does nothing and always returns nil.
func WithShutdown(shutdown component.ShutdownFunc) Option {
return optionFunc(func(o *baseSettings) {
o.ShutdownFunc = shutdown
})
}
// WithCapabilities overrides the default GetCapabilities function for an processor.
// The default GetCapabilities function returns mutable capabilities.
func WithCapabilities(capabilities consumer.Capabilities) Option {
return optionFunc(func(o *baseSettings) {
o.consumerOptions = append(o.consumerOptions, consumer.WithCapabilities(capabilities))
})
}
type baseSettings struct {
component.StartFunc
component.ShutdownFunc
consumerOptions []consumer.Option
}
// 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{
consumerOptions: []consumer.Option{consumer.WithCapabilities(consumer.Capabilities{MutatesData: true})},
}
for _, op := range options {
op.apply(opts)
}
return opts
}
func spanAttributes(id component.ID) trace.EventOption {
return trace.WithAttributes(attribute.String(internal.ProcessorKey, id.String()))
}
================================================
FILE: processor/processorhelper/traces.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package processorhelper // import "go.opentelemetry.io/collector/processor/processorhelper"
import (
"context"
"errors"
"time"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/processor"
)
// ProcessTracesFunc 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.
type ProcessTracesFunc func(context.Context, ptrace.Traces) (ptrace.Traces, error)
type traces struct {
component.StartFunc
component.ShutdownFunc
consumer.Traces
}
// NewTraces creates a processor.Traces that ensure context propagation and the right tags are set.
func NewTraces(
_ context.Context,
set processor.Settings,
_ component.Config,
nextConsumer consumer.Traces,
tracesFunc ProcessTracesFunc,
options ...Option,
) (processor.Traces, error) {
if tracesFunc == nil {
return nil, errors.New("nil tracesFunc")
}
obs, err := newObsReport(set, pipeline.SignalTraces)
if err != nil {
return nil, err
}
eventOptions := spanAttributes(set.ID)
bs := fromOptions(options)
traceConsumer, err := consumer.NewTraces(func(ctx context.Context, td ptrace.Traces) error {
span := trace.SpanFromContext(ctx)
span.AddEvent("Start processing.", eventOptions)
startTime := time.Now()
spansIn := td.SpanCount()
var errFunc error
td, errFunc = tracesFunc(ctx, td)
obs.recordInternalDuration(ctx, startTime)
span.AddEvent("End processing.", eventOptions)
if errFunc != nil {
obs.recordInOut(ctx, spansIn, 0)
if errors.Is(errFunc, ErrSkipProcessingData) {
return nil
}
return errFunc
}
spansOut := td.SpanCount()
obs.recordInOut(ctx, spansIn, spansOut)
return nextConsumer.ConsumeTraces(ctx, td)
}, bs.consumerOptions...)
if err != nil {
return nil, err
}
return &traces{
StartFunc: bs.StartFunc,
ShutdownFunc: bs.ShutdownFunc,
Traces: traceConsumer,
}, nil
}
================================================
FILE: processor/processorhelper/traces_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package processorhelper
import (
"context"
"errors"
"sync"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"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/pdata/ptrace"
"go.opentelemetry.io/collector/processor/processorhelper/internal/metadatatest"
"go.opentelemetry.io/collector/processor/processortest"
)
var testTracesCfg = struct{}{}
func TestNewTraces(t *testing.T) {
tp, err := NewTraces(context.Background(), processortest.NewNopSettings(processortest.NopType), &testTracesCfg, consumertest.NewNop(), newTestTProcessor(nil))
require.NoError(t, err)
assert.True(t, tp.Capabilities().MutatesData)
assert.NoError(t, tp.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, tp.ConsumeTraces(context.Background(), ptrace.NewTraces()))
assert.NoError(t, tp.Shutdown(context.Background()))
}
func TestNewTraces_WithOptions(t *testing.T) {
want := errors.New("my_error")
tp, err := NewTraces(context.Background(), processortest.NewNopSettings(processortest.NopType), &testTracesCfg, consumertest.NewNop(), newTestTProcessor(nil),
WithStart(func(context.Context, component.Host) error { return want }),
WithShutdown(func(context.Context) error { return want }),
WithCapabilities(consumer.Capabilities{MutatesData: false}))
require.NoError(t, err)
assert.Equal(t, want, tp.Start(context.Background(), componenttest.NewNopHost()))
assert.Equal(t, want, tp.Shutdown(context.Background()))
assert.False(t, tp.Capabilities().MutatesData)
}
func TestNewTraces_NilRequiredFields(t *testing.T) {
_, err := NewTraces(context.Background(), processortest.NewNopSettings(processortest.NopType), &testTracesCfg, consumertest.NewNop(), nil)
assert.Error(t, err)
}
func TestNewTraces_ProcessTraceError(t *testing.T) {
want := errors.New("my_error")
tp, err := NewTraces(context.Background(), processortest.NewNopSettings(processortest.NopType), &testTracesCfg, consumertest.NewNop(), newTestTProcessor(want))
require.NoError(t, err)
assert.Equal(t, want, tp.ConsumeTraces(context.Background(), ptrace.NewTraces()))
}
func TestNewTraces_ProcessTracesErrSkipProcessingData(t *testing.T) {
tp, err := NewTraces(context.Background(), processortest.NewNopSettings(processortest.NopType), &testTracesCfg, consumertest.NewNop(), newTestTProcessor(ErrSkipProcessingData))
require.NoError(t, err)
assert.NoError(t, tp.ConsumeTraces(context.Background(), ptrace.NewTraces()))
}
func newTestTProcessor(retError error) ProcessTracesFunc {
return func(_ context.Context, td ptrace.Traces) (ptrace.Traces, error) {
return td, retError
}
}
func TestTracesConcurrency(t *testing.T) {
tracesFunc := func(_ context.Context, td ptrace.Traces) (ptrace.Traces, error) {
return td, nil
}
incomingTraces := ptrace.NewTraces()
incomingSpans := incomingTraces.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans()
// Add 4 records to the incoming
incomingSpans.AppendEmpty()
incomingSpans.AppendEmpty()
incomingSpans.AppendEmpty()
incomingSpans.AppendEmpty()
mp, err := NewTraces(context.Background(), processortest.NewNopSettings(processortest.NopType), &testLogsCfg, consumertest.NewNop(), tracesFunc)
require.NoError(t, err)
assert.NoError(t, mp.Start(context.Background(), componenttest.NewNopHost()))
var wg sync.WaitGroup
for range 10 {
wg.Go(func() {
for range 10000 {
assert.NoError(t, mp.ConsumeTraces(context.Background(), incomingTraces))
}
})
}
wg.Wait()
assert.NoError(t, mp.Shutdown(context.Background()))
}
func TestTraces_RecordInOut(t *testing.T) {
// Regardless of how many spans are ingested, emit just one
mockAggregate := func(_ context.Context, _ ptrace.Traces) (ptrace.Traces, error) {
td := ptrace.NewTraces()
td.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans().AppendEmpty()
return td, nil
}
incomingTraces := ptrace.NewTraces()
incomingSpans := incomingTraces.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans()
// Add 4 records to the incoming
incomingSpans.AppendEmpty()
incomingSpans.AppendEmpty()
incomingSpans.AppendEmpty()
incomingSpans.AppendEmpty()
tel := componenttest.NewTelemetry()
tp, err := NewTraces(context.Background(), newSettings(tel), &testLogsCfg, consumertest.NewNop(), mockAggregate)
require.NoError(t, err)
assert.NoError(t, tp.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, tp.ConsumeTraces(context.Background(), incomingTraces))
assert.NoError(t, tp.Shutdown(context.Background()))
metadatatest.AssertEqualProcessorIncomingItems(t, tel,
[]metricdata.DataPoint[int64]{
{
Value: 4,
Attributes: attribute.NewSet(attribute.String("processor", "processorhelper"), attribute.String("otel.signal", "traces")),
},
}, metricdatatest.IgnoreTimestamp())
metadatatest.AssertEqualProcessorOutgoingItems(t, tel,
[]metricdata.DataPoint[int64]{
{
Value: 1,
Attributes: attribute.NewSet(attribute.String("processor", "processorhelper"), attribute.String("otel.signal", "traces")),
},
}, metricdatatest.IgnoreTimestamp())
}
func TestTraces_RecordIn_ErrorOut(t *testing.T) {
// Regardless of input, return error
mockErr := func(_ context.Context, _ ptrace.Traces) (ptrace.Traces, error) {
return ptrace.NewTraces(), errors.New("fake")
}
incomingTraces := ptrace.NewTraces()
incomingSpans := incomingTraces.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans()
// Add 4 records to the incoming
incomingSpans.AppendEmpty()
incomingSpans.AppendEmpty()
incomingSpans.AppendEmpty()
incomingSpans.AppendEmpty()
tel := componenttest.NewTelemetry()
tp, err := NewTraces(context.Background(), newSettings(tel), &testLogsCfg, consumertest.NewNop(), mockErr)
require.NoError(t, err)
require.NoError(t, tp.Start(context.Background(), componenttest.NewNopHost()))
require.Error(t, tp.ConsumeTraces(context.Background(), incomingTraces))
require.NoError(t, tp.Shutdown(context.Background()))
metadatatest.AssertEqualProcessorIncomingItems(t, tel,
[]metricdata.DataPoint[int64]{
{
Value: 4,
Attributes: attribute.NewSet(attribute.String("processor", "processorhelper"), attribute.String("otel.signal", "traces")),
},
}, metricdatatest.IgnoreTimestamp())
metadatatest.AssertEqualProcessorOutgoingItems(t, tel,
[]metricdata.DataPoint[int64]{
{
Value: 0,
Attributes: attribute.NewSet(attribute.String("processor", "processorhelper"), attribute.String("otel.signal", "traces")),
},
}, metricdatatest.IgnoreTimestamp())
}
func TestTraces_ProcessInternalDuration(t *testing.T) {
mockAggregate := func(_ context.Context, _ ptrace.Traces) (ptrace.Traces, error) {
td := ptrace.NewTraces()
td.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans().AppendEmpty()
return td, nil
}
incomingTraces := ptrace.NewTraces()
tel := componenttest.NewTelemetry()
tp, err := NewTraces(context.Background(), newSettings(tel), &testLogsCfg, consumertest.NewNop(), mockAggregate)
require.NoError(t, err)
assert.NoError(t, tp.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, tp.ConsumeTraces(context.Background(), incomingTraces))
assert.NoError(t, tp.Shutdown(context.Background()))
metadatatest.AssertEqualProcessorInternalDuration(t, tel,
[]metricdata.HistogramDataPoint[float64]{
{
Count: 1,
BucketCounts: []uint64{1},
Attributes: attribute.NewSet(attribute.String("processor", "processorhelper"), attribute.String("otel.signal", "traces")),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue())
}
================================================
FILE: processor/processorhelper/xprocessorhelper/Makefile
================================================
include ../../../Makefile.Common
================================================
FILE: processor/processorhelper/xprocessorhelper/go.mod
================================================
module go.opentelemetry.io/collector/processor/processorhelper/xprocessorhelper
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.opentelemetry.io/collector/consumer v1.54.0
go.opentelemetry.io/collector/consumer/consumertest v0.148.0
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0
go.opentelemetry.io/collector/pdata/pprofile v0.148.0
go.opentelemetry.io/collector/processor v1.54.0
go.opentelemetry.io/collector/processor/processorhelper v0.148.0
go.opentelemetry.io/collector/processor/processortest v0.148.0
go.opentelemetry.io/collector/processor/xprocessor v0.148.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/component/componentstatus v0.148.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
go.opentelemetry.io/collector/pdata v1.54.0 // indirect
go.opentelemetry.io/collector/pdata/testdata v0.148.0 // indirect
go.opentelemetry.io/collector/pipeline v1.54.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
golang.org/x/sys v0.41.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/consumer/consumertest => ../../../consumer/consumertest
replace go.opentelemetry.io/collector/pdata/pprofile => ../../../pdata/pprofile
replace go.opentelemetry.io/collector/pdata/testdata => ../../../pdata/testdata
replace go.opentelemetry.io/collector/processor => ../../../processor
replace go.opentelemetry.io/collector/consumer => ../../../consumer
replace go.opentelemetry.io/collector/consumer/xconsumer => ../../../consumer/xconsumer
replace go.opentelemetry.io/collector/component => ../../../component
replace go.opentelemetry.io/collector/component/componenttest => ../../../component/componenttest
replace go.opentelemetry.io/collector/pdata => ../../../pdata
replace go.opentelemetry.io/collector/pipeline => ../../../pipeline
replace go.opentelemetry.io/collector/component/componentstatus => ../../../component/componentstatus
replace go.opentelemetry.io/collector/processor/processortest => ../../processortest
replace go.opentelemetry.io/collector/processor/processorhelper => ../
replace go.opentelemetry.io/collector/processor/xprocessor => ../../xprocessor
replace go.opentelemetry.io/collector/featuregate => ../../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../../internal/componentalias
================================================
FILE: processor/processorhelper/xprocessorhelper/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: processor/processorhelper/xprocessorhelper/metadata.yaml
================================================
type: xprocessorhelper
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: processor/processorhelper/xprocessorhelper/processor.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xprocessorhelper // import "go.opentelemetry.io/collector/processor/processorhelper/xprocessorhelper"
import (
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
)
// Option apply changes to internalOptions.
type Option interface {
apply(*baseSettings)
}
type optionFunc func(*baseSettings)
func (of optionFunc) apply(e *baseSettings) {
of(e)
}
// WithStart overrides the default Start function for an processor.
// The default shutdown function does nothing and always returns nil.
func WithStart(start component.StartFunc) Option {
return optionFunc(func(o *baseSettings) {
o.StartFunc = start
})
}
// WithShutdown overrides the default Shutdown function for an processor.
// The default shutdown function does nothing and always returns nil.
func WithShutdown(shutdown component.ShutdownFunc) Option {
return optionFunc(func(o *baseSettings) {
o.ShutdownFunc = shutdown
})
}
// WithCapabilities overrides the default GetCapabilities function for an processor.
// The default GetCapabilities function returns mutable capabilities.
func WithCapabilities(capabilities consumer.Capabilities) Option {
return optionFunc(func(o *baseSettings) {
o.consumerOptions = append(o.consumerOptions, consumer.WithCapabilities(capabilities))
})
}
type baseSettings struct {
component.StartFunc
component.ShutdownFunc
consumerOptions []consumer.Option
}
// 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{
consumerOptions: []consumer.Option{consumer.WithCapabilities(consumer.Capabilities{MutatesData: true})},
}
for _, op := range options {
op.apply(opts)
}
return opts
}
================================================
FILE: processor/processorhelper/xprocessorhelper/profiles.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xprocessorhelper // import "go.opentelemetry.io/collector/processor/processorhelper/xprocessorhelper"
import (
"context"
"errors"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/processorhelper"
"go.opentelemetry.io/collector/processor/xprocessor"
)
// ProcessProfilesFunc 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.
type ProcessProfilesFunc func(context.Context, pprofile.Profiles) (pprofile.Profiles, error)
type profiles struct {
component.StartFunc
component.ShutdownFunc
xconsumer.Profiles
}
// NewProfiles creates a xprocessor.Profiles that ensure context propagation.
func NewProfiles(
_ context.Context,
_ processor.Settings,
_ component.Config,
nextConsumer xconsumer.Profiles,
profilesFunc ProcessProfilesFunc,
options ...Option,
) (xprocessor.Profiles, error) {
if profilesFunc == nil {
return nil, errors.New("nil profilesFunc")
}
bs := fromOptions(options)
profilesConsumer, err := xconsumer.NewProfiles(func(ctx context.Context, pd pprofile.Profiles) (err error) {
pd, err = profilesFunc(ctx, pd)
if err != nil {
if errors.Is(err, processorhelper.ErrSkipProcessingData) {
return nil
}
return err
}
return nextConsumer.ConsumeProfiles(ctx, pd)
}, bs.consumerOptions...)
if err != nil {
return nil, err
}
return &profiles{
StartFunc: bs.StartFunc,
ShutdownFunc: bs.ShutdownFunc,
Profiles: profilesConsumer,
}, nil
}
================================================
FILE: processor/processorhelper/xprocessorhelper/profiles_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xprocessorhelper
import (
"context"
"errors"
"sync"
"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/consumer"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/processor/processorhelper"
"go.opentelemetry.io/collector/processor/processortest"
)
var testProfilesCfg = struct{}{}
func TestNewProfiles(t *testing.T) {
pp, err := NewProfiles(context.Background(), processortest.NewNopSettings(processortest.NopType), &testProfilesCfg, consumertest.NewNop(), newTestPProcessor(nil))
require.NoError(t, err)
assert.True(t, pp.Capabilities().MutatesData)
assert.NoError(t, pp.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, pp.ConsumeProfiles(context.Background(), pprofile.NewProfiles()))
assert.NoError(t, pp.Shutdown(context.Background()))
}
func TestNewProfiles_WithOptions(t *testing.T) {
want := errors.New("my_error")
pp, err := NewProfiles(context.Background(), processortest.NewNopSettings(processortest.NopType), &testProfilesCfg, consumertest.NewNop(), newTestPProcessor(nil),
WithStart(func(context.Context, component.Host) error { return want }),
WithShutdown(func(context.Context) error { return want }),
WithCapabilities(consumer.Capabilities{MutatesData: false}))
require.NoError(t, err)
assert.Equal(t, want, pp.Start(context.Background(), componenttest.NewNopHost()))
assert.Equal(t, want, pp.Shutdown(context.Background()))
assert.False(t, pp.Capabilities().MutatesData)
}
func TestNewProfiles_NilRequiredFields(t *testing.T) {
_, err := NewProfiles(context.Background(), processortest.NewNopSettings(processortest.NopType), &testProfilesCfg, consumertest.NewNop(), nil)
assert.Error(t, err)
}
func TestNewProfiles_ProcessProfileError(t *testing.T) {
want := errors.New("my_error")
pp, err := NewProfiles(context.Background(), processortest.NewNopSettings(processortest.NopType), &testProfilesCfg, consumertest.NewNop(), newTestPProcessor(want))
require.NoError(t, err)
assert.Equal(t, want, pp.ConsumeProfiles(context.Background(), pprofile.NewProfiles()))
}
func TestNewProfiles_ProcessProfilesErrSkipProcessingData(t *testing.T) {
pp, err := NewProfiles(context.Background(), processortest.NewNopSettings(processortest.NopType), &testProfilesCfg, consumertest.NewNop(), newTestPProcessor(processorhelper.ErrSkipProcessingData))
require.NoError(t, err)
assert.NoError(t, pp.ConsumeProfiles(context.Background(), pprofile.NewProfiles()))
}
func newTestPProcessor(retError error) ProcessProfilesFunc {
return func(_ context.Context, pd pprofile.Profiles) (pprofile.Profiles, error) {
return pd, retError
}
}
func TestProfilesConcurrency(t *testing.T) {
profilesFunc := func(_ context.Context, pd pprofile.Profiles) (pprofile.Profiles, error) {
return pd, nil
}
incomingProfiles := pprofile.NewProfiles()
ps := incomingProfiles.ResourceProfiles().AppendEmpty().ScopeProfiles().AppendEmpty().Profiles()
// Add 3 profiles to the incoming
ps.AppendEmpty()
ps.AppendEmpty()
ps.AppendEmpty()
pp, err := NewProfiles(context.Background(), processortest.NewNopSettings(processortest.NopType), &testProfilesCfg, consumertest.NewNop(), profilesFunc)
require.NoError(t, err)
assert.NoError(t, pp.Start(context.Background(), componenttest.NewNopHost()))
var wg sync.WaitGroup
for range 10 {
wg.Go(func() {
for range 10000 {
assert.NoError(t, pp.ConsumeProfiles(context.Background(), incomingProfiles))
}
})
}
wg.Wait()
assert.NoError(t, pp.Shutdown(context.Background()))
}
================================================
FILE: processor/processortest/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: processor/processortest/go.mod
================================================
module go.opentelemetry.io/collector/processor/processortest
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componentstatus v0.148.0
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.opentelemetry.io/collector/consumer v1.54.0
go.opentelemetry.io/collector/consumer/consumertest v0.148.0
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/collector/pdata/pprofile v0.148.0
go.opentelemetry.io/collector/pdata/testdata v0.148.0
go.opentelemetry.io/collector/pipeline v1.54.0
go.opentelemetry.io/collector/processor v1.54.0
go.opentelemetry.io/collector/processor/xprocessor v0.148.0
go.uber.org/goleak v1.3.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
golang.org/x/sys v0.41.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest
replace go.opentelemetry.io/collector/component/componentstatus => ../../component/componentstatus
replace go.opentelemetry.io/collector/consumer => ../../consumer
replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer
replace go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile
replace go.opentelemetry.io/collector/pdata/testdata => ../../pdata/testdata
replace go.opentelemetry.io/collector/processor => ../../processor
replace go.opentelemetry.io/collector/processor/xprocessor => ../../processor/xprocessor
replace go.opentelemetry.io/collector/pipeline => ../../pipeline
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
================================================
FILE: processor/processortest/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: processor/processortest/metadata.yaml
================================================
type: processor/processortest
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: processor/processortest/nop_processor.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package processortest // import "go.opentelemetry.io/collector/processor/processortest"
import (
"context"
"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/consumer/xconsumer"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/xprocessor"
)
var NopType = component.MustNewType("nop")
// NewNopSettings returns a new nop settings for Create* functions with the given type.
func NewNopSettings(typ component.Type) processor.Settings {
return processor.Settings{
ID: component.NewID(typ),
TelemetrySettings: componenttest.NewNopTelemetrySettings(),
BuildInfo: component.NewDefaultBuildInfo(),
}
}
// NewNopFactory returns a component.ProcessorFactory that constructs nop processors.
func NewNopFactory() processor.Factory {
return xprocessor.NewFactory(
NopType,
func() component.Config { return &nopConfig{} },
xprocessor.WithTraces(createTraces, component.StabilityLevelStable),
xprocessor.WithMetrics(createMetrics, component.StabilityLevelStable),
xprocessor.WithLogs(createLogs, component.StabilityLevelStable),
xprocessor.WithProfiles(createProfiles, component.StabilityLevelAlpha),
)
}
func createTraces(context.Context, processor.Settings, component.Config, consumer.Traces) (processor.Traces, error) {
return nopInstance, nil
}
func createMetrics(context.Context, processor.Settings, component.Config, consumer.Metrics) (processor.Metrics, error) {
return nopInstance, nil
}
func createLogs(context.Context, processor.Settings, component.Config, consumer.Logs) (processor.Logs, error) {
return nopInstance, nil
}
func createProfiles(context.Context, processor.Settings, component.Config, xconsumer.Profiles) (xprocessor.Profiles, error) {
return nopInstance, nil
}
type nopConfig struct{}
var nopInstance = &nop{
Consumer: consumertest.NewNop(),
}
// nop acts as a processor for testing purposes.
type nop struct {
component.StartFunc
component.ShutdownFunc
consumertest.Consumer
}
================================================
FILE: processor/processortest/nop_processor_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package processortest
import (
"context"
"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/consumer"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/processor/xprocessor"
)
func TestNewNopFactory(t *testing.T) {
factory := NewNopFactory()
require.NotNil(t, factory)
assert.Equal(t, component.MustNewType("nop"), factory.Type())
cfg := factory.CreateDefaultConfig()
assert.Equal(t, &nopConfig{}, cfg)
traces, err := factory.CreateTraces(context.Background(), NewNopSettings(NopType), cfg, consumertest.NewNop())
require.NoError(t, err)
assert.Equal(t, consumer.Capabilities{MutatesData: false}, traces.Capabilities())
assert.NoError(t, traces.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, traces.ConsumeTraces(context.Background(), ptrace.NewTraces()))
assert.NoError(t, traces.Shutdown(context.Background()))
metrics, err := factory.CreateMetrics(context.Background(), NewNopSettings(NopType), cfg, consumertest.NewNop())
require.NoError(t, err)
assert.Equal(t, consumer.Capabilities{MutatesData: false}, metrics.Capabilities())
assert.NoError(t, metrics.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, metrics.ConsumeMetrics(context.Background(), pmetric.NewMetrics()))
assert.NoError(t, metrics.Shutdown(context.Background()))
logs, err := factory.CreateLogs(context.Background(), NewNopSettings(NopType), cfg, consumertest.NewNop())
require.NoError(t, err)
assert.Equal(t, consumer.Capabilities{MutatesData: false}, logs.Capabilities())
assert.NoError(t, logs.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, logs.ConsumeLogs(context.Background(), plog.NewLogs()))
assert.NoError(t, logs.Shutdown(context.Background()))
profiles, err := factory.(xprocessor.Factory).CreateProfiles(context.Background(), NewNopSettings(NopType), cfg, consumertest.NewNop())
require.NoError(t, err)
assert.Equal(t, consumer.Capabilities{MutatesData: false}, profiles.Capabilities())
assert.NoError(t, profiles.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, profiles.ConsumeProfiles(context.Background(), pprofile.NewProfiles()))
assert.NoError(t, profiles.Shutdown(context.Background()))
}
================================================
FILE: processor/processortest/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package processortest
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: processor/processortest/shutdown_verifier.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package processortest // import "go.opentelemetry.io/collector/processor/processortest"
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"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/pdata/testdata"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/processor"
)
func verifyTracesDoesNotProduceAfterShutdown(t *testing.T, factory processor.Factory, cfg component.Config) {
// Create a proc and output its produce to a sink.
nextSink := new(consumertest.TracesSink)
proc, err := factory.CreateTraces(context.Background(), NewNopSettings(factory.Type()), cfg, nextSink)
if errors.Is(err, pipeline.ErrSignalNotSupported) {
return
}
require.NoError(t, err)
assert.NoError(t, proc.Start(context.Background(), componenttest.NewNopHost()))
// Send some traces to the proc.
const generatedCount = 10
for range generatedCount {
require.NoError(t, proc.ConsumeTraces(context.Background(), testdata.GenerateTraces(1)))
}
// Now shutdown the proc.
assert.NoError(t, proc.Shutdown(context.Background()))
// The Shutdown() is done. It means the proc must have sent everything we
// gave it to the next sink.
assert.Equal(t, generatedCount, nextSink.SpanCount())
}
func verifyLogsDoesNotProduceAfterShutdown(t *testing.T, factory processor.Factory, cfg component.Config) {
// Create a proc and output its produce to a sink.
nextSink := new(consumertest.LogsSink)
proc, err := factory.CreateLogs(context.Background(), NewNopSettings(factory.Type()), cfg, nextSink)
if errors.Is(err, pipeline.ErrSignalNotSupported) {
return
}
require.NoError(t, err)
assert.NoError(t, proc.Start(context.Background(), componenttest.NewNopHost()))
// Send some logs to the proc.
const generatedCount = 10
for range generatedCount {
require.NoError(t, proc.ConsumeLogs(context.Background(), testdata.GenerateLogs(1)))
}
// Now shutdown the proc.
assert.NoError(t, proc.Shutdown(context.Background()))
// The Shutdown() is done. It means the proc must have sent everything we
// gave it to the next sink.
assert.Equal(t, generatedCount, nextSink.LogRecordCount())
}
func verifyMetricsDoesNotProduceAfterShutdown(t *testing.T, factory processor.Factory, cfg component.Config) {
// Create a proc and output its produce to a sink.
nextSink := new(consumertest.MetricsSink)
proc, err := factory.CreateMetrics(context.Background(), NewNopSettings(factory.Type()), cfg, nextSink)
if errors.Is(err, pipeline.ErrSignalNotSupported) {
return
}
require.NoError(t, err)
assert.NoError(t, proc.Start(context.Background(), componenttest.NewNopHost()))
// Send some metrics to the proc. testdata.GenerateMetrics creates metrics with 2 data points each.
const generatedCount = 10
for range generatedCount {
require.NoError(t, proc.ConsumeMetrics(context.Background(), testdata.GenerateMetrics(1)))
}
// Now shutdown the proc.
assert.NoError(t, proc.Shutdown(context.Background()))
// The Shutdown() is done. It means the proc must have sent everything we
// gave it to the next sink.
assert.Equal(t, generatedCount*2, nextSink.DataPointCount())
}
// VerifyShutdown verifies the processor doesn't produce telemetry data after shutdown.
func VerifyShutdown(t *testing.T, factory processor.Factory, cfg component.Config) {
verifyTracesDoesNotProduceAfterShutdown(t, factory, cfg)
verifyLogsDoesNotProduceAfterShutdown(t, factory, cfg)
verifyMetricsDoesNotProduceAfterShutdown(t, factory, cfg)
}
================================================
FILE: processor/processortest/shutdown_verifier_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package processortest
import (
"context"
"testing"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/processor"
)
func TestShutdownVerifier(t *testing.T) {
f := processor.NewFactory(
component.MustNewType("passthrough"),
func() component.Config { return struct{}{} },
processor.WithTraces(createPassthroughTraces, component.StabilityLevelStable),
processor.WithMetrics(createPassthroughMetrics, component.StabilityLevelStable),
processor.WithLogs(createPassthroughLogs, component.StabilityLevelStable),
)
VerifyShutdown(t, f, &struct{}{})
}
func TestShutdownVerifierLogsOnly(t *testing.T) {
f := processor.NewFactory(
component.MustNewType("passthrough"),
func() component.Config { return struct{}{} },
processor.WithLogs(createPassthroughLogs, component.StabilityLevelStable),
)
VerifyShutdown(t, f, &struct{}{})
}
func TestShutdownVerifierMetricsOnly(t *testing.T) {
f := processor.NewFactory(
component.MustNewType("passthrough"),
func() component.Config { return struct{}{} },
processor.WithMetrics(createPassthroughMetrics, component.StabilityLevelStable),
)
VerifyShutdown(t, f, &struct{}{})
}
func TestShutdownVerifierTracesOnly(t *testing.T) {
f := processor.NewFactory(
component.MustNewType("passthrough"),
func() component.Config { return struct{}{} },
processor.WithTraces(createPassthroughTraces, component.StabilityLevelStable),
)
VerifyShutdown(t, f, &struct{}{})
}
type passthrough struct {
processor.Traces
nextLogs consumer.Logs
nextMetrics consumer.Metrics
nextTraces consumer.Traces
}
func (passthrough) Start(context.Context, component.Host) error {
return nil
}
func (passthrough) Shutdown(context.Context) error {
return nil
}
func (passthrough) Capabilities() consumer.Capabilities {
return consumer.Capabilities{}
}
func createPassthroughLogs(_ context.Context, _ processor.Settings, _ component.Config, logs consumer.Logs) (processor.Logs, error) {
return passthrough{
nextLogs: logs,
}, nil
}
func createPassthroughMetrics(_ context.Context, _ processor.Settings, _ component.Config, metrics consumer.Metrics) (processor.Metrics, error) {
return passthrough{
nextMetrics: metrics,
}, nil
}
func createPassthroughTraces(_ context.Context, _ processor.Settings, _ component.Config, traces consumer.Traces) (processor.Traces, error) {
return passthrough{
nextTraces: traces,
}, nil
}
func (p passthrough) ConsumeTraces(ctx context.Context, td ptrace.Traces) error {
return p.nextTraces.ConsumeTraces(ctx, td)
}
func (p passthrough) ConsumeMetrics(ctx context.Context, md pmetric.Metrics) error {
return p.nextMetrics.ConsumeMetrics(ctx, md)
}
func (p passthrough) ConsumeLogs(ctx context.Context, ld plog.Logs) error {
return p.nextLogs.ConsumeLogs(ctx, ld)
}
================================================
FILE: processor/processortest/unhealthy_processor.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package processortest // import "go.opentelemetry.io/collector/processor/processortest"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componentstatus"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/processor"
)
// NewUnhealthyProcessorFactory returns a processor.Factory that constructs nop processors.
func NewUnhealthyProcessorFactory() processor.Factory {
return processor.NewFactory(
component.MustNewType("unhealthy"),
func() component.Config {
return &struct{}{}
},
processor.WithTraces(createUnhealthyTraces, component.StabilityLevelStable),
processor.WithMetrics(createUnhealthyMetrics, component.StabilityLevelStable),
processor.WithLogs(createUnhealthyLogs, component.StabilityLevelStable),
)
}
func createUnhealthyTraces(_ context.Context, set processor.Settings, _ component.Config, _ consumer.Traces) (processor.Traces, error) {
return &unhealthy{
Consumer: consumertest.NewNop(),
telemetry: set.TelemetrySettings,
}, nil
}
func createUnhealthyMetrics(_ context.Context, set processor.Settings, _ component.Config, _ consumer.Metrics) (processor.Metrics, error) {
return &unhealthy{
Consumer: consumertest.NewNop(),
telemetry: set.TelemetrySettings,
}, nil
}
func createUnhealthyLogs(_ context.Context, set processor.Settings, _ component.Config, _ consumer.Logs) (processor.Logs, error) {
return &unhealthy{
Consumer: consumertest.NewNop(),
telemetry: set.TelemetrySettings,
}, nil
}
type unhealthy struct {
component.StartFunc
component.ShutdownFunc
consumertest.Consumer
telemetry component.TelemetrySettings
}
func (p unhealthy) Start(_ context.Context, host component.Host) error {
go func() {
componentstatus.ReportStatus(host, componentstatus.NewEvent(componentstatus.StatusRecoverableError))
}()
return nil
}
================================================
FILE: processor/processortest/unhealthy_processor_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package processortest
import (
"context"
"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/consumer"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
)
func TestNewUnhealthyProcessorFactory(t *testing.T) {
factory := NewUnhealthyProcessorFactory()
require.NotNil(t, factory)
assert.Equal(t, component.MustNewType("unhealthy"), factory.Type())
cfg := factory.CreateDefaultConfig()
assert.Equal(t, &struct{}{}, cfg)
traces, err := factory.CreateTraces(context.Background(), NewNopSettings(factory.Type()), cfg, consumertest.NewNop())
require.NoError(t, err)
assert.Equal(t, consumer.Capabilities{MutatesData: false}, traces.Capabilities())
assert.NoError(t, traces.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, traces.ConsumeTraces(context.Background(), ptrace.NewTraces()))
assert.NoError(t, traces.Shutdown(context.Background()))
metrics, err := factory.CreateMetrics(context.Background(), NewNopSettings(factory.Type()), cfg, consumertest.NewNop())
require.NoError(t, err)
assert.Equal(t, consumer.Capabilities{MutatesData: false}, metrics.Capabilities())
assert.NoError(t, metrics.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, metrics.ConsumeMetrics(context.Background(), pmetric.NewMetrics()))
assert.NoError(t, metrics.Shutdown(context.Background()))
logs, err := factory.CreateLogs(context.Background(), NewNopSettings(factory.Type()), cfg, consumertest.NewNop())
require.NoError(t, err)
assert.Equal(t, consumer.Capabilities{MutatesData: false}, logs.Capabilities())
assert.NoError(t, logs.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, logs.ConsumeLogs(context.Background(), plog.NewLogs()))
assert.NoError(t, logs.Shutdown(context.Background()))
}
================================================
FILE: processor/xprocessor/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: processor/xprocessor/go.mod
================================================
module go.opentelemetry.io/collector/processor/xprocessor
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/consumer/consumertest v0.148.0
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0
go.opentelemetry.io/collector/internal/componentalias v0.148.0
go.opentelemetry.io/collector/pipeline v1.54.0
go.opentelemetry.io/collector/processor v1.54.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
go.opentelemetry.io/collector/consumer v1.54.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/pdata v1.54.0 // indirect
go.opentelemetry.io/collector/pdata/pprofile v0.148.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/processor => ../
replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile
replace go.opentelemetry.io/collector/consumer => ../../consumer
replace go.opentelemetry.io/collector/pdata/testdata => ../../pdata/testdata
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer
replace go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest
replace go.opentelemetry.io/collector/pipeline => ../../pipeline
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
================================================
FILE: processor/xprocessor/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: processor/xprocessor/metadata.yaml
================================================
type: xprocessor
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
codeowners:
active:
- mx-psi
- dmathieu
stability:
alpha: [profiles]
================================================
FILE: processor/xprocessor/processor.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xprocessor // import "go.opentelemetry.io/collector/processor/xprocessor"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/internal/componentalias"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/processor"
)
// Factory is a component.Factory interface for processors.
//
// This interface cannot be directly implemented. Implementations must
// use the NewFactory to implement it.
type Factory interface {
processor.Factory
// CreateProfiles creates a Profiles processor based on this config.
// If the processor type does not support tracing or if the config is not valid,
// an error will be returned instead.
CreateProfiles(ctx context.Context, set processor.Settings, cfg component.Config, next xconsumer.Profiles) (Profiles, error)
// ProfilesStability gets the stability level of the Profiles processor.
ProfilesStability() component.StabilityLevel
}
// Profiles is a processor that can consume profiles.
type Profiles interface {
component.Component
xconsumer.Profiles
}
// CreateProfilesFunc is the equivalent of Factory.CreateProfiles().
// CreateProfilesFunc is the equivalent of Factory.CreateProfiles().
type CreateProfilesFunc func(context.Context, processor.Settings, component.Config, xconsumer.Profiles) (Profiles, error)
// FactoryOption apply changes to ReceiverOptions.
type FactoryOption interface {
// applyOption applies the option.
applyOption(o *factory)
}
// factoryOptionFunc is an ReceiverFactoryOption created through a function.
type factoryOptionFunc func(*factory)
func (f factoryOptionFunc) applyOption(o *factory) {
f(o)
}
type factory struct {
processor.Factory
componentalias.TypeAliasHolder
opts []processor.FactoryOption
createProfilesFunc CreateProfilesFunc
profilesStabilityLevel component.StabilityLevel
}
func (f *factory) ProfilesStability() component.StabilityLevel {
return f.profilesStabilityLevel
}
func (f *factory) CreateProfiles(ctx context.Context, set processor.Settings, cfg component.Config, next xconsumer.Profiles) (Profiles, error) {
if f.createProfilesFunc == nil {
return nil, pipeline.ErrSignalNotSupported
}
if err := componentalias.ValidateComponentType(f.Factory, set.ID); err != nil {
return nil, err
}
return f.createProfilesFunc(ctx, set, cfg, next)
}
// WithTraces overrides the default "error not supported" implementation for CreateTraces and the default "undefined" stability level.
func WithTraces(createTraces processor.CreateTracesFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.opts = append(o.opts, processor.WithTraces(createTraces, sl))
})
}
// WithMetrics overrides the default "error not supported" implementation for CreateMetrics and the default "undefined" stability level.
func WithMetrics(createMetrics processor.CreateMetricsFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.opts = append(o.opts, processor.WithMetrics(createMetrics, sl))
})
}
// WithLogs overrides the default "error not supported" implementation for CreateLogs and the default "undefined" stability level.
func WithLogs(createLogs processor.CreateLogsFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.opts = append(o.opts, processor.WithLogs(createLogs, sl))
})
}
// WithProfiles overrides the default "error not supported" implementation for CreateProfiles and the default "undefined" stability level.
func WithProfiles(createProfiles CreateProfilesFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.profilesStabilityLevel = sl
o.createProfilesFunc = createProfiles
})
}
// WithDeprecatedTypeAlias configures a deprecated type alias for the processor. Only one alias is supported per processor.
// When the alias is used in configuration, a deprecation warning is automatically logged.
func WithDeprecatedTypeAlias(alias component.Type) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.SetDeprecatedAlias(alias)
})
}
// NewFactory creates a wrapped processor.Factory with experimental capabilities.
func NewFactory(cfgType component.Type, createDefaultConfig component.CreateDefaultConfigFunc, options ...FactoryOption) Factory {
f := &factory{TypeAliasHolder: componentalias.NewTypeAliasHolder()}
for _, opt := range options {
opt.applyOption(f)
}
f.Factory = processor.NewFactory(cfgType, createDefaultConfig, f.opts...)
f.Factory.(componentalias.TypeAliasHolder).SetDeprecatedAlias(f.DeprecatedAlias())
return f
}
================================================
FILE: processor/xprocessor/processor_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xprocessor
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/internal/componentalias"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/internal"
)
var testID = component.MustNewID("test")
func TestNewFactoryWithProfiles(t *testing.T) {
testType := component.MustNewType("test")
defaultCfg := struct{}{}
factory := NewFactory(
testType,
func() component.Config { return &defaultCfg },
WithProfiles(createProfiles, component.StabilityLevelAlpha),
)
assert.Equal(t, testType, factory.Type())
assert.EqualValues(t, &defaultCfg, factory.CreateDefaultConfig())
assert.Equal(t, component.StabilityLevelAlpha, factory.ProfilesStability())
_, err := factory.CreateProfiles(context.Background(), processor.Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
require.NoError(t, err)
wrongID := component.MustNewID("wrong")
wrongIDErrStr := internal.ErrIDMismatch(wrongID, testType).Error()
_, err = factory.CreateProfiles(context.Background(), processor.Settings{ID: wrongID}, &defaultCfg, consumertest.NewNop())
assert.EqualError(t, err, wrongIDErrStr)
}
var nopInstance = &nopProcessor{
Consumer: consumertest.NewNop(),
}
// nopProcessor stores consumed traces and metrics for testing purposes.
type nopProcessor struct {
component.StartFunc
component.ShutdownFunc
consumertest.Consumer
}
func createProfiles(context.Context, processor.Settings, component.Config, xconsumer.Profiles) (Profiles, error) {
return nopInstance, nil
}
func TestNewFactoryWithDeprecatedAlias(t *testing.T) {
testType := component.MustNewType("newname")
aliasType := component.MustNewType("oldname")
defaultCfg := struct{}{}
f := NewFactory(
testType,
func() component.Config { return &defaultCfg },
WithProfiles(createProfiles, component.StabilityLevelAlpha),
WithDeprecatedTypeAlias(aliasType),
)
assert.Equal(t, testType, f.Type())
assert.Equal(t, aliasType, f.(*factory).Factory.(componentalias.TypeAliasHolder).DeprecatedAlias())
assert.EqualValues(t, &defaultCfg, f.CreateDefaultConfig())
_, err := f.CreateProfiles(context.Background(), processor.Settings{ID: component.MustNewID("newname")}, &defaultCfg, consumertest.NewNop())
require.NoError(t, err)
_, err = f.CreateProfiles(context.Background(), processor.Settings{ID: component.MustNewID("oldname")}, &defaultCfg, consumertest.NewNop())
require.NoError(t, err)
_, err = f.CreateProfiles(context.Background(), processor.Settings{ID: component.MustNewID("wrongname")}, &defaultCfg, consumertest.NewNop())
require.Error(t, err)
}
================================================
FILE: receiver/Makefile
================================================
include ../Makefile.Common
================================================
FILE: receiver/README.md
================================================
# 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.
This repository hosts the following receiver available in traces, metrics
and logs pipelines:
- [OTLP Receiver](otlpreceiver/README.md)
The [contrib repository](https://github.com/open-telemetry/opentelemetry-collector-contrib)
has more receivers available in its builds.
## 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.
================================================
FILE: receiver/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package receiver defines components that allow the Collector to receive metrics, traces and logs.
//
// A receiver receives data from a source (either from a remote source via network
// or scrapes from a local host) and pushes the data to the pipelines it is attached
// to by calling the nextConsumer.Consume*() function.
//
// # Error Handling
//
// The nextConsumer.Consume*() function may return an error to indicate that the data was not
// accepted. This error should be handled as documented in the consumererror package.
//
// Depending on the error type, the receiver must indicate to the source from which it received the
// data the type of error in a protocol-dependent way, if that is supported by the receiving protocol.
// For example, a receiver for the OTLP/HTTP protocol would use the HTTP status codes as defined in
// the OTLP specification.
//
// # Acknowledgment and Checkpointing
//
// The receivers that receive data via a network protocol that support acknowledgments
// MUST follow this order of operations:
// - Receive data from some sender (typically from a network).
// - Push received data to the pipeline by calling nextConsumer.Consume*() function.
// - Acknowledge successful data receipt to the sender if Consume*() succeeded or
// return a failure to the sender if Consume*() returned an error.
//
// This ensures there are strong delivery guarantees once the data is acknowledged
// by the Collector.
//
// Similarly, receivers that use checkpointing to remember the position of last processed
// data (e.g. via storage extension) MUST store the checkpoint only AFTER the Consume*()
// call returns.
package receiver // import "go.opentelemetry.io/collector/receiver"
================================================
FILE: receiver/example_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package receiver_test
import (
"context"
"fmt"
"time"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/receiver"
)
var typeStr = component.MustNewType("example")
type exampleConfig struct {
Interval time.Duration
}
// Reciver must implement the Start() and Shutdown() methods so the receiver type can be compliant
// with the receiver.Traces interface.
type exampleReceiver struct {
host component.Host
cancel context.CancelFunc
nextConsumer consumer.Traces
config exampleConfig
}
func (rcvr *exampleReceiver) Start(ctx context.Context, host component.Host) error {
rcvr.host = host
ctx, rcvr.cancel = context.WithCancel(ctx)
go func() {
ticker := time.NewTicker(rcvr.config.Interval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
// Your receiver processing code should come here
err := rcvr.nextConsumer.ConsumeTraces(ctx, generateTrace())
if err != nil {
fmt.Println("Error when consuming trace: %w", err)
}
case <-ctx.Done():
return
}
}
}()
return nil
}
func (rcvr *exampleReceiver) Shutdown(_ context.Context) error {
if rcvr.cancel != nil {
rcvr.cancel()
}
return nil
}
func generateTrace() ptrace.Traces {
traces := ptrace.NewTraces()
// In reallity you may need to fetch or receive and transform
// some telemetry data.
// For this example we will just generate some dummy data
resourceSpan := traces.ResourceSpans().AppendEmpty()
resource := resourceSpan.Resource()
attrs := resource.Attributes()
attrs.PutInt("id", 1)
scopeSpans := resourceSpan.ScopeSpans().AppendEmpty()
scopeSpans.Scope().SetName("example-system")
scopeSpans.Scope().SetVersion("v1.0")
traceID := pcommon.TraceID([]byte("1"))
spanID := pcommon.SpanID([]byte("2"))
span := scopeSpans.Spans().AppendEmpty()
span.SetTraceID(traceID)
span.SetSpanID(spanID)
span.SetName("Operation 1")
span.SetKind(ptrace.SpanKindClient)
span.Status().SetCode(ptrace.StatusCodeOk)
span.SetStartTimestamp(pcommon.NewTimestampFromTime(time.Now()))
span.SetEndTimestamp(pcommon.NewTimestampFromTime(time.Now().Add(4 * time.Millisecond)))
// Other resources and spans could also be added.
return traces
}
func createDefaultConfig() component.Config {
return &exampleConfig{
Interval: time.Minute,
}
}
func createExampleReceiver(_ context.Context, _ receiver.Settings,
baseCfg component.Config, consumer consumer.Traces,
) (receiver.Traces, error) {
exampleCfg := baseCfg.(*exampleConfig)
rcvr := &exampleReceiver{
nextConsumer: consumer,
config: *exampleCfg,
}
return rcvr, nil
}
func NewFactory() receiver.Factory {
return receiver.NewFactory(
typeStr,
createDefaultConfig,
receiver.WithTraces(createExampleReceiver, component.StabilityLevelAlpha))
}
func Example() {
// The NewFactory method can then be used on the Collector's initialization process
// on your components.go file.
// In this example we will just instantiate and print it's type
exampleReceiver := NewFactory()
fmt.Println(exampleReceiver.Type())
// Output:
// example
}
================================================
FILE: receiver/go.mod
================================================
module go.opentelemetry.io/collector/receiver
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/consumer v1.54.0
go.opentelemetry.io/collector/consumer/consumertest v0.148.0
go.opentelemetry.io/collector/internal/componentalias v0.148.0
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/collector/pipeline v1.54.0
go.uber.org/goleak v1.3.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/pdata/pprofile v0.148.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/component => ../component
replace go.opentelemetry.io/collector/consumer => ../consumer
replace go.opentelemetry.io/collector/pdata => ../pdata
replace go.opentelemetry.io/collector/pdata/testdata => ../pdata/testdata
replace go.opentelemetry.io/collector/pdata/pprofile => ../pdata/pprofile
replace go.opentelemetry.io/collector/consumer/xconsumer => ../consumer/xconsumer
replace go.opentelemetry.io/collector/consumer/consumertest => ../consumer/consumertest
retract v0.76.0 // Depends on retracted pdata v1.0.0-rc10 module
replace go.opentelemetry.io/collector/pipeline => ../pipeline
replace go.opentelemetry.io/collector/featuregate => ../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../internal/componentalias
================================================
FILE: receiver/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: receiver/internal/err.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/receiver/internal"
import (
"fmt"
"go.opentelemetry.io/collector/component"
)
func ErrIDMismatch(id component.ID, typ component.Type) error {
return fmt.Errorf("component type mismatch: component ID %q does not have type %q", id, typ)
}
================================================
FILE: receiver/metadata.yaml
================================================
type: receiver
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: receiver/nopreceiver/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: receiver/nopreceiver/README.md
================================================
# No-op Receiver
| Status | |
| ------------- |-----------|
| Stability | [alpha]: profiles |
| | [beta]: traces, metrics, logs |
| Distributions | [core], [contrib] |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Areceiver%2Fnop) [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aclosed+is%3Aissue+label%3Areceiver%2Fnop) |
| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@evan-bradley](https://www.github.com/evan-bradley) |
[alpha]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#alpha
[beta]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#beta
[core]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
Serves as a placeholder receiver in a pipeline. This can be useful if you want
to e.g. start a Collector with only extensions enabled.
## Getting Started
All that is required to enable the No-op receiver is to include it in the
receiver definitions. It takes no configuration.
```yaml
receivers:
nop:
```
================================================
FILE: receiver/nopreceiver/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:generate mdatagen metadata.yaml
// Package nopreceiver serves as a placeholder receiver.
package nopreceiver // import "go.opentelemetry.io/collector/receiver/nopreceiver"
================================================
FILE: receiver/nopreceiver/generated_component_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package nopreceiver
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/confmap/confmaptest"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/receivertest"
"go.opentelemetry.io/collector/receiver/xreceiver"
)
var typ = component.MustNewType("nop")
func TestComponentFactoryType(t *testing.T) {
require.Equal(t, typ, NewFactory().Type())
}
func TestComponentConfigStruct(t *testing.T) {
require.NoError(t, componenttest.CheckConfigStruct(NewFactory().CreateDefaultConfig()))
}
func TestComponentLifecycle(t *testing.T) {
factory := NewFactory()
tests := []struct {
createFn func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error)
name string
}{
{
name: "logs",
createFn: func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateLogs(ctx, set, cfg, consumertest.NewNop())
},
},
{
name: "metrics",
createFn: func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateMetrics(ctx, set, cfg, consumertest.NewNop())
},
},
{
name: "traces",
createFn: func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateTraces(ctx, set, cfg, consumertest.NewNop())
},
},
{
name: "profiles",
createFn: func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error) {
return factory.(xreceiver.Factory).CreateProfiles(ctx, set, cfg, consumertest.NewNop())
},
},
}
cm, err := confmaptest.LoadConf("metadata.yaml")
require.NoError(t, err)
cfg := factory.CreateDefaultConfig()
sub, err := cm.Sub("tests::config")
require.NoError(t, err)
require.NoError(t, sub.Unmarshal(&cfg))
for _, tt := range tests {
t.Run(tt.name+"-shutdown", func(t *testing.T) {
c, err := tt.createFn(context.Background(), receivertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
err = c.Shutdown(context.Background())
require.NoError(t, err)
})
t.Run(tt.name+"-lifecycle", func(t *testing.T) {
firstRcvr, err := tt.createFn(context.Background(), receivertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
host := newMdatagenNopHost()
require.NoError(t, err)
require.NoError(t, firstRcvr.Start(context.Background(), host))
require.NoError(t, firstRcvr.Shutdown(context.Background()))
secondRcvr, err := tt.createFn(context.Background(), receivertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
require.NoError(t, secondRcvr.Start(context.Background(), host))
require.NoError(t, secondRcvr.Shutdown(context.Background()))
})
}
}
var _ component.Host = (*mdatagenNopHost)(nil)
type mdatagenNopHost struct{}
func newMdatagenNopHost() component.Host {
return &mdatagenNopHost{}
}
func (mnh *mdatagenNopHost) GetExtensions() map[component.ID]component.Component {
return nil
}
func (mnh *mdatagenNopHost) GetFactory(_ component.Kind, _ component.Type) component.Factory {
return nil
}
================================================
FILE: receiver/nopreceiver/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package nopreceiver
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: receiver/nopreceiver/go.mod
================================================
module go.opentelemetry.io/collector/receiver/nopreceiver
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.opentelemetry.io/collector/confmap v1.54.0
go.opentelemetry.io/collector/consumer v1.54.0
go.opentelemetry.io/collector/consumer/consumertest v0.148.0
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/collector/receiver v1.54.0
go.opentelemetry.io/collector/receiver/receivertest v0.148.0
go.opentelemetry.io/collector/receiver/xreceiver v0.148.0
go.uber.org/goleak v1.3.0
go.uber.org/zap v1.27.1
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/consumer/consumererror v0.148.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
go.opentelemetry.io/collector/pdata/pprofile v0.148.0 // indirect
go.opentelemetry.io/collector/pipeline v1.54.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/sys v0.41.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest
replace go.opentelemetry.io/collector/confmap => ../../confmap
replace go.opentelemetry.io/collector/consumer => ../../consumer
replace go.opentelemetry.io/collector/receiver => ../
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/pdata/testdata => ../../pdata/testdata
replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile
replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer
replace go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest
replace go.opentelemetry.io/collector/receiver/xreceiver => ../xreceiver
replace go.opentelemetry.io/collector/receiver/receivertest => ../receivertest
replace go.opentelemetry.io/collector/pipeline => ../../pipeline
replace go.opentelemetry.io/collector/consumer/consumererror => ../../consumer/consumererror
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
================================================
FILE: receiver/nopreceiver/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: receiver/nopreceiver/internal/metadata/generated_logs.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/receiver"
)
// LogsBuilder provides an interface for scrapers to report logs while taking care of all the transformations
// required to produce log representation defined in metadata and user config.
type LogsBuilder struct {
logsBuffer plog.Logs
logRecordsBuffer plog.LogRecordSlice
buildInfo component.BuildInfo // contains version information.
}
// LogBuilderOption applies changes to default logs builder.
type LogBuilderOption interface {
apply(*LogsBuilder)
}
func NewLogsBuilder(settings receiver.Settings) *LogsBuilder {
lb := &LogsBuilder{
logsBuffer: plog.NewLogs(),
logRecordsBuffer: plog.NewLogRecordSlice(),
buildInfo: settings.BuildInfo,
}
return lb
}
// ResourceLogsOption applies changes to provided resource logs.
type ResourceLogsOption interface {
apply(plog.ResourceLogs)
}
type resourceLogsOptionFunc func(plog.ResourceLogs)
func (rlof resourceLogsOptionFunc) apply(rl plog.ResourceLogs) {
rlof(rl)
}
// WithLogsResource sets the provided resource on the emitted ResourceLogs.
// It's recommended to use ResourceBuilder to create the resource.
func WithLogsResource(res pcommon.Resource) ResourceLogsOption {
return resourceLogsOptionFunc(func(rl plog.ResourceLogs) {
res.CopyTo(rl.Resource())
})
}
// AppendLogRecord adds a log record to the logs builder.
func (lb *LogsBuilder) AppendLogRecord(lr plog.LogRecord) {
lr.MoveTo(lb.logRecordsBuffer.AppendEmpty())
}
// EmitForResource saves all the generated logs under a new resource and updates the internal state to be ready for
// recording another set of log records as part of another resource. This function can be helpful when one scraper
// needs to emit logs from several resources. Otherwise calling this function is not required,
// just `Emit` function can be called instead.
// Resource attributes should be provided as ResourceLogsOption arguments.
func (lb *LogsBuilder) EmitForResource(options ...ResourceLogsOption) {
rl := plog.NewResourceLogs()
ils := rl.ScopeLogs().AppendEmpty()
ils.Scope().SetName(ScopeName)
ils.Scope().SetVersion(lb.buildInfo.Version)
for _, op := range options {
op.apply(rl)
}
if lb.logRecordsBuffer.Len() > 0 {
lb.logRecordsBuffer.MoveAndAppendTo(ils.LogRecords())
lb.logRecordsBuffer = plog.NewLogRecordSlice()
}
if ils.LogRecords().Len() > 0 {
rl.MoveTo(lb.logsBuffer.ResourceLogs().AppendEmpty())
}
}
// Emit returns all the logs accumulated by the logs builder and updates the internal state to be ready for
// recording another set of logs. This function will be responsible for applying all the transformations required to
// produce logs representation defined in metadata and user config.
func (lb *LogsBuilder) Emit(options ...ResourceLogsOption) plog.Logs {
lb.EmitForResource(options...)
logs := lb.logsBuffer
lb.logsBuffer = plog.NewLogs()
return logs
}
================================================
FILE: receiver/nopreceiver/internal/metadata/generated_logs_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/receiver/receivertest"
)
func TestLogsBuilderAppendLogRecord(t *testing.T) {
observedZapCore, _ := observer.New(zap.WarnLevel)
settings := receivertest.NewNopSettings(receivertest.NopType)
settings.Logger = zap.New(observedZapCore)
lb := NewLogsBuilder(settings)
res := pcommon.NewResource()
// append the first log record
lr := plog.NewLogRecord()
lr.SetTimestamp(pcommon.NewTimestampFromTime(time.Now()))
lr.Attributes().PutStr("type", "log")
lr.Body().SetStr("the first log record")
// append the second log record
lr2 := plog.NewLogRecord()
lr2.SetTimestamp(pcommon.NewTimestampFromTime(time.Now()))
lr2.Attributes().PutStr("type", "event")
lr2.Body().SetStr("the second log record")
lb.AppendLogRecord(lr)
lb.AppendLogRecord(lr2)
logs := lb.Emit(WithLogsResource(res))
assert.Equal(t, 1, logs.ResourceLogs().Len())
rl := logs.ResourceLogs().At(0)
assert.Equal(t, 1, rl.ScopeLogs().Len())
sl := rl.ScopeLogs().At(0)
assert.Equal(t, ScopeName, sl.Scope().Name())
assert.Equal(t, lb.buildInfo.Version, sl.Scope().Version())
assert.Equal(t, 2, sl.LogRecords().Len())
attrVal, ok := sl.LogRecords().At(0).Attributes().Get("type")
assert.True(t, ok)
assert.Equal(t, "log", attrVal.Str())
assert.Equal(t, pcommon.ValueTypeStr, sl.LogRecords().At(0).Body().Type())
assert.Equal(t, "the first log record", sl.LogRecords().At(0).Body().Str())
attrVal, ok = sl.LogRecords().At(1).Attributes().Get("type")
assert.True(t, ok)
assert.Equal(t, "event", attrVal.Str())
assert.Equal(t, pcommon.ValueTypeStr, sl.LogRecords().At(1).Body().Type())
assert.Equal(t, "the second log record", sl.LogRecords().At(1).Body().Str())
}
================================================
FILE: receiver/nopreceiver/internal/metadata/generated_status.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/component"
)
var (
Type = component.MustNewType("nop")
ScopeName = "go.opentelemetry.io/collector/receiver/nopreceiver"
)
const (
ProfilesStability = component.StabilityLevelAlpha
TracesStability = component.StabilityLevelBeta
MetricsStability = component.StabilityLevelBeta
LogsStability = component.StabilityLevelBeta
)
================================================
FILE: receiver/nopreceiver/metadata.yaml
================================================
display_name: No-op Receiver
type: nop
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
codeowners:
active:
- evan-bradley
class: receiver
stability:
beta: [traces, metrics, logs]
alpha: [profiles]
distributions: [core, contrib]
================================================
FILE: receiver/nopreceiver/nop_receiver.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package nopreceiver // import "go.opentelemetry.io/collector/receiver/nopreceiver"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/nopreceiver/internal/metadata"
"go.opentelemetry.io/collector/receiver/xreceiver"
)
// NewFactory returns a receiver.Factory that constructs nop receivers.
func NewFactory() xreceiver.Factory {
return xreceiver.NewFactory(
metadata.Type,
func() component.Config { return &struct{}{} },
xreceiver.WithTraces(createTraces, metadata.TracesStability),
xreceiver.WithMetrics(createMetrics, metadata.MetricsStability),
xreceiver.WithProfiles(createProfiles, metadata.ProfilesStability),
xreceiver.WithLogs(createLogs, metadata.LogsStability))
}
func createTraces(context.Context, receiver.Settings, component.Config, consumer.Traces) (receiver.Traces, error) {
return nopInstance, nil
}
func createMetrics(context.Context, receiver.Settings, component.Config, consumer.Metrics) (receiver.Metrics, error) {
return nopInstance, nil
}
func createLogs(context.Context, receiver.Settings, component.Config, consumer.Logs) (receiver.Logs, error) {
return nopInstance, nil
}
func createProfiles(context.Context, receiver.Settings, component.Config, xconsumer.Profiles) (xreceiver.Profiles, error) {
return nopInstance, nil
}
var nopInstance = &nopReceiver{}
type nopReceiver struct {
component.StartFunc
component.ShutdownFunc
}
================================================
FILE: receiver/nopreceiver/nop_receiver_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package nopreceiver // import "go.opentelemetry.io/collector/receiver/nopreceiver"
import (
"context"
"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/consumer/consumertest"
"go.opentelemetry.io/collector/receiver/receivertest"
)
func TestNewNopFactory(t *testing.T) {
factory := NewFactory()
require.NotNil(t, factory)
assert.Equal(t, component.MustNewType("nop"), factory.Type())
cfg := factory.CreateDefaultConfig()
assert.Equal(t, &struct{}{}, cfg)
traces, err := factory.CreateTraces(context.Background(), receivertest.NewNopSettings(receivertest.NopType), cfg, consumertest.NewNop())
require.NoError(t, err)
assert.NoError(t, traces.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, traces.Shutdown(context.Background()))
metrics, err := factory.CreateMetrics(context.Background(), receivertest.NewNopSettings(receivertest.NopType), cfg, consumertest.NewNop())
require.NoError(t, err)
assert.NoError(t, metrics.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, metrics.Shutdown(context.Background()))
logs, err := factory.CreateLogs(context.Background(), receivertest.NewNopSettings(receivertest.NopType), cfg, consumertest.NewNop())
require.NoError(t, err)
assert.NoError(t, logs.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, logs.Shutdown(context.Background()))
profiles, err := factory.CreateProfiles(context.Background(), receivertest.NewNopSettings(receivertest.NopType), cfg, consumertest.NewNop())
require.NoError(t, err)
assert.NoError(t, profiles.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, profiles.Shutdown(context.Background()))
}
================================================
FILE: receiver/otlpreceiver/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: receiver/otlpreceiver/README.md
================================================
# OTLP Receiver
| Status | |
| ------------- |-----------|
| Stability | [alpha]: profiles |
| | [stable]: traces, metrics, logs |
| Distributions | [core], [contrib], [k8s], [otlp] |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Areceiver%2Fotlp) [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aclosed+is%3Aissue+label%3Areceiver%2Fotlp) |
[alpha]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#alpha
[stable]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#stable
[core]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
[k8s]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-k8s
[otlp]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-otlp
Receives data via gRPC or HTTP using [OTLP](
https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/otlp.md)
format.
## 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 = localhost:4317 for grpc protocol, localhost:4318 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. See our
[security best practices doc](https://opentelemetry.io/docs/security/config-best-practices/#protect-against-denial-of-service-attacks)
to understand how to set the endpoint in different environments.
## Advanced Configuration
Several helper files are leveraged to provide additional capabilities automatically:
- [gRPC settings](https://github.com/open-telemetry/opentelemetry-collector/blob/main/config/configgrpc/README.md) including CORS
- [HTTP settings](https://github.com/open-telemetry/opentelemetry-collector/blob/main/config/confighttp/README.md)
- [TLS and mTLS settings](https://github.com/open-telemetry/opentelemetry-collector/blob/main/config/configtls/README.md)
- [Auth settings](https://github.com/open-telemetry/opentelemetry-collector/blob/main/config/configauth/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 serialization format needs to be [OTLP JSON](https://opentelemetry.io/docs/specs/otlp/#json-protobuf-encoding).
The HTTP/JSON configuration also provides `traces_url_path`,
`metrics_url_path`, `logs_url_path`, and `profiles_url_path` configurations to
allow the URL paths that signal data needs to be sent to be modified per signal
type. These default to `/v1/traces`, `/v1/metrics`, `/v1/logs`, and
`/v1/profiles` respectively.
To write traces with HTTP/JSON, `POST` to `[address]/[traces_url_path]` for
traces, to `[address]/[metrics_url_path]` for metrics, to
`[address]/[logs_url_path]` for logs, and to `[address]/[profiles_url_path]` for
profiles.
The default port is `4318`. When using the `otlphttpexporter` peer to
communicate with this component, use the `traces_endpoint`,
`metrics_endpoint`, `logs_endpoint`, and `profiles_endpoint` settings in the
`otlphttpexporter` to set the proper URL to match the address and URL signal
path on the `otlpreceiver`.
### CORS (Cross-origin resource sharing)
The HTTP/JSON endpoint can also optionally configure [CORS][cors] under `cors:`.
Specify what origins (or wildcard patterns) to allow requests from as
`allowed_origins`. To allow additional request headers outside of the [default
safelist][cors-headers], set `allowed_headers`. Browsers can be instructed to
[cache][cors-max-age] responses to preflight requests by setting `max_age`.
[cors]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
[cors-headers]: https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_request_header
[cors-max-age]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age
```yaml
receivers:
otlp:
protocols:
http:
endpoint: "localhost:4318"
cors:
allowed_origins:
- http://test.com
# Origins can have wildcards with *, use * by itself to match any origin.
- https://*.example.com
allowed_headers:
- Example-Header
max_age: 7200
```
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
[core]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol
================================================
FILE: receiver/otlpreceiver/config.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlpreceiver // import "go.opentelemetry.io/collector/receiver/otlpreceiver"
import (
"encoding"
"errors"
"fmt"
"net/url"
"path"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configgrpc"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/config/configoptional"
)
type SanitizedURLPath string
var _ encoding.TextUnmarshaler = (*SanitizedURLPath)(nil)
func (s *SanitizedURLPath) UnmarshalText(text []byte) error {
u, err := url.Parse(string(text))
if err != nil {
return fmt.Errorf("invalid HTTP URL path set for signal: %w", err)
}
if !path.IsAbs(u.Path) {
u.Path = "/" + u.Path
}
*s = SanitizedURLPath(u.Path)
return nil
}
type HTTPConfig struct {
ServerConfig confighttp.ServerConfig `mapstructure:",squash"`
// The URL path to receive traces on. If omitted "/v1/traces" will be used.
TracesURLPath SanitizedURLPath `mapstructure:"traces_url_path,omitempty"`
// The URL path to receive metrics on. If omitted "/v1/metrics" will be used.
MetricsURLPath SanitizedURLPath `mapstructure:"metrics_url_path,omitempty"`
// The URL path to receive logs on. If omitted "/v1/logs" will be used.
LogsURLPath SanitizedURLPath `mapstructure:"logs_url_path,omitempty"`
// prevent unkeyed literal initialization
_ struct{}
}
// Protocols is the configuration for the supported protocols.
type Protocols struct {
GRPC configoptional.Optional[configgrpc.ServerConfig] `mapstructure:"grpc"`
HTTP configoptional.Optional[HTTPConfig] `mapstructure:"http"`
// prevent unkeyed literal initialization
_ struct{}
}
// Config defines configuration for OTLP receiver.
type Config struct {
// Protocols is the configuration for the supported protocols, currently gRPC and HTTP (Proto and JSON).
Protocols `mapstructure:"protocols"`
// prevent unkeyed literal initialization
_ struct{}
}
var _ component.Config = (*Config)(nil)
// Validate checks the receiver configuration is valid
func (cfg *Config) Validate() error {
if !cfg.GRPC.HasValue() && !cfg.HTTP.HasValue() {
return errors.New("must specify at least one protocol when using the OTLP receiver")
}
return nil
}
================================================
FILE: receiver/otlpreceiver/config.md
================================================
# "otlp" Receiver Reference
Config defines configuration for OTLP receiver.
### Config
| Name | Type | Default | Docs |
|-----------|---------------------------------------------------|------------|-------------------------------------------------------------------------------------------------------|
| protocols | [otlpreceiver-Protocols](#otlpreceiver-protocols) | | Protocols is the configuration for the supported protocols, currently gRPC and HTTP (Proto and JSON). |
### otlpreceiver-Protocols
| Name | Type | Default | Docs |
|------|-----------------------------------------------------------------|------------|-----------------------------------------------------------------------------|
| grpc | [configgrpc-GRPCServerSettings](#configgrpc-grpcserversettings) | | GRPCServerSettings defines common settings for a gRPC server configuration. |
| http | [confighttp-HTTPServerSettings](#confighttp-httpserversettings) | | HTTPServerSettings defines settings for creating an HTTP server. |
### configgrpc-GRPCServerSettings
| Name | Type | Default | Docs |
|------------------------|-----------------------------------------------------------------------|--------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| endpoint | string | localhost:4317 | 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 | string | tcp | 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". |
| tls | [configtls-TLSServerSetting](#configtls-tlsserversetting) | | Configures the protocol to use TLS. The default value is nil, which will cause the protocol to not use TLS. |
| max_recv_msg_size_mib | uint64 | | MaxRecvMsgSizeMiB sets the maximum size (in MiB) of messages accepted by the server. |
| max_concurrent_streams | uint32 | | MaxConcurrentStreams sets the limit on the number of concurrent streams to each ServerTransport. It has effect only for streaming RPCs. |
| read_buffer_size | int | 524288 | ReadBufferSize for gRPC server. See grpc.ReadBufferSize (https://godoc.org/google.golang.org/grpc#ReadBufferSize). |
| write_buffer_size | int | | WriteBufferSize for gRPC server. See grpc.WriteBufferSize (https://godoc.org/google.golang.org/grpc#WriteBufferSize). |
| keepalive | [configgrpc-KeepaliveServerConfig](#configgrpc-keepaliveserverconfig) | | Keepalive anchor for all the settings related to keepalive. |
| auth | [configauth-Authentication](#configauth-authentication) | | Auth for this receiver |
### configtls-TLSServerSetting
| Name | Type | Default | Docs |
|----------------|--------|------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ca_file | string | | 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) |
| cert_file | string | | Path to the TLS cert to use for TLS required connections. (optional) |
| key_file | string | | Path to the TLS key to use for TLS required connections. (optional) |
| client_ca_file | string | | 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) |
### configgrpc-KeepaliveServerConfig
| Name | Type | Default | Docs |
|--------------------|---------------------------------------------------------------------------------|------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| server_parameters | [configgrpc-KeepaliveServerParameters](#configgrpc-keepaliveserverparameters) | | 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. |
| enforcement_policy | [configgrpc-KeepaliveEnforcementPolicy](#configgrpc-keepaliveenforcementpolicy) | | 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. |
### configgrpc-KeepaliveServerParameters
| Name | Type | Default | Docs |
|--------------------------|---------------------------------|------------|------|
| max_connection_idle | [time-Duration](#time-duration) | | |
| max_connection_age | [time-Duration](#time-duration) | | |
| max_connection_age_grace | [time-Duration](#time-duration) | | |
| time | [time-Duration](#time-duration) | | |
| timeout | [time-Duration](#time-duration) | | |
### configgrpc-KeepaliveEnforcementPolicy
| Name | Type | Default | Docs |
|-----------------------|---------------------------------|------------|------|
| min_time | [time-Duration](#time-duration) | | |
| permit_without_stream | bool | | |
### configauth-Authentication
| Name | Type | Default | Docs |
|---------------|--------|------------|----------------------------------------------------------------------------------------------------------------|
| authenticator | string | | AuthenticatorName specifies the name of the extension to use in order to authenticate the incoming data point. |
### confighttp-HTTPServerSettings
| Name | Type | Default | Docs |
|-----------------------|-----------------------------------------------------------|--------------|-----------------------------------------------------------------------------------------------------------------------------------------|
| endpoint | string | localhost:4318 | Endpoint configures the listening address for the server. |
| tls | [configtls-TLSServerSetting](#configtls-tlsserversetting) | | TLSSetting struct exposes TLS client configuration. |
| cors | [confighttp-CORSConfig](#confighttp-corsconfig) | | CORSConfig configures a receiver for HTTP cross-origin resource sharing (CORS). |
| max_request_body_size | int | 20971520 | MaxRequestBodySize configures the maximum allowed body size in bytes for a single request. The default `20971520` means 20MiB |
### confighttp-CORSConfig
| Name | Type | Default | Docs |
|-----------------|----------|------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| allowed_origins | []string | | AllowedOrigins sets the allowed values of the Origin header for HTTP/JSON requests to an OTLP receiver. An origin may contain a wildcard (`*`) to replace 0 or more characters (e.g., `"https://*.example.com"`, or `"*"` to allow any origin). |
| allowed_headers | []string | | AllowedHeaders sets what headers will be allowed in CORS requests. The Accept, Accept-Language, Content-Type, and Content-Language headers are implicitly allowed. If no headers are listed, X-Requested-With will also be accepted by default. Include `"*"` to allow any request header. |
| max_age | int | | MaxAge sets the value of the Access-Control-Max-Age response header. Set it to the number of seconds that browsers should cache a CORS preflight response for. |
### configtls-TLSServerSetting
| Name | Type | Default | Docs |
|----------------|--------|------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ca_file | string | | 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) |
| cert_file | string | | Path to the TLS cert to use for TLS required connections. (optional) |
| key_file | string | | Path to the TLS key to use for TLS required connections. (optional) |
| client_ca_file | string | | 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) |
### time-Duration
An optionally signed sequence of decimal numbers, each with a unit suffix, such as `300ms`, `-1.5h`, or `2h45m`. Valid time units are `ns`, `us`, `ms`, `s`, `m`, `h`.
================================================
FILE: receiver/otlpreceiver/config_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlpreceiver
import (
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configauth"
"go.opentelemetry.io/collector/config/configgrpc"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/config/confignet"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/config/configtls"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/confmaptest"
"go.opentelemetry.io/collector/confmap/xconfmap"
)
func TestUnmarshalDefaultConfig(t *testing.T) {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "default.yaml"))
require.NoError(t, err)
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
require.NoError(t, cm.Unmarshal(&cfg))
expectedCfg := factory.CreateDefaultConfig().(*Config)
expectedCfg.GRPC.GetOrInsertDefault()
expectedCfg.HTTP.GetOrInsertDefault()
assert.Equal(t, expectedCfg, cfg)
}
func TestUnmarshalConfigOnlyGRPC(t *testing.T) {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "only_grpc.yaml"))
require.NoError(t, err)
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
require.NoError(t, cm.Unmarshal(&cfg))
defaultOnlyGRPC := factory.CreateDefaultConfig().(*Config)
defaultOnlyGRPC.GRPC.GetOrInsertDefault()
assert.Equal(t, defaultOnlyGRPC, cfg)
}
func TestUnmarshalConfigOnlyHTTP(t *testing.T) {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "only_http.yaml"))
require.NoError(t, err)
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
require.NoError(t, cm.Unmarshal(&cfg))
defaultOnlyHTTP := factory.CreateDefaultConfig().(*Config)
defaultOnlyHTTP.HTTP.GetOrInsertDefault()
assert.Equal(t, defaultOnlyHTTP, cfg)
}
func TestUnmarshalConfigOnlyHTTPNull(t *testing.T) {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "only_http_null.yaml"))
require.NoError(t, err)
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
require.NoError(t, cm.Unmarshal(&cfg))
defaultOnlyHTTP := factory.CreateDefaultConfig().(*Config)
defaultOnlyHTTP.HTTP.GetOrInsertDefault()
assert.Equal(t, defaultOnlyHTTP, cfg)
}
func TestUnmarshalConfigOnlyHTTPEmptyMap(t *testing.T) {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "only_http_empty_map.yaml"))
require.NoError(t, err)
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
require.NoError(t, cm.Unmarshal(&cfg))
defaultOnlyHTTP := factory.CreateDefaultConfig().(*Config)
defaultOnlyHTTP.HTTP.GetOrInsertDefault()
assert.Equal(t, defaultOnlyHTTP, cfg)
}
func TestUnmarshalConfig(t *testing.T) {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
require.NoError(t, err)
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
require.NoError(t, cm.Unmarshal(&cfg))
assert.Equal(t,
&Config{
Protocols: Protocols{
GRPC: configoptional.Some(configgrpc.ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:4317",
Transport: confignet.TransportTypeTCP,
},
TLS: configoptional.Some(configtls.ServerConfig{
Config: configtls.Config{
CertFile: "test.crt",
KeyFile: "test.key",
},
}),
MaxRecvMsgSizeMiB: 32,
MaxConcurrentStreams: 16,
ReadBufferSize: 1024,
WriteBufferSize: 1024,
Keepalive: configoptional.Some(configgrpc.KeepaliveServerConfig{
ServerParameters: configoptional.Some(configgrpc.KeepaliveServerParameters{
MaxConnectionIdle: 11 * time.Second,
MaxConnectionAge: 12 * time.Second,
MaxConnectionAgeGrace: 13 * time.Second,
Time: 30 * time.Second,
Timeout: 5 * time.Second,
}),
EnforcementPolicy: configoptional.Some(configgrpc.KeepaliveEnforcementPolicy{
MinTime: 10 * time.Second,
PermitWithoutStream: true,
}),
}),
}),
HTTP: configoptional.Some(HTTPConfig{
ServerConfig: confighttp.ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:4318",
Transport: confignet.TransportTypeTCP,
},
Auth: configoptional.Some(confighttp.AuthConfig{
Config: configauth.Config{
AuthenticatorID: component.MustNewID("test"),
},
}),
TLS: configoptional.Some(configtls.ServerConfig{
Config: configtls.Config{
CertFile: "test.crt",
KeyFile: "test.key",
},
}),
CORS: configoptional.Some(confighttp.CORSConfig{
AllowedOrigins: []string{"https://*.test.com", "https://test.com"},
MaxAge: 7200,
}),
KeepAlivesEnabled: true,
},
TracesURLPath: "/traces",
MetricsURLPath: "/v2/metrics",
LogsURLPath: "/log/ingest",
}),
},
}, cfg)
}
func TestUnmarshalConfigUnix(t *testing.T) {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "uds.yaml"))
require.NoError(t, err)
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
require.NoError(t, cm.Unmarshal(&cfg))
assert.Equal(t,
&Config{
Protocols: Protocols{
GRPC: configoptional.Some(configgrpc.ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "/tmp/grpc_otlp.sock",
Transport: confignet.TransportTypeUnix,
},
ReadBufferSize: 512 * 1024,
Keepalive: configoptional.Some(configgrpc.NewDefaultKeepaliveServerConfig()),
}),
HTTP: configoptional.Some(HTTPConfig{
ServerConfig: confighttp.ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "/tmp/http_otlp.sock",
Transport: confignet.TransportTypeUnix,
},
KeepAlivesEnabled: true,
},
TracesURLPath: defaultTracesURLPath,
MetricsURLPath: defaultMetricsURLPath,
LogsURLPath: defaultLogsURLPath,
}),
},
}, cfg)
}
// cspell:ignore htttp
func TestUnmarshalConfigTypoDefaultProtocol(t *testing.T) {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "typo_default_proto_config.yaml"))
require.NoError(t, err)
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
assert.ErrorContains(t, cm.Unmarshal(&cfg), "'protocols' has invalid keys: htttp")
}
func TestUnmarshalConfigInvalidProtocol(t *testing.T) {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "bad_proto_config.yaml"))
require.NoError(t, err)
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
assert.ErrorContains(t, cm.Unmarshal(&cfg), "'protocols' has invalid keys: thrift")
}
func TestUnmarshalConfigEmptyProtocols(t *testing.T) {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", "bad_no_proto_config.yaml"))
require.NoError(t, err)
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
require.NoError(t, cm.Unmarshal(&cfg))
assert.EqualError(t, xconfmap.Validate(cfg), "must specify at least one protocol when using the OTLP receiver")
}
func TestUnmarshalConfigInvalidSignalPath(t *testing.T) {
tests := []struct {
name string
testDataFn string
}{
{
name: "Invalid traces URL path",
testDataFn: "invalid_traces_path.yaml",
},
{
name: "Invalid metrics URL path",
testDataFn: "invalid_metrics_path.yaml",
},
{
name: "Invalid logs URL path",
testDataFn: "invalid_logs_path.yaml",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cm, err := confmaptest.LoadConf(filepath.Join("testdata", tt.testDataFn))
require.NoError(t, err)
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
assert.ErrorContains(t, cm.Unmarshal(&cfg), "invalid HTTP URL path set for signal: parse \":invalid\": missing protocol scheme")
})
}
}
func TestUnmarshalConfigEmpty(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
require.NoError(t, confmap.New().Unmarshal(&cfg))
assert.EqualError(t, xconfmap.Validate(cfg), "must specify at least one protocol when using the OTLP receiver")
}
================================================
FILE: receiver/otlpreceiver/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:generate mdatagen metadata.yaml
// Package otlpreceiver receives data in OTLP format.
package otlpreceiver // import "go.opentelemetry.io/collector/receiver/otlpreceiver"
================================================
FILE: receiver/otlpreceiver/encoder.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlpreceiver // import "go.opentelemetry.io/collector/receiver/otlpreceiver"
import (
spb "google.golang.org/genproto/googleapis/rpc/status"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/collector/pdata/plog/plogotlp"
"go.opentelemetry.io/collector/pdata/pmetric/pmetricotlp"
"go.opentelemetry.io/collector/pdata/pprofile/pprofileotlp"
"go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp"
)
const (
pbContentType = "application/x-protobuf"
jsonContentType = "application/json"
)
var (
pbEncoder = &protoEncoder{}
jsEncoder = &jsonEncoder{}
)
type encoder interface {
unmarshalTracesRequest(buf []byte) (ptraceotlp.ExportRequest, error)
unmarshalMetricsRequest(buf []byte) (pmetricotlp.ExportRequest, error)
unmarshalLogsRequest(buf []byte) (plogotlp.ExportRequest, error)
unmarshalProfilesRequest(buf []byte) (pprofileotlp.ExportRequest, error)
marshalTracesResponse(ptraceotlp.ExportResponse) ([]byte, error)
marshalMetricsResponse(pmetricotlp.ExportResponse) ([]byte, error)
marshalLogsResponse(plogotlp.ExportResponse) ([]byte, error)
marshalProfilesResponse(pprofileotlp.ExportResponse) ([]byte, error)
marshalStatus(rsp *spb.Status) ([]byte, error)
contentType() string
}
type protoEncoder struct{}
func (protoEncoder) unmarshalTracesRequest(buf []byte) (ptraceotlp.ExportRequest, error) {
req := ptraceotlp.NewExportRequest()
err := req.UnmarshalProto(buf)
return req, err
}
func (protoEncoder) unmarshalMetricsRequest(buf []byte) (pmetricotlp.ExportRequest, error) {
req := pmetricotlp.NewExportRequest()
err := req.UnmarshalProto(buf)
return req, err
}
func (protoEncoder) unmarshalLogsRequest(buf []byte) (plogotlp.ExportRequest, error) {
req := plogotlp.NewExportRequest()
err := req.UnmarshalProto(buf)
return req, err
}
func (protoEncoder) unmarshalProfilesRequest(buf []byte) (pprofileotlp.ExportRequest, error) {
req := pprofileotlp.NewExportRequest()
err := req.UnmarshalProto(buf)
return req, err
}
func (protoEncoder) marshalTracesResponse(resp ptraceotlp.ExportResponse) ([]byte, error) {
return resp.MarshalProto()
}
func (protoEncoder) marshalMetricsResponse(resp pmetricotlp.ExportResponse) ([]byte, error) {
return resp.MarshalProto()
}
func (protoEncoder) marshalLogsResponse(resp plogotlp.ExportResponse) ([]byte, error) {
return resp.MarshalProto()
}
func (protoEncoder) marshalProfilesResponse(resp pprofileotlp.ExportResponse) ([]byte, error) {
return resp.MarshalProto()
}
func (protoEncoder) marshalStatus(resp *spb.Status) ([]byte, error) {
return proto.Marshal(resp)
}
func (protoEncoder) contentType() string {
return pbContentType
}
type jsonEncoder struct{}
func (jsonEncoder) unmarshalTracesRequest(buf []byte) (ptraceotlp.ExportRequest, error) {
req := ptraceotlp.NewExportRequest()
err := req.UnmarshalJSON(buf)
return req, err
}
func (jsonEncoder) unmarshalMetricsRequest(buf []byte) (pmetricotlp.ExportRequest, error) {
req := pmetricotlp.NewExportRequest()
err := req.UnmarshalJSON(buf)
return req, err
}
func (jsonEncoder) unmarshalLogsRequest(buf []byte) (plogotlp.ExportRequest, error) {
req := plogotlp.NewExportRequest()
err := req.UnmarshalJSON(buf)
return req, err
}
func (jsonEncoder) unmarshalProfilesRequest(buf []byte) (pprofileotlp.ExportRequest, error) {
req := pprofileotlp.NewExportRequest()
err := req.UnmarshalJSON(buf)
return req, err
}
func (jsonEncoder) marshalTracesResponse(resp ptraceotlp.ExportResponse) ([]byte, error) {
return resp.MarshalJSON()
}
func (jsonEncoder) marshalMetricsResponse(resp pmetricotlp.ExportResponse) ([]byte, error) {
return resp.MarshalJSON()
}
func (jsonEncoder) marshalLogsResponse(resp plogotlp.ExportResponse) ([]byte, error) {
return resp.MarshalJSON()
}
func (jsonEncoder) marshalProfilesResponse(resp pprofileotlp.ExportResponse) ([]byte, error) {
return resp.MarshalJSON()
}
func (jsonEncoder) marshalStatus(resp *spb.Status) ([]byte, error) {
return protojson.Marshal(resp)
}
func (jsonEncoder) contentType() string {
return jsonContentType
}
================================================
FILE: receiver/otlpreceiver/factory.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlpreceiver // import "go.opentelemetry.io/collector/receiver/otlpreceiver"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configgrpc"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/config/configtls"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/internal/sharedcomponent"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/otlpreceiver/internal/metadata"
"go.opentelemetry.io/collector/receiver/xreceiver"
)
const (
defaultTracesURLPath = "/v1/traces"
defaultMetricsURLPath = "/v1/metrics"
defaultLogsURLPath = "/v1/logs"
defaultProfilesURLPath = "/v1development/profiles"
)
// NewFactory creates a new OTLP receiver factory.
func NewFactory() receiver.Factory {
return xreceiver.NewFactory(
metadata.Type,
createDefaultConfig,
xreceiver.WithTraces(createTraces, metadata.TracesStability),
xreceiver.WithMetrics(createMetrics, metadata.MetricsStability),
xreceiver.WithLogs(createLog, metadata.LogsStability),
xreceiver.WithProfiles(createProfiles, metadata.ProfilesStability),
)
}
// createDefaultConfig creates the default configuration for receiver.
func createDefaultConfig() component.Config {
grpcCfg := configgrpc.NewDefaultServerConfig()
grpcCfg.NetAddr.Endpoint = "localhost:4317"
// We almost write 0 bytes, so no need to tune WriteBufferSize.
grpcCfg.ReadBufferSize = 512 * 1024
httpCfg := confighttp.NewDefaultServerConfig()
httpCfg.NetAddr.Endpoint = "localhost:4318"
// For backward compatibility:
httpCfg.TLS = configoptional.None[configtls.ServerConfig]()
httpCfg.WriteTimeout = 0
httpCfg.ReadHeaderTimeout = 0
httpCfg.IdleTimeout = 0
return &Config{
Protocols: Protocols{
GRPC: configoptional.Default(grpcCfg),
HTTP: configoptional.Default(HTTPConfig{
ServerConfig: httpCfg,
TracesURLPath: defaultTracesURLPath,
MetricsURLPath: defaultMetricsURLPath,
LogsURLPath: defaultLogsURLPath,
}),
},
}
}
// createTraces creates a trace receiver based on provided config.
func createTraces(
_ context.Context,
set receiver.Settings,
cfg component.Config,
nextConsumer consumer.Traces,
) (receiver.Traces, error) {
oCfg := cfg.(*Config)
r, err := receivers.LoadOrStore(
oCfg,
func() (*otlpReceiver, error) {
return newOtlpReceiver(oCfg, &set)
},
)
if err != nil {
return nil, err
}
r.Unwrap().registerTraceConsumer(nextConsumer)
return r, nil
}
// createMetrics creates a metrics receiver based on provided config.
func createMetrics(
_ context.Context,
set receiver.Settings,
cfg component.Config,
consumer consumer.Metrics,
) (receiver.Metrics, error) {
oCfg := cfg.(*Config)
r, err := receivers.LoadOrStore(
oCfg,
func() (*otlpReceiver, error) {
return newOtlpReceiver(oCfg, &set)
},
)
if err != nil {
return nil, err
}
r.Unwrap().registerMetricsConsumer(consumer)
return r, nil
}
// createLog creates a log receiver based on provided config.
func createLog(
_ context.Context,
set receiver.Settings,
cfg component.Config,
consumer consumer.Logs,
) (receiver.Logs, error) {
oCfg := cfg.(*Config)
r, err := receivers.LoadOrStore(
oCfg,
func() (*otlpReceiver, error) {
return newOtlpReceiver(oCfg, &set)
},
)
if err != nil {
return nil, err
}
r.Unwrap().registerLogsConsumer(consumer)
return r, nil
}
// createProfiles creates a trace receiver based on provided config.
func createProfiles(
_ context.Context,
set receiver.Settings,
cfg component.Config,
nextConsumer xconsumer.Profiles,
) (xreceiver.Profiles, error) {
oCfg := cfg.(*Config)
r, err := receivers.LoadOrStore(
oCfg,
func() (*otlpReceiver, error) {
return newOtlpReceiver(oCfg, &set)
},
)
if err != nil {
return nil, err
}
r.Unwrap().registerProfilesConsumer(nextConsumer)
return r, nil
}
// This is the map of already created OTLP receivers for particular configurations.
// We maintain this map because the receiver.Factory is asked trace and metric receivers separately
// when it gets CreateTraces() and CreateMetrics() but they must not
// create separate objects, they must use one otlpReceiver object per configuration.
// When the receiver is shutdown it should be removed from this map so the same configuration
// can be recreated successfully.
var receivers = sharedcomponent.NewMap[*Config, *otlpReceiver]()
================================================
FILE: receiver/otlpreceiver/factory_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlpreceiver
import (
"context"
"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/confignet"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/internal/telemetry"
"go.opentelemetry.io/collector/internal/telemetry/telemetrytest"
"go.opentelemetry.io/collector/internal/testutil"
"go.opentelemetry.io/collector/receiver/otlpreceiver/internal/metadata"
"go.opentelemetry.io/collector/receiver/receivertest"
"go.opentelemetry.io/collector/receiver/xreceiver"
)
func TestCreateDefaultConfig(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
assert.NotNil(t, cfg, "failed to create default config")
assert.NoError(t, componenttest.CheckConfigStruct(cfg))
}
func TestCreateSameReceiver(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig().(*Config)
cfg.GRPC.GetOrInsertDefault().NetAddr.Endpoint = testutil.GetAvailableLocalAddress(t)
cfg.HTTP.GetOrInsertDefault().ServerConfig.NetAddr.Endpoint = testutil.GetAvailableLocalAddress(t)
creationSet := receivertest.NewNopSettings(factory.Type())
var droppedAttrs []string
creationSet.Logger = telemetrytest.MockInjectorLogger(creationSet.Logger, &droppedAttrs)
tReceiver, err := factory.CreateTraces(context.Background(), creationSet, cfg, consumertest.NewNop())
assert.NotNil(t, tReceiver)
require.NoError(t, err)
mReceiver, err := factory.CreateMetrics(context.Background(), creationSet, cfg, consumertest.NewNop())
assert.NotNil(t, mReceiver)
require.NoError(t, err)
lReceiver, err := factory.CreateMetrics(context.Background(), creationSet, cfg, consumertest.NewNop())
assert.NotNil(t, lReceiver)
require.NoError(t, err)
pReceiver, err := factory.(xreceiver.Factory).CreateProfiles(context.Background(), creationSet, cfg, consumertest.NewNop())
assert.NotNil(t, pReceiver)
require.NoError(t, err)
assert.Same(t, tReceiver, mReceiver)
assert.Same(t, tReceiver, lReceiver)
assert.Same(t, tReceiver, pReceiver)
// Test that we've dropped the relevant injected attributes exactly once
assert.ElementsMatch(t, droppedAttrs, []string{telemetry.SignalKey})
}
func TestCreateTraces(t *testing.T) {
factory := NewFactory()
defaultGRPCSettings := configoptional.Some(configgrpc.ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: testutil.GetAvailableLocalAddress(t),
Transport: confignet.TransportTypeTCP,
},
})
defaultServerConfig := confighttp.NewDefaultServerConfig()
defaultServerConfig.NetAddr.Endpoint = testutil.GetAvailableLocalAddress(t)
defaultHTTPSettings := configoptional.Some(HTTPConfig{
ServerConfig: defaultServerConfig,
TracesURLPath: defaultTracesURLPath,
MetricsURLPath: defaultMetricsURLPath,
LogsURLPath: defaultLogsURLPath,
})
tests := []struct {
name string
cfg *Config
wantStartErr bool
wantErr bool
sink consumer.Traces
}{
{
name: "default",
cfg: &Config{
Protocols: Protocols{
GRPC: defaultGRPCSettings,
HTTP: defaultHTTPSettings,
},
},
sink: consumertest.NewNop(),
},
{
name: "invalid_grpc_port",
cfg: &Config{
Protocols: Protocols{
GRPC: configoptional.Some(configgrpc.ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:112233",
Transport: confignet.TransportTypeTCP,
},
}),
HTTP: defaultHTTPSettings,
},
},
wantStartErr: true,
sink: consumertest.NewNop(),
},
{
name: "invalid_http_port",
cfg: &Config{
Protocols: Protocols{
GRPC: defaultGRPCSettings,
HTTP: configoptional.Some(HTTPConfig{
ServerConfig: confighttp.ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:112233",
Transport: confignet.TransportTypeTCP,
},
},
TracesURLPath: defaultTracesURLPath,
}),
},
},
wantStartErr: true,
sink: consumertest.NewNop(),
},
{
name: "no_http_or_grcp_config",
cfg: &Config{
Protocols: Protocols{},
},
sink: consumertest.NewNop(),
},
}
creationSet := receivertest.NewNopSettings(metadata.Type)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
tr, err := factory.CreateTraces(ctx, creationSet, tt.cfg, tt.sink)
if tt.wantErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
if tt.wantStartErr {
assert.Error(t, tr.Start(ctx, componenttest.NewNopHost()))
} else {
assert.NoError(t, tr.Start(ctx, componenttest.NewNopHost()))
assert.NoError(t, tr.Shutdown(ctx))
}
})
}
}
func TestCreateMetric(t *testing.T) {
factory := NewFactory()
defaultGRPCSettings := configoptional.Some(configgrpc.ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "127.0.0.1:0",
Transport: confignet.TransportTypeTCP,
},
})
defaultServerConfig := confighttp.NewDefaultServerConfig()
defaultServerConfig.NetAddr.Endpoint = "127.0.0.1:0"
defaultHTTPSettings := configoptional.Some(HTTPConfig{
ServerConfig: defaultServerConfig,
TracesURLPath: defaultTracesURLPath,
MetricsURLPath: defaultMetricsURLPath,
LogsURLPath: defaultLogsURLPath,
})
tests := []struct {
name string
cfg *Config
wantStartErr bool
wantErr bool
sink consumer.Metrics
}{
{
name: "default",
cfg: &Config{
Protocols: Protocols{
GRPC: defaultGRPCSettings,
HTTP: defaultHTTPSettings,
},
},
sink: consumertest.NewNop(),
},
{
name: "invalid_grpc_address",
cfg: &Config{
Protocols: Protocols{
GRPC: configoptional.Some(configgrpc.ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "327.0.0.1:1122",
Transport: confignet.TransportTypeTCP,
},
}),
HTTP: defaultHTTPSettings,
},
},
wantStartErr: true,
sink: consumertest.NewNop(),
},
{
name: "invalid_http_address",
cfg: &Config{
Protocols: Protocols{
GRPC: defaultGRPCSettings,
HTTP: configoptional.Some(HTTPConfig{
ServerConfig: confighttp.ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "327.0.0.1:1122",
Transport: confignet.TransportTypeTCP,
},
},
MetricsURLPath: defaultMetricsURLPath,
}),
},
},
wantStartErr: true,
sink: consumertest.NewNop(),
},
{
name: "no_http_or_grcp_config",
cfg: &Config{
Protocols: Protocols{},
},
sink: consumertest.NewNop(),
},
}
creationSet := receivertest.NewNopSettings(metadata.Type)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
mr, err := factory.CreateMetrics(ctx, creationSet, tt.cfg, tt.sink)
if tt.wantErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
if tt.wantStartErr {
assert.Error(t, mr.Start(ctx, componenttest.NewNopHost()))
} else {
require.NoError(t, mr.Start(ctx, componenttest.NewNopHost()))
assert.NoError(t, mr.Shutdown(ctx))
}
})
}
}
func TestCreateLogs(t *testing.T) {
factory := NewFactory()
defaultGRPCSettings := configoptional.Some(configgrpc.ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: testutil.GetAvailableLocalAddress(t),
Transport: confignet.TransportTypeTCP,
},
})
defaultServerConfig := confighttp.NewDefaultServerConfig()
defaultServerConfig.NetAddr.Endpoint = testutil.GetAvailableLocalAddress(t)
defaultHTTPSettings := configoptional.Some(HTTPConfig{
ServerConfig: defaultServerConfig,
TracesURLPath: defaultTracesURLPath,
MetricsURLPath: defaultMetricsURLPath,
LogsURLPath: defaultLogsURLPath,
})
tests := []struct {
name string
cfg *Config
wantStartErr bool
wantErr bool
sink consumer.Logs
}{
{
name: "default",
cfg: &Config{
Protocols: Protocols{
GRPC: defaultGRPCSettings,
HTTP: defaultHTTPSettings,
},
},
sink: consumertest.NewNop(),
},
{
name: "invalid_grpc_address",
cfg: &Config{
Protocols: Protocols{
GRPC: configoptional.Some(configgrpc.ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "327.0.0.1:1122",
Transport: confignet.TransportTypeTCP,
},
}),
HTTP: defaultHTTPSettings,
},
},
wantStartErr: true,
sink: consumertest.NewNop(),
},
{
name: "invalid_http_address",
cfg: &Config{
Protocols: Protocols{
GRPC: defaultGRPCSettings,
HTTP: configoptional.Some(HTTPConfig{
ServerConfig: confighttp.ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "327.0.0.1:1122",
Transport: confignet.TransportTypeTCP,
},
},
LogsURLPath: defaultLogsURLPath,
}),
},
},
wantStartErr: true,
sink: consumertest.NewNop(),
},
{
name: "no_http_or_grcp_config",
cfg: &Config{
Protocols: Protocols{},
},
sink: consumertest.NewNop(),
},
}
creationSet := receivertest.NewNopSettings(metadata.Type)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
mr, err := factory.CreateLogs(ctx, creationSet, tt.cfg, tt.sink)
if tt.wantErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
if tt.wantStartErr {
assert.Error(t, mr.Start(ctx, componenttest.NewNopHost()))
} else {
require.NoError(t, mr.Start(ctx, componenttest.NewNopHost()))
assert.NoError(t, mr.Shutdown(ctx))
}
})
}
}
func TestCreateProfiles(t *testing.T) {
factory := NewFactory()
defaultGRPCSettings := configoptional.Some(configgrpc.ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: testutil.GetAvailableLocalAddress(t),
Transport: confignet.TransportTypeTCP,
},
})
defaultServerConfig := confighttp.NewDefaultServerConfig()
defaultServerConfig.NetAddr.Endpoint = testutil.GetAvailableLocalAddress(t)
defaultHTTPSettings := configoptional.Some(HTTPConfig{
ServerConfig: defaultServerConfig,
TracesURLPath: defaultTracesURLPath,
MetricsURLPath: defaultMetricsURLPath,
LogsURLPath: defaultLogsURLPath,
})
tests := []struct {
name string
cfg *Config
wantStartErr bool
wantErr bool
sink xconsumer.Profiles
}{
{
name: "default",
cfg: &Config{
Protocols: Protocols{
GRPC: defaultGRPCSettings,
HTTP: defaultHTTPSettings,
},
},
sink: consumertest.NewNop(),
},
{
name: "invalid_grpc_port",
cfg: &Config{
Protocols: Protocols{
GRPC: configoptional.Some(configgrpc.ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:112233",
Transport: confignet.TransportTypeTCP,
},
}),
HTTP: defaultHTTPSettings,
},
},
wantStartErr: true,
sink: consumertest.NewNop(),
},
{
name: "invalid_http_port",
cfg: &Config{
Protocols: Protocols{
GRPC: defaultGRPCSettings,
HTTP: configoptional.Some(HTTPConfig{
ServerConfig: confighttp.ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:112233",
Transport: confignet.TransportTypeTCP,
},
},
}),
},
},
wantStartErr: true,
sink: consumertest.NewNop(),
},
{
name: "no_http_or_grcp_config",
cfg: &Config{
Protocols: Protocols{},
},
sink: consumertest.NewNop(),
},
}
creationSet := receivertest.NewNopSettings(metadata.Type)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
tr, err := factory.(xreceiver.Factory).CreateProfiles(ctx, creationSet, tt.cfg, tt.sink)
if tt.wantErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
if tt.wantStartErr {
assert.Error(t, tr.Start(ctx, componenttest.NewNopHost()))
} else {
assert.NoError(t, tr.Start(ctx, componenttest.NewNopHost()))
assert.NoError(t, tr.Shutdown(ctx))
}
})
}
}
================================================
FILE: receiver/otlpreceiver/fuzz_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlpreceiver
import (
"bytes"
"net/http"
"net/http/httptest"
"testing"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/receiver/otlpreceiver/internal/logs"
"go.opentelemetry.io/collector/receiver/otlpreceiver/internal/metrics"
"go.opentelemetry.io/collector/receiver/otlpreceiver/internal/trace"
"go.opentelemetry.io/collector/receiver/receivertest"
)
func FuzzReceiverHandlers(f *testing.F) {
f.Fuzz(func(_ *testing.T, data []byte, pb bool, handler int) {
req, err := http.NewRequest(http.MethodPost, "", bytes.NewReader(data))
if err != nil {
return
}
if pb {
req.Header.Add("Content-Type", pbContentType)
} else {
req.Header.Add("Content-Type", jsonContentType)
}
set := receivertest.NewNopSettings(receivertest.NopType)
set.TelemetrySettings = componenttest.NewNopTelemetrySettings()
set.ID = otlpReceiverID
cfg := createDefaultConfig().(*Config)
r, err := newOtlpReceiver(cfg, &set)
if err != nil {
panic(err)
}
r.nextTraces = consumertest.NewNop()
r.nextLogs = consumertest.NewNop()
r.nextMetrics = consumertest.NewNop()
r.nextProfiles = consumertest.NewNop()
resp := httptest.NewRecorder()
switch handler % 3 {
case 0:
httpTracesReceiver := trace.New(r.nextTraces, r.obsrepHTTP)
handleTraces(resp, req, httpTracesReceiver)
case 1:
httpMetricsReceiver := metrics.New(r.nextMetrics, r.obsrepHTTP)
handleMetrics(resp, req, httpMetricsReceiver)
case 2:
httpLogsReceiver := logs.New(r.nextLogs, r.obsrepHTTP)
handleLogs(resp, req, httpLogsReceiver)
}
})
}
================================================
FILE: receiver/otlpreceiver/generated_component_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package otlpreceiver
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/confmap/confmaptest"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/receivertest"
"go.opentelemetry.io/collector/receiver/xreceiver"
)
var typ = component.MustNewType("otlp")
func TestComponentFactoryType(t *testing.T) {
require.Equal(t, typ, NewFactory().Type())
}
func TestComponentConfigStruct(t *testing.T) {
require.NoError(t, componenttest.CheckConfigStruct(NewFactory().CreateDefaultConfig()))
}
func TestComponentLifecycle(t *testing.T) {
factory := NewFactory()
tests := []struct {
createFn func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error)
name string
}{
{
name: "logs",
createFn: func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateLogs(ctx, set, cfg, consumertest.NewNop())
},
},
{
name: "metrics",
createFn: func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateMetrics(ctx, set, cfg, consumertest.NewNop())
},
},
{
name: "traces",
createFn: func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error) {
return factory.CreateTraces(ctx, set, cfg, consumertest.NewNop())
},
},
{
name: "profiles",
createFn: func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error) {
return factory.(xreceiver.Factory).CreateProfiles(ctx, set, cfg, consumertest.NewNop())
},
},
}
cm, err := confmaptest.LoadConf("metadata.yaml")
require.NoError(t, err)
cfg := factory.CreateDefaultConfig()
sub, err := cm.Sub("tests::config")
require.NoError(t, err)
require.NoError(t, sub.Unmarshal(&cfg))
for _, tt := range tests {
t.Run(tt.name+"-shutdown", func(t *testing.T) {
c, err := tt.createFn(context.Background(), receivertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
err = c.Shutdown(context.Background())
require.NoError(t, err)
})
t.Run(tt.name+"-lifecycle", func(t *testing.T) {
firstRcvr, err := tt.createFn(context.Background(), receivertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
host := newMdatagenNopHost()
require.NoError(t, err)
require.NoError(t, firstRcvr.Start(context.Background(), host))
require.NoError(t, firstRcvr.Shutdown(context.Background()))
secondRcvr, err := tt.createFn(context.Background(), receivertest.NewNopSettings(typ), cfg)
require.NoError(t, err)
require.NoError(t, secondRcvr.Start(context.Background(), host))
require.NoError(t, secondRcvr.Shutdown(context.Background()))
})
}
}
var _ component.Host = (*mdatagenNopHost)(nil)
type mdatagenNopHost struct{}
func newMdatagenNopHost() component.Host {
return &mdatagenNopHost{}
}
func (mnh *mdatagenNopHost) GetExtensions() map[component.ID]component.Component {
return nil
}
func (mnh *mdatagenNopHost) GetFactory(_ component.Kind, _ component.Type) component.Factory {
return nil
}
================================================
FILE: receiver/otlpreceiver/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package otlpreceiver
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: receiver/otlpreceiver/go.mod
================================================
module go.opentelemetry.io/collector/receiver/otlpreceiver
go 1.25.0
require (
github.com/klauspost/compress v1.18.4
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector v0.148.0
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componentstatus v0.148.0
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.opentelemetry.io/collector/config/configauth v1.54.0
go.opentelemetry.io/collector/config/configgrpc v0.148.0
go.opentelemetry.io/collector/config/confighttp v0.148.0
go.opentelemetry.io/collector/config/confignet v1.54.0
go.opentelemetry.io/collector/config/configoptional v1.54.0
go.opentelemetry.io/collector/config/configtls v1.54.0
go.opentelemetry.io/collector/confmap v1.54.0
go.opentelemetry.io/collector/confmap/xconfmap v0.148.0
go.opentelemetry.io/collector/consumer v1.54.0
go.opentelemetry.io/collector/consumer/consumererror v0.148.0
go.opentelemetry.io/collector/consumer/consumertest v0.148.0
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0
go.opentelemetry.io/collector/internal/sharedcomponent v0.148.0
go.opentelemetry.io/collector/internal/telemetry v0.148.0
go.opentelemetry.io/collector/internal/testutil v0.148.0
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/collector/pdata/pprofile v0.148.0
go.opentelemetry.io/collector/pdata/testdata v0.148.0
go.opentelemetry.io/collector/receiver v1.54.0
go.opentelemetry.io/collector/receiver/receiverhelper v0.148.0
go.opentelemetry.io/collector/receiver/receivertest v0.148.0
go.opentelemetry.io/collector/receiver/xreceiver v0.148.0
go.opentelemetry.io/otel v1.42.0
go.opentelemetry.io/otel/sdk/metric v1.42.0
go.uber.org/goleak v1.3.0
go.uber.org/zap v1.27.1
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171
google.golang.org/grpc v1.79.3
google.golang.org/protobuf v1.36.11
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/go-tpm v0.9.8 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/mostynb/go-grpc-compression v1.2.3 // indirect
github.com/pierrec/lz4/v4 v4.1.26 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rs/cors v1.11.1 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/client v1.54.0 // indirect
go.opentelemetry.io/collector/config/configcompression v1.54.0 // indirect
go.opentelemetry.io/collector/config/configmiddleware v1.54.0 // indirect
go.opentelemetry.io/collector/config/configopaque v1.54.0 // indirect
go.opentelemetry.io/collector/extension/extensionauth v1.54.0 // indirect
go.opentelemetry.io/collector/extension/extensionmiddleware v0.148.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
go.opentelemetry.io/collector/pipeline v1.54.0 // indirect
go.opentelemetry.io/collector/pipeline/xpipeline v0.148.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector => ../../
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest
replace go.opentelemetry.io/collector/config/configauth => ../../config/configauth
replace go.opentelemetry.io/collector/config/configcompression => ../../config/configcompression
replace go.opentelemetry.io/collector/config/confighttp => ../../config/confighttp
replace go.opentelemetry.io/collector/config/configgrpc => ../../config/configgrpc
replace go.opentelemetry.io/collector/config/confignet => ../../config/confignet
replace go.opentelemetry.io/collector/config/configopaque => ../../config/configopaque
replace go.opentelemetry.io/collector/config/configoptional => ../../config/configoptional
replace go.opentelemetry.io/collector/config/configtls => ../../config/configtls
replace go.opentelemetry.io/collector/confmap => ../../confmap
replace go.opentelemetry.io/collector/confmap/xconfmap => ../../confmap/xconfmap
replace go.opentelemetry.io/collector/extension => ../../extension
replace go.opentelemetry.io/collector/extension/extensionauth => ../../extension/extensionauth
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/pdata/testdata => ../../pdata/testdata
replace go.opentelemetry.io/collector/receiver => ../
replace go.opentelemetry.io/collector/receiver/receiverhelper => ../receiverhelper
replace go.opentelemetry.io/collector/consumer => ../../consumer
replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile
replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer
replace go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest
replace go.opentelemetry.io/collector/client => ../../client
replace go.opentelemetry.io/collector/component/componentstatus => ../../component/componentstatus
replace go.opentelemetry.io/collector/receiver/xreceiver => ../xreceiver
replace go.opentelemetry.io/collector/receiver/receivertest => ../receivertest
replace go.opentelemetry.io/collector/pipeline => ../../pipeline
replace go.opentelemetry.io/collector/consumer/consumererror => ../../consumer/consumererror
replace go.opentelemetry.io/collector/internal/sharedcomponent => ../../internal/sharedcomponent
replace go.opentelemetry.io/collector/internal/telemetry => ../../internal/telemetry
retract (
v0.76.0 // Depends on retracted pdata v1.0.0-rc10 module, use v0.76.1
v0.69.0 // Release failed, use v0.69.1
)
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest => ../../extension/extensionauth/extensionauthtest
replace go.opentelemetry.io/collector/extension/extensionmiddleware => ../../extension/extensionmiddleware
replace go.opentelemetry.io/collector/config/configmiddleware => ../../config/configmiddleware
replace go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest => ../../extension/extensionmiddleware/extensionmiddlewaretest
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/pipeline/xpipeline => ../../pipeline/xpipeline
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
================================================
FILE: receiver/otlpreceiver/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f h1:RJ+BDPLSHQO7cSjKBqjPJSbi1qfk9WcsjQDtZiw3dZw=
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f/go.mod h1:VHbbch/X4roIY22jL1s3qRbZhCiRIgUAF/PdSUcx2io=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/go-tpm-tools v0.4.7 h1:J3ycC8umYxM9A4eF73EofRZu4BxY0jjQnUnkhIBbvws=
github.com/google/go-tpm-tools v0.4.7/go.mod h1:gSyXTZHe3fgbzb6WEGd90QucmsnT1SRdlye82gH8QjQ=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mostynb/go-grpc-compression v1.2.3 h1:42/BKWMy0KEJGSdWvzqIyOZ95YcR9mLPqKctH7Uo//I=
github.com/mostynb/go-grpc-compression v1.2.3/go.mod h1:AghIxF3P57umzqM9yz795+y1Vjs47Km/Y2FE6ouQ7Lg=
github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY=
github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: receiver/otlpreceiver/internal/errors/errors.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package errors // import "go.opentelemetry.io/collector/receiver/otlpreceiver/internal/errors"
import (
"net/http"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"go.opentelemetry.io/collector/consumer/consumererror"
)
func GetStatusFromError(err error) error {
s, ok := status.FromError(err)
if !ok {
// Default to a retryable error
// https://github.com/open-telemetry/opentelemetry-proto/blob/main/docs/specification.md#failures
code := codes.Unavailable
if consumererror.IsPermanent(err) {
// If an error is permanent but doesn't have an attached gRPC status, assume it is server-side.
code = codes.Internal
}
s = status.New(code, err.Error())
}
return s.Err()
}
func GetHTTPStatusCodeFromStatus(s *status.Status) int {
// See https://github.com/open-telemetry/opentelemetry-proto/blob/main/docs/specification.md#failures
// to see if a code is retryable.
// See https://github.com/open-telemetry/opentelemetry-proto/blob/main/docs/specification.md#failures-1
// to see a list of retryable http status codes.
switch s.Code() {
// Retryable
case codes.Canceled, codes.DeadlineExceeded, codes.Aborted, codes.OutOfRange, codes.Unavailable, codes.DataLoss:
return http.StatusServiceUnavailable
// Retryable
case codes.ResourceExhausted:
return http.StatusTooManyRequests
// Not Retryable
case codes.InvalidArgument:
return http.StatusBadRequest
// Not Retryable
case codes.Unauthenticated:
return http.StatusUnauthorized
// Not Retryable
case codes.PermissionDenied:
return http.StatusForbidden
// Not Retryable
case codes.Unimplemented:
return http.StatusNotFound
// Not Retryable
default:
return http.StatusInternalServerError
}
}
================================================
FILE: receiver/otlpreceiver/internal/errors/errors_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package errors // import "go.opentelemetry.io/collector/receiver/otlpreceiver/internal/util"
import (
"errors"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"go.opentelemetry.io/collector/consumer/consumererror"
)
func Test_GetStatusFromError(t *testing.T) {
tests := []struct {
name string
input error
expected *status.Status
}{
{
name: "Status",
input: status.Error(codes.Aborted, "test"),
expected: status.New(codes.Aborted, "test"),
},
{
name: "Permanent Error",
input: consumererror.NewPermanent(errors.New("test")),
expected: status.New(codes.Internal, "Permanent error: test"),
},
{
name: "Non-Permanent Error",
input: errors.New("test"),
expected: status.New(codes.Unavailable, "test"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := GetStatusFromError(tt.input)
assert.Equal(t, tt.expected.Err(), result)
})
}
}
func Test_GetHTTPStatusCodeFromStatus(t *testing.T) {
tests := []struct {
name string
input *status.Status
expected int
}{
{
name: "Retryable Status",
input: status.New(codes.Unavailable, "test"),
expected: http.StatusServiceUnavailable,
},
{
name: "Non-retryable Status",
input: status.New(codes.Internal, "test"),
expected: http.StatusInternalServerError,
},
{
name: "Specifically 429",
input: status.New(codes.ResourceExhausted, "test"),
expected: http.StatusTooManyRequests,
},
{
name: "Specifically 400",
input: status.New(codes.InvalidArgument, "test"),
expected: http.StatusBadRequest,
},
{
name: "Specifically 401",
input: status.New(codes.Unauthenticated, "test"),
expected: http.StatusUnauthorized,
},
{
name: "Specifically 403",
input: status.New(codes.PermissionDenied, "test"),
expected: http.StatusForbidden,
},
{
name: "Specifically 404",
input: status.New(codes.Unimplemented, "test"),
expected: http.StatusNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := GetHTTPStatusCodeFromStatus(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}
================================================
FILE: receiver/otlpreceiver/internal/logs/otlp.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package logs // import "go.opentelemetry.io/collector/receiver/otlpreceiver/internal/logs"
import (
"context"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/pdata/plog/plogotlp"
"go.opentelemetry.io/collector/receiver/otlpreceiver/internal/errors"
"go.opentelemetry.io/collector/receiver/receiverhelper"
)
const dataFormatProtobuf = "protobuf"
// Receiver is the type used to handle logs from OpenTelemetry exporters.
type Receiver struct {
plogotlp.UnimplementedGRPCServer
nextConsumer consumer.Logs
obsreport *receiverhelper.ObsReport
}
// New creates a new Receiver reference.
func New(nextConsumer consumer.Logs, obsreport *receiverhelper.ObsReport) *Receiver {
return &Receiver{
nextConsumer: nextConsumer,
obsreport: obsreport,
}
}
// Export implements the service Export logs func.
func (r *Receiver) Export(ctx context.Context, req plogotlp.ExportRequest) (plogotlp.ExportResponse, error) {
ld := req.Logs()
numSpans := ld.LogRecordCount()
if numSpans == 0 {
return plogotlp.NewExportResponse(), nil
}
ctx = r.obsreport.StartLogsOp(ctx)
err := r.nextConsumer.ConsumeLogs(ctx, ld)
r.obsreport.EndLogsOp(ctx, dataFormatProtobuf, numSpans, err)
// Use appropriate status codes for permanent/non-permanent errors
// If we return the error straightaway, then the grpc implementation will set status code to Unknown
// Refer: https://github.com/grpc/grpc-go/blob/v1.59.0/server.go#L1345
// So, convert the error to appropriate grpc status and return the error
// NonPermanent errors will be converted to codes.Unavailable (equivalent to HTTP 503)
// Permanent errors will be converted to codes.InvalidArgument (equivalent to HTTP 400)
if err != nil {
return plogotlp.NewExportResponse(), errors.GetStatusFromError(err)
}
return plogotlp.NewExportResponse(), nil
}
================================================
FILE: receiver/otlpreceiver/internal/logs/otlp_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package logs
import (
"context"
"errors"
"net"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/status"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/pdata/plog/plogotlp"
"go.opentelemetry.io/collector/pdata/testdata"
"go.opentelemetry.io/collector/receiver/otlpreceiver/internal/metadata"
"go.opentelemetry.io/collector/receiver/receiverhelper"
"go.opentelemetry.io/collector/receiver/receivertest"
)
func TestExport(t *testing.T) {
ld := testdata.GenerateLogs(1)
req := plogotlp.NewExportRequestFromLogs(ld)
logSink := new(consumertest.LogsSink)
logClient := makeLogsServiceClient(t, logSink)
resp, err := logClient.Export(context.Background(), req)
require.NoError(t, err, "Failed to export trace: %v", err)
require.NotNil(t, resp, "The response is missing")
lds := logSink.AllLogs()
require.Len(t, lds, 1)
assert.Equal(t, ld, lds[0])
}
func TestExport_EmptyRequest(t *testing.T) {
logSink := new(consumertest.LogsSink)
logClient := makeLogsServiceClient(t, logSink)
resp, err := logClient.Export(context.Background(), plogotlp.NewExportRequest())
require.NoError(t, err, "Failed to export trace: %v", err)
assert.NotNil(t, resp, "The response is missing")
}
func TestExport_NonPermanentErrorConsumer(t *testing.T) {
ld := testdata.GenerateLogs(1)
req := plogotlp.NewExportRequestFromLogs(ld)
logClient := makeLogsServiceClient(t, consumertest.NewErr(errors.New("my error")))
resp, err := logClient.Export(context.Background(), req)
require.EqualError(t, err, "rpc error: code = Unavailable desc = my error")
require.ErrorIs(t, err, status.Error(codes.Unavailable, "my error"))
assert.Equal(t, plogotlp.ExportResponse{}, resp)
}
func TestExport_PermanentErrorConsumer(t *testing.T) {
ld := testdata.GenerateLogs(1)
req := plogotlp.NewExportRequestFromLogs(ld)
logClient := makeLogsServiceClient(t, consumertest.NewErr(consumererror.NewPermanent(errors.New("my error"))))
resp, err := logClient.Export(context.Background(), req)
require.EqualError(t, err, "rpc error: code = Internal desc = Permanent error: my error")
require.ErrorIs(t, err, status.Error(codes.Internal, "Permanent error: my error"))
assert.Equal(t, plogotlp.ExportResponse{}, resp)
}
func makeLogsServiceClient(t *testing.T, lc consumer.Logs) plogotlp.GRPCClient {
addr := otlpReceiverOnGRPCServer(t, lc)
cc, err := grpc.NewClient(addr.String(), grpc.WithTransportCredentials(insecure.NewCredentials()))
require.NoError(t, err, "Failed to create the TraceServiceClient: %v", err)
t.Cleanup(func() {
require.NoError(t, cc.Close())
})
return plogotlp.NewGRPCClient(cc)
}
func otlpReceiverOnGRPCServer(t *testing.T, lc consumer.Logs) net.Addr {
ln, err := net.Listen("tcp", "localhost:")
require.NoError(t, err, "Failed to find an available address to run the gRPC server: %v", err)
t.Cleanup(func() {
require.NoError(t, ln.Close())
})
set := receivertest.NewNopSettings(metadata.Type)
set.ID = component.MustNewIDWithName("otlp", "log")
obsreport, err := receiverhelper.NewObsReport(receiverhelper.ObsReportSettings{
ReceiverID: set.ID,
Transport: "grpc",
ReceiverCreateSettings: set,
})
require.NoError(t, err)
r := New(lc, obsreport)
// Now run it as a gRPC server
srv := grpc.NewServer()
plogotlp.RegisterGRPCServer(srv, r)
go func() {
_ = srv.Serve(ln)
}()
return ln.Addr()
}
================================================
FILE: receiver/otlpreceiver/internal/logs/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package logs
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: receiver/otlpreceiver/internal/metadata/generated_logs.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/receiver"
)
// LogsBuilder provides an interface for scrapers to report logs while taking care of all the transformations
// required to produce log representation defined in metadata and user config.
type LogsBuilder struct {
logsBuffer plog.Logs
logRecordsBuffer plog.LogRecordSlice
buildInfo component.BuildInfo // contains version information.
}
// LogBuilderOption applies changes to default logs builder.
type LogBuilderOption interface {
apply(*LogsBuilder)
}
func NewLogsBuilder(settings receiver.Settings) *LogsBuilder {
lb := &LogsBuilder{
logsBuffer: plog.NewLogs(),
logRecordsBuffer: plog.NewLogRecordSlice(),
buildInfo: settings.BuildInfo,
}
return lb
}
// ResourceLogsOption applies changes to provided resource logs.
type ResourceLogsOption interface {
apply(plog.ResourceLogs)
}
type resourceLogsOptionFunc func(plog.ResourceLogs)
func (rlof resourceLogsOptionFunc) apply(rl plog.ResourceLogs) {
rlof(rl)
}
// WithLogsResource sets the provided resource on the emitted ResourceLogs.
// It's recommended to use ResourceBuilder to create the resource.
func WithLogsResource(res pcommon.Resource) ResourceLogsOption {
return resourceLogsOptionFunc(func(rl plog.ResourceLogs) {
res.CopyTo(rl.Resource())
})
}
// AppendLogRecord adds a log record to the logs builder.
func (lb *LogsBuilder) AppendLogRecord(lr plog.LogRecord) {
lr.MoveTo(lb.logRecordsBuffer.AppendEmpty())
}
// EmitForResource saves all the generated logs under a new resource and updates the internal state to be ready for
// recording another set of log records as part of another resource. This function can be helpful when one scraper
// needs to emit logs from several resources. Otherwise calling this function is not required,
// just `Emit` function can be called instead.
// Resource attributes should be provided as ResourceLogsOption arguments.
func (lb *LogsBuilder) EmitForResource(options ...ResourceLogsOption) {
rl := plog.NewResourceLogs()
ils := rl.ScopeLogs().AppendEmpty()
ils.Scope().SetName(ScopeName)
ils.Scope().SetVersion(lb.buildInfo.Version)
for _, op := range options {
op.apply(rl)
}
if lb.logRecordsBuffer.Len() > 0 {
lb.logRecordsBuffer.MoveAndAppendTo(ils.LogRecords())
lb.logRecordsBuffer = plog.NewLogRecordSlice()
}
if ils.LogRecords().Len() > 0 {
rl.MoveTo(lb.logsBuffer.ResourceLogs().AppendEmpty())
}
}
// Emit returns all the logs accumulated by the logs builder and updates the internal state to be ready for
// recording another set of logs. This function will be responsible for applying all the transformations required to
// produce logs representation defined in metadata and user config.
func (lb *LogsBuilder) Emit(options ...ResourceLogsOption) plog.Logs {
lb.EmitForResource(options...)
logs := lb.logsBuffer
lb.logsBuffer = plog.NewLogs()
return logs
}
================================================
FILE: receiver/otlpreceiver/internal/metadata/generated_logs_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/receiver/receivertest"
)
func TestLogsBuilderAppendLogRecord(t *testing.T) {
observedZapCore, _ := observer.New(zap.WarnLevel)
settings := receivertest.NewNopSettings(receivertest.NopType)
settings.Logger = zap.New(observedZapCore)
lb := NewLogsBuilder(settings)
res := pcommon.NewResource()
// append the first log record
lr := plog.NewLogRecord()
lr.SetTimestamp(pcommon.NewTimestampFromTime(time.Now()))
lr.Attributes().PutStr("type", "log")
lr.Body().SetStr("the first log record")
// append the second log record
lr2 := plog.NewLogRecord()
lr2.SetTimestamp(pcommon.NewTimestampFromTime(time.Now()))
lr2.Attributes().PutStr("type", "event")
lr2.Body().SetStr("the second log record")
lb.AppendLogRecord(lr)
lb.AppendLogRecord(lr2)
logs := lb.Emit(WithLogsResource(res))
assert.Equal(t, 1, logs.ResourceLogs().Len())
rl := logs.ResourceLogs().At(0)
assert.Equal(t, 1, rl.ScopeLogs().Len())
sl := rl.ScopeLogs().At(0)
assert.Equal(t, ScopeName, sl.Scope().Name())
assert.Equal(t, lb.buildInfo.Version, sl.Scope().Version())
assert.Equal(t, 2, sl.LogRecords().Len())
attrVal, ok := sl.LogRecords().At(0).Attributes().Get("type")
assert.True(t, ok)
assert.Equal(t, "log", attrVal.Str())
assert.Equal(t, pcommon.ValueTypeStr, sl.LogRecords().At(0).Body().Type())
assert.Equal(t, "the first log record", sl.LogRecords().At(0).Body().Str())
attrVal, ok = sl.LogRecords().At(1).Attributes().Get("type")
assert.True(t, ok)
assert.Equal(t, "event", attrVal.Str())
assert.Equal(t, pcommon.ValueTypeStr, sl.LogRecords().At(1).Body().Type())
assert.Equal(t, "the second log record", sl.LogRecords().At(1).Body().Str())
}
================================================
FILE: receiver/otlpreceiver/internal/metadata/generated_status.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/component"
)
var (
Type = component.MustNewType("otlp")
ScopeName = "go.opentelemetry.io/collector/receiver/otlpreceiver"
)
const (
ProfilesStability = component.StabilityLevelAlpha
TracesStability = component.StabilityLevelStable
MetricsStability = component.StabilityLevelStable
LogsStability = component.StabilityLevelStable
)
================================================
FILE: receiver/otlpreceiver/internal/metrics/otlp.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package metrics // import "go.opentelemetry.io/collector/receiver/otlpreceiver/internal/metrics"
import (
"context"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/pdata/pmetric/pmetricotlp"
"go.opentelemetry.io/collector/receiver/otlpreceiver/internal/errors"
"go.opentelemetry.io/collector/receiver/receiverhelper"
)
const dataFormatProtobuf = "protobuf"
// Receiver is the type used to handle metrics from OpenTelemetry exporters.
type Receiver struct {
pmetricotlp.UnimplementedGRPCServer
nextConsumer consumer.Metrics
obsreport *receiverhelper.ObsReport
}
// New creates a new Receiver reference.
func New(nextConsumer consumer.Metrics, obsreport *receiverhelper.ObsReport) *Receiver {
return &Receiver{
nextConsumer: nextConsumer,
obsreport: obsreport,
}
}
// Export implements the service Export metrics func.
func (r *Receiver) Export(ctx context.Context, req pmetricotlp.ExportRequest) (pmetricotlp.ExportResponse, error) {
md := req.Metrics()
dataPointCount := md.DataPointCount()
if dataPointCount == 0 {
return pmetricotlp.NewExportResponse(), nil
}
ctx = r.obsreport.StartMetricsOp(ctx)
err := r.nextConsumer.ConsumeMetrics(ctx, md)
r.obsreport.EndMetricsOp(ctx, dataFormatProtobuf, dataPointCount, err)
// Use appropriate status codes for permanent/non-permanent errors
// If we return the error straightaway, then the grpc implementation will set status code to Unknown
// Refer: https://github.com/grpc/grpc-go/blob/v1.59.0/server.go#L1345
// So, convert the error to appropriate grpc status and return the error
// NonPermanent errors will be converted to codes.Unavailable (equivalent to HTTP 503)
// Permanent errors will be converted to codes.InvalidArgument (equivalent to HTTP 400)
if err != nil {
return pmetricotlp.NewExportResponse(), errors.GetStatusFromError(err)
}
return pmetricotlp.NewExportResponse(), nil
}
================================================
FILE: receiver/otlpreceiver/internal/metrics/otlp_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package metrics
import (
"context"
"errors"
"net"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/status"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/pdata/pmetric/pmetricotlp"
"go.opentelemetry.io/collector/pdata/testdata"
"go.opentelemetry.io/collector/receiver/otlpreceiver/internal/metadata"
"go.opentelemetry.io/collector/receiver/receiverhelper"
"go.opentelemetry.io/collector/receiver/receivertest"
)
func TestExport(t *testing.T) {
md := testdata.GenerateMetrics(1)
req := pmetricotlp.NewExportRequestFromMetrics(md)
metricSink := new(consumertest.MetricsSink)
metricsClient := makeMetricsServiceClient(t, metricSink)
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")
mds := metricSink.AllMetrics()
require.Len(t, mds, 1)
assert.Equal(t, md, mds[0])
}
func TestExport_EmptyRequest(t *testing.T) {
metricSink := new(consumertest.MetricsSink)
metricsClient := makeMetricsServiceClient(t, metricSink)
resp, err := metricsClient.Export(context.Background(), pmetricotlp.NewExportRequest())
require.NoError(t, err)
require.NotNil(t, resp)
}
func TestExport_NonPermanentErrorConsumer(t *testing.T) {
md := testdata.GenerateMetrics(1)
req := pmetricotlp.NewExportRequestFromMetrics(md)
metricsClient := makeMetricsServiceClient(t, consumertest.NewErr(errors.New("my error")))
resp, err := metricsClient.Export(context.Background(), req)
require.EqualError(t, err, "rpc error: code = Unavailable desc = my error")
require.ErrorIs(t, err, status.Error(codes.Unavailable, "my error"))
assert.Equal(t, pmetricotlp.ExportResponse{}, resp)
}
func TestExport_PermanentErrorConsumer(t *testing.T) {
ld := testdata.GenerateMetrics(1)
req := pmetricotlp.NewExportRequestFromMetrics(ld)
metricsClient := makeMetricsServiceClient(t, consumertest.NewErr(consumererror.NewPermanent(errors.New("my error"))))
resp, err := metricsClient.Export(context.Background(), req)
require.EqualError(t, err, "rpc error: code = Internal desc = Permanent error: my error")
require.ErrorIs(t, err, status.Error(codes.Internal, "Permanent error: my error"))
assert.Equal(t, pmetricotlp.ExportResponse{}, resp)
}
func makeMetricsServiceClient(t *testing.T, mc consumer.Metrics) pmetricotlp.GRPCClient {
addr := otlpReceiverOnGRPCServer(t, mc)
cc, err := grpc.NewClient(addr.String(), grpc.WithTransportCredentials(insecure.NewCredentials()))
require.NoError(t, err, "Failed to create the MetricsServiceClient: %v", err)
t.Cleanup(func() {
require.NoError(t, cc.Close())
})
return pmetricotlp.NewGRPCClient(cc)
}
func otlpReceiverOnGRPCServer(t *testing.T, mc consumer.Metrics) net.Addr {
ln, err := net.Listen("tcp", "localhost:")
require.NoError(t, err, "Failed to find an available address to run the gRPC server: %v", err)
t.Cleanup(func() {
require.NoError(t, ln.Close())
})
set := receivertest.NewNopSettings(metadata.Type)
set.ID = component.MustNewIDWithName("otlp", "metrics")
obsreport, err := receiverhelper.NewObsReport(receiverhelper.ObsReportSettings{
ReceiverID: set.ID,
Transport: "grpc",
ReceiverCreateSettings: set,
})
require.NoError(t, err)
r := New(mc, obsreport)
// Now run it as a gRPC server
srv := grpc.NewServer()
pmetricotlp.RegisterGRPCServer(srv, r)
go func() {
_ = srv.Serve(ln)
}()
return ln.Addr()
}
================================================
FILE: receiver/otlpreceiver/internal/metrics/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package metrics
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: receiver/otlpreceiver/internal/profiles/otlp.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package profiles // import "go.opentelemetry.io/collector/receiver/otlpreceiver/internal/profiles"
import (
"context"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/pdata/pprofile/pprofileotlp"
"go.opentelemetry.io/collector/receiver/otlpreceiver/internal/errors"
"go.opentelemetry.io/collector/receiver/receiverhelper"
)
const dataFormatProtobuf = "protobuf"
// Receiver is the type used to handle spans from OpenTelemetry exporters.
type Receiver struct {
pprofileotlp.UnimplementedGRPCServer
nextConsumer xconsumer.Profiles
obsreport *receiverhelper.ObsReport
}
// New creates a new Receiver reference.
func New(nextConsumer xconsumer.Profiles, obsreport *receiverhelper.ObsReport) *Receiver {
return &Receiver{
nextConsumer: nextConsumer,
obsreport: obsreport,
}
}
// Export implements the service Export profiles func.
func (r *Receiver) Export(ctx context.Context, req pprofileotlp.ExportRequest) (pprofileotlp.ExportResponse, error) {
td := req.Profiles()
// We need to ensure that it propagates the receiver name as a tag
numSamples := td.SampleCount()
if numSamples == 0 {
return pprofileotlp.NewExportResponse(), nil
}
ctx = r.obsreport.StartTracesOp(ctx)
err := r.nextConsumer.ConsumeProfiles(ctx, td)
r.obsreport.EndTracesOp(ctx, dataFormatProtobuf, numSamples, err)
// Use appropriate status codes for permanent/non-permanent errors
// If we return the error straightaway, then the grpc implementation will set status code to Unknown
// Refer: https://github.com/grpc/grpc-go/blob/v1.59.0/server.go#L1345
// So, convert the error to appropriate grpc status and return the error
// NonPermanent errors will be converted to codes.Unavailable (equivalent to HTTP 503)
// Permanent errors will be converted to codes.InvalidArgument (equivalent to HTTP 400)
if err != nil {
return pprofileotlp.NewExportResponse(), errors.GetStatusFromError(err)
}
return pprofileotlp.NewExportResponse(), nil
}
================================================
FILE: receiver/otlpreceiver/internal/profiles/otlp_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package profiles
import (
"context"
"errors"
"net"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/status"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/pdata/pprofile/pprofileotlp"
"go.opentelemetry.io/collector/pdata/testdata"
"go.opentelemetry.io/collector/receiver/otlpreceiver/internal/metadata"
"go.opentelemetry.io/collector/receiver/receiverhelper"
"go.opentelemetry.io/collector/receiver/receivertest"
)
func TestExport(t *testing.T) {
td := testdata.GenerateProfiles(1)
req := pprofileotlp.NewExportRequestFromProfiles(td)
profileSink := new(consumertest.ProfilesSink)
profileClient := makeProfileServiceClient(t, profileSink)
resp, err := profileClient.Export(context.Background(), req)
require.NoError(t, err, "Failed to export profile: %v", err)
require.NotNil(t, resp, "The response is missing")
require.Len(t, profileSink.AllProfiles(), 1)
assert.Equal(t, td, profileSink.AllProfiles()[0])
}
func TestExport_EmptyRequest(t *testing.T) {
profileSink := new(consumertest.ProfilesSink)
profileClient := makeProfileServiceClient(t, profileSink)
resp, err := profileClient.Export(context.Background(), pprofileotlp.NewExportRequest())
require.NoError(t, err, "Failed to export profile: %v", err)
assert.NotNil(t, resp, "The response is missing")
}
func TestExport_NonPermanentErrorConsumer(t *testing.T) {
td := testdata.GenerateProfiles(1)
req := pprofileotlp.NewExportRequestFromProfiles(td)
profileClient := makeProfileServiceClient(t, consumertest.NewErr(errors.New("my error")))
resp, err := profileClient.Export(context.Background(), req)
require.EqualError(t, err, "rpc error: code = Unavailable desc = my error")
require.ErrorIs(t, err, status.Error(codes.Unavailable, "my error"))
assert.Equal(t, pprofileotlp.ExportResponse{}, resp)
}
func TestExport_PermanentErrorConsumer(t *testing.T) {
ld := testdata.GenerateProfiles(1)
req := pprofileotlp.NewExportRequestFromProfiles(ld)
profileClient := makeProfileServiceClient(t, consumertest.NewErr(consumererror.NewPermanent(errors.New("my error"))))
resp, err := profileClient.Export(context.Background(), req)
require.EqualError(t, err, "rpc error: code = Internal desc = Permanent error: my error")
require.ErrorIs(t, err, status.Error(codes.Internal, "Permanent error: my error"))
assert.Equal(t, pprofileotlp.ExportResponse{}, resp)
}
func makeProfileServiceClient(t *testing.T, tc xconsumer.Profiles) pprofileotlp.GRPCClient {
addr := otlpReceiverOnGRPCServer(t, tc)
cc, err := grpc.NewClient(addr.String(), grpc.WithTransportCredentials(insecure.NewCredentials()))
require.NoError(t, err, "Failed to create the profileServiceClient: %v", err)
t.Cleanup(func() {
require.NoError(t, cc.Close())
})
return pprofileotlp.NewGRPCClient(cc)
}
func otlpReceiverOnGRPCServer(t *testing.T, tc xconsumer.Profiles) net.Addr {
ln, err := net.Listen("tcp", "localhost:")
require.NoError(t, err, "Failed to find an available address to run the gRPC server: %v", err)
t.Cleanup(func() {
require.NoError(t, ln.Close())
})
set := receivertest.NewNopSettings(metadata.Type)
set.ID = component.MustNewIDWithName("otlp", "profiles")
obsreport, err := receiverhelper.NewObsReport(receiverhelper.ObsReportSettings{
ReceiverID: set.ID,
Transport: "grpc",
ReceiverCreateSettings: set,
})
require.NoError(t, err)
r := New(tc, obsreport)
// Now run it as a gRPC server
srv := grpc.NewServer()
pprofileotlp.RegisterGRPCServer(srv, r)
go func() {
_ = srv.Serve(ln)
}()
return ln.Addr()
}
================================================
FILE: receiver/otlpreceiver/internal/profiles/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package profiles
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: receiver/otlpreceiver/internal/trace/otlp.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package trace // import "go.opentelemetry.io/collector/receiver/otlpreceiver/internal/trace"
import (
"context"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp"
"go.opentelemetry.io/collector/receiver/otlpreceiver/internal/errors"
"go.opentelemetry.io/collector/receiver/receiverhelper"
)
const dataFormatProtobuf = "protobuf"
// Receiver is the type used to handle spans from OpenTelemetry exporters.
type Receiver struct {
ptraceotlp.UnimplementedGRPCServer
nextConsumer consumer.Traces
obsreport *receiverhelper.ObsReport
}
// New creates a new Receiver reference.
func New(nextConsumer consumer.Traces, obsreport *receiverhelper.ObsReport) *Receiver {
return &Receiver{
nextConsumer: nextConsumer,
obsreport: obsreport,
}
}
// Export implements the service Export traces func.
func (r *Receiver) Export(ctx context.Context, req ptraceotlp.ExportRequest) (ptraceotlp.ExportResponse, error) {
td := req.Traces()
// We need to ensure that it propagates the receiver name as a tag
numSpans := td.SpanCount()
if numSpans == 0 {
return ptraceotlp.NewExportResponse(), nil
}
ctx = r.obsreport.StartTracesOp(ctx)
err := r.nextConsumer.ConsumeTraces(ctx, td)
r.obsreport.EndTracesOp(ctx, dataFormatProtobuf, numSpans, err)
// Use appropriate status codes for permanent/non-permanent errors
// If we return the error straightaway, then the grpc implementation will set status code to Unknown
// Refer: https://github.com/grpc/grpc-go/blob/v1.59.0/server.go#L1345
// So, convert the error to appropriate grpc status and return the error
// NonPermanent errors will be converted to codes.Unavailable (equivalent to HTTP 503)
// Permanent errors will be converted to codes.InvalidArgument (equivalent to HTTP 400)
if err != nil {
return ptraceotlp.NewExportResponse(), errors.GetStatusFromError(err)
}
return ptraceotlp.NewExportResponse(), nil
}
================================================
FILE: receiver/otlpreceiver/internal/trace/otlp_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package trace
import (
"context"
"errors"
"net"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/status"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp"
"go.opentelemetry.io/collector/pdata/testdata"
"go.opentelemetry.io/collector/receiver/otlpreceiver/internal/metadata"
"go.opentelemetry.io/collector/receiver/receiverhelper"
"go.opentelemetry.io/collector/receiver/receivertest"
)
func TestExport(t *testing.T) {
td := testdata.GenerateTraces(1)
req := ptraceotlp.NewExportRequestFromTraces(td)
traceSink := new(consumertest.TracesSink)
traceClient := makeTraceServiceClient(t, traceSink)
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.Len(t, traceSink.AllTraces(), 1)
assert.Equal(t, td, traceSink.AllTraces()[0])
}
func TestExport_EmptyRequest(t *testing.T) {
traceSink := new(consumertest.TracesSink)
traceClient := makeTraceServiceClient(t, traceSink)
resp, err := traceClient.Export(context.Background(), ptraceotlp.NewExportRequest())
require.NoError(t, err, "Failed to export trace: %v", err)
assert.NotNil(t, resp, "The response is missing")
}
func TestExport_NonPermanentErrorConsumer(t *testing.T) {
td := testdata.GenerateTraces(1)
req := ptraceotlp.NewExportRequestFromTraces(td)
traceClient := makeTraceServiceClient(t, consumertest.NewErr(errors.New("my error")))
resp, err := traceClient.Export(context.Background(), req)
require.EqualError(t, err, "rpc error: code = Unavailable desc = my error")
require.ErrorIs(t, err, status.Error(codes.Unavailable, "my error"))
assert.Equal(t, ptraceotlp.ExportResponse{}, resp)
}
func TestExport_PermanentErrorConsumer(t *testing.T) {
ld := testdata.GenerateTraces(1)
req := ptraceotlp.NewExportRequestFromTraces(ld)
traceClient := makeTraceServiceClient(t, consumertest.NewErr(consumererror.NewPermanent(errors.New("my error"))))
resp, err := traceClient.Export(context.Background(), req)
require.EqualError(t, err, "rpc error: code = Internal desc = Permanent error: my error")
require.ErrorIs(t, err, status.Error(codes.Internal, "Permanent error: my error"))
assert.Equal(t, ptraceotlp.ExportResponse{}, resp)
}
func makeTraceServiceClient(t *testing.T, tc consumer.Traces) ptraceotlp.GRPCClient {
addr := otlpReceiverOnGRPCServer(t, tc)
cc, err := grpc.NewClient(addr.String(), grpc.WithTransportCredentials(insecure.NewCredentials()))
require.NoError(t, err, "Failed to create the TraceServiceClient: %v", err)
t.Cleanup(func() {
require.NoError(t, cc.Close())
})
return ptraceotlp.NewGRPCClient(cc)
}
func otlpReceiverOnGRPCServer(t *testing.T, tc consumer.Traces) net.Addr {
ln, err := net.Listen("tcp", "localhost:")
require.NoError(t, err, "Failed to find an available address to run the gRPC server: %v", err)
t.Cleanup(func() {
require.NoError(t, ln.Close())
})
set := receivertest.NewNopSettings(metadata.Type)
set.ID = component.MustNewIDWithName("otlp", "trace")
obsreport, err := receiverhelper.NewObsReport(receiverhelper.ObsReportSettings{
ReceiverID: set.ID,
Transport: "grpc",
ReceiverCreateSettings: set,
})
require.NoError(t, err)
r := New(tc, obsreport)
// Now run it as a gRPC server
srv := grpc.NewServer()
ptraceotlp.RegisterGRPCServer(srv, r)
go func() {
_ = srv.Serve(ln)
}()
return ln.Addr()
}
================================================
FILE: receiver/otlpreceiver/internal/trace/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package trace
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: receiver/otlpreceiver/metadata.yaml
================================================
display_name: OTLP Receiver
type: otlp
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: receiver
stability:
stable: [traces, metrics, logs]
alpha: [profiles]
distributions: [core, contrib, k8s, otlp]
================================================
FILE: receiver/otlpreceiver/otlp.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlpreceiver // import "go.opentelemetry.io/collector/receiver/otlpreceiver"
import (
"context"
"errors"
"net"
"net/http"
"sync"
"go.uber.org/zap"
"google.golang.org/grpc"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componentstatus"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/internal/telemetry"
"go.opentelemetry.io/collector/pdata/plog/plogotlp"
"go.opentelemetry.io/collector/pdata/pmetric/pmetricotlp"
"go.opentelemetry.io/collector/pdata/pprofile/pprofileotlp"
"go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/otlpreceiver/internal/logs"
"go.opentelemetry.io/collector/receiver/otlpreceiver/internal/metrics"
"go.opentelemetry.io/collector/receiver/otlpreceiver/internal/profiles"
"go.opentelemetry.io/collector/receiver/otlpreceiver/internal/trace"
"go.opentelemetry.io/collector/receiver/receiverhelper"
)
// otlpReceiver is the type that exposes Trace and Metrics reception.
type otlpReceiver struct {
cfg *Config
serverGRPC *grpc.Server
serverHTTP *http.Server
nextTraces consumer.Traces
nextMetrics consumer.Metrics
nextLogs consumer.Logs
nextProfiles xconsumer.Profiles
shutdownWG sync.WaitGroup
obsrepGRPC *receiverhelper.ObsReport
obsrepHTTP *receiverhelper.ObsReport
settings *receiver.Settings
}
// 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, set *receiver.Settings) (*otlpReceiver, error) {
set.TelemetrySettings = telemetry.DropInjectedAttributes(set.TelemetrySettings, telemetry.SignalKey)
set.Logger.Debug("created signal-agnostic logger")
r := &otlpReceiver{
cfg: cfg,
nextTraces: nil,
nextMetrics: nil,
nextLogs: nil,
nextProfiles: nil,
settings: set,
}
var err error
r.obsrepGRPC, err = receiverhelper.NewObsReport(receiverhelper.ObsReportSettings{
ReceiverID: set.ID,
Transport: "grpc",
ReceiverCreateSettings: *set,
})
if err != nil {
return nil, err
}
r.obsrepHTTP, err = receiverhelper.NewObsReport(receiverhelper.ObsReportSettings{
ReceiverID: set.ID,
Transport: "http",
ReceiverCreateSettings: *set,
})
if err != nil {
return nil, err
}
return r, nil
}
func (r *otlpReceiver) startGRPCServer(ctx context.Context, host component.Host) error {
// If GRPC is not enabled, nothing to start.
if !r.cfg.GRPC.HasValue() {
return nil
}
grpcCfg := r.cfg.GRPC.Get()
var err error
if r.serverGRPC, err = grpcCfg.ToServer(ctx, host.GetExtensions(), r.settings.TelemetrySettings); err != nil {
return err
}
if r.nextTraces != nil {
ptraceotlp.RegisterGRPCServer(r.serverGRPC, trace.New(r.nextTraces, r.obsrepGRPC))
}
if r.nextMetrics != nil {
pmetricotlp.RegisterGRPCServer(r.serverGRPC, metrics.New(r.nextMetrics, r.obsrepGRPC))
}
if r.nextLogs != nil {
plogotlp.RegisterGRPCServer(r.serverGRPC, logs.New(r.nextLogs, r.obsrepGRPC))
}
if r.nextProfiles != nil {
pprofileotlp.RegisterGRPCServer(r.serverGRPC, profiles.New(r.nextProfiles, r.obsrepGRPC))
}
var gln net.Listener
if gln, err = grpcCfg.NetAddr.Listen(ctx); err != nil {
return err
}
r.settings.Logger.Info("Starting GRPC server", zap.String("endpoint", gln.Addr().String()))
r.shutdownWG.Go(func() {
if errGrpc := r.serverGRPC.Serve(gln); errGrpc != nil && !errors.Is(errGrpc, grpc.ErrServerStopped) {
componentstatus.ReportStatus(host, componentstatus.NewFatalErrorEvent(errGrpc))
}
})
return nil
}
func (r *otlpReceiver) startHTTPServer(ctx context.Context, host component.Host) error {
// If HTTP is not enabled, nothing to start.
if !r.cfg.HTTP.HasValue() {
return nil
}
httpCfg := r.cfg.HTTP.Get()
httpMux := http.NewServeMux()
if r.nextTraces != nil {
httpTracesReceiver := trace.New(r.nextTraces, r.obsrepHTTP)
httpMux.HandleFunc(string(httpCfg.TracesURLPath), func(resp http.ResponseWriter, req *http.Request) {
handleTraces(resp, req, httpTracesReceiver)
})
}
if r.nextMetrics != nil {
httpMetricsReceiver := metrics.New(r.nextMetrics, r.obsrepHTTP)
httpMux.HandleFunc(string(httpCfg.MetricsURLPath), func(resp http.ResponseWriter, req *http.Request) {
handleMetrics(resp, req, httpMetricsReceiver)
})
}
if r.nextLogs != nil {
httpLogsReceiver := logs.New(r.nextLogs, r.obsrepHTTP)
httpMux.HandleFunc(string(httpCfg.LogsURLPath), func(resp http.ResponseWriter, req *http.Request) {
handleLogs(resp, req, httpLogsReceiver)
})
}
if r.nextProfiles != nil {
httpProfilesReceiver := profiles.New(r.nextProfiles, r.obsrepHTTP)
httpMux.HandleFunc(defaultProfilesURLPath, func(resp http.ResponseWriter, req *http.Request) {
handleProfiles(resp, req, httpProfilesReceiver)
})
}
var err error
if r.serverHTTP, err = httpCfg.ServerConfig.ToServer(ctx, host.GetExtensions(), r.settings.TelemetrySettings, httpMux, confighttp.WithErrorHandler(errorHandler)); err != nil {
return err
}
var hln net.Listener
if hln, err = httpCfg.ServerConfig.ToListener(ctx); err != nil {
return err
}
r.settings.Logger.Info("Starting HTTP server", zap.String("endpoint", hln.Addr().String()))
r.shutdownWG.Go(func() {
if errHTTP := r.serverHTTP.Serve(hln); errHTTP != nil && !errors.Is(errHTTP, http.ErrServerClosed) {
componentstatus.ReportStatus(host, componentstatus.NewFatalErrorEvent(errHTTP))
}
})
return nil
}
// Start runs the trace receiver on the gRPC server. Currently
// it also enables the metrics receiver too.
func (r *otlpReceiver) Start(ctx context.Context, host component.Host) error {
if err := r.startGRPCServer(ctx, host); err != nil {
return err
}
if err := r.startHTTPServer(ctx, host); err != nil {
// It's possible that a valid GRPC server configuration was specified,
// but an invalid HTTP configuration. If that's the case, the successfully
// started GRPC server must be shutdown to ensure no goroutines are leaked.
return errors.Join(err, r.Shutdown(ctx))
}
return nil
}
// Shutdown is a method to turn off receiving.
func (r *otlpReceiver) Shutdown(ctx context.Context) error {
var err error
if r.serverHTTP != nil {
err = r.serverHTTP.Shutdown(ctx)
}
if r.serverGRPC != nil {
r.serverGRPC.GracefulStop()
}
r.shutdownWG.Wait()
return err
}
func (r *otlpReceiver) registerTraceConsumer(tc consumer.Traces) {
r.nextTraces = tc
}
func (r *otlpReceiver) registerMetricsConsumer(mc consumer.Metrics) {
r.nextMetrics = mc
}
func (r *otlpReceiver) registerLogsConsumer(lc consumer.Logs) {
r.nextLogs = lc
}
func (r *otlpReceiver) registerProfilesConsumer(tc xconsumer.Profiles) {
r.nextProfiles = tc
}
================================================
FILE: receiver/otlpreceiver/otlp_benchmark_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlpreceiver
import (
"bytes"
"context"
"io"
"net/http"
"testing"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/internal/testutil"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/plog/plogotlp"
"go.opentelemetry.io/collector/pdata/testdata"
"go.opentelemetry.io/collector/receiver/otlpreceiver/internal/metadata"
"go.opentelemetry.io/collector/receiver/receivertest"
)
const (
itemsPerRequest = 10_000
protobufContentType = "application/x-protobuf"
)
func startLogsReceiver(b *testing.B, cfg *Config, sink *consumertest.LogsSink) {
set := receivertest.NewNopSettings(metadata.Type)
factory := NewFactory()
r, err := factory.CreateLogs(b.Context(), set, cfg, sink)
require.NoError(b, err)
require.NoError(b, r.Start(b.Context(), componenttest.NewNopHost()))
b.Cleanup(func() {
require.NoError(b, r.Shutdown(context.Background()))
})
}
// BenchmarkGRPCLogsSequential benchmarks sequentially receiving logs over OTLP/gRPC.
// A typical deployment would receive multiple concurrent requests, this benchmark tries to
// measure the performance of the receiver without concurrency to have a more stable benchmark.
func BenchmarkGRPCLogsSequential(b *testing.B) {
endpoint := testutil.GetAvailableLocalAddress(b)
cfg := createDefaultConfig().(*Config)
cfg.GRPC.GetOrInsertDefault().NetAddr.Endpoint = endpoint
var sink consumertest.LogsSink
startLogsReceiver(b, cfg, &sink)
cc, err := grpc.NewClient(endpoint, grpc.WithTransportCredentials(insecure.NewCredentials()))
require.NoError(b, err)
b.Cleanup(func() { require.NoError(b, cc.Close()) })
logClient := plogotlp.NewGRPCClient(cc)
req := plogotlp.NewExportRequestFromLogs(testdata.GenerateLogs(itemsPerRequest))
for b.Loop() {
_, err := logClient.Export(b.Context(), req)
require.NoError(b, err)
}
require.Equal(b, b.N*itemsPerRequest, sink.LogRecordCount())
}
// BenchmarkHTTPProtoLogsSequential benchmarks sequentially receiving logs over OTLP/HTTP (proto).
// A typical deployment would receive multiple concurrent requests, this benchmark tries to
// measure the performance of the receiver without concurrency to have a more stable benchmark.
func BenchmarkHTTPProtoLogsSequential(b *testing.B) {
endpoint := testutil.GetAvailableLocalAddress(b)
cfg := createDefaultConfig().(*Config)
cfg.HTTP.GetOrInsertDefault().ServerConfig.NetAddr.Endpoint = endpoint
var sink consumertest.LogsSink
startLogsReceiver(b, cfg, &sink)
marshaler := &plog.ProtoMarshaler{}
bodyBytes, err := marshaler.MarshalLogs(testdata.GenerateLogs(itemsPerRequest))
require.NoError(b, err)
req, err := http.NewRequest(http.MethodPost, "http://"+endpoint+defaultLogsURLPath, bytes.NewReader(bodyBytes))
require.NoError(b, err)
req.Header.Set("Content-Type", protobufContentType)
reader := bytes.NewReader(bodyBytes)
for b.Loop() {
reader.Reset(bodyBytes)
req.Body = io.NopCloser(reader)
resp, err := http.DefaultClient.Do(req)
require.NoError(b, err)
require.Equal(b, http.StatusOK, resp.StatusCode)
resp.Body.Close()
}
require.Equal(b, b.N*itemsPerRequest, sink.LogRecordCount())
}
================================================
FILE: receiver/otlpreceiver/otlp_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlpreceiver
import (
"bytes"
"compress/gzip"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"strings"
"sync"
"testing"
"time"
"github.com/klauspost/compress/zstd"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
spb "google.golang.org/genproto/googleapis/rpc/status"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
"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/confignet"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/config/configtls"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/internal/testutil"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/pmetric/pmetricotlp"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp"
"go.opentelemetry.io/collector/pdata/testdata"
"go.opentelemetry.io/collector/receiver/otlpreceiver/internal/metadata"
"go.opentelemetry.io/collector/receiver/receiverhelper"
"go.opentelemetry.io/collector/receiver/receivertest"
)
const otlpReceiverName = "receiver_test"
var otlpReceiverID = component.MustNewIDWithName("otlp", otlpReceiverName)
func TestJSONHTTP(t *testing.T) {
tests := []struct {
name string
encoding string
contentType string
err error
expectedStatus *spb.Status
expectedStatusCode int
}{
{
name: "JSONUncompressed",
encoding: "",
contentType: "application/json",
},
{
name: "JSONUncompressedUTF8",
encoding: "",
contentType: "application/json; charset=utf-8",
},
{
name: "JSONUncompressedUppercase",
encoding: "",
contentType: "APPLICATION/JSON",
},
{
name: "JSONGzipCompressed",
encoding: "gzip",
contentType: "application/json",
},
{
name: "JSONZstdCompressed",
encoding: "zstd",
contentType: "application/json",
},
{
name: "Permanent NotGRPCError",
encoding: "",
contentType: "application/json",
err: consumererror.NewPermanent(errors.New("my error")),
expectedStatus: &spb.Status{Code: int32(codes.Internal), Message: "Permanent error: my error"},
expectedStatusCode: http.StatusInternalServerError,
},
{
name: "Retryable NotGRPCError",
encoding: "",
contentType: "application/json",
err: errors.New("my error"),
expectedStatus: &spb.Status{Code: int32(codes.Unavailable), Message: "my error"},
expectedStatusCode: http.StatusServiceUnavailable,
},
{
name: "Permanent GRPCError",
encoding: "",
contentType: "application/json",
err: status.New(codes.Internal, "").Err(),
expectedStatus: &spb.Status{Code: int32(codes.Internal), Message: ""},
expectedStatusCode: http.StatusInternalServerError,
},
{
name: "Retryable GRPCError",
encoding: "",
contentType: "application/json",
err: status.New(codes.Unavailable, "Service Unavailable").Err(),
expectedStatus: &spb.Status{Code: int32(codes.Unavailable), Message: "Service Unavailable"},
expectedStatusCode: http.StatusServiceUnavailable,
},
}
addr := testutil.GetAvailableLocalAddress(t)
sink := newErrOrSinkConsumer()
recv := newHTTPReceiver(t, componenttest.NewNopTelemetrySettings(), addr, sink)
require.NoError(t, recv.Start(context.Background(), componenttest.NewNopHost()), "Failed to start trace receiver")
t.Cleanup(func() { require.NoError(t, recv.Shutdown(context.Background())) })
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sink.Reset()
sink.SetConsumeError(tt.err)
for _, dr := range generateDataRequests(t) {
url := "http://" + addr + dr.path
respBytes := doHTTPRequest(t, url, tt.encoding, tt.contentType, dr.jsonBytes, tt.expectedStatusCode)
if tt.err == nil {
tr := ptraceotlp.NewExportResponse()
require.NoError(t, tr.UnmarshalJSON(respBytes), "Unable to unmarshal response to Response")
sink.checkData(t, dr.data, 1)
} else {
errStatus := &spb.Status{}
require.NoError(t, json.Unmarshal(respBytes, errStatus))
if s, ok := status.FromError(tt.err); ok {
assert.Equal(t, s.Proto().Code, errStatus.Code)
assert.Equal(t, s.Proto().Message, errStatus.Message)
} else {
fmt.Println(errStatus)
assert.True(t, proto.Equal(errStatus, tt.expectedStatus))
}
sink.checkData(t, dr.data, 0)
}
}
})
}
}
func TestHandleInvalidRequests(t *testing.T) {
addr := testutil.GetAvailableLocalAddress(t)
sink := newErrOrSinkConsumer()
recv := newHTTPReceiver(t, componenttest.NewNopTelemetrySettings(), addr, sink)
require.NoError(t, recv.Start(context.Background(), componenttest.NewNopHost()), "Failed to start trace receiver")
t.Cleanup(func() { require.NoError(t, recv.Shutdown(context.Background())) })
tests := []struct {
name string
uri string
method string
contentType string
expectedStatus int
expectedResponseBody string
}{
{
name: "no content type",
uri: defaultTracesURLPath,
method: http.MethodPost,
contentType: "",
expectedStatus: http.StatusUnsupportedMediaType,
expectedResponseBody: "415 unsupported media type, supported: [application/json, application/x-protobuf]",
},
{
name: "invalid content type",
uri: defaultTracesURLPath,
method: http.MethodPost,
contentType: "invalid",
expectedStatus: http.StatusUnsupportedMediaType,
expectedResponseBody: "415 unsupported media type, supported: [application/json, application/x-protobuf]",
},
{
name: "invalid request",
uri: defaultTracesURLPath,
method: http.MethodPost,
contentType: "application/json",
expectedStatus: http.StatusBadRequest,
},
{
uri: defaultTracesURLPath,
method: http.MethodPatch,
contentType: "application/json",
expectedStatus: http.StatusMethodNotAllowed,
expectedResponseBody: "405 method not allowed, supported: [POST]",
},
{
uri: defaultTracesURLPath,
method: http.MethodGet,
contentType: "application/json",
expectedStatus: http.StatusMethodNotAllowed,
expectedResponseBody: "405 method not allowed, supported: [POST]",
},
{
name: "no content type",
uri: defaultMetricsURLPath,
method: http.MethodPost,
contentType: "",
expectedStatus: http.StatusUnsupportedMediaType,
expectedResponseBody: "415 unsupported media type, supported: [application/json, application/x-protobuf]",
},
{
name: "invalid content type",
uri: defaultMetricsURLPath,
method: http.MethodPost,
contentType: "invalid",
expectedStatus: http.StatusUnsupportedMediaType,
expectedResponseBody: "415 unsupported media type, supported: [application/json, application/x-protobuf]",
},
{
name: "invalid request",
uri: defaultMetricsURLPath,
method: http.MethodPost,
contentType: "application/json",
expectedStatus: http.StatusBadRequest,
},
{
uri: defaultMetricsURLPath,
method: http.MethodPatch,
contentType: "application/json",
expectedStatus: http.StatusMethodNotAllowed,
expectedResponseBody: "405 method not allowed, supported: [POST]",
},
{
uri: defaultMetricsURLPath,
method: http.MethodGet,
contentType: "application/json",
expectedStatus: http.StatusMethodNotAllowed,
expectedResponseBody: "405 method not allowed, supported: [POST]",
},
{
name: "no content type",
uri: defaultLogsURLPath,
method: http.MethodPost,
contentType: "",
expectedStatus: http.StatusUnsupportedMediaType,
expectedResponseBody: "415 unsupported media type, supported: [application/json, application/x-protobuf]",
},
{
name: "invalid content type",
uri: defaultLogsURLPath,
method: http.MethodPost,
contentType: "invalid",
expectedStatus: http.StatusUnsupportedMediaType,
expectedResponseBody: "415 unsupported media type, supported: [application/json, application/x-protobuf]",
},
{
name: "invalid request",
uri: defaultLogsURLPath,
method: http.MethodPost,
contentType: "application/json",
expectedStatus: http.StatusBadRequest,
},
{
uri: defaultLogsURLPath,
method: http.MethodPatch,
contentType: "application/json",
expectedStatus: http.StatusMethodNotAllowed,
expectedResponseBody: "405 method not allowed, supported: [POST]",
},
{
uri: defaultLogsURLPath,
method: http.MethodGet,
contentType: "application/json",
expectedStatus: http.StatusMethodNotAllowed,
expectedResponseBody: "405 method not allowed, supported: [POST]",
},
}
for _, tt := range tests {
t.Run(tt.method+" "+tt.uri+" "+tt.name, func(t *testing.T) {
url := "http://" + addr + tt.uri
req, err := http.NewRequest(tt.method, url, bytes.NewReader([]byte(`1234`)))
require.NoError(t, err)
req.Header.Set("Content-Type", tt.contentType)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
if tt.name == "invalid request" {
assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
assert.Equal(t, tt.expectedStatus, resp.StatusCode)
return
}
assert.Equal(t, "text/plain", resp.Header.Get("Content-Type"))
assert.Equal(t, tt.expectedStatus, resp.StatusCode)
assert.Equal(t, tt.expectedResponseBody, string(body))
})
}
require.NoError(t, recv.Shutdown(context.Background()))
}
func TestProtoHTTP(t *testing.T) {
tests := []struct {
name string
encoding string
err error
expectedStatus *spb.Status
expectedStatusCode int
}{
{
name: "ProtoUncompressed",
encoding: "",
},
{
name: "ProtoGzipCompressed",
encoding: "gzip",
},
{
name: "ProtoZstdCompressed",
encoding: "zstd",
},
{
name: "Permanent NotGRPCError",
encoding: "",
err: consumererror.NewPermanent(errors.New("my error")),
expectedStatus: &spb.Status{Code: int32(codes.Internal), Message: "Permanent error: my error"},
expectedStatusCode: http.StatusInternalServerError,
},
{
name: "Retryable NotGRPCError",
encoding: "",
err: errors.New("my error"),
expectedStatus: &spb.Status{Code: int32(codes.Unavailable), Message: "my error"},
expectedStatusCode: http.StatusServiceUnavailable,
},
{
name: "Permanent GRPCError",
encoding: "",
err: status.New(codes.InvalidArgument, "Bad Request").Err(),
expectedStatus: &spb.Status{Code: int32(codes.InvalidArgument), Message: "Bad Request"},
expectedStatusCode: http.StatusBadRequest,
},
{
name: "Retryable GRPCError",
encoding: "",
err: status.New(codes.Unavailable, "Service Unavailable").Err(),
expectedStatus: &spb.Status{Code: int32(codes.Unavailable), Message: "Service Unavailable"},
expectedStatusCode: http.StatusServiceUnavailable,
},
}
addr := testutil.GetAvailableLocalAddress(t)
// Set the buffer count to 1 to make it flush the test span immediately.
sink := newErrOrSinkConsumer()
recv := newHTTPReceiver(t, componenttest.NewNopTelemetrySettings(), addr, sink)
require.NoError(t, recv.Start(context.Background(), componenttest.NewNopHost()), "Failed to start trace receiver")
t.Cleanup(func() { require.NoError(t, recv.Shutdown(context.Background())) })
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sink.Reset()
sink.SetConsumeError(tt.err)
for _, dr := range generateDataRequests(t) {
url := "http://" + addr + dr.path
respBytes := doHTTPRequest(t, url, tt.encoding, "application/x-protobuf", dr.protoBytes, tt.expectedStatusCode)
if tt.err == nil {
tr := ptraceotlp.NewExportResponse()
require.NoError(t, tr.UnmarshalProto(respBytes))
sink.checkData(t, dr.data, 1)
} else {
errStatus := &spb.Status{}
require.NoError(t, proto.Unmarshal(respBytes, errStatus))
if s, ok := status.FromError(tt.err); ok {
assert.True(t, proto.Equal(errStatus, s.Proto()))
} else {
assert.True(t, proto.Equal(errStatus, tt.expectedStatus))
}
sink.checkData(t, dr.data, 0)
}
}
})
}
}
func TestOTLPReceiverInvalidContentEncoding(t *testing.T) {
tests := []struct {
name string
content string
encoding string
reqBodyFunc func() (*bytes.Buffer, error)
checkBody func(tb testing.TB, got []byte)
status int
}{
{
name: "JsonGzipUncompressed",
content: "application/json",
encoding: "gzip",
reqBodyFunc: func() (*bytes.Buffer, error) {
return bytes.NewBuffer([]byte(`{"key": "value"}`)), nil
},
checkBody: func(tb testing.TB, got []byte) {
assert.JSONEq(tb, `{"code":3,"message": "gzip: invalid header"}`, string(got))
},
status: 400,
},
{
name: "ProtoGzipUncompressed",
content: "application/x-protobuf",
encoding: "gzip",
reqBodyFunc: func() (*bytes.Buffer, error) {
return bytes.NewBuffer([]byte(`{"key": "value"}`)), nil
},
checkBody: func(tb testing.TB, got []byte) {
expected, err := proto.Marshal(status.New(codes.InvalidArgument, "gzip: invalid header").Proto())
require.NoError(tb, err)
assert.Equal(tb, expected, got)
},
status: 400,
},
{
name: "ProtoZstdUncompressed",
content: "application/x-protobuf",
encoding: "zstd",
reqBodyFunc: func() (*bytes.Buffer, error) {
return bytes.NewBuffer([]byte(`{"key": "value"}`)), nil
},
checkBody: func(tb testing.TB, got []byte) {
expected, err := proto.Marshal(status.New(codes.InvalidArgument, "invalid input: magic number mismatch").Proto())
require.NoError(tb, err)
assert.Equal(tb, expected, got)
},
status: 400,
},
}
addr := testutil.GetAvailableLocalAddress(t)
// Set the buffer count to 1 to make it flush the test span immediately.
recv := newHTTPReceiver(t, componenttest.NewNopTelemetrySettings(), addr, consumertest.NewNop())
require.NoError(t, recv.Start(context.Background(), componenttest.NewNopHost()), "Failed to start trace receiver")
t.Cleanup(func() { require.NoError(t, recv.Shutdown(context.Background())) })
url := fmt.Sprintf("http://%s%s", addr, defaultTracesURLPath)
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
body, err := test.reqBodyFunc()
require.NoError(t, err)
req, err := http.NewRequest(http.MethodPost, url, body)
require.NoError(t, err)
req.Header.Set("Content-Type", test.content)
req.Header.Set("Content-Encoding", test.encoding)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
assert.Equal(t, test.status, resp.StatusCode, "Unexpected return status")
assert.Equal(t, test.content, resp.Header.Get("Content-Type"), "Unexpected response Content-Type")
respBytes, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
test.checkBody(t, respBytes)
})
}
}
func TestOTLPReceiverNoContentType(t *testing.T) {
addr := testutil.GetAvailableLocalAddress(t)
// Set the buffer count to 1 to make it flush the test span immediately.
recv := newHTTPReceiver(t, componenttest.NewNopTelemetrySettings(), addr, consumertest.NewNop())
require.NoError(t, recv.Start(context.Background(), componenttest.NewNopHost()), "Failed to start trace receiver")
t.Cleanup(func() { require.NoError(t, recv.Shutdown(context.Background())) })
url := fmt.Sprintf("http://%s%s", addr, defaultTracesURLPath)
t.Run("NoContentType", func(t *testing.T) {
body := bytes.NewBuffer([]byte(`{"key": "value"}`))
req, err := http.NewRequest(http.MethodPost, url, body)
require.NoError(t, err, "Error creating trace POST request: %v", err)
// Set invalid encoding to trigger an error
req.Header.Set("Content-Encoding", "invalid")
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err, "Error posting to server: %v", err)
// Don't care about the response body, just check the content type
defer resp.Body.Close()
require.Equal(t, fallbackContentType, resp.Header.Get("Content-Type"), "Unexpected response Content-Type")
})
}
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)
t.Cleanup(func() {
assert.NoError(t, ln.Close())
})
r := newGRPCReceiver(t, componenttest.NewNopTelemetrySettings(), addr, consumertest.NewNop())
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)
t.Cleanup(func() {
assert.NoError(t, ln.Close())
})
r := newHTTPReceiver(t, componenttest.NewNopTelemetrySettings(), addr, consumertest.NewNop())
require.NotNil(t, r)
require.Error(t, r.Start(context.Background(), componenttest.NewNopHost()))
}
// TestOTLPReceiverGRPCMetricsIngestTest checks that the metrics receiver
// is returning the proper response (return and metrics) when the next consumer
// in the pipeline reports error.
func TestOTLPReceiverGRPCMetricsIngestTest(t *testing.T) {
// Get a new available port
addr := testutil.GetAvailableLocalAddress(t)
// Create a sink
sink := &errOrSinkConsumer{MetricsSink: new(consumertest.MetricsSink)}
// Create a telemetry instance
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
// Create telemetry settings
settings := tt.NewTelemetrySettings()
recv := newGRPCReceiver(t, settings, addr, sink)
require.NotNil(t, recv)
require.NoError(t, recv.Start(context.Background(), componenttest.NewNopHost()))
t.Cleanup(func() { require.NoError(t, recv.Shutdown(context.Background())) })
cc, err := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
require.NoError(t, err)
defer func() {
assert.NoError(t, cc.Close())
}()
// Set up the error case
sink.SetConsumeError(errors.New("consumer error"))
md := testdata.GenerateMetrics(1)
_, err = pmetricotlp.NewGRPCClient(cc).Export(context.Background(), pmetricotlp.NewExportRequestFromMetrics(md))
errStatus, ok := status.FromError(err)
require.True(t, ok)
assert.Equal(t, codes.Unavailable, errStatus.Code())
// Assert receiver metrics including receiver_requests
assertReceiverMetrics(t, tt, otlpReceiverID, "grpc", 0, 2)
}
// TestOTLPReceiverGRPCTracesIngestTest checks that the gRPC 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 TestOTLPReceiverGRPCTracesIngestTest(t *testing.T) {
type ingestionStateTest struct {
okToIngest bool
permanent bool
expectedCode codes.Code
}
expectedReceivedBatches := 2
expectedIngestionBlockedRPCs := 2
ingestionStates := []ingestionStateTest{
{
okToIngest: true,
expectedCode: codes.OK,
},
{
okToIngest: false,
expectedCode: codes.Unavailable,
},
{
okToIngest: false,
expectedCode: codes.Internal,
permanent: true,
},
{
okToIngest: true,
expectedCode: codes.OK,
},
}
addr := testutil.GetAvailableLocalAddress(t)
td := testdata.GenerateTraces(1)
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
sink := &errOrSinkConsumer{TracesSink: new(consumertest.TracesSink)}
recv := newGRPCReceiver(t, tt.NewTelemetrySettings(), addr, sink)
require.NotNil(t, recv)
require.NoError(t, recv.Start(context.Background(), componenttest.NewNopHost()))
t.Cleanup(func() { require.NoError(t, recv.Shutdown(context.Background())) })
cc, err := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
require.NoError(t, err)
defer func() {
assert.NoError(t, cc.Close())
}()
for _, ingestionState := range ingestionStates {
if ingestionState.okToIngest {
sink.SetConsumeError(nil)
} else {
if ingestionState.permanent {
sink.SetConsumeError(consumererror.NewPermanent(errors.New("consumer error")))
} else {
sink.SetConsumeError(errors.New("consumer error"))
}
}
_, err = ptraceotlp.NewGRPCClient(cc).Export(context.Background(), ptraceotlp.NewExportRequestFromTraces(td))
errStatus, ok := status.FromError(err)
require.True(t, ok)
assert.Equal(t, ingestionState.expectedCode, errStatus.Code())
}
require.Len(t, sink.AllTraces(), expectedReceivedBatches)
assertReceiverTraces(t, tt, otlpReceiverID, "grpc", int64(expectedReceivedBatches), int64(expectedIngestionBlockedRPCs))
}
// TestOTLPReceiverHTTPTracesIngestTest checks that the HTTP 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 TestOTLPReceiverHTTPTracesIngestTest(t *testing.T) {
type ingestionStateTest struct {
okToIngest bool
err error
expectedCode codes.Code
expectedStatusCode int
}
expectedReceivedBatches := 2
expectedIngestionBlockedRPCs := 2
ingestionStates := []ingestionStateTest{
{
okToIngest: true,
expectedCode: codes.OK,
},
{
okToIngest: false,
err: consumererror.NewPermanent(errors.New("consumer error")),
expectedCode: codes.Internal,
expectedStatusCode: http.StatusInternalServerError,
},
{
okToIngest: false,
err: errors.New("consumer error"),
expectedCode: codes.Unavailable,
expectedStatusCode: http.StatusServiceUnavailable,
},
{
okToIngest: true,
expectedCode: codes.OK,
},
}
addr := testutil.GetAvailableLocalAddress(t)
td := testdata.GenerateTraces(1)
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
sink := &errOrSinkConsumer{TracesSink: new(consumertest.TracesSink)}
recv := newHTTPReceiver(t, tt.NewTelemetrySettings(), addr, sink)
require.NotNil(t, recv)
require.NoError(t, recv.Start(context.Background(), componenttest.NewNopHost()))
t.Cleanup(func() { require.NoError(t, recv.Shutdown(context.Background())) })
for _, ingestionState := range ingestionStates {
if ingestionState.okToIngest {
sink.SetConsumeError(nil)
} else {
sink.SetConsumeError(ingestionState.err)
}
pbMarshaler := ptrace.ProtoMarshaler{}
pbBytes, err := pbMarshaler.MarshalTraces(td)
require.NoError(t, err)
req, err := http.NewRequest(http.MethodPost, "http://"+addr+defaultTracesURLPath, bytes.NewReader(pbBytes))
require.NoError(t, err)
req.Header.Set("Content-Type", pbContentType)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
respBytes, err := io.ReadAll(resp.Body)
require.NoError(t, err)
if ingestionState.expectedCode == codes.OK {
require.Equal(t, 200, resp.StatusCode)
tr := ptraceotlp.NewExportResponse()
require.NoError(t, tr.UnmarshalProto(respBytes))
} else {
errStatus := &spb.Status{}
require.NoError(t, proto.Unmarshal(respBytes, errStatus))
assert.Equal(t, ingestionState.expectedStatusCode, resp.StatusCode)
assert.EqualValues(t, ingestionState.expectedCode, errStatus.Code)
}
}
require.Len(t, sink.AllTraces(), expectedReceivedBatches)
assertReceiverTraces(t, tt, otlpReceiverID, "http", int64(expectedReceivedBatches), int64(expectedIngestionBlockedRPCs))
}
func TestGRPCInvalidTLSCredentials(t *testing.T) {
cfg := &Config{
Protocols: Protocols{
GRPC: configoptional.Some(configgrpc.ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: testutil.GetAvailableLocalAddress(t),
Transport: confignet.TransportTypeTCP,
},
TLS: configoptional.Some(configtls.ServerConfig{
Config: configtls.Config{
CertFile: "willfail",
},
}),
}),
},
}
r, err := NewFactory().CreateTraces(
context.Background(),
receivertest.NewNopSettings(metadata.Type),
cfg,
consumertest.NewNop())
require.NoError(t, err)
assert.NotNil(t, r)
assert.EqualError(t,
r.Start(context.Background(), componenttest.NewNopHost()),
`failed to load TLS config: failed to load TLS cert and key: for auth via TLS, provide both certificate and key, or neither`)
}
func TestGRPCMaxRecvSize(t *testing.T) {
addr := testutil.GetAvailableLocalAddress(t)
sink := newErrOrSinkConsumer()
cfg := createDefaultConfig().(*Config)
cfg.GRPC.GetOrInsertDefault().NetAddr.Endpoint = addr
recv := newReceiver(t, componenttest.NewNopTelemetrySettings(), cfg, otlpReceiverID, sink)
require.NoError(t, recv.Start(context.Background(), componenttest.NewNopHost()))
cc, err := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
require.NoError(t, err)
td := testdata.GenerateTraces(50000)
err = exportTraces(cc, td)
require.Error(t, err)
assert.NoError(t, cc.Close())
require.NoError(t, recv.Shutdown(context.Background()))
cfg.GRPC.Get().MaxRecvMsgSizeMiB = 100
recv = newReceiver(t, componenttest.NewNopTelemetrySettings(), cfg, otlpReceiverID, sink)
require.NoError(t, recv.Start(context.Background(), componenttest.NewNopHost()))
t.Cleanup(func() { require.NoError(t, recv.Shutdown(context.Background())) })
cc, err = grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
require.NoError(t, err)
defer func() {
assert.NoError(t, cc.Close())
}()
td = testdata.GenerateTraces(50000)
require.NoError(t, exportTraces(cc, td))
require.Len(t, sink.AllTraces(), 1)
assert.Equal(t, td, sink.AllTraces()[0])
}
func TestHTTPInvalidTLSCredentials(t *testing.T) {
cfg := &Config{
Protocols: Protocols{
HTTP: configoptional.Some(HTTPConfig{
ServerConfig: confighttp.ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: testutil.GetAvailableLocalAddress(t),
Transport: confignet.TransportTypeTCP,
},
TLS: configoptional.Some(configtls.ServerConfig{
Config: configtls.Config{
CertFile: "willfail",
},
}),
},
TracesURLPath: defaultTracesURLPath,
MetricsURLPath: defaultMetricsURLPath,
LogsURLPath: defaultLogsURLPath,
}),
},
}
// TLS is resolved during Start for HTTP.
r, err := NewFactory().CreateTraces(
context.Background(),
receivertest.NewNopSettings(metadata.Type),
cfg,
consumertest.NewNop())
require.NoError(t, err)
assert.NotNil(t, r)
assert.EqualError(t, r.Start(context.Background(), componenttest.NewNopHost()),
`failed to load TLS config: failed to load TLS cert and key: for auth via TLS, provide both certificate and key, or neither`)
}
func testHTTPMaxRequestBodySize(t *testing.T, path, contentType string, payload []byte, size, expectedStatusCode int) {
addr := testutil.GetAvailableLocalAddress(t)
url := "http://" + addr + path
cfg := &Config{
Protocols: Protocols{
HTTP: configoptional.Some(HTTPConfig{
ServerConfig: confighttp.ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: addr,
Transport: confignet.TransportTypeTCP,
},
MaxRequestBodySize: int64(size),
},
TracesURLPath: defaultTracesURLPath,
MetricsURLPath: defaultMetricsURLPath,
LogsURLPath: defaultLogsURLPath,
}),
},
}
recv := newReceiver(t, componenttest.NewNopTelemetrySettings(), cfg, otlpReceiverID, consumertest.NewNop())
require.NoError(t, recv.Start(context.Background(), componenttest.NewNopHost()))
req := createHTTPRequest(t, url, "", contentType, payload)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
_, err = io.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, expectedStatusCode, resp.StatusCode)
require.NoError(t, recv.Shutdown(context.Background()))
}
func TestHTTPMaxRequestBodySize(t *testing.T) {
dataReqs := generateDataRequests(t)
for _, dr := range dataReqs {
testHTTPMaxRequestBodySize(t, dr.path, "application/json", dr.jsonBytes, len(dr.jsonBytes), 200)
testHTTPMaxRequestBodySize(t, dr.path, "application/json", dr.jsonBytes, len(dr.jsonBytes)-1, 400)
testHTTPMaxRequestBodySize(t, dr.path, "application/x-protobuf", dr.protoBytes, len(dr.protoBytes), 200)
testHTTPMaxRequestBodySize(t, dr.path, "application/x-protobuf", dr.protoBytes, len(dr.protoBytes)-1, 400)
}
}
func newGRPCReceiver(t *testing.T, settings component.TelemetrySettings, endpoint string, c consumertest.Consumer) component.Component {
cfg := createDefaultConfig().(*Config)
cfg.GRPC.GetOrInsertDefault().NetAddr.Endpoint = endpoint
return newReceiver(t, settings, cfg, otlpReceiverID, c)
}
func newHTTPReceiver(t *testing.T, settings component.TelemetrySettings, endpoint string, c consumertest.Consumer) component.Component {
cfg := createDefaultConfig().(*Config)
cfg.HTTP.GetOrInsertDefault().ServerConfig.NetAddr.Endpoint = endpoint
return newReceiver(t, settings, cfg, otlpReceiverID, c)
}
func newReceiver(t *testing.T, settings component.TelemetrySettings, cfg *Config, id component.ID, c consumertest.Consumer) component.Component {
set := receivertest.NewNopSettings(metadata.Type)
set.TelemetrySettings = settings
set.ID = id
r, err := newOtlpReceiver(cfg, &set)
require.NoError(t, err)
r.registerTraceConsumer(c)
r.registerMetricsConsumer(c)
r.registerLogsConsumer(c)
r.registerProfilesConsumer(c)
return r
}
type dataRequest struct {
data any
path string
jsonBytes []byte
protoBytes []byte
}
func generateDataRequests(t *testing.T) []dataRequest {
return []dataRequest{generateTracesRequest(t), generateMetricsRequests(t), generateLogsRequest(t), generateProfilesRequest(t)}
}
func generateTracesRequest(t *testing.T) dataRequest {
protoMarshaler := &ptrace.ProtoMarshaler{}
jsonMarshaler := &ptrace.JSONMarshaler{}
td := testdata.GenerateTraces(2)
traceProto, err := protoMarshaler.MarshalTraces(td)
require.NoError(t, err)
traceJSON, err := jsonMarshaler.MarshalTraces(td)
require.NoError(t, err)
return dataRequest{data: td, path: defaultTracesURLPath, jsonBytes: traceJSON, protoBytes: traceProto}
}
func generateMetricsRequests(t *testing.T) dataRequest {
protoMarshaler := &pmetric.ProtoMarshaler{}
jsonMarshaler := &pmetric.JSONMarshaler{}
md := testdata.GenerateMetrics(2)
metricProto, err := protoMarshaler.MarshalMetrics(md)
require.NoError(t, err)
metricJSON, err := jsonMarshaler.MarshalMetrics(md)
require.NoError(t, err)
return dataRequest{data: md, path: defaultMetricsURLPath, jsonBytes: metricJSON, protoBytes: metricProto}
}
func generateLogsRequest(t *testing.T) dataRequest {
protoMarshaler := &plog.ProtoMarshaler{}
jsonMarshaler := &plog.JSONMarshaler{}
ld := testdata.GenerateLogs(2)
logProto, err := protoMarshaler.MarshalLogs(ld)
require.NoError(t, err)
logJSON, err := jsonMarshaler.MarshalLogs(ld)
require.NoError(t, err)
return dataRequest{data: ld, path: defaultLogsURLPath, jsonBytes: logJSON, protoBytes: logProto}
}
func generateProfilesRequest(t *testing.T) dataRequest {
protoMarshaler := &pprofile.ProtoMarshaler{}
jsonMarshaler := &pprofile.JSONMarshaler{}
md := testdata.GenerateProfiles(2)
profileProto, err := protoMarshaler.MarshalProfiles(md)
require.NoError(t, err)
profileJSON, err := jsonMarshaler.MarshalProfiles(md)
require.NoError(t, err)
return dataRequest{data: md, path: defaultProfilesURLPath, jsonBytes: profileJSON, protoBytes: profileProto}
}
func doHTTPRequest(
t *testing.T,
url string,
encoding string,
contentType string,
data []byte,
expectStatusCode int,
) []byte {
req := createHTTPRequest(t, url, encoding, contentType, data)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
respBytes, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
// For cases like "application/json; charset=utf-8", the response will be only "application/json"
require.True(t, strings.HasPrefix(strings.ToLower(contentType), resp.Header.Get("Content-Type")))
if expectStatusCode == 0 {
require.Equal(t, http.StatusOK, resp.StatusCode)
} else {
require.Equal(t, expectStatusCode, resp.StatusCode)
}
return respBytes
}
func createHTTPRequest(
t *testing.T,
url string,
encoding string,
contentType string,
data []byte,
) *http.Request {
var buf *bytes.Buffer
switch encoding {
case "gzip":
buf = compressGzip(t, data)
case "zstd":
buf = compressZstd(t, data)
case "":
buf = bytes.NewBuffer(data)
default:
t.Fatalf("Unsupported compression type %v", encoding)
}
req, err := http.NewRequest(http.MethodPost, url, buf)
require.NoError(t, err)
req.Header.Set("Content-Type", contentType)
req.Header.Set("Content-Encoding", encoding)
return req
}
func compressGzip(t *testing.T, body []byte) *bytes.Buffer {
var buf bytes.Buffer
gw := gzip.NewWriter(&buf)
defer func() {
require.NoError(t, gw.Close())
}()
_, err := gw.Write(body)
require.NoError(t, err)
return &buf
}
func compressZstd(t *testing.T, body []byte) *bytes.Buffer {
var buf bytes.Buffer
zw, err := zstd.NewWriter(&buf)
require.NoError(t, err)
defer func() {
require.NoError(t, zw.Close())
}()
_, err = zw.Write(body)
require.NoError(t, err)
return &buf
}
type senderFunc func(td ptrace.Traces)
func TestShutdown(t *testing.T) {
endpointGrpc := testutil.GetAvailableLocalAddress(t)
endpointHTTP := testutil.GetAvailableLocalAddress(t)
nextSink := new(consumertest.TracesSink)
// Create OTLP receiver with gRPC and HTTP protocols.
factory := NewFactory()
cfg := factory.CreateDefaultConfig().(*Config)
cfg.GRPC.GetOrInsertDefault().NetAddr.Endpoint = endpointGrpc
cfg.HTTP.GetOrInsertDefault().ServerConfig.NetAddr.Endpoint = endpointHTTP
set := receivertest.NewNopSettings(metadata.Type)
set.ID = otlpReceiverID
r, err := NewFactory().CreateTraces(
context.Background(),
set,
cfg,
nextSink)
require.NoError(t, err)
require.NotNil(t, r)
require.NoError(t, r.Start(context.Background(), componenttest.NewNopHost()))
conn, err := grpc.NewClient(endpointGrpc, grpc.WithTransportCredentials(insecure.NewCredentials()))
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, conn.Close())
})
doneSignalGrpc := make(chan bool)
doneSignalHTTP := make(chan bool)
senderGrpc := func(td ptrace.Traces) {
// Ignore error, may be executed after the receiver shutdown.
_ = exportTraces(conn, td)
}
senderHTTP := func(td ptrace.Traces) {
// Send request via OTLP/HTTP.
marshaler := &ptrace.ProtoMarshaler{}
traceBytes, err2 := marshaler.MarshalTraces(td)
require.NoError(t, err2)
url := "http://" + endpointHTTP + defaultTracesURLPath
req := createHTTPRequest(t, url, "", "application/x-protobuf", traceBytes)
if resp, errResp := http.DefaultClient.Do(req); errResp == nil {
require.NoError(t, resp.Body.Close())
}
}
// Send traces to the receiver until we signal via done channel, and then
// send one more trace after that.
go generateTraces(senderGrpc, doneSignalGrpc)
go generateTraces(senderHTTP, doneSignalHTTP)
// Wait until the receiver outputs anything to the sink.
assert.Eventually(t, func() bool {
return nextSink.SpanCount() > 0
}, time.Second, 10*time.Millisecond)
// Now shutdown the receiver, while continuing sending traces to it.
ctx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second)
defer cancelFn()
require.NoError(t, r.Shutdown(ctx))
// Remember how many spans the sink received. This number should not change after this
// point because after Shutdown() returns the component is not allowed to produce
// any more data.
sinkSpanCountAfterShutdown := nextSink.SpanCount()
// Now signal to generateTraces to exit the main generation loop, then send
// one more trace and stop.
doneSignalGrpc <- true
doneSignalHTTP <- true
// Wait until all follow up traces are sent.
<-doneSignalGrpc
<-doneSignalHTTP
// The last, additional trace should not be received by sink, so the number of spans in
// the sink should not change.
assert.Equal(t, sinkSpanCountAfterShutdown, nextSink.SpanCount())
}
func generateTraces(senderFn senderFunc, doneSignal chan bool) {
// Continuously generate spans until signaled to stop.
loop:
for {
select {
case <-doneSignal:
break loop
default:
}
senderFn(testdata.GenerateTraces(1))
}
// After getting the signal to stop, send one more span and then
// finally stop. We should never receive this last span.
senderFn(testdata.GenerateTraces(1))
// Indicate that we are done.
close(doneSignal)
}
func exportTraces(cc *grpc.ClientConn, td ptrace.Traces) error {
acc := ptraceotlp.NewGRPCClient(cc)
req := ptraceotlp.NewExportRequestFromTraces(td)
_, err := acc.Export(context.Background(), req)
return err
}
type errOrSinkConsumer struct {
consumertest.Consumer
*consumertest.TracesSink
*consumertest.MetricsSink
*consumertest.LogsSink
*consumertest.ProfilesSink
mu sync.Mutex
consumeError error // to be returned by ConsumeTraces, if set
}
func newErrOrSinkConsumer() *errOrSinkConsumer {
return &errOrSinkConsumer{
TracesSink: new(consumertest.TracesSink),
MetricsSink: new(consumertest.MetricsSink),
LogsSink: new(consumertest.LogsSink),
ProfilesSink: new(consumertest.ProfilesSink),
}
}
// SetConsumeError sets an error that will be returned by the Consume function.
func (esc *errOrSinkConsumer) SetConsumeError(err error) {
esc.mu.Lock()
defer esc.mu.Unlock()
esc.consumeError = err
}
func (esc *errOrSinkConsumer) Capabilities() consumer.Capabilities {
return consumer.Capabilities{MutatesData: false}
}
// ConsumeTraces stores traces to this sink.
func (esc *errOrSinkConsumer) ConsumeTraces(ctx context.Context, td ptrace.Traces) error {
esc.mu.Lock()
defer esc.mu.Unlock()
if esc.consumeError != nil {
return esc.consumeError
}
return esc.TracesSink.ConsumeTraces(ctx, td)
}
// ConsumeMetrics stores metrics to this sink.
func (esc *errOrSinkConsumer) ConsumeMetrics(ctx context.Context, md pmetric.Metrics) error {
esc.mu.Lock()
defer esc.mu.Unlock()
if esc.consumeError != nil {
return esc.consumeError
}
return esc.MetricsSink.ConsumeMetrics(ctx, md)
}
// ConsumeLogs stores metrics to this sink.
func (esc *errOrSinkConsumer) ConsumeLogs(ctx context.Context, ld plog.Logs) error {
esc.mu.Lock()
defer esc.mu.Unlock()
if esc.consumeError != nil {
return esc.consumeError
}
return esc.LogsSink.ConsumeLogs(ctx, ld)
}
// ConsumeProfiles stores profiles to this sink.
func (esc *errOrSinkConsumer) ConsumeProfiles(ctx context.Context, md pprofile.Profiles) error {
esc.mu.Lock()
defer esc.mu.Unlock()
if esc.consumeError != nil {
return esc.consumeError
}
return esc.ProfilesSink.ConsumeProfiles(ctx, md)
}
// Reset deletes any stored in the sinks, resets error to nil.
func (esc *errOrSinkConsumer) Reset() {
esc.mu.Lock()
defer esc.mu.Unlock()
esc.consumeError = nil
esc.TracesSink.Reset()
esc.MetricsSink.Reset()
esc.LogsSink.Reset()
esc.ProfilesSink.Reset()
}
// Reset deletes any stored in the sinks, resets error to nil.
func (esc *errOrSinkConsumer) checkData(t *testing.T, data any, dataLen int) {
switch data.(type) {
case ptrace.Traces:
allTraces := esc.AllTraces()
require.Len(t, allTraces, dataLen)
if dataLen > 0 {
require.Equal(t, allTraces[0], data)
}
case pmetric.Metrics:
allMetrics := esc.AllMetrics()
require.Len(t, allMetrics, dataLen)
if dataLen > 0 {
require.Equal(t, allMetrics[0], data)
}
case plog.Logs:
allLogs := esc.AllLogs()
require.Len(t, allLogs, dataLen)
if dataLen > 0 {
require.Equal(t, allLogs[0], data)
}
case pprofile.Profiles:
allProfiles := esc.AllProfiles()
require.Len(t, allProfiles, dataLen)
if dataLen > 0 {
require.Equal(t, allProfiles[0], data)
}
}
}
func assertReceiverTraces(t *testing.T, tt *componenttest.Telemetry, id component.ID, transport string, accepted, rejected int64) {
var refused, failed int64
var outcome string
gateEnabled := receiverhelper.NewReceiverMetricsGate.IsEnabled()
// The errors in the OTLP tests are not downstream, so they should be "failed" when the gate is enabled.
if gateEnabled {
failed = rejected
outcome = "failure"
} else {
// When the gate is disabled, all errors are "refused".
refused = rejected
}
got, err := tt.GetMetric("otelcol_receiver_failed_spans")
require.NoError(t, err)
metricdatatest.AssertEqual(t,
metricdata.Metrics{
Name: "otelcol_receiver_failed_spans",
Description: "The number of spans that failed to be processed by the receiver due to internal errors. [Alpha]",
Unit: "{span}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: []metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String("receiver", id.String()),
attribute.String("transport", transport)),
Value: failed,
},
},
},
}, got, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
got, err = tt.GetMetric("otelcol_receiver_accepted_spans")
require.NoError(t, err)
metricdatatest.AssertEqual(t,
metricdata.Metrics{
Name: "otelcol_receiver_accepted_spans",
Description: "Number of spans successfully pushed into the pipeline. [Alpha]",
Unit: "{span}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: []metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String("receiver", id.String()),
attribute.String("transport", transport)),
Value: accepted,
},
},
},
}, got, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
got, err = tt.GetMetric("otelcol_receiver_refused_spans")
require.NoError(t, err)
metricdatatest.AssertEqual(t,
metricdata.Metrics{
Name: "otelcol_receiver_refused_spans",
Description: "Number of spans that could not be pushed into the pipeline. [Alpha]",
Unit: "{span}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: []metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String("receiver", id.String()),
attribute.String("transport", transport)),
Value: refused,
},
},
},
}, got, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
// Assert receiver_requests metric
if gateEnabled {
got, err := tt.GetMetric("otelcol_receiver_requests")
require.NoError(t, err)
// Calculate expected requests based on accepted and refused counts
var expectedRequests []metricdata.DataPoint[int64]
if accepted > 0 {
expectedRequests = append(expectedRequests, metricdata.DataPoint[int64]{
Attributes: attribute.NewSet(
attribute.String("receiver", id.String()),
attribute.String("transport", transport),
attribute.String("outcome", "success")),
Value: accepted,
})
}
if rejected > 0 {
expectedRequests = append(expectedRequests, metricdata.DataPoint[int64]{
Attributes: attribute.NewSet(
attribute.String("receiver", id.String()),
attribute.String("transport", transport),
attribute.String("outcome", outcome)),
Value: rejected,
})
}
metricdatatest.AssertEqual(t,
metricdata.Metrics{
Name: "otelcol_receiver_requests",
Description: "The number of requests performed.",
Unit: "{request}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: expectedRequests,
},
}, got, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
} else {
_, err := tt.GetMetric("otelcol_receiver_requests")
require.Error(t, err)
}
}
func assertReceiverMetrics(t *testing.T, tt *componenttest.Telemetry, id component.ID, transport string, accepted, rejected int64) {
var refused, failed int64
var outcome string
gateEnabled := receiverhelper.NewReceiverMetricsGate.IsEnabled()
// The error used in the metrics test is not downstream.
if gateEnabled {
failed = rejected
outcome = "failure"
} else {
// When the gate is disabled, all errors are "refused".
refused = rejected
}
got, err := tt.GetMetric("otelcol_receiver_failed_metric_points")
require.NoError(t, err)
metricdatatest.AssertEqual(t,
metricdata.Metrics{
Name: "otelcol_receiver_failed_metric_points",
Description: "The number of metric points that failed to be processed by the receiver due to internal errors. [Alpha]",
Unit: "{datapoint}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: []metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String("receiver", id.String()),
attribute.String("transport", transport)),
Value: failed,
},
},
},
}, got, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
got, err = tt.GetMetric("otelcol_receiver_accepted_metric_points")
require.NoError(t, err)
metricdatatest.AssertEqual(t,
metricdata.Metrics{
Name: "otelcol_receiver_accepted_metric_points",
Description: "Number of metric points successfully pushed into the pipeline. [Alpha]",
Unit: "{datapoint}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: []metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String("receiver", id.String()),
attribute.String("transport", transport)),
Value: accepted,
},
},
},
}, got, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
got, err = tt.GetMetric("otelcol_receiver_refused_metric_points")
require.NoError(t, err)
metricdatatest.AssertEqual(t,
metricdata.Metrics{
Name: "otelcol_receiver_refused_metric_points",
Description: "Number of metric points that could not be pushed into the pipeline. [Alpha]",
Unit: "{datapoint}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: []metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String("receiver", id.String()),
attribute.String("transport", transport)),
Value: refused,
},
},
},
}, got, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
// Assert receiver_requests metric
if gateEnabled {
got, err := tt.GetMetric("otelcol_receiver_requests")
require.NoError(t, err)
// Calculate expected requests based on accepted and refused counts
var expectedRequests []metricdata.DataPoint[int64]
if accepted > 0 {
expectedRequests = append(expectedRequests, metricdata.DataPoint[int64]{
Attributes: attribute.NewSet(
attribute.String("receiver", id.String()),
attribute.String("transport", transport),
attribute.String("outcome", "success")),
Value: accepted,
})
}
if rejected > 0 {
expectedRequests = append(expectedRequests, metricdata.DataPoint[int64]{
Attributes: attribute.NewSet(
attribute.String("receiver", id.String()),
attribute.String("transport", transport),
attribute.String("outcome", outcome)),
Value: 1, // One request failed
})
}
metricdatatest.AssertEqual(t,
metricdata.Metrics{
Name: "otelcol_receiver_requests",
Description: "The number of requests performed.",
Unit: "{request}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: expectedRequests,
},
}, got, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
} else {
_, err := tt.GetMetric("otelcol_receiver_requests")
require.Error(t, err)
}
}
================================================
FILE: receiver/otlpreceiver/otlphttp.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlpreceiver // import "go.opentelemetry.io/collector/receiver/otlpreceiver"
import (
"fmt"
"io"
"mime"
"net/http"
"strconv"
"time"
"google.golang.org/grpc/status"
"go.opentelemetry.io/collector/internal/statusutil"
"go.opentelemetry.io/collector/receiver/otlpreceiver/internal/errors"
"go.opentelemetry.io/collector/receiver/otlpreceiver/internal/logs"
"go.opentelemetry.io/collector/receiver/otlpreceiver/internal/metrics"
"go.opentelemetry.io/collector/receiver/otlpreceiver/internal/profiles"
"go.opentelemetry.io/collector/receiver/otlpreceiver/internal/trace"
)
// Pre-computed status with code=Internal to be used in case of a marshaling error.
var fallbackMsg = []byte(`{"code": 13, "message": "failed to marshal error message"}`)
const fallbackContentType = "application/json"
func handleTraces(resp http.ResponseWriter, req *http.Request, tracesReceiver *trace.Receiver) {
enc, ok := readContentType(resp, req)
if !ok {
return
}
body, ok := readAndCloseBody(resp, req, enc)
if !ok {
return
}
otlpReq, err := enc.unmarshalTracesRequest(body)
if err != nil {
writeError(resp, enc, err, http.StatusBadRequest)
return
}
otlpResp, err := tracesReceiver.Export(req.Context(), otlpReq)
if err != nil {
writeError(resp, enc, err, http.StatusInternalServerError)
return
}
msg, err := enc.marshalTracesResponse(otlpResp)
if err != nil {
writeError(resp, enc, err, http.StatusInternalServerError)
return
}
writeResponse(resp, enc.contentType(), http.StatusOK, msg)
}
func handleMetrics(resp http.ResponseWriter, req *http.Request, metricsReceiver *metrics.Receiver) {
enc, ok := readContentType(resp, req)
if !ok {
return
}
body, ok := readAndCloseBody(resp, req, enc)
if !ok {
return
}
otlpReq, err := enc.unmarshalMetricsRequest(body)
if err != nil {
writeError(resp, enc, err, http.StatusBadRequest)
return
}
otlpResp, err := metricsReceiver.Export(req.Context(), otlpReq)
if err != nil {
writeError(resp, enc, err, http.StatusInternalServerError)
return
}
msg, err := enc.marshalMetricsResponse(otlpResp)
if err != nil {
writeError(resp, enc, err, http.StatusInternalServerError)
return
}
writeResponse(resp, enc.contentType(), http.StatusOK, msg)
}
func handleLogs(resp http.ResponseWriter, req *http.Request, logsReceiver *logs.Receiver) {
enc, ok := readContentType(resp, req)
if !ok {
return
}
body, ok := readAndCloseBody(resp, req, enc)
if !ok {
return
}
otlpReq, err := enc.unmarshalLogsRequest(body)
if err != nil {
writeError(resp, enc, err, http.StatusBadRequest)
return
}
otlpResp, err := logsReceiver.Export(req.Context(), otlpReq)
if err != nil {
writeError(resp, enc, err, http.StatusInternalServerError)
return
}
msg, err := enc.marshalLogsResponse(otlpResp)
if err != nil {
writeError(resp, enc, err, http.StatusInternalServerError)
return
}
writeResponse(resp, enc.contentType(), http.StatusOK, msg)
}
func handleProfiles(resp http.ResponseWriter, req *http.Request, profilesReceiver *profiles.Receiver) {
enc, ok := readContentType(resp, req)
if !ok {
return
}
body, ok := readAndCloseBody(resp, req, enc)
if !ok {
return
}
otlpReq, err := enc.unmarshalProfilesRequest(body)
if err != nil {
writeError(resp, enc, err, http.StatusBadRequest)
return
}
otlpResp, err := profilesReceiver.Export(req.Context(), otlpReq)
if err != nil {
writeError(resp, enc, err, http.StatusInternalServerError)
return
}
msg, err := enc.marshalProfilesResponse(otlpResp)
if err != nil {
writeError(resp, enc, err, http.StatusInternalServerError)
return
}
writeResponse(resp, enc.contentType(), http.StatusOK, msg)
}
func readContentType(resp http.ResponseWriter, req *http.Request) (encoder, bool) {
if req.Method != http.MethodPost {
handleUnmatchedMethod(resp)
return nil, false
}
switch getMimeTypeFromContentType(req.Header.Get("Content-Type")) {
case pbContentType:
return pbEncoder, true
case jsonContentType:
return jsEncoder, true
default:
handleUnmatchedContentType(resp)
return nil, false
}
}
func readAndCloseBody(resp http.ResponseWriter, req *http.Request, enc encoder) ([]byte, bool) {
body, err := io.ReadAll(req.Body)
if err != nil {
writeError(resp, enc, err, http.StatusBadRequest)
return nil, false
}
if err = req.Body.Close(); err != nil {
writeError(resp, enc, err, http.StatusBadRequest)
return nil, false
}
return body, true
}
// writeError encodes the HTTP error inside a rpc.Status message as required by the OTLP protocol.
func writeError(w http.ResponseWriter, encoder encoder, err error, statusCode int) {
s, ok := status.FromError(err)
if ok {
statusCode = errors.GetHTTPStatusCodeFromStatus(s)
} else {
s = statusutil.NewStatusFromMsgAndHTTPCode(err.Error(), statusCode)
}
writeStatusResponse(w, encoder, statusCode, s)
}
// 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) {
s := statusutil.NewStatusFromMsgAndHTTPCode(errMsg, statusCode)
contentType := r.Header.Get("Content-Type")
if contentType == "" {
contentType = fallbackContentType
}
switch getMimeTypeFromContentType(contentType) {
case pbContentType:
writeStatusResponse(w, pbEncoder, statusCode, s)
return
case jsonContentType:
writeStatusResponse(w, jsEncoder, statusCode, s)
return
}
writeResponse(w, fallbackContentType, http.StatusInternalServerError, fallbackMsg)
}
func writeStatusResponse(w http.ResponseWriter, enc encoder, statusCode int, st *status.Status) {
// https://github.com/open-telemetry/opentelemetry-proto/blob/main/docs/specification.md#otlphttp-throttling
if statusCode == http.StatusTooManyRequests || statusCode == http.StatusServiceUnavailable {
retryInfo := statusutil.GetRetryInfo(st)
// Check if server returned throttling information.
if retryInfo != nil {
// We are throttled. Wait before retrying as requested by the server.
// The value of Retry-After field can be either an HTTP-date or a number of
// seconds to delay after the response is received. See https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.3
//
// Retry-After = HTTP-date / delay-seconds
//
// Use delay-seconds since is easier to format as well as does not require clock synchronization.
w.Header().Set("Retry-After", strconv.FormatInt(int64(retryInfo.GetRetryDelay().AsDuration()/time.Second), 10))
}
}
msg, err := enc.marshalStatus(st.Proto())
if err != nil {
writeResponse(w, fallbackContentType, http.StatusInternalServerError, fallbackMsg)
return
}
writeResponse(w, enc.contentType(), statusCode, msg)
}
func writeResponse(w http.ResponseWriter, contentType string, statusCode int, msg []byte) {
w.Header().Set("Content-Type", contentType)
w.WriteHeader(statusCode)
// Nothing we can do with the error if we cannot write to the response.
_, _ = w.Write(msg)
}
func getMimeTypeFromContentType(contentType string) string {
mediatype, _, err := mime.ParseMediaType(contentType)
if err != nil {
return ""
}
return mediatype
}
func handleUnmatchedMethod(resp http.ResponseWriter) {
hst := http.StatusMethodNotAllowed
writeResponse(resp, "text/plain", hst, fmt.Appendf(nil, "%v method not allowed, supported: [POST]", hst))
}
func handleUnmatchedContentType(resp http.ResponseWriter) {
hst := http.StatusUnsupportedMediaType
writeResponse(resp, "text/plain", hst, fmt.Appendf(nil, "%v unsupported media type, supported: [%s, %s]", hst, jsonContentType, pbContentType))
}
================================================
FILE: receiver/otlpreceiver/otlphttp_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlpreceiver
import (
"context"
"io"
"net/http"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/genproto/googleapis/rpc/errdetails"
spb "google.golang.org/genproto/googleapis/rpc/status"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/durationpb"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/internal/testutil"
"go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp"
"go.opentelemetry.io/collector/receiver/otlpreceiver/internal/errors"
)
func TestHTTPRetryAfter(t *testing.T) {
tests := []struct {
name string
contentType string
err error
expectedStatusCode int
expectedHasRetryAfter bool
expectedRetryAfter string
}{
{
name: "StatusErrorRetryableNoRetryAfter",
err: status.New(codes.DeadlineExceeded, "").Err(),
expectedStatusCode: http.StatusServiceUnavailable,
},
{
name: "StatusErrorRetryableWithZeroRetryAfter",
err: func() error {
st := status.New(codes.ResourceExhausted, "")
dt, err := st.WithDetails(&errdetails.RetryInfo{
RetryDelay: durationpb.New(0),
})
require.NoError(t, err)
return dt.Err()
}(),
expectedStatusCode: http.StatusTooManyRequests,
expectedHasRetryAfter: true,
expectedRetryAfter: "0",
},
{
name: "StatusErrorRetryableRetryAfter",
err: func() error {
st := status.New(codes.ResourceExhausted, "")
dt, err := st.WithDetails(&errdetails.RetryInfo{
RetryDelay: durationpb.New(13 * time.Second),
})
require.NoError(t, err)
return dt.Err()
}(),
expectedStatusCode: http.StatusTooManyRequests,
expectedHasRetryAfter: true,
expectedRetryAfter: "13",
},
{
name: "StatusErrorNotRetryableRetryAfter",
err: func() error {
st := status.New(codes.Unknown, "")
dt, err := st.WithDetails(&errdetails.RetryInfo{
RetryDelay: durationpb.New(12 * time.Second),
})
require.NoError(t, err)
return dt.Err()
}(),
expectedStatusCode: http.StatusInternalServerError,
},
{
name: "StatusErrorNotRetryableNoRetryAfter",
err: status.New(codes.InvalidArgument, "").Err(),
expectedStatusCode: http.StatusBadRequest,
},
}
addr := testutil.GetAvailableLocalAddress(t)
// Set the buffer count to 1 to make it flush the test span immediately.
sink := newErrOrSinkConsumer()
recv := newHTTPReceiver(t, componenttest.NewNopTelemetrySettings(), addr, sink)
require.NoError(t, recv.Start(context.Background(), componenttest.NewNopHost()), "Failed to start trace receiver")
t.Cleanup(func() { require.NoError(t, recv.Shutdown(context.Background())) })
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sink.Reset()
sink.SetConsumeError(tt.err)
for _, dr := range generateDataRequests(t) {
url := "http://" + addr + dr.path
req := createHTTPRequest(t, url, "", "application/x-protobuf", dr.protoBytes)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
respBytes, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
// For cases like "application/json; charset=utf-8", the response will be only "application/json"
require.True(t, strings.HasPrefix(strings.ToLower("application/x-protobuf"), resp.Header.Get("Content-Type")))
if tt.expectedHasRetryAfter {
require.Equal(t, tt.expectedRetryAfter, resp.Header.Get("Retry-After"))
} else {
require.Empty(t, resp.Header.Get("Retry-After"))
}
assert.Equal(t, tt.expectedStatusCode, resp.StatusCode)
if tt.err == nil {
tr := ptraceotlp.NewExportResponse()
require.NoError(t, tr.UnmarshalProto(respBytes))
sink.checkData(t, dr.data, 1)
} else {
errStatus := &spb.Status{}
require.NoError(t, proto.Unmarshal(respBytes, errStatus))
// The HTTP receiver transforms errors through GetStatusFromError
// We need to get the expected transformed error, not the original
expectedErr := errors.GetStatusFromError(tt.err)
s, ok := status.FromError(expectedErr)
require.True(t, ok)
assert.True(t, proto.Equal(errStatus, s.Proto()))
}
}
})
}
}
================================================
FILE: receiver/otlpreceiver/testdata/bad_no_proto_config.yaml
================================================
protocols:
================================================
FILE: receiver/otlpreceiver/testdata/bad_proto_config.yaml
================================================
protocols:
thrift:
endpoint: "127.0.0.1:1234"
================================================
FILE: receiver/otlpreceiver/testdata/config.yaml
================================================
protocols:
grpc:
# 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.
tls:
cert_file: test.crt
key_file: test.key
# 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.
max_recv_msg_size_mib: 32
max_concurrent_streams: 16
read_buffer_size: 1024
write_buffer_size: 1024
# The following entry configures all of the keep alive settings. These settings are used to configure the receiver.
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
http:
auth:
authenticator: test
# 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.
tls:
cert_file: test.crt
key_file: test.key
# 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.
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.
max_age: 7200
# The following shows URL paths for endpoints where signals are listened for bt the OTLP receiver
traces_url_path: traces
metrics_url_path: /v2/metrics
logs_url_path: log/ingest
================================================
FILE: receiver/otlpreceiver/testdata/default.yaml
================================================
# The following entry initializes the default OTLP receiver.
# The full name of this receiver is `otlp` and can be referenced in pipelines by 'otlp'.
protocols:
grpc:
http:
================================================
FILE: receiver/otlpreceiver/testdata/invalid_logs_path.yaml
================================================
protocols:
http:
logs_url_path: ":invalid"
================================================
FILE: receiver/otlpreceiver/testdata/invalid_metrics_path.yaml
================================================
protocols:
http:
metrics_url_path: ":invalid"
================================================
FILE: receiver/otlpreceiver/testdata/invalid_profiles_path.yaml
================================================
protocols:
http:
profiles_url_path: ":invalid"
================================================
FILE: receiver/otlpreceiver/testdata/invalid_traces_path.yaml
================================================
protocols:
http:
traces_url_path: ":invalid"
================================================
FILE: receiver/otlpreceiver/testdata/only_grpc.yaml
================================================
# The following entry initializes the default OTLP receiver with only gRPC support.
protocols:
grpc:
================================================
FILE: receiver/otlpreceiver/testdata/only_http.yaml
================================================
# The following entry initializes the default OTLP receiver with only http support.
protocols:
http:
================================================
FILE: receiver/otlpreceiver/testdata/only_http_empty_map.yaml
================================================
# The following entry initializes the default OTLP receiver with only http support by setting it explicitly to an empty map.
protocols:
http: {}
================================================
FILE: receiver/otlpreceiver/testdata/only_http_null.yaml
================================================
# The following entry initializes the default OTLP receiver with only http support by setting it explicitly to null.
protocols:
http: null
================================================
FILE: receiver/otlpreceiver/testdata/typo_default_proto_config.yaml
================================================
# cspell:ignore htttp
protocols:
grpc:
htttp:
================================================
FILE: receiver/otlpreceiver/testdata/uds.yaml
================================================
# The following entry demonstrates how to specify a Unix Domain Socket for the server.
protocols:
grpc:
transport: unix
endpoint: /tmp/grpc_otlp.sock
http:
transport: unix
endpoint: /tmp/http_otlp.sock
================================================
FILE: receiver/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package receiver
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: receiver/receiver.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package receiver // import "go.opentelemetry.io/collector/receiver"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/internal/componentalias"
"go.opentelemetry.io/collector/pipeline"
)
// Traces receiver receives traces.
// Its purpose is to translate data from any format to the collector's internal trace format.
// Traces receiver feeds a consumer.Traces with data.
//
// For example, it could be Zipkin data source which translates Zipkin spans into ptrace.Traces.
type Traces interface {
component.Component
}
// Metrics receiver receives metrics.
// Its purpose is to translate data from any format to the collector's internal metrics format.
// Metrics receiver feeds a consumer.Metrics with data.
//
// For example, it could be Prometheus data source which translates Prometheus metrics into pmetric.Metrics.
type Metrics interface {
component.Component
}
// Logs receiver receives logs.
// Its purpose is to translate data from any format to the collector's internal logs data format.
// Logs receiver feeds a consumer.Logs with data.
//
// For example, it could be a receiver that reads syslogs and convert them into plog.Logs.
type Logs interface {
component.Component
}
// Settings configures receiver creators.
type Settings struct {
// ID returns the ID of the component that will be created.
ID component.ID
component.TelemetrySettings
// BuildInfo can be used by components for informational purposes.
BuildInfo component.BuildInfo
// prevent unkeyed literal initialization
_ struct{}
}
// Factory is a factory interface for receivers.
//
// This interface cannot be directly implemented. Implementations must
// use the NewFactory to implement it.
type Factory interface {
component.Factory
// CreateTraces creates a Traces based on this config.
// If the receiver type does not support traces,
// this function returns the error [pipeline.ErrSignalNotSupported].
// Implementers can assume `next` is never nil.
CreateTraces(ctx context.Context, set Settings, cfg component.Config, next consumer.Traces) (Traces, error)
// TracesStability gets the stability level of the Traces receiver.
TracesStability() component.StabilityLevel
// CreateMetrics creates a Metrics based on this config.
// If the receiver type does not support metrics,
// this function returns the error [pipeline.ErrSignalNotSupported].
// Implementers can assume `next` is never nil.
CreateMetrics(ctx context.Context, set Settings, cfg component.Config, next consumer.Metrics) (Metrics, error)
// MetricsStability gets the stability level of the Metrics receiver.
MetricsStability() component.StabilityLevel
// CreateLogs creates a Logs based on this config.
// If the receiver type does not support logs,
// this function returns the error [pipeline.ErrSignalNotSupported].
// Implementers can assume `next` is never nil.
CreateLogs(ctx context.Context, set Settings, cfg component.Config, next consumer.Logs) (Logs, error)
// LogsStability gets the stability level of the Logs receiver.
LogsStability() component.StabilityLevel
unexportedFactoryFunc()
}
// FactoryOption apply changes to Factory.
type FactoryOption interface {
// applyOption applies the option.
applyOption(o *factory)
}
// factoryOptionFunc is an FactoryOption created through a function.
type factoryOptionFunc func(*factory)
func (f factoryOptionFunc) applyOption(o *factory) {
f(o)
}
// CreateTracesFunc is the equivalent of Factory.CreateTraces.
type CreateTracesFunc func(context.Context, Settings, component.Config, consumer.Traces) (Traces, error)
// CreateMetricsFunc is the equivalent of Factory.CreateMetrics.
type CreateMetricsFunc func(context.Context, Settings, component.Config, consumer.Metrics) (Metrics, error)
// CreateLogsFunc is the equivalent of Factory.CreateLogs.
type CreateLogsFunc func(context.Context, Settings, component.Config, consumer.Logs) (Logs, error)
type factory struct {
cfgType component.Type
component.CreateDefaultConfigFunc
componentalias.TypeAliasHolder
createTracesFunc CreateTracesFunc
tracesStabilityLevel component.StabilityLevel
createMetricsFunc CreateMetricsFunc
metricsStabilityLevel component.StabilityLevel
createLogsFunc CreateLogsFunc
logsStabilityLevel component.StabilityLevel
}
func (f *factory) Type() component.Type {
return f.cfgType
}
func (f *factory) unexportedFactoryFunc() {}
func (f *factory) TracesStability() component.StabilityLevel {
return f.tracesStabilityLevel
}
func (f *factory) MetricsStability() component.StabilityLevel {
return f.metricsStabilityLevel
}
func (f *factory) LogsStability() component.StabilityLevel {
return f.logsStabilityLevel
}
func (f *factory) CreateTraces(ctx context.Context, set Settings, cfg component.Config, next consumer.Traces) (Traces, error) {
if f.createTracesFunc == nil {
return nil, pipeline.ErrSignalNotSupported
}
if err := componentalias.ValidateComponentType(f, set.ID); err != nil {
return nil, err
}
return f.createTracesFunc(ctx, set, cfg, next)
}
func (f *factory) CreateMetrics(ctx context.Context, set Settings, cfg component.Config, next consumer.Metrics) (Metrics, error) {
if f.createMetricsFunc == nil {
return nil, pipeline.ErrSignalNotSupported
}
if err := componentalias.ValidateComponentType(f, set.ID); err != nil {
return nil, err
}
return f.createMetricsFunc(ctx, set, cfg, next)
}
func (f *factory) CreateLogs(ctx context.Context, set Settings, cfg component.Config, next consumer.Logs) (Logs, error) {
if f.createLogsFunc == nil {
return nil, pipeline.ErrSignalNotSupported
}
if err := componentalias.ValidateComponentType(f, set.ID); err != nil {
return nil, err
}
return f.createLogsFunc(ctx, set, cfg, next)
}
// WithTraces overrides the default "error not supported" implementation for Factory.CreateTraces and the default "undefined" stability level.
func WithTraces(createTraces CreateTracesFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.tracesStabilityLevel = sl
o.createTracesFunc = createTraces
})
}
// WithMetrics overrides the default "error not supported" implementation for Factory.CreateMetrics and the default "undefined" stability level.
func WithMetrics(createMetrics CreateMetricsFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.metricsStabilityLevel = sl
o.createMetricsFunc = createMetrics
})
}
// WithLogs overrides the default "error not supported" implementation for Factory.CreateLogs and the default "undefined" stability level.
func WithLogs(createLogs CreateLogsFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.logsStabilityLevel = sl
o.createLogsFunc = createLogs
})
}
// NewFactory returns a Factory.
func NewFactory(cfgType component.Type, createDefaultConfig component.CreateDefaultConfigFunc, options ...FactoryOption) Factory {
f := &factory{
cfgType: cfgType,
CreateDefaultConfigFunc: createDefaultConfig,
TypeAliasHolder: componentalias.NewTypeAliasHolder(),
}
for _, opt := range options {
opt.applyOption(f)
}
return f
}
================================================
FILE: receiver/receiver_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package receiver
import (
"context"
"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/pipeline"
"go.opentelemetry.io/collector/receiver/internal"
)
var (
testType = component.MustNewType("test")
testID = component.NewID(testType)
)
func TestNewFactory(t *testing.T) {
defaultCfg := struct{}{}
f := NewFactory(
testType,
func() component.Config { return &defaultCfg })
assert.Equal(t, testType, f.Type())
assert.EqualValues(t, &defaultCfg, f.CreateDefaultConfig())
_, err := f.CreateTraces(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
require.ErrorIs(t, err, pipeline.ErrSignalNotSupported)
_, err = f.CreateMetrics(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
require.ErrorIs(t, err, pipeline.ErrSignalNotSupported)
_, err = f.CreateLogs(context.Background(), Settings{ID: testID}, &defaultCfg, consumertest.NewNop())
require.ErrorIs(t, err, pipeline.ErrSignalNotSupported)
}
func TestNewFactoryWithOptions(t *testing.T) {
defaultCfg := struct{}{}
f := NewFactory(
testType,
func() component.Config { return &defaultCfg },
WithTraces(createTraces, component.StabilityLevelDeprecated),
WithMetrics(createMetrics, component.StabilityLevelAlpha),
WithLogs(createLogs, component.StabilityLevelStable))
assert.Equal(t, testType, f.Type())
assert.EqualValues(t, &defaultCfg, f.CreateDefaultConfig())
wrongID := component.MustNewID("wrong")
wrongIDErrStr := internal.ErrIDMismatch(wrongID, testType).Error()
assert.Equal(t, component.StabilityLevelDeprecated, f.TracesStability())
_, err := f.CreateTraces(context.Background(), Settings{ID: testID}, &defaultCfg, nil)
require.NoError(t, err)
_, err = f.CreateTraces(context.Background(), Settings{ID: wrongID}, &defaultCfg, nil)
require.EqualError(t, err, wrongIDErrStr)
assert.Equal(t, component.StabilityLevelAlpha, f.MetricsStability())
_, err = f.CreateMetrics(context.Background(), Settings{ID: testID}, &defaultCfg, nil)
require.NoError(t, err)
_, err = f.CreateMetrics(context.Background(), Settings{ID: wrongID}, &defaultCfg, nil)
require.EqualError(t, err, wrongIDErrStr)
assert.Equal(t, component.StabilityLevelStable, f.LogsStability())
_, err = f.CreateLogs(context.Background(), Settings{ID: testID}, &defaultCfg, nil)
require.NoError(t, err)
_, err = f.CreateLogs(context.Background(), Settings{ID: wrongID}, &defaultCfg, nil)
require.EqualError(t, err, wrongIDErrStr)
}
var nopInstance = &nopReceiver{
Consumer: consumertest.NewNop(),
}
// nopReceiver stores consumed traces and metrics for testing purposes.
type nopReceiver struct {
component.StartFunc
component.ShutdownFunc
consumertest.Consumer
}
func createTraces(context.Context, Settings, component.Config, consumer.Traces) (Traces, error) {
return nopInstance, nil
}
func createMetrics(context.Context, Settings, component.Config, consumer.Metrics) (Metrics, error) {
return nopInstance, nil
}
func createLogs(context.Context, Settings, component.Config, consumer.Logs) (Logs, error) {
return nopInstance, nil
}
================================================
FILE: receiver/receiverhelper/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: receiver/receiverhelper/documentation.md
================================================
[comment]: <> (Code generated by mdatagen. DO NOT EDIT.)
# receiverhelper
## Internal Telemetry
The following telemetry is emitted by this component.
### otelcol_receiver_accepted_log_records
Number of log records successfully pushed into the pipeline.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {record} | Sum | Int | true | Alpha |
### otelcol_receiver_accepted_metric_points
Number of metric points successfully pushed into the pipeline.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {datapoint} | Sum | Int | true | Alpha |
### otelcol_receiver_accepted_profile_samples
Number of profile samples successfully pushed into the pipeline.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {sample} | Sum | Int | true | Alpha |
### otelcol_receiver_accepted_spans
Number of spans successfully pushed into the pipeline.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {span} | Sum | Int | true | Alpha |
### otelcol_receiver_failed_log_records
The number of log records that failed to be processed by the receiver due to internal errors.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {record} | Sum | Int | true | Alpha |
### otelcol_receiver_failed_metric_points
The number of metric points that failed to be processed by the receiver due to internal errors.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {datapoint} | Sum | Int | true | Alpha |
### otelcol_receiver_failed_profile_samples
The number of profile samples that failed to be processed by the receiver due to internal errors.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {sample} | Sum | Int | true | Alpha |
### otelcol_receiver_failed_spans
The number of spans that failed to be processed by the receiver due to internal errors.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {span} | Sum | Int | true | Alpha |
### otelcol_receiver_refused_log_records
Number of log records that could not be pushed into the pipeline.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {record} | Sum | Int | true | Alpha |
### otelcol_receiver_refused_metric_points
Number of metric points that could not be pushed into the pipeline.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {datapoint} | Sum | Int | true | Alpha |
### otelcol_receiver_refused_profile_samples
Number of profile samples that could not be pushed into the pipeline.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {sample} | Sum | Int | true | Alpha |
### otelcol_receiver_refused_spans
Number of spans that could not be pushed into the pipeline.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {span} | Sum | Int | true | Alpha |
### otelcol_receiver_requests
The number of requests performed.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {request} | Sum | Int | true | Alpha |
#### Attributes
| Name | Description | Values |
| ---- | ----------- | ------ |
| outcome | The outcome of receiver requests | Str: ``success``, ``refused``, ``failure`` |
## Feature Gates
This component has the following feature gates:
| Feature Gate | Stage | Description | From Version | To Version | Reference |
| ------------ | ----- | ----------- | ------------ | ---------- | --------- |
| `receiverhelper.newReceiverMetrics` | alpha | Controls whether receivers emit new metrics and span attributes to distinguish downstream errors from internal errors. This is a breaking change for the semantics of the otelcol_receiver_refused_metric_points, otelcol_receiver_refused_log_records and otelcol_receiver_refused_spans. | v0.138.0 | N/A | [Link](https://github.com/open-telemetry/opentelemetry-collector/pull/12802) |
For more information about feature gates, see the [Feature Gates](https://github.com/open-telemetry/opentelemetry-collector/blob/main/featuregate/README.md) documentation.
================================================
FILE: receiver/receiverhelper/featuregates.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package receiverhelper // import "go.opentelemetry.io/collector/receiver/receiverhelper"
import "go.opentelemetry.io/collector/receiver/receiverhelper/internal/metadata"
// NewReceiverMetricsGate is the feature gate that controls whether to distinguish downstream errors from internal errors in pipeline telemetry.
// This feature gate is used in OTLP receiver tests, and therefore needs to be public.
var NewReceiverMetricsGate = metadata.ReceiverhelperNewReceiverMetricsFeatureGate
================================================
FILE: receiver/receiverhelper/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package receiverhelper
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: receiver/receiverhelper/go.mod
================================================
module go.opentelemetry.io/collector/receiver/receiverhelper
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.opentelemetry.io/collector/consumer/consumererror v0.148.0
go.opentelemetry.io/collector/featuregate v1.54.0
go.opentelemetry.io/collector/pipeline v1.54.0
go.opentelemetry.io/collector/pipeline/xpipeline v0.148.0
go.opentelemetry.io/collector/receiver v1.54.0
go.opentelemetry.io/otel v1.42.0
go.opentelemetry.io/otel/metric v1.42.0
go.opentelemetry.io/otel/sdk/metric v1.42.0
go.opentelemetry.io/otel/trace v1.42.0
go.uber.org/goleak v1.3.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/consumer v1.54.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
go.opentelemetry.io/collector/pdata v1.54.0 // indirect
go.opentelemetry.io/collector/pdata/pprofile v0.148.0 // indirect
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
golang.org/x/sys v0.41.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/receiver => ../
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest
replace go.opentelemetry.io/collector/consumer => ../../consumer
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/pdata/testdata => ../../pdata/testdata
replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile
replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer
replace go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest
replace go.opentelemetry.io/collector/pipeline => ../../pipeline
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/consumer/consumererror => ../../consumer/consumererror
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/pipeline/xpipeline => ../../pipeline/xpipeline
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
================================================
FILE: receiver/receiverhelper/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: receiver/receiverhelper/internal/metadata/generated_feature_gates.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/featuregate"
)
var ReceiverhelperNewReceiverMetricsFeatureGate = featuregate.GlobalRegistry().MustRegister(
"receiverhelper.newReceiverMetrics",
featuregate.StageAlpha,
featuregate.WithRegisterDescription("Controls whether receivers emit new metrics and span attributes to distinguish downstream errors from internal errors. This is a breaking change for the semantics of the otelcol_receiver_refused_metric_points, otelcol_receiver_refused_log_records and otelcol_receiver_refused_spans."),
featuregate.WithRegisterReferenceURL("https://github.com/open-telemetry/opentelemetry-collector/pull/12802"),
featuregate.WithRegisterFromVersion("v0.138.0"),
)
================================================
FILE: receiver/receiverhelper/internal/metadata/generated_telemetry.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"errors"
"sync"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/collector/component"
)
func Meter(settings component.TelemetrySettings) metric.Meter {
return settings.MeterProvider.Meter("go.opentelemetry.io/collector/receiver/receiverhelper")
}
func Tracer(settings component.TelemetrySettings) trace.Tracer {
return settings.TracerProvider.Tracer("go.opentelemetry.io/collector/receiver/receiverhelper")
}
// TelemetryBuilder provides an interface for components to report telemetry
// as defined in metadata and user config.
type TelemetryBuilder struct {
meter metric.Meter
mu sync.Mutex
registrations []metric.Registration
ReceiverAcceptedLogRecords metric.Int64Counter
ReceiverAcceptedMetricPoints metric.Int64Counter
ReceiverAcceptedProfileSamples metric.Int64Counter
ReceiverAcceptedSpans metric.Int64Counter
ReceiverFailedLogRecords metric.Int64Counter
ReceiverFailedMetricPoints metric.Int64Counter
ReceiverFailedProfileSamples metric.Int64Counter
ReceiverFailedSpans metric.Int64Counter
ReceiverRefusedLogRecords metric.Int64Counter
ReceiverRefusedMetricPoints metric.Int64Counter
ReceiverRefusedProfileSamples metric.Int64Counter
ReceiverRefusedSpans metric.Int64Counter
ReceiverRequests metric.Int64Counter
}
// TelemetryBuilderOption applies changes to default builder.
type TelemetryBuilderOption interface {
apply(*TelemetryBuilder)
}
type telemetryBuilderOptionFunc func(mb *TelemetryBuilder)
func (tbof telemetryBuilderOptionFunc) apply(mb *TelemetryBuilder) {
tbof(mb)
}
// Shutdown unregister all registered callbacks for async instruments.
func (builder *TelemetryBuilder) Shutdown() {
builder.mu.Lock()
defer builder.mu.Unlock()
for _, reg := range builder.registrations {
reg.Unregister()
}
}
// NewTelemetryBuilder provides a struct with methods to update all internal telemetry
// for a component
func NewTelemetryBuilder(settings component.TelemetrySettings, options ...TelemetryBuilderOption) (*TelemetryBuilder, error) {
builder := TelemetryBuilder{}
for _, op := range options {
op.apply(&builder)
}
builder.meter = Meter(settings)
var err, errs error
builder.ReceiverAcceptedLogRecords, err = builder.meter.Int64Counter(
"otelcol_receiver_accepted_log_records",
metric.WithDescription("Number of log records successfully pushed into the pipeline. [Alpha]"),
metric.WithUnit("{record}"),
)
errs = errors.Join(errs, err)
builder.ReceiverAcceptedMetricPoints, err = builder.meter.Int64Counter(
"otelcol_receiver_accepted_metric_points",
metric.WithDescription("Number of metric points successfully pushed into the pipeline. [Alpha]"),
metric.WithUnit("{datapoint}"),
)
errs = errors.Join(errs, err)
builder.ReceiverAcceptedProfileSamples, err = builder.meter.Int64Counter(
"otelcol_receiver_accepted_profile_samples",
metric.WithDescription("Number of profile samples successfully pushed into the pipeline. [Alpha]"),
metric.WithUnit("{sample}"),
)
errs = errors.Join(errs, err)
builder.ReceiverAcceptedSpans, err = builder.meter.Int64Counter(
"otelcol_receiver_accepted_spans",
metric.WithDescription("Number of spans successfully pushed into the pipeline. [Alpha]"),
metric.WithUnit("{span}"),
)
errs = errors.Join(errs, err)
builder.ReceiverFailedLogRecords, err = builder.meter.Int64Counter(
"otelcol_receiver_failed_log_records",
metric.WithDescription("The number of log records that failed to be processed by the receiver due to internal errors. [Alpha]"),
metric.WithUnit("{record}"),
)
errs = errors.Join(errs, err)
builder.ReceiverFailedMetricPoints, err = builder.meter.Int64Counter(
"otelcol_receiver_failed_metric_points",
metric.WithDescription("The number of metric points that failed to be processed by the receiver due to internal errors. [Alpha]"),
metric.WithUnit("{datapoint}"),
)
errs = errors.Join(errs, err)
builder.ReceiverFailedProfileSamples, err = builder.meter.Int64Counter(
"otelcol_receiver_failed_profile_samples",
metric.WithDescription("The number of profile samples that failed to be processed by the receiver due to internal errors. [Alpha]"),
metric.WithUnit("{sample}"),
)
errs = errors.Join(errs, err)
builder.ReceiverFailedSpans, err = builder.meter.Int64Counter(
"otelcol_receiver_failed_spans",
metric.WithDescription("The number of spans that failed to be processed by the receiver due to internal errors. [Alpha]"),
metric.WithUnit("{span}"),
)
errs = errors.Join(errs, err)
builder.ReceiverRefusedLogRecords, err = builder.meter.Int64Counter(
"otelcol_receiver_refused_log_records",
metric.WithDescription("Number of log records that could not be pushed into the pipeline. [Alpha]"),
metric.WithUnit("{record}"),
)
errs = errors.Join(errs, err)
builder.ReceiverRefusedMetricPoints, err = builder.meter.Int64Counter(
"otelcol_receiver_refused_metric_points",
metric.WithDescription("Number of metric points that could not be pushed into the pipeline. [Alpha]"),
metric.WithUnit("{datapoint}"),
)
errs = errors.Join(errs, err)
builder.ReceiverRefusedProfileSamples, err = builder.meter.Int64Counter(
"otelcol_receiver_refused_profile_samples",
metric.WithDescription("Number of profile samples that could not be pushed into the pipeline. [Alpha]"),
metric.WithUnit("{sample}"),
)
errs = errors.Join(errs, err)
builder.ReceiverRefusedSpans, err = builder.meter.Int64Counter(
"otelcol_receiver_refused_spans",
metric.WithDescription("Number of spans that could not be pushed into the pipeline. [Alpha]"),
metric.WithUnit("{span}"),
)
errs = errors.Join(errs, err)
builder.ReceiverRequests, err = builder.meter.Int64Counter(
"otelcol_receiver_requests",
metric.WithDescription("The number of requests performed. [Alpha]"),
metric.WithUnit("{request}"),
)
errs = errors.Join(errs, err)
return &builder, errs
}
================================================
FILE: receiver/receiverhelper/internal/metadata/generated_telemetry_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/metric"
embeddedmetric "go.opentelemetry.io/otel/metric/embedded"
noopmetric "go.opentelemetry.io/otel/metric/noop"
"go.opentelemetry.io/otel/trace"
embeddedtrace "go.opentelemetry.io/otel/trace/embedded"
nooptrace "go.opentelemetry.io/otel/trace/noop"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
)
type mockMeter struct {
noopmetric.Meter
name string
}
type mockMeterProvider struct {
embeddedmetric.MeterProvider
}
func (m mockMeterProvider) Meter(name string, opts ...metric.MeterOption) metric.Meter {
return mockMeter{name: name}
}
type mockTracer struct {
nooptrace.Tracer
name string
}
type mockTracerProvider struct {
embeddedtrace.TracerProvider
}
func (m mockTracerProvider) Tracer(name string, opts ...trace.TracerOption) trace.Tracer {
return mockTracer{name: name}
}
func TestProviders(t *testing.T) {
set := component.TelemetrySettings{
MeterProvider: mockMeterProvider{},
TracerProvider: mockTracerProvider{},
}
meter := Meter(set)
if m, ok := meter.(mockMeter); ok {
require.Equal(t, "go.opentelemetry.io/collector/receiver/receiverhelper", m.name)
} else {
require.Fail(t, "returned Meter not mockMeter")
}
tracer := Tracer(set)
if m, ok := tracer.(mockTracer); ok {
require.Equal(t, "go.opentelemetry.io/collector/receiver/receiverhelper", m.name)
} else {
require.Fail(t, "returned Meter not mockTracer")
}
}
func TestNewTelemetryBuilder(t *testing.T) {
set := componenttest.NewNopTelemetrySettings()
applied := false
_, err := NewTelemetryBuilder(set, telemetryBuilderOptionFunc(func(b *TelemetryBuilder) {
applied = true
}))
require.NoError(t, err)
require.True(t, applied)
}
================================================
FILE: receiver/receiverhelper/internal/metadatatest/generated_telemetrytest.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadatatest
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"go.opentelemetry.io/collector/component/componenttest"
)
func AssertEqualReceiverAcceptedLogRecords(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_receiver_accepted_log_records",
Description: "Number of log records successfully pushed into the pipeline. [Alpha]",
Unit: "{record}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_receiver_accepted_log_records")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualReceiverAcceptedMetricPoints(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_receiver_accepted_metric_points",
Description: "Number of metric points successfully pushed into the pipeline. [Alpha]",
Unit: "{datapoint}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_receiver_accepted_metric_points")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualReceiverAcceptedProfileSamples(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_receiver_accepted_profile_samples",
Description: "Number of profile samples successfully pushed into the pipeline. [Alpha]",
Unit: "{sample}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_receiver_accepted_profile_samples")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualReceiverAcceptedSpans(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_receiver_accepted_spans",
Description: "Number of spans successfully pushed into the pipeline. [Alpha]",
Unit: "{span}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_receiver_accepted_spans")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualReceiverFailedLogRecords(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_receiver_failed_log_records",
Description: "The number of log records that failed to be processed by the receiver due to internal errors. [Alpha]",
Unit: "{record}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_receiver_failed_log_records")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualReceiverFailedMetricPoints(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_receiver_failed_metric_points",
Description: "The number of metric points that failed to be processed by the receiver due to internal errors. [Alpha]",
Unit: "{datapoint}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_receiver_failed_metric_points")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualReceiverFailedProfileSamples(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_receiver_failed_profile_samples",
Description: "The number of profile samples that failed to be processed by the receiver due to internal errors. [Alpha]",
Unit: "{sample}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_receiver_failed_profile_samples")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualReceiverFailedSpans(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_receiver_failed_spans",
Description: "The number of spans that failed to be processed by the receiver due to internal errors. [Alpha]",
Unit: "{span}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_receiver_failed_spans")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualReceiverRefusedLogRecords(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_receiver_refused_log_records",
Description: "Number of log records that could not be pushed into the pipeline. [Alpha]",
Unit: "{record}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_receiver_refused_log_records")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualReceiverRefusedMetricPoints(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_receiver_refused_metric_points",
Description: "Number of metric points that could not be pushed into the pipeline. [Alpha]",
Unit: "{datapoint}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_receiver_refused_metric_points")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualReceiverRefusedProfileSamples(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_receiver_refused_profile_samples",
Description: "Number of profile samples that could not be pushed into the pipeline. [Alpha]",
Unit: "{sample}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_receiver_refused_profile_samples")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualReceiverRefusedSpans(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_receiver_refused_spans",
Description: "Number of spans that could not be pushed into the pipeline. [Alpha]",
Unit: "{span}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_receiver_refused_spans")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualReceiverRequests(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_receiver_requests",
Description: "The number of requests performed. [Alpha]",
Unit: "{request}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_receiver_requests")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
================================================
FILE: receiver/receiverhelper/internal/metadatatest/generated_telemetrytest_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadatatest
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/receiver/receiverhelper/internal/metadata"
)
func TestSetupTelemetry(t *testing.T) {
testTel := componenttest.NewTelemetry()
tb, err := metadata.NewTelemetryBuilder(testTel.NewTelemetrySettings())
require.NoError(t, err)
defer tb.Shutdown()
tb.ReceiverAcceptedLogRecords.Add(context.Background(), 1)
tb.ReceiverAcceptedMetricPoints.Add(context.Background(), 1)
tb.ReceiverAcceptedProfileSamples.Add(context.Background(), 1)
tb.ReceiverAcceptedSpans.Add(context.Background(), 1)
tb.ReceiverFailedLogRecords.Add(context.Background(), 1)
tb.ReceiverFailedMetricPoints.Add(context.Background(), 1)
tb.ReceiverFailedProfileSamples.Add(context.Background(), 1)
tb.ReceiverFailedSpans.Add(context.Background(), 1)
tb.ReceiverRefusedLogRecords.Add(context.Background(), 1)
tb.ReceiverRefusedMetricPoints.Add(context.Background(), 1)
tb.ReceiverRefusedProfileSamples.Add(context.Background(), 1)
tb.ReceiverRefusedSpans.Add(context.Background(), 1)
tb.ReceiverRequests.Add(context.Background(), 1)
AssertEqualReceiverAcceptedLogRecords(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualReceiverAcceptedMetricPoints(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualReceiverAcceptedProfileSamples(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualReceiverAcceptedSpans(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualReceiverFailedLogRecords(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualReceiverFailedMetricPoints(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualReceiverFailedProfileSamples(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualReceiverFailedSpans(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualReceiverRefusedLogRecords(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualReceiverRefusedMetricPoints(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualReceiverRefusedProfileSamples(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualReceiverRefusedSpans(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualReceiverRequests(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
require.NoError(t, testTel.Shutdown(context.Background()))
}
================================================
FILE: receiver/receiverhelper/internal/obsmetrics.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/receiver/receiverhelper/internal"
const (
// SpanNameSep is duplicate between receiver and exporter.
SpanNameSep = "/"
// ReceiverKey used to identify receivers in metrics and traces.
ReceiverKey = "receiver"
// TransportKey used to identify the transport used to received the data.
TransportKey = "transport"
// FormatKey used to identify the format of the data received.
FormatKey = "format"
// AcceptedSpansKey used to identify spans accepted by the Collector.
AcceptedSpansKey = "accepted_spans"
// RefusedSpansKey used to identify spans refused (ie.: not ingested) by the Collector.
RefusedSpansKey = "refused_spans"
// FailedSpansKey used to identify spans failed to be processed by the Collector.
FailedSpansKey = "failed_spans"
// AcceptedMetricPointsKey used to identify metric points accepted by the Collector.
AcceptedMetricPointsKey = "accepted_metric_points"
// RefusedMetricPointsKey used to identify metric points refused (ie.: not ingested) by the
// Collector.
RefusedMetricPointsKey = "refused_metric_points"
// FailedMetricPointKey used to identify metric points failed to be processed by the Collector.
FailedMetricPointsKey = "failed_metric_points"
// AcceptedLogRecordsKey used to identify log records accepted by the Collector.
AcceptedLogRecordsKey = "accepted_log_records"
// RefusedLogRecordsKey used to identify log records refused (ie.: not ingested) by the
// Collector.
RefusedLogRecordsKey = "refused_log_records"
// FailedLogRecordsKey used to identify log records failed to be processed by the Collector.
FailedLogRecordsKey = "failed_log_records"
// AcceptedProfileSamplesKey used to identify profile samples accepted by the Collector.
AcceptedProfileSamplesKey = "accepted_profile_samples"
// RefusedProfileSamplesKey used to identify profile samples refused (ie.: not ingested) by the Collector.
RefusedProfileSamplesKey = "refused_profile_samples"
// FailedProfileSamplesKey used to identify profile samples failed to be processed by the Collector.
FailedProfileSamplesKey = "failed_profile_samples"
ReceiveTraceDataOperationSuffix = SpanNameSep + "TraceDataReceived"
ReceiverMetricsOperationSuffix = SpanNameSep + "MetricsReceived"
ReceiverLogsOperationSuffix = SpanNameSep + "LogsReceived"
ReceiverProfilesOperationSuffix = SpanNameSep + "ProfilesReceived"
)
================================================
FILE: receiver/receiverhelper/metadata.yaml
================================================
type: receiverhelper
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
stability:
beta: [metrics, traces, logs]
telemetry:
metrics:
receiver_accepted_log_records:
enabled: true
stability: alpha
description: Number of log records successfully pushed into the pipeline.
unit: "{record}"
sum:
value_type: int
monotonic: true
receiver_accepted_metric_points:
stability: alpha
enabled: true
description: Number of metric points successfully pushed into the pipeline.
unit: "{datapoint}"
sum:
value_type: int
monotonic: true
receiver_accepted_profile_samples:
enabled: true
stability: alpha
description: Number of profile samples successfully pushed into the pipeline.
unit: "{sample}"
sum:
value_type: int
monotonic: true
receiver_accepted_spans:
enabled: true
stability: alpha
description: Number of spans successfully pushed into the pipeline.
unit: "{span}"
sum:
value_type: int
monotonic: true
receiver_failed_log_records:
enabled: true
stability: alpha
description: The number of log records that failed to be processed by the receiver due to internal errors.
unit: "{record}"
sum:
value_type: int
monotonic: true
receiver_failed_metric_points:
enabled: true
stability: alpha
description: The number of metric points that failed to be processed by the receiver due to internal errors.
unit: "{datapoint}"
sum:
value_type: int
monotonic: true
receiver_failed_profile_samples:
enabled: true
stability: alpha
description: The number of profile samples that failed to be processed by the receiver due to internal errors.
unit: "{sample}"
sum:
value_type: int
monotonic: true
receiver_failed_spans:
enabled: true
stability: alpha
description: The number of spans that failed to be processed by the receiver due to internal errors.
unit: "{span}"
sum:
value_type: int
monotonic: true
receiver_refused_log_records:
enabled: true
stability: alpha
description: Number of log records that could not be pushed into the pipeline.
unit: "{record}"
sum:
value_type: int
monotonic: true
receiver_refused_metric_points:
enabled: true
stability: alpha
description: Number of metric points that could not be pushed into the pipeline.
unit: "{datapoint}"
sum:
value_type: int
monotonic: true
receiver_refused_profile_samples:
enabled: true
stability: alpha
description: Number of profile samples that could not be pushed into the pipeline.
unit: "{sample}"
sum:
value_type: int
monotonic: true
receiver_refused_spans:
enabled: true
stability: alpha
description: Number of spans that could not be pushed into the pipeline.
unit: "{span}"
sum:
value_type: int
monotonic: true
receiver_requests:
enabled: true
stability: alpha
description: The number of requests performed.
unit: "{request}"
sum:
value_type: int
monotonic: true
attributes:
- outcome
attributes:
outcome:
description: The outcome of receiver requests
type: string
enum:
- success
- refused
- failure
feature_gates:
- id: receiverhelper.newReceiverMetrics
description: 'Controls whether receivers emit new metrics and span attributes to distinguish downstream errors from internal errors. This is a breaking change for the semantics of the otelcol_receiver_refused_metric_points, otelcol_receiver_refused_log_records and otelcol_receiver_refused_spans.'
stage: alpha
from_version: 'v0.138.0'
reference_url: 'https://github.com/open-telemetry/opentelemetry-collector/pull/12802'
================================================
FILE: receiver/receiverhelper/obsreport.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:generate mdatagen metadata.yaml
package receiverhelper // import "go.opentelemetry.io/collector/receiver/receiverhelper"
import (
"context"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/pipeline/xpipeline"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/receiverhelper/internal"
"go.opentelemetry.io/collector/receiver/receiverhelper/internal/metadata"
)
// ObsReport is a helper to add observability to a receiver.
type ObsReport struct {
spanNamePrefix string
transport string
longLivedCtx bool
tracer trace.Tracer
otelAttrs metric.MeasurementOption
telemetryBuilder *metadata.TelemetryBuilder
}
// ObsReportSettings are settings for creating an ObsReport.
type ObsReportSettings struct {
ReceiverID component.ID
Transport string
// LongLivedCtx when true indicates that the context passed in the call
// outlives the individual receive operation.
// Typically the long lived context is associated to a connection,
// eg.: a gRPC stream, for which many batches of data are received in individual
// operations without a corresponding new context per operation.
LongLivedCtx bool
ReceiverCreateSettings receiver.Settings
// prevent unkeyed literal initialization
_ struct{}
}
// NewObsReport creates a new ObsReport.
func NewObsReport(cfg ObsReportSettings) (*ObsReport, error) {
return newReceiver(cfg)
}
func newReceiver(cfg ObsReportSettings) (*ObsReport, error) {
telemetryBuilder, err := metadata.NewTelemetryBuilder(cfg.ReceiverCreateSettings.TelemetrySettings)
if err != nil {
return nil, err
}
return &ObsReport{
spanNamePrefix: internal.ReceiverKey + internal.SpanNameSep + cfg.ReceiverID.String(),
transport: cfg.Transport,
longLivedCtx: cfg.LongLivedCtx,
tracer: cfg.ReceiverCreateSettings.TracerProvider.Tracer(cfg.ReceiverID.String()),
otelAttrs: metric.WithAttributeSet(attribute.NewSet(
attribute.String(internal.ReceiverKey, cfg.ReceiverID.String()),
attribute.String(internal.TransportKey, cfg.Transport),
)),
telemetryBuilder: telemetryBuilder,
}, nil
}
// StartTracesOp 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 (rec *ObsReport) StartTracesOp(operationCtx context.Context) context.Context {
return rec.startOp(operationCtx, internal.ReceiveTraceDataOperationSuffix)
}
// EndTracesOp completes the receive operation that was started with
// StartTracesOp.
func (rec *ObsReport) EndTracesOp(
receiverCtx context.Context,
format string,
numReceivedSpans int,
err error,
) {
rec.endOp(receiverCtx, format, numReceivedSpans, err, pipeline.SignalTraces)
}
// StartLogsOp 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 (rec *ObsReport) StartLogsOp(operationCtx context.Context) context.Context {
return rec.startOp(operationCtx, internal.ReceiverLogsOperationSuffix)
}
// EndLogsOp completes the receive operation that was started with
// StartLogsOp.
func (rec *ObsReport) EndLogsOp(
receiverCtx context.Context,
format string,
numReceivedLogRecords int,
err error,
) {
rec.endOp(receiverCtx, format, numReceivedLogRecords, err, pipeline.SignalLogs)
}
// StartMetricsOp 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 (rec *ObsReport) StartMetricsOp(operationCtx context.Context) context.Context {
return rec.startOp(operationCtx, internal.ReceiverMetricsOperationSuffix)
}
// EndMetricsOp completes the receive operation that was started with
// StartMetricsOp.
func (rec *ObsReport) EndMetricsOp(
receiverCtx context.Context,
format string,
numReceivedPoints int,
err error,
) {
rec.endOp(receiverCtx, format, numReceivedPoints, err, pipeline.SignalMetrics)
}
// StartProfilesOp 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 (rec *ObsReport) StartProfilesOp(operationCtx context.Context) context.Context {
return rec.startOp(operationCtx, internal.ReceiverProfilesOperationSuffix)
}
// EndProfilesOp completes the receive operation that was started with
// StartProfilesOp.
func (rec *ObsReport) EndProfilesOp(
receiverCtx context.Context,
format string,
numReceivedProfileSamples int,
err error,
) {
rec.endOp(receiverCtx, format, numReceivedProfileSamples, err, xpipeline.SignalProfiles)
}
// startOp creates the span used to trace the operation. Returning
// the updated context with the created span.
func (rec *ObsReport) startOp(receiverCtx context.Context, operationSuffix string) context.Context {
var ctx context.Context
var span trace.Span
spanName := rec.spanNamePrefix + operationSuffix
if !rec.longLivedCtx {
ctx, span = rec.tracer.Start(receiverCtx, spanName)
} else {
// Since the receiverCtx is long lived do not use it to start the span.
// This way this trace ends when the EndTracesOp is called.
// Here is safe to ignore the returned context since it is not used below.
_, span = rec.tracer.Start(context.Background(), spanName, trace.WithLinks(trace.Link{
SpanContext: trace.SpanContextFromContext(receiverCtx),
}))
ctx = trace.ContextWithSpan(receiverCtx, span)
}
if rec.transport != "" {
span.SetAttributes(attribute.String(internal.TransportKey, rec.transport))
}
return ctx
}
// endOp records the observability signals at the end of an operation.
func (rec *ObsReport) endOp(
receiverCtx context.Context,
format string,
numReceivedItems int,
err error,
signal pipeline.Signal,
) {
numAccepted := numReceivedItems
numRefused := 0
numFailedErrors := 0
if err != nil {
numAccepted = 0
// If gate is enabled, we distinguish between refused and failed.
if metadata.ReceiverhelperNewReceiverMetricsFeatureGate.IsEnabled() {
if consumererror.IsDownstream(err) {
numRefused = numReceivedItems
} else {
numFailedErrors = numReceivedItems
}
} else {
// When the gate is disabled, all errors are considered "refused".
numRefused = numReceivedItems
}
}
span := trace.SpanFromContext(receiverCtx)
rec.recordMetrics(receiverCtx, signal, numAccepted, numRefused, numFailedErrors)
// The new otelcol_receiver_requests metric is only emitted when the feature gate is enabled.
if metadata.ReceiverhelperNewReceiverMetricsFeatureGate.IsEnabled() {
var outcome string
switch {
case err == nil:
outcome = "success"
case consumererror.IsDownstream(err):
outcome = "refused"
default:
outcome = "failure"
}
rec.telemetryBuilder.ReceiverRequests.Add(receiverCtx, 1, rec.otelAttrs, metric.WithAttributeSet(attribute.NewSet(attribute.String("outcome", outcome))))
}
// end span according to errors
if span.IsRecording() {
var acceptedItemsKey, refusedItemsKey, failedItemsKey string
switch signal {
case pipeline.SignalTraces:
acceptedItemsKey = internal.AcceptedSpansKey
refusedItemsKey = internal.RefusedSpansKey
failedItemsKey = internal.FailedSpansKey
case pipeline.SignalMetrics:
acceptedItemsKey = internal.AcceptedMetricPointsKey
refusedItemsKey = internal.RefusedMetricPointsKey
failedItemsKey = internal.FailedMetricPointsKey
case pipeline.SignalLogs:
acceptedItemsKey = internal.AcceptedLogRecordsKey
refusedItemsKey = internal.RefusedLogRecordsKey
failedItemsKey = internal.FailedLogRecordsKey
case xpipeline.SignalProfiles:
acceptedItemsKey = internal.AcceptedProfileSamplesKey
refusedItemsKey = internal.RefusedProfileSamplesKey
failedItemsKey = internal.FailedProfileSamplesKey
}
span.SetAttributes(
attribute.String(internal.FormatKey, format),
attribute.Int64(acceptedItemsKey, int64(numAccepted)),
attribute.Int64(refusedItemsKey, int64(numRefused)),
attribute.Int64(failedItemsKey, int64(numFailedErrors)),
)
if err != nil {
span.SetStatus(codes.Error, err.Error())
}
}
span.End()
}
func (rec *ObsReport) recordMetrics(receiverCtx context.Context, signal pipeline.Signal, numAccepted, numRefused, numFailedErrors int) {
var acceptedMeasure, refusedMeasure, failedMeasure metric.Int64Counter
switch signal {
case pipeline.SignalTraces:
acceptedMeasure = rec.telemetryBuilder.ReceiverAcceptedSpans
refusedMeasure = rec.telemetryBuilder.ReceiverRefusedSpans
failedMeasure = rec.telemetryBuilder.ReceiverFailedSpans
case pipeline.SignalMetrics:
acceptedMeasure = rec.telemetryBuilder.ReceiverAcceptedMetricPoints
refusedMeasure = rec.telemetryBuilder.ReceiverRefusedMetricPoints
failedMeasure = rec.telemetryBuilder.ReceiverFailedMetricPoints
case pipeline.SignalLogs:
acceptedMeasure = rec.telemetryBuilder.ReceiverAcceptedLogRecords
refusedMeasure = rec.telemetryBuilder.ReceiverRefusedLogRecords
failedMeasure = rec.telemetryBuilder.ReceiverFailedLogRecords
case xpipeline.SignalProfiles:
acceptedMeasure = rec.telemetryBuilder.ReceiverAcceptedProfileSamples
refusedMeasure = rec.telemetryBuilder.ReceiverRefusedProfileSamples
failedMeasure = rec.telemetryBuilder.ReceiverFailedProfileSamples
}
acceptedMeasure.Add(receiverCtx, int64(numAccepted), rec.otelAttrs)
refusedMeasure.Add(receiverCtx, int64(numRefused), rec.otelAttrs)
failedMeasure.Add(receiverCtx, int64(numFailedErrors), rec.otelAttrs)
}
================================================
FILE: receiver/receiverhelper/obsreport_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package receiverhelper
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/receiverhelper/internal"
"go.opentelemetry.io/collector/receiver/receiverhelper/internal/metadata"
"go.opentelemetry.io/collector/receiver/receiverhelper/internal/metadatatest"
)
const (
transport = "fakeTransport"
format = "fakeFormat"
)
var (
receiverID = component.MustNewID("fakeReceiver")
errFake = errors.New("errFake")
)
type testParams struct {
items int
err error
}
func TestReceiveTraceDataOp(t *testing.T) {
originalState := metadata.ReceiverhelperNewReceiverMetricsFeatureGate.IsEnabled()
t.Cleanup(func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ReceiverhelperNewReceiverMetricsFeatureGate.ID(), originalState))
})
for _, tc := range []struct {
name string
enabled bool
}{{"gate_enabled", true}, {"gate_disabled", false}} {
t.Run(tc.name, func(t *testing.T) {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ReceiverhelperNewReceiverMetricsFeatureGate.ID(), tc.enabled))
testTelemetry(t, func(t *testing.T, tt *componenttest.Telemetry) {
parentCtx, parentSpan := tt.NewTelemetrySettings().TracerProvider.Tracer("test").Start(context.Background(), t.Name())
defer parentSpan.End()
params := []testParams{
{items: 13, err: consumererror.NewDownstream(errFake)},
{items: 42, err: nil},
{items: 7, err: errors.New("non-downstream error")}, // Regular error to test numFailedErrors path
}
for i, param := range params {
rec, err := newReceiver(ObsReportSettings{
ReceiverID: receiverID,
Transport: transport,
ReceiverCreateSettings: receiver.Settings{ID: receiverID, TelemetrySettings: tt.NewTelemetrySettings(), BuildInfo: component.NewDefaultBuildInfo()},
})
require.NoError(t, err)
ctx := rec.StartTracesOp(parentCtx)
assert.NotNil(t, ctx)
rec.EndTracesOp(ctx, format, params[i].items, param.err)
}
spans := tt.SpanRecorder.Ended()
require.Len(t, spans, len(params))
var acceptedSpans, refusedSpans, failedSpans int
for i, span := range spans {
assert.Equal(t, "receiver/"+receiverID.String()+"/TraceDataReceived", span.Name())
err := params[i].err
if err == nil {
acceptedSpans += params[i].items
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.AcceptedSpansKey, Value: attribute.Int64Value(int64(params[i].items))})
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.RefusedSpansKey, Value: attribute.Int64Value(0)})
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.FailedSpansKey, Value: attribute.Int64Value(0)})
assert.Equal(t, codes.Unset, span.Status().Code)
} else {
isDownstream := consumererror.IsDownstream(err)
if !tc.enabled || (tc.enabled && isDownstream) {
refusedSpans += params[i].items
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.RefusedSpansKey, Value: attribute.Int64Value(int64(params[i].items))})
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.FailedSpansKey, Value: attribute.Int64Value(0)})
} else {
failedSpans += params[i].items
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.RefusedSpansKey, Value: attribute.Int64Value(0)})
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.FailedSpansKey, Value: attribute.Int64Value(int64(params[i].items))})
}
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.AcceptedSpansKey, Value: attribute.Int64Value(0)})
assert.Equal(t, codes.Error, span.Status().Code)
assert.Equal(t, err.Error(), span.Status().Description)
}
}
metadatatest.AssertEqualReceiverAcceptedSpans(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(internal.ReceiverKey, receiverID.String()),
attribute.String(internal.TransportKey, transport)),
Value: int64(acceptedSpans),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
metadatatest.AssertEqualReceiverRefusedSpans(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(internal.ReceiverKey, receiverID.String()),
attribute.String(internal.TransportKey, transport)),
Value: int64(refusedSpans),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
metadatatest.AssertEqualReceiverFailedSpans(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(internal.ReceiverKey, receiverID.String()),
attribute.String(internal.TransportKey, transport)),
Value: int64(failedSpans),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
// Assert otelcol_receiver_requests metric with outcome attribute
if tc.enabled {
outcomes := make(map[string]int64)
for _, param := range params {
var outcome string
switch {
case param.err == nil:
outcome = "success"
case consumererror.IsDownstream(param.err):
outcome = "refused"
default:
outcome = "failure"
}
outcomes[outcome]++
}
var expectedRequests []metricdata.DataPoint[int64]
for outcome, count := range outcomes {
expectedRequests = append(expectedRequests, metricdata.DataPoint[int64]{
Attributes: attribute.NewSet(
attribute.String(internal.ReceiverKey, receiverID.String()),
attribute.String(internal.TransportKey, transport),
attribute.String("outcome", outcome)),
Value: count,
})
}
metadatatest.AssertEqualReceiverRequests(t, tt, expectedRequests, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
}
})
})
}
}
func TestReceiveLogsOp(t *testing.T) {
originalState := metadata.ReceiverhelperNewReceiverMetricsFeatureGate.IsEnabled()
t.Cleanup(func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ReceiverhelperNewReceiverMetricsFeatureGate.ID(), originalState))
})
for _, tc := range []struct {
name string
enabled bool
}{{"gate_enabled", true}, {"gate_disabled", false}} {
t.Run(tc.name, func(t *testing.T) {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ReceiverhelperNewReceiverMetricsFeatureGate.ID(), tc.enabled))
testTelemetry(t, func(t *testing.T, tt *componenttest.Telemetry) {
parentCtx, parentSpan := tt.NewTelemetrySettings().TracerProvider.Tracer("test").Start(context.Background(), t.Name())
defer parentSpan.End()
params := []testParams{
{items: 13, err: consumererror.NewDownstream(errFake)},
{items: 42, err: nil},
{items: 7, err: errors.New("non-downstream error")},
}
for i, param := range params {
rec, err := newReceiver(ObsReportSettings{
ReceiverID: receiverID,
Transport: transport,
ReceiverCreateSettings: receiver.Settings{ID: receiverID, TelemetrySettings: tt.NewTelemetrySettings(), BuildInfo: component.NewDefaultBuildInfo()},
})
require.NoError(t, err)
ctx := rec.StartLogsOp(parentCtx)
assert.NotNil(t, ctx)
rec.EndLogsOp(ctx, format, params[i].items, param.err)
}
spans := tt.SpanRecorder.Ended()
require.Len(t, spans, len(params))
var acceptedLogRecords, refusedLogRecords, failedLogRecords int
for i, span := range spans {
assert.Equal(t, "receiver/"+receiverID.String()+"/LogsReceived", span.Name())
err := params[i].err
if err == nil {
acceptedLogRecords += params[i].items
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.AcceptedLogRecordsKey, Value: attribute.Int64Value(int64(params[i].items))})
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.RefusedLogRecordsKey, Value: attribute.Int64Value(0)})
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.FailedLogRecordsKey, Value: attribute.Int64Value(0)})
assert.Equal(t, codes.Unset, span.Status().Code)
} else {
isDownstream := consumererror.IsDownstream(err)
if !tc.enabled || (tc.enabled && isDownstream) {
refusedLogRecords += params[i].items
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.RefusedLogRecordsKey, Value: attribute.Int64Value(int64(params[i].items))})
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.FailedLogRecordsKey, Value: attribute.Int64Value(0)})
} else {
failedLogRecords += params[i].items
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.RefusedLogRecordsKey, Value: attribute.Int64Value(0)})
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.FailedLogRecordsKey, Value: attribute.Int64Value(int64(params[i].items))})
}
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.AcceptedLogRecordsKey, Value: attribute.Int64Value(0)})
assert.Equal(t, codes.Error, span.Status().Code)
assert.Equal(t, err.Error(), span.Status().Description)
}
}
metadatatest.AssertEqualReceiverAcceptedLogRecords(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(internal.ReceiverKey, receiverID.String()),
attribute.String(internal.TransportKey, transport)),
Value: int64(acceptedLogRecords),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
metadatatest.AssertEqualReceiverRefusedLogRecords(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(internal.ReceiverKey, receiverID.String()),
attribute.String(internal.TransportKey, transport)),
Value: int64(refusedLogRecords),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
metadatatest.AssertEqualReceiverFailedLogRecords(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(internal.ReceiverKey, receiverID.String()),
attribute.String(internal.TransportKey, transport)),
Value: int64(failedLogRecords),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
// Assert otelcol_receiver_requests metric with outcome attribute
if tc.enabled {
outcomes := make(map[string]int64)
for _, param := range params {
var outcome string
switch {
case param.err == nil:
outcome = "success"
case consumererror.IsDownstream(param.err):
outcome = "refused"
default:
outcome = "failure"
}
outcomes[outcome]++
}
var expectedRequests []metricdata.DataPoint[int64]
for outcome, count := range outcomes {
expectedRequests = append(expectedRequests, metricdata.DataPoint[int64]{
Attributes: attribute.NewSet(
attribute.String(internal.ReceiverKey, receiverID.String()),
attribute.String(internal.TransportKey, transport),
attribute.String("outcome", outcome)),
Value: count,
})
}
metadatatest.AssertEqualReceiverRequests(t, tt, expectedRequests, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
}
})
})
}
}
func TestReceiveMetricsOp(t *testing.T) {
originalState := metadata.ReceiverhelperNewReceiverMetricsFeatureGate.IsEnabled()
t.Cleanup(func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ReceiverhelperNewReceiverMetricsFeatureGate.ID(), originalState))
})
for _, tc := range []struct {
name string
enabled bool
}{{"gate_enabled", true}, {"gate_disabled", false}} {
t.Run(tc.name, func(t *testing.T) {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ReceiverhelperNewReceiverMetricsFeatureGate.ID(), tc.enabled))
testTelemetry(t, func(t *testing.T, tt *componenttest.Telemetry) {
parentCtx, parentSpan := tt.NewTelemetrySettings().TracerProvider.Tracer("test").Start(context.Background(), t.Name())
defer parentSpan.End()
params := []testParams{
{items: 13, err: consumererror.NewDownstream(errFake)},
{items: 42, err: nil},
{items: 7, err: errors.New("non-downstream error")},
}
for i, param := range params {
rec, err := newReceiver(ObsReportSettings{
ReceiverID: receiverID,
Transport: transport,
ReceiverCreateSettings: receiver.Settings{ID: receiverID, TelemetrySettings: tt.NewTelemetrySettings(), BuildInfo: component.NewDefaultBuildInfo()},
})
require.NoError(t, err)
ctx := rec.StartMetricsOp(parentCtx)
assert.NotNil(t, ctx)
rec.EndMetricsOp(ctx, format, params[i].items, param.err)
}
spans := tt.SpanRecorder.Ended()
require.Len(t, spans, len(params))
var acceptedMetricPoints, refusedMetricPoints, failedMetricPoints int
for i, span := range spans {
assert.Equal(t, "receiver/"+receiverID.String()+"/MetricsReceived", span.Name())
err := params[i].err
if err == nil {
acceptedMetricPoints += params[i].items
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.AcceptedMetricPointsKey, Value: attribute.Int64Value(int64(params[i].items))})
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.RefusedMetricPointsKey, Value: attribute.Int64Value(0)})
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.FailedMetricPointsKey, Value: attribute.Int64Value(0)})
assert.Equal(t, codes.Unset, span.Status().Code)
} else {
isDownstream := consumererror.IsDownstream(err)
if !tc.enabled || (tc.enabled && isDownstream) {
refusedMetricPoints += params[i].items
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.RefusedMetricPointsKey, Value: attribute.Int64Value(int64(params[i].items))})
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.FailedMetricPointsKey, Value: attribute.Int64Value(0)})
} else {
failedMetricPoints += params[i].items
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.RefusedMetricPointsKey, Value: attribute.Int64Value(0)})
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.FailedMetricPointsKey, Value: attribute.Int64Value(int64(params[i].items))})
}
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.AcceptedMetricPointsKey, Value: attribute.Int64Value(0)})
assert.Equal(t, codes.Error, span.Status().Code)
assert.Equal(t, err.Error(), span.Status().Description)
}
}
metadatatest.AssertEqualReceiverAcceptedMetricPoints(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(internal.ReceiverKey, receiverID.String()),
attribute.String(internal.TransportKey, transport)),
Value: int64(acceptedMetricPoints),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
metadatatest.AssertEqualReceiverRefusedMetricPoints(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(internal.ReceiverKey, receiverID.String()),
attribute.String(internal.TransportKey, transport)),
Value: int64(refusedMetricPoints),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
metadatatest.AssertEqualReceiverFailedMetricPoints(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(internal.ReceiverKey, receiverID.String()),
attribute.String(internal.TransportKey, transport)),
Value: int64(failedMetricPoints),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
// Assert otelcol_receiver_requests metric with outcome attribute
if tc.enabled {
outcomes := make(map[string]int64)
for _, param := range params {
var outcome string
switch {
case param.err == nil:
outcome = "success"
case consumererror.IsDownstream(param.err):
outcome = "refused"
default:
outcome = "failure"
}
outcomes[outcome]++
}
var expectedRequests []metricdata.DataPoint[int64]
for outcome, count := range outcomes {
expectedRequests = append(expectedRequests, metricdata.DataPoint[int64]{
Attributes: attribute.NewSet(
attribute.String(internal.ReceiverKey, receiverID.String()),
attribute.String(internal.TransportKey, transport),
attribute.String("outcome", outcome)),
Value: count,
})
}
metadatatest.AssertEqualReceiverRequests(t, tt, expectedRequests, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
}
})
})
}
}
func TestReceiveProfilesOp(t *testing.T) {
originalState := metadata.ReceiverhelperNewReceiverMetricsFeatureGate.IsEnabled()
t.Cleanup(func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ReceiverhelperNewReceiverMetricsFeatureGate.ID(), originalState))
})
for _, tc := range []struct {
name string
enabled bool
}{{"gate_enabled", true}, {"gate_disabled", false}} {
t.Run(tc.name, func(t *testing.T) {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ReceiverhelperNewReceiverMetricsFeatureGate.ID(), tc.enabled))
testTelemetry(t, func(t *testing.T, tt *componenttest.Telemetry) {
parentCtx, parentSpan := tt.NewTelemetrySettings().TracerProvider.Tracer("test").Start(context.Background(), t.Name())
defer parentSpan.End()
params := []testParams{
{items: 13, err: consumererror.NewDownstream(errFake)},
{items: 42, err: nil},
{items: 7, err: errors.New("non-downstream error")},
}
for i, param := range params {
rec, err := newReceiver(ObsReportSettings{
ReceiverID: receiverID,
Transport: transport,
ReceiverCreateSettings: receiver.Settings{ID: receiverID, TelemetrySettings: tt.NewTelemetrySettings(), BuildInfo: component.NewDefaultBuildInfo()},
})
require.NoError(t, err)
ctx := rec.StartProfilesOp(parentCtx)
assert.NotNil(t, ctx)
rec.EndProfilesOp(ctx, format, params[i].items, param.err)
}
spans := tt.SpanRecorder.Ended()
require.Len(t, spans, len(params))
var acceptedSamples, refusedSamples, failedSamples int
for i, span := range spans {
assert.Equal(t, "receiver/"+receiverID.String()+"/ProfilesReceived", span.Name())
err := params[i].err
if err == nil {
acceptedSamples += params[i].items
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.AcceptedProfileSamplesKey, Value: attribute.Int64Value(int64(params[i].items))})
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.RefusedProfileSamplesKey, Value: attribute.Int64Value(0)})
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.FailedProfileSamplesKey, Value: attribute.Int64Value(0)})
assert.Equal(t, codes.Unset, span.Status().Code)
} else {
isDownstream := consumererror.IsDownstream(err)
if !tc.enabled || (tc.enabled && isDownstream) {
refusedSamples += params[i].items
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.RefusedProfileSamplesKey, Value: attribute.Int64Value(int64(params[i].items))})
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.FailedProfileSamplesKey, Value: attribute.Int64Value(0)})
} else {
failedSamples += params[i].items
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.RefusedProfileSamplesKey, Value: attribute.Int64Value(0)})
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.FailedProfileSamplesKey, Value: attribute.Int64Value(int64(params[i].items))})
}
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.AcceptedProfileSamplesKey, Value: attribute.Int64Value(0)})
assert.Equal(t, codes.Error, span.Status().Code)
assert.Equal(t, err.Error(), span.Status().Description)
}
}
metadatatest.AssertEqualReceiverAcceptedProfileSamples(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(internal.ReceiverKey, receiverID.String()),
attribute.String(internal.TransportKey, transport)),
Value: int64(acceptedSamples),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
metadatatest.AssertEqualReceiverRefusedProfileSamples(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(internal.ReceiverKey, receiverID.String()),
attribute.String(internal.TransportKey, transport)),
Value: int64(refusedSamples),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
metadatatest.AssertEqualReceiverFailedProfileSamples(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(internal.ReceiverKey, receiverID.String()),
attribute.String(internal.TransportKey, transport)),
Value: int64(failedSamples),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
// Assert otelcol_receiver_requests metric with outcome attribute
if tc.enabled {
outcomes := make(map[string]int64)
for _, param := range params {
var outcome string
switch {
case param.err == nil:
outcome = "success"
case consumererror.IsDownstream(param.err):
outcome = "refused"
default:
outcome = "failure"
}
outcomes[outcome]++
}
var expectedRequests []metricdata.DataPoint[int64]
for outcome, count := range outcomes {
expectedRequests = append(expectedRequests, metricdata.DataPoint[int64]{
Attributes: attribute.NewSet(
attribute.String(internal.ReceiverKey, receiverID.String()),
attribute.String(internal.TransportKey, transport),
attribute.String("outcome", outcome)),
Value: count,
})
}
metadatatest.AssertEqualReceiverRequests(t, tt, expectedRequests, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
}
})
})
}
}
func TestReceiveWithLongLivedCtx(t *testing.T) {
originalState := metadata.ReceiverhelperNewReceiverMetricsFeatureGate.IsEnabled()
t.Cleanup(func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ReceiverhelperNewReceiverMetricsFeatureGate.ID(), originalState))
})
for _, tc := range []struct {
name string
enabled bool
}{{"gate_enabled", true}, {"gate_disabled", false}} {
t.Run(tc.name, func(t *testing.T) {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ReceiverhelperNewReceiverMetricsFeatureGate.ID(), tc.enabled))
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
longLivedCtx, parentSpan := tt.NewTelemetrySettings().TracerProvider.Tracer("test").Start(context.Background(), t.Name())
defer parentSpan.End()
params := []testParams{
{items: 17, err: nil},
{items: 23, err: consumererror.NewDownstream(errFake)},
}
for i := range params {
// Use a new context on each operation to simulate distinct operations
// under the same long lived context.
rec, rerr := NewObsReport(ObsReportSettings{
ReceiverID: receiverID,
Transport: transport,
LongLivedCtx: true,
ReceiverCreateSettings: receiver.Settings{ID: receiverID, TelemetrySettings: tt.NewTelemetrySettings(), BuildInfo: component.NewDefaultBuildInfo()},
})
require.NoError(t, rerr)
ctx := rec.StartTracesOp(longLivedCtx)
assert.NotNil(t, ctx)
rec.EndTracesOp(ctx, format, params[i].items, params[i].err)
}
spans := tt.SpanRecorder.Ended()
require.Len(t, spans, len(params))
for i, span := range spans {
assert.False(t, span.Parent().IsValid())
require.Len(t, span.Links(), 1)
link := span.Links()[0]
assert.Equal(t, parentSpan.SpanContext().TraceID(), link.SpanContext.TraceID())
assert.Equal(t, parentSpan.SpanContext().SpanID(), link.SpanContext.SpanID())
assert.Equal(t, "receiver/"+receiverID.String()+"/TraceDataReceived", span.Name())
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.TransportKey, Value: attribute.StringValue(transport)})
switch {
case params[i].err == nil:
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.AcceptedSpansKey, Value: attribute.Int64Value(int64(params[i].items))})
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.RefusedSpansKey, Value: attribute.Int64Value(0)})
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.FailedSpansKey, Value: attribute.Int64Value(0)})
assert.Equal(t, codes.Unset, span.Status().Code)
case consumererror.IsDownstream(params[i].err):
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.AcceptedSpansKey, Value: attribute.Int64Value(0)})
// For downstream errors
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.RefusedSpansKey, Value: attribute.Int64Value(int64(params[i].items))})
require.Contains(t, span.Attributes(), attribute.KeyValue{Key: internal.FailedSpansKey, Value: attribute.Int64Value(0)})
assert.Equal(t, codes.Error, span.Status().Code)
assert.Equal(t, params[i].err.Error(), span.Status().Description)
default:
t.Fatalf("unexpected error: %v", params[i].err)
}
}
})
}
}
func TestCheckReceiverTracesViews(t *testing.T) {
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
rec, err := NewObsReport(ObsReportSettings{
ReceiverID: receiverID,
Transport: transport,
ReceiverCreateSettings: receiver.Settings{ID: receiverID, TelemetrySettings: tt.NewTelemetrySettings(), BuildInfo: component.NewDefaultBuildInfo()},
})
require.NoError(t, err)
ctx := rec.StartTracesOp(context.Background())
require.NotNil(t, ctx)
rec.EndTracesOp(ctx, format, 7, nil)
metadatatest.AssertEqualReceiverAcceptedSpans(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(internal.ReceiverKey, receiverID.String()),
attribute.String(internal.TransportKey, transport)),
Value: int64(7),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
metadatatest.AssertEqualReceiverRefusedSpans(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(internal.ReceiverKey, receiverID.String()),
attribute.String(internal.TransportKey, transport)),
Value: int64(0),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
metadatatest.AssertEqualReceiverFailedSpans(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(internal.ReceiverKey, receiverID.String()),
attribute.String(internal.TransportKey, transport)),
Value: int64(0),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
}
func TestCheckReceiverMetricsViews(t *testing.T) {
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
rec, err := NewObsReport(ObsReportSettings{
ReceiverID: receiverID,
Transport: transport,
ReceiverCreateSettings: receiver.Settings{ID: receiverID, TelemetrySettings: tt.NewTelemetrySettings(), BuildInfo: component.NewDefaultBuildInfo()},
})
require.NoError(t, err)
ctx := rec.StartMetricsOp(context.Background())
require.NotNil(t, ctx)
rec.EndMetricsOp(ctx, format, 7, nil)
metadatatest.AssertEqualReceiverAcceptedMetricPoints(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(internal.ReceiverKey, receiverID.String()),
attribute.String(internal.TransportKey, transport)),
Value: int64(7),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
metadatatest.AssertEqualReceiverRefusedMetricPoints(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(internal.ReceiverKey, receiverID.String()),
attribute.String(internal.TransportKey, transport)),
Value: int64(0),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
metadatatest.AssertEqualReceiverFailedMetricPoints(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(internal.ReceiverKey, receiverID.String()),
attribute.String(internal.TransportKey, transport)),
Value: int64(0),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
}
func TestCheckReceiverLogsViews(t *testing.T) {
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
rec, err := NewObsReport(ObsReportSettings{
ReceiverID: receiverID,
Transport: transport,
ReceiverCreateSettings: receiver.Settings{ID: receiverID, TelemetrySettings: tt.NewTelemetrySettings(), BuildInfo: component.NewDefaultBuildInfo()},
})
require.NoError(t, err)
ctx := rec.StartLogsOp(context.Background())
require.NotNil(t, ctx)
rec.EndLogsOp(ctx, format, 7, nil)
metadatatest.AssertEqualReceiverAcceptedLogRecords(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(internal.ReceiverKey, receiverID.String()),
attribute.String(internal.TransportKey, transport)),
Value: int64(7),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
metadatatest.AssertEqualReceiverRefusedLogRecords(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(internal.ReceiverKey, receiverID.String()),
attribute.String(internal.TransportKey, transport)),
Value: int64(0),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
metadatatest.AssertEqualReceiverFailedLogRecords(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(internal.ReceiverKey, receiverID.String()),
attribute.String(internal.TransportKey, transport)),
Value: int64(0),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
}
func TestCheckReceiverProfilesViews(t *testing.T) {
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
rec, err := NewObsReport(ObsReportSettings{
ReceiverID: receiverID,
Transport: transport,
ReceiverCreateSettings: receiver.Settings{ID: receiverID, TelemetrySettings: tt.NewTelemetrySettings(), BuildInfo: component.NewDefaultBuildInfo()},
})
require.NoError(t, err)
ctx := rec.StartProfilesOp(context.Background())
require.NotNil(t, ctx)
rec.EndProfilesOp(ctx, format, 7, nil)
metadatatest.AssertEqualReceiverAcceptedProfileSamples(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(internal.ReceiverKey, receiverID.String()),
attribute.String(internal.TransportKey, transport)),
Value: int64(7),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
metadatatest.AssertEqualReceiverRefusedProfileSamples(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(internal.ReceiverKey, receiverID.String()),
attribute.String(internal.TransportKey, transport)),
Value: int64(0),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
metadatatest.AssertEqualReceiverFailedProfileSamples(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(internal.ReceiverKey, receiverID.String()),
attribute.String(internal.TransportKey, transport)),
Value: int64(0),
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
}
func testTelemetry(t *testing.T, testFunc func(t *testing.T, tt *componenttest.Telemetry)) {
tt := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tt.Shutdown(context.Background())) })
testFunc(t, tt)
}
================================================
FILE: receiver/receivertest/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: receiver/receivertest/contract_checker.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package receivertest // import "go.opentelemetry.io/collector/receiver/receivertest"
import (
"context"
"errors"
"fmt"
"maps"
"math/rand/v2"
"sync"
"sync/atomic"
"testing"
"time"
"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/consumer"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/receiver"
)
// UniqueIDAttrName is the attribute name that is used in log records/spans/datapoints as the unique identifier.
const UniqueIDAttrName = "test_id"
// UniqueIDAttrVal is the value type of the UniqueIDAttrName.
type UniqueIDAttrVal string
type Generator interface {
// Start the generator and prepare to generate. Will be followed by calls to Generate().
// Start() may be called again after Stop() is called to begin a new test scenario.
Start()
// Stop generating. There will be no more calls to Generate() until Start() is called again.
Stop()
// Generate must generate and send at least one data element (span, log record or metric data point)
// to the receiver and return a copy of generated element ids.
// The generated data must contain uniquely identifiable elements, each with a
// different value of attribute named UniqueIDAttrName.
// CreateOneLogWithID() can be used a helper to create such logs.
// May be called concurrently from multiple goroutines.
Generate() []UniqueIDAttrVal
}
type CheckConsumeContractParams struct {
T *testing.T
// Factory that allows to create a receiver.
Factory receiver.Factory
Signal pipeline.Signal
// Config of the receiver to use.
Config component.Config
// Generator that can send data to the receiver.
Generator Generator
// GenerateCount specifies the number of times to call the generator.Generate()
// for each test scenario.
GenerateCount int
// prevent unkeyed literal initialization
_ struct{}
}
// CheckConsumeContract checks the contract between the receiver and its next consumer. For the contract
// description see ../doc.go. The checker will detect violations of contract on different scenarios: on success,
// on permanent and non-permanent errors and mix of error types.
func CheckConsumeContract(params CheckConsumeContractParams) {
// Different scenarios to test for.
// The decision function defines the testing scenario (i.e. to test for
// success case or for error case or a mix of both). See for example randomErrorsConsumeDecision.
scenarios := []struct {
name string
decisionFunc func(ids idSet) error
}{
{
name: "always_succeed",
// Always succeed. We expect all data to be delivered as is.
decisionFunc: func(idSet) error { return nil },
},
{
name: "random_non_permanent_error",
decisionFunc: randomNonPermanentErrorConsumeDecision,
},
{
name: "random_permanent_error",
decisionFunc: randomPermanentErrorConsumeDecision,
},
{
name: "random_error",
decisionFunc: randomErrorsConsumeDecision,
},
}
for _, scenario := range scenarios {
params.T.Run(
scenario.name, func(*testing.T) {
checkConsumeContractScenario(params, scenario.decisionFunc)
},
)
}
}
func checkConsumeContractScenario(params CheckConsumeContractParams, decisionFunc func(ids idSet) error) {
consumer := &mockConsumer{t: params.T, consumeDecisionFunc: decisionFunc, acceptedIDs: make(idSet), droppedIDs: make(idSet)}
ctx := context.Background()
// Create and start the receiver.
var receiver component.Component
var err error
switch params.Signal {
case pipeline.SignalLogs:
receiver, err = params.Factory.CreateLogs(ctx, NewNopSettings(params.Factory.Type()), params.Config, consumer)
case pipeline.SignalTraces:
receiver, err = params.Factory.CreateTraces(ctx, NewNopSettings(params.Factory.Type()), params.Config, consumer)
case pipeline.SignalMetrics:
receiver, err = params.Factory.CreateMetrics(ctx, NewNopSettings(params.Factory.Type()), params.Config, consumer)
default:
require.FailNow(params.T, "must specify a valid DataType to test for")
}
require.NoError(params.T, err)
err = receiver.Start(ctx, componenttest.NewNopHost())
require.NoError(params.T, err)
// Begin generating data to the receiver.
generatedIDs := make(idSet)
var generatedIndex int64
var mux sync.Mutex
var wg sync.WaitGroup
const concurrency = 4
params.Generator.Start()
defer params.Generator.Stop()
// Create concurrent goroutines that use the generator.
// The total number of generator calls will be equal to params.GenerateCount.
for range concurrency {
wg.Go(func() {
for atomic.AddInt64(&generatedIndex, 1) <= int64(params.GenerateCount) {
ids := params.Generator.Generate()
require.NotEmpty(params.T, ids)
mux.Lock()
duplicates := generatedIDs.mergeSlice(ids)
mux.Unlock()
// Check that the generator works correctly. There may not be any duplicates in the
// generated data set.
require.Empty(params.T, duplicates)
}
})
}
// Wait until all generator goroutines are done.
wg.Wait()
// Wait until all data is seen by the consumer.
assert.Eventually(params.T, func() bool {
// Calculate the union of accepted and dropped data.
acceptedAndDropped, duplicates := consumer.acceptedAndDropped()
if len(duplicates) != 0 {
assert.Failf(params.T, "found duplicate elements in received and dropped data", "keys=%v", duplicates)
}
// Compare accepted+dropped with generated. Once they are equal it means all data is seen by the consumer.
missingInOther, onlyInOther := generatedIDs.compare(acceptedAndDropped)
return len(missingInOther) == 0 && len(onlyInOther) == 0
}, 5*time.Second, 10*time.Millisecond)
// Do some final checks. Need the union of accepted and dropped data again.
acceptedAndDropped, duplicates := consumer.acceptedAndDropped()
if len(duplicates) != 0 {
assert.Failf(params.T, "found duplicate elements in accepted and dropped data", "keys=%v", duplicates)
}
// Make sure generated and accepted+dropped are exactly the same.
missingInOther, onlyInOther := generatedIDs.compare(acceptedAndDropped)
if len(missingInOther) != 0 {
assert.Failf(params.T, "found elements sent that were not delivered", "keys=%v", missingInOther)
}
if len(onlyInOther) != 0 {
assert.Failf(params.T, "found elements in accepted and dropped data that was never sent", "keys=%v", onlyInOther)
}
err = receiver.Shutdown(ctx)
require.NoError(params.T, err)
// Print some stats to help debug test failures.
fmt.Printf(
"Sent %d, accepted=%d, expected dropped=%d, non-permanent errors retried=%d\n",
len(generatedIDs),
len(consumer.acceptedIDs),
len(consumer.droppedIDs),
consumer.nonPermanentFailures,
)
}
// idSet is a set of unique ids of data elements used in the test (logs, spans or metric data points).
type idSet map[UniqueIDAttrVal]bool
// compare to another set and calculate the differences from this set.
func (ds idSet) compare(other idSet) (missingInOther, onlyInOther []UniqueIDAttrVal) {
for k := range ds {
if _, ok := other[k]; !ok {
missingInOther = append(missingInOther, k)
}
}
for k := range other {
if _, ok := ds[k]; !ok {
onlyInOther = append(onlyInOther, k)
}
}
return missingInOther, onlyInOther
}
// merge another set into this one and return a list of duplicate ids.
func (ds idSet) merge(other idSet) (duplicates []UniqueIDAttrVal) {
for k, v := range other {
if _, ok := ds[k]; ok {
duplicates = append(duplicates, k)
} else {
ds[k] = v
}
}
return duplicates
}
// mergeSlice merges another set into this one and return a list of duplicate ids.
func (ds idSet) mergeSlice(other []UniqueIDAttrVal) (duplicates []UniqueIDAttrVal) {
for _, id := range other {
if _, ok := ds[id]; ok {
duplicates = append(duplicates, id)
} else {
ds[id] = true
}
}
return duplicates
}
// union computes the union of this and another sets. A new set if created to return the result.
// Also returns a list of any duplicate ids found.
func (ds idSet) union(other idSet) (union idSet, duplicates []UniqueIDAttrVal) {
union = map[UniqueIDAttrVal]bool{}
maps.Copy(union, ds)
for k, v := range other {
if _, ok := union[k]; ok {
duplicates = append(duplicates, k)
} else {
union[k] = v
}
}
return union, duplicates
}
// A function that returns a value indicating what the receiver's next consumer decides
// to do as a result of ConsumeLogs/Trace/Metrics call.
// The result of the decision function becomes the return value of ConsumeLogs/Trace/Metrics.
// Supplying different decision functions allows to test different scenarios of the contract
// between the receiver and it next consumer.
type consumeDecisionFunc func(ids idSet) error
var (
errNonPermanent = errors.New("non permanent error")
errPermanent = errors.New("permanent error")
)
// randomNonPermanentErrorConsumeDecision is a decision function that succeeds approximately
// half of the time and fails with a non-permanent error the rest of the time.
func randomNonPermanentErrorConsumeDecision(idSet) error {
if rand.Float32() < 0.5 {
return errNonPermanent
}
return nil
}
// randomPermanentErrorConsumeDecision is a decision function that succeeds approximately
// half of the time and fails with a permanent error the rest of the time.
func randomPermanentErrorConsumeDecision(idSet) error {
if rand.Float32() < 0.5 {
return consumererror.NewPermanent(errPermanent)
}
return nil
}
// randomErrorsConsumeDecision is a decision function that succeeds approximately
// a third of the time, fails with a permanent error the third of the time and fails with
// a non-permanent error the rest of the time.
func randomErrorsConsumeDecision(idSet) error {
r := rand.Float64()
third := 1.0 / 3.0
if r < third {
return consumererror.NewPermanent(errPermanent)
}
if r < 2*third {
return errNonPermanent
}
return nil
}
// mockConsumer accepts or drops the data from the receiver based on the decision made by
// consumeDecisionFunc and remembers the accepted and dropped data sets for later checks.
// mockConsumer implements all 3 consume functions: ConsumeLogs/ConsumeTraces/ConsumeMetrics
// and can be used for testing any of the 3 signals.
type mockConsumer struct {
t *testing.T
consumeDecisionFunc consumeDecisionFunc
mux sync.Mutex
acceptedIDs idSet
droppedIDs idSet
nonPermanentFailures int
}
func (m *mockConsumer) Capabilities() consumer.Capabilities {
return consumer.Capabilities{}
}
func (m *mockConsumer) ConsumeTraces(_ context.Context, data ptrace.Traces) error {
ids, err := idSetFromTraces(data)
require.NoError(m.t, err)
return m.consume(ids)
}
// idSetFromTraces computes an idSet from given ptrace.Traces. The idSet will contain ids of all spans.
func idSetFromTraces(data ptrace.Traces) (idSet, error) {
ds := map[UniqueIDAttrVal]bool{}
rss := data.ResourceSpans()
for i := 0; i < rss.Len(); i++ {
ils := rss.At(i).ScopeSpans()
for j := 0; j < ils.Len(); j++ {
ss := ils.At(j).Spans()
for k := 0; k < ss.Len(); k++ {
elem := ss.At(k)
key, exists := elem.Attributes().Get(UniqueIDAttrName)
if !exists {
return ds, fmt.Errorf("invalid data element, attribute %q is missing", UniqueIDAttrName)
}
if key.Type() != pcommon.ValueTypeStr {
return ds, fmt.Errorf("invalid data element, attribute %q is wrong type %v", UniqueIDAttrName, key.Type())
}
ds[UniqueIDAttrVal(key.Str())] = true
}
}
}
return ds, nil
}
func (m *mockConsumer) ConsumeLogs(_ context.Context, data plog.Logs) error {
ids, err := idSetFromLogs(data)
require.NoError(m.t, err)
return m.consume(ids)
}
// idSetFromLogs computes an idSet from given plog.Logs. The idSet will contain ids of all log records.
func idSetFromLogs(data plog.Logs) (idSet, error) {
ds := map[UniqueIDAttrVal]bool{}
rss := data.ResourceLogs()
for i := 0; i < rss.Len(); i++ {
ils := rss.At(i).ScopeLogs()
for j := 0; j < ils.Len(); j++ {
ss := ils.At(j).LogRecords()
for k := 0; k < ss.Len(); k++ {
elem := ss.At(k)
key, exists := elem.Attributes().Get(UniqueIDAttrName)
if !exists {
return ds, fmt.Errorf("invalid data element, attribute %q is missing", UniqueIDAttrName)
}
if key.Type() != pcommon.ValueTypeStr {
return ds, fmt.Errorf("invalid data element, attribute %q is wrong type %v", UniqueIDAttrName, key.Type())
}
ds[UniqueIDAttrVal(key.Str())] = true
}
}
}
return ds, nil
}
func (m *mockConsumer) ConsumeMetrics(_ context.Context, data pmetric.Metrics) error {
ids, err := idSetFromMetrics(data)
require.NoError(m.t, err)
return m.consume(ids)
}
// idSetFromMetrics computes an idSet from given pmetric.Metrics. The idSet will contain ids of all metric data points.
func idSetFromMetrics(data pmetric.Metrics) (idSet, error) {
ds := map[UniqueIDAttrVal]bool{}
rss := data.ResourceMetrics()
for i := 0; i < rss.Len(); i++ {
ils := rss.At(i).ScopeMetrics()
for j := 0; j < ils.Len(); j++ {
ss := ils.At(j).Metrics()
for k := 0; k < ss.Len(); k++ {
elem := ss.At(k)
switch elem.Type() {
case pmetric.MetricTypeGauge:
for l := 0; l < elem.Gauge().DataPoints().Len(); l++ {
dp := elem.Gauge().DataPoints().At(l)
if err := idSetFromDataPoint(ds, dp.Attributes()); err != nil {
return ds, err
}
}
case pmetric.MetricTypeSum:
for l := 0; l < elem.Sum().DataPoints().Len(); l++ {
dp := elem.Sum().DataPoints().At(l)
if err := idSetFromDataPoint(ds, dp.Attributes()); err != nil {
return ds, err
}
}
case pmetric.MetricTypeSummary:
for l := 0; l < elem.Summary().DataPoints().Len(); l++ {
dp := elem.Summary().DataPoints().At(l)
if err := idSetFromDataPoint(ds, dp.Attributes()); err != nil {
return ds, err
}
}
case pmetric.MetricTypeHistogram:
for l := 0; l < elem.Histogram().DataPoints().Len(); l++ {
dp := elem.Histogram().DataPoints().At(l)
if err := idSetFromDataPoint(ds, dp.Attributes()); err != nil {
return ds, err
}
}
case pmetric.MetricTypeExponentialHistogram:
for l := 0; l < elem.ExponentialHistogram().DataPoints().Len(); l++ {
dp := elem.ExponentialHistogram().DataPoints().At(l)
if err := idSetFromDataPoint(ds, dp.Attributes()); err != nil {
return ds, err
}
}
}
}
}
}
return ds, nil
}
func idSetFromDataPoint(ds map[UniqueIDAttrVal]bool, attributes pcommon.Map) error {
key, exists := attributes.Get(UniqueIDAttrName)
if !exists {
return fmt.Errorf("invalid data element, attribute %q is missing", UniqueIDAttrName)
}
if key.Type() != pcommon.ValueTypeStr {
return fmt.Errorf("invalid data element, attribute %q is wrong type %v", UniqueIDAttrName, key.Type())
}
ds[UniqueIDAttrVal(key.Str())] = true
return nil
}
// consume the elements with the specified ids, regardless of the element data type.
func (m *mockConsumer) consume(ids idSet) error {
m.mux.Lock()
defer m.mux.Unlock()
// Consult with user-defined decision function to decide what to do with the data.
if err := m.consumeDecisionFunc(ids); err != nil {
// The decision is to return an error to the receiver.
if consumererror.IsPermanent(err) {
// It is a permanent error, which means we need to drop the data.
// Remember the ids of dropped elements.
duplicates := m.droppedIDs.merge(ids)
require.Empty(m.t, duplicates, "elements that were dropped previously were sent again")
} else {
// It is a non-permanent error. Don't add it to the drop list. Remember the number of
// failures to print at the end of the test.
m.nonPermanentFailures++
}
// Return the error to the receiver.
return err
}
// The decision is a success. Remember the ids of the data in the accepted list.
duplicates := m.acceptedIDs.merge(ids)
require.Empty(m.t, duplicates, "elements that were accepted previously were sent again")
return nil
}
// Calculate union of accepted and dropped ids.
// Returns the union and the list of duplicates between the two sets (if any)
func (m *mockConsumer) acceptedAndDropped() (acceptedAndDropped idSet, duplicates []UniqueIDAttrVal) {
m.mux.Lock()
defer m.mux.Unlock()
return m.acceptedIDs.union(m.droppedIDs)
}
func CreateOneLogWithID(id UniqueIDAttrVal) plog.Logs {
data := plog.NewLogs()
data.ResourceLogs().AppendEmpty().ScopeLogs().AppendEmpty().LogRecords().AppendEmpty().Attributes().PutStr(
UniqueIDAttrName,
string(id),
)
return data
}
func CreateGaugeMetricWithID(id UniqueIDAttrVal) pmetric.Metrics {
data := pmetric.NewMetrics()
gauge := data.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics()
gauge.AppendEmpty().SetEmptyGauge().DataPoints().AppendEmpty().Attributes().PutStr(
UniqueIDAttrName,
string(id),
)
return data
}
func CreateSumMetricWithID(id UniqueIDAttrVal) pmetric.Metrics {
data := pmetric.NewMetrics()
sum := data.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics()
sum.AppendEmpty().SetEmptySum().DataPoints().AppendEmpty().Attributes().PutStr(
UniqueIDAttrName,
string(id),
)
return data
}
func CreateSummaryMetricWithID(id UniqueIDAttrVal) pmetric.Metrics {
data := pmetric.NewMetrics()
summary := data.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics()
summary.AppendEmpty().SetEmptySummary().DataPoints().AppendEmpty().Attributes().PutStr(
UniqueIDAttrName,
string(id),
)
return data
}
func CreateHistogramMetricWithID(id UniqueIDAttrVal) pmetric.Metrics {
data := pmetric.NewMetrics()
histogram := data.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics()
histogram.AppendEmpty().SetEmptyHistogram().DataPoints().AppendEmpty().Attributes().PutStr(
UniqueIDAttrName,
string(id),
)
return data
}
func CreateExponentialHistogramMetricWithID(id UniqueIDAttrVal) pmetric.Metrics {
data := pmetric.NewMetrics()
exponentialHistogram := data.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics()
exponentialHistogram.AppendEmpty().SetEmptyExponentialHistogram().DataPoints().AppendEmpty().Attributes().PutStr(
UniqueIDAttrName,
string(id),
)
return data
}
func CreateOneSpanWithID(id UniqueIDAttrVal) ptrace.Traces {
data := ptrace.NewTraces()
data.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans().AppendEmpty().Attributes().PutStr(
UniqueIDAttrName,
string(id),
)
return data
}
================================================
FILE: receiver/receivertest/contract_checker_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package receivertest
import (
"context"
"strconv"
"sync/atomic"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/receiver"
)
// This file is an example that demonstrates how to use the CheckConsumeContract() function.
// We declare a trivial example receiver, a data generator and then use them in TestConsumeContract().
type exampleReceiver struct {
nextLogsConsumer consumer.Logs
nextTracesConsumer consumer.Traces
nextMetricsConsumer consumer.Metrics
}
func (s *exampleReceiver) Start(context.Context, component.Host) error {
return nil
}
func (s *exampleReceiver) Shutdown(context.Context) error {
return nil
}
func (s *exampleReceiver) ReceiveLogs(data plog.Logs) {
// This very simple implementation demonstrates how a single items receiving should happen.
for {
err := s.nextLogsConsumer.ConsumeLogs(context.Background(), data)
if err != nil {
// The next consumer returned an error.
if !consumererror.IsPermanent(err) {
// It is not a permanent error, so we must retry sending it again. In network-based
// receivers instead we can ask our sender to re-retry the same data again later.
// We may also pause here a bit if we don't want to hammer the next consumer.
continue
}
}
// If we are hear either the ConsumeLogs returned success or it returned a permanent error.
// In either case we don't need to retry the same data, we are done.
return
}
}
func (s *exampleReceiver) ReceiveMetrics(data pmetric.Metrics) {
// This very simple implementation demonstrates how a single items receiving should happen.
for {
err := s.nextMetricsConsumer.ConsumeMetrics(context.Background(), data)
if err != nil {
// The next consumer returned an error.
if !consumererror.IsPermanent(err) {
// It is not a permanent error, so we must retry sending it again. In network-based
// receivers instead we can ask our sender to re-retry the same data again later.
// We may also pause here a bit if we don't want to hammer the next consumer.
continue
}
}
// If we are hear either the ConsumeLogs returned success or it returned a permanent error.
// In either case we don't need to retry the same data, we are done.
return
}
}
func (s *exampleReceiver) ReceiveTraces(data ptrace.Traces) {
// This very simple implementation demonstrates how a single items receiving should happen.
for {
err := s.nextTracesConsumer.ConsumeTraces(context.Background(), data)
if err != nil {
// The next consumer returned an error.
if !consumererror.IsPermanent(err) {
// It is not a permanent error, so we must retry sending it again. In network-based
// receivers instead we can ask our sender to re-retry the same data again later.
// We may also pause here a bit if we don't want to hammer the next consumer.
continue
}
}
// If we are hear either the ConsumeLogs returned success or it returned a permanent error.
// In either case we don't need to retry the same data, we are done.
return
}
}
// A config for exampleReceiver.
type exampleReceiverConfig struct {
generator Generator
}
// A generator that can send data to exampleReceiver.
type exampleLogGenerator struct {
t *testing.T
receiver *exampleReceiver
sequenceNum int64
}
func (g *exampleLogGenerator) Start() {
g.sequenceNum = 0
}
func (g *exampleLogGenerator) Stop() {}
func (g *exampleLogGenerator) Generate() []UniqueIDAttrVal {
// Make sure the id is atomically incremented. Generate() may be called concurrently.
id := UniqueIDAttrVal(strconv.FormatInt(atomic.AddInt64(&g.sequenceNum, 1), 10))
data := CreateOneLogWithID(id)
// Send the generated data to the receiver.
g.receiver.ReceiveLogs(data)
// And return the ids for bookkeeping by the test.
return []UniqueIDAttrVal{id}
}
// A generator that can send data to exampleReceiver.
type exampleTraceGenerator struct {
t *testing.T
receiver *exampleReceiver
sequenceNum int64
}
func (g *exampleTraceGenerator) Start() {
g.sequenceNum = 0
}
func (g *exampleTraceGenerator) Stop() {}
func (g *exampleTraceGenerator) Generate() []UniqueIDAttrVal {
// Make sure the id is atomically incremented. Generate() may be called concurrently.
id := UniqueIDAttrVal(strconv.FormatInt(atomic.AddInt64(&g.sequenceNum, 1), 10))
data := CreateOneSpanWithID(id)
// Send the generated data to the receiver.
g.receiver.ReceiveTraces(data)
// And return the ids for bookkeeping by the test.
return []UniqueIDAttrVal{id}
}
// A generator that can send data to exampleReceiver.
type exampleMetricGenerator struct {
t *testing.T
receiver *exampleReceiver
sequenceNum int64
}
func (g *exampleMetricGenerator) Start() {
g.sequenceNum = 0
}
func (g *exampleMetricGenerator) Stop() {}
func (g *exampleMetricGenerator) Generate() []UniqueIDAttrVal {
// Make sure the id is atomically incremented. Generate() may be called concurrently.
next := atomic.AddInt64(&g.sequenceNum, 1)
id := UniqueIDAttrVal(strconv.FormatInt(next, 10))
var data pmetric.Metrics
switch next % 5 {
case 0:
data = CreateGaugeMetricWithID(id)
case 1:
data = CreateSumMetricWithID(id)
case 2:
data = CreateSummaryMetricWithID(id)
case 3:
data = CreateHistogramMetricWithID(id)
case 4:
data = CreateExponentialHistogramMetricWithID(id)
}
// Send the generated data to the receiver.
g.receiver.ReceiveMetrics(data)
// And return the ids for bookkeeping by the test.
return []UniqueIDAttrVal{id}
}
func newExampleFactory() receiver.Factory {
return receiver.NewFactory(
component.MustNewType("example_receiver"),
func() component.Config {
return &exampleReceiverConfig{}
},
receiver.WithLogs(createLog, component.StabilityLevelBeta),
receiver.WithMetrics(createMetric, component.StabilityLevelBeta),
receiver.WithTraces(createTrace, component.StabilityLevelBeta),
)
}
func createTrace(_ context.Context, _ receiver.Settings, cfg component.Config, consumer consumer.Traces) (receiver.Traces, error) {
rcv := &exampleReceiver{nextTracesConsumer: consumer}
cfg.(*exampleReceiverConfig).generator.(*exampleTraceGenerator).receiver = rcv
return rcv, nil
}
func createMetric(_ context.Context, _ receiver.Settings, cfg component.Config, consumer consumer.Metrics) (receiver.Metrics, error) {
rcv := &exampleReceiver{nextMetricsConsumer: consumer}
cfg.(*exampleReceiverConfig).generator.(*exampleMetricGenerator).receiver = rcv
return rcv, nil
}
func createLog(
_ context.Context,
_ receiver.Settings,
cfg component.Config,
consumer consumer.Logs,
) (receiver.Logs, error) {
rcv := &exampleReceiver{nextLogsConsumer: consumer}
cfg.(*exampleReceiverConfig).generator.(*exampleLogGenerator).receiver = rcv
return rcv, nil
}
// TestConsumeContract is an example of testing of the receiver for the contract between the
// receiver and next consumer.
func TestConsumeContract(t *testing.T) {
// Number of log records to send per scenario.
const logsPerTest = 100
generator := &exampleLogGenerator{t: t}
cfg := &exampleReceiverConfig{generator: generator}
params := CheckConsumeContractParams{
T: t,
Factory: newExampleFactory(),
Signal: pipeline.SignalLogs,
Config: cfg,
Generator: generator,
GenerateCount: logsPerTest,
}
// Run the contract checker. This will trigger test failures if any problems are found.
CheckConsumeContract(params)
}
// TestConsumeMetricsContract is an example of testing of the receiver for the contract between the
// receiver and next consumer.
func TestConsumeMetricsContract(t *testing.T) {
// Number of metric data points to send per scenario.
const metricsPerTest = 100
generator := &exampleMetricGenerator{t: t}
cfg := &exampleReceiverConfig{generator: generator}
params := CheckConsumeContractParams{
T: t,
Factory: newExampleFactory(),
Signal: pipeline.SignalMetrics,
Config: cfg,
Generator: generator,
GenerateCount: metricsPerTest,
}
// Run the contract checker. This will trigger test failures if any problems are found.
CheckConsumeContract(params)
}
// TestConsumeTracesContract is an example of testing of the receiver for the contract between the
// receiver and next consumer.
func TestConsumeTracesContract(t *testing.T) {
// Number of trace spans to send per scenario.
const spansPerTest = 100
generator := &exampleTraceGenerator{t: t}
cfg := &exampleReceiverConfig{generator: generator}
params := CheckConsumeContractParams{
T: t,
Factory: newExampleFactory(),
Signal: pipeline.SignalTraces,
Config: cfg,
Generator: generator,
GenerateCount: spansPerTest,
}
// Run the contract checker. This will trigger test failures if any problems are found.
CheckConsumeContract(params)
}
func TestIDSetFromDataPoint(t *testing.T) {
require.Error(t, idSetFromDataPoint(map[UniqueIDAttrVal]bool{}, pcommon.NewMap()))
m := pcommon.NewMap()
m.PutStr("foo", "bar")
require.Error(t, idSetFromDataPoint(map[UniqueIDAttrVal]bool{}, m))
m.PutInt(UniqueIDAttrName, 64)
require.Error(t, idSetFromDataPoint(map[UniqueIDAttrVal]bool{}, m))
m.PutStr(UniqueIDAttrName, "myid")
result := map[UniqueIDAttrVal]bool{}
require.NoError(t, idSetFromDataPoint(result, m))
require.True(t, result["myid"])
}
func TestBadMetricPoint(t *testing.T) {
for _, test := range []struct {
name string
metrics pmetric.Metrics
}{
{
name: "gauge",
metrics: func() pmetric.Metrics {
m := pmetric.NewMetrics()
m.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty().SetEmptyGauge().DataPoints().AppendEmpty()
return m
}(),
},
{
name: "sum",
metrics: func() pmetric.Metrics {
m := pmetric.NewMetrics()
m.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty().SetEmptySum().DataPoints().AppendEmpty()
return m
}(),
},
{
name: "summary",
metrics: func() pmetric.Metrics {
m := pmetric.NewMetrics()
m.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty().SetEmptySummary().DataPoints().AppendEmpty()
return m
}(),
},
{
name: "histogram",
metrics: func() pmetric.Metrics {
m := pmetric.NewMetrics()
m.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty().SetEmptyHistogram().DataPoints().AppendEmpty()
return m
}(),
},
{
name: "exponential histogram",
metrics: func() pmetric.Metrics {
m := pmetric.NewMetrics()
m.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty().SetEmptyExponentialHistogram().DataPoints().AppendEmpty()
return m
}(),
},
} {
t.Run(test.name, func(t *testing.T) {
_, err := idSetFromMetrics(test.metrics)
require.Error(t, err)
})
}
}
================================================
FILE: receiver/receivertest/go.mod
================================================
module go.opentelemetry.io/collector/receiver/receivertest
go 1.25.0
require (
github.com/google/uuid v1.6.0
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.opentelemetry.io/collector/consumer v1.54.0
go.opentelemetry.io/collector/consumer/consumererror v0.148.0
go.opentelemetry.io/collector/consumer/consumertest v0.148.0
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/collector/pipeline v1.54.0
go.opentelemetry.io/collector/receiver v1.54.0
go.opentelemetry.io/collector/receiver/xreceiver v0.148.0
go.uber.org/goleak v1.3.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
go.opentelemetry.io/collector/pdata/pprofile v0.148.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
golang.org/x/sys v0.41.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/receiver => ../
replace go.opentelemetry.io/collector/pipeline => ../../pipeline
replace go.opentelemetry.io/collector/pdata/testdata => ../../pdata/testdata
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest
replace go.opentelemetry.io/collector/receiver/xreceiver => ../xreceiver
replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer
replace go.opentelemetry.io/collector/consumer/consumererror => ../../consumer/consumererror
replace go.opentelemetry.io/collector/consumer => ../../consumer
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile
replace go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
================================================
FILE: receiver/receivertest/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: receiver/receivertest/metadata.yaml
================================================
type: receiver/receivertest
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: receiver/receivertest/nop_receiver.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package receivertest // import "go.opentelemetry.io/collector/receiver/receivertest"
import (
"context"
"github.com/google/uuid"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/xreceiver"
)
var NopType = component.MustNewType("nop")
// NewNopSettings returns a new nop settings for Create*Receiver functions with the given type.
func NewNopSettings(typ component.Type) receiver.Settings {
return receiver.Settings{
ID: component.NewIDWithName(typ, uuid.NewString()),
TelemetrySettings: componenttest.NewNopTelemetrySettings(),
BuildInfo: component.NewDefaultBuildInfo(),
}
}
// NewNopFactory returns a receiver.Factory that constructs nop receivers supporting all data types.
func NewNopFactory() receiver.Factory {
return xreceiver.NewFactory(
NopType,
func() component.Config { return &nopConfig{} },
xreceiver.WithTraces(createTraces, component.StabilityLevelStable),
xreceiver.WithMetrics(createMetrics, component.StabilityLevelStable),
xreceiver.WithLogs(createLogs, component.StabilityLevelStable),
xreceiver.WithProfiles(createProfiles, component.StabilityLevelAlpha),
)
}
type nopConfig struct{}
func createTraces(context.Context, receiver.Settings, component.Config, consumer.Traces) (receiver.Traces, error) {
return nopInstance, nil
}
func createMetrics(context.Context, receiver.Settings, component.Config, consumer.Metrics) (receiver.Metrics, error) {
return nopInstance, nil
}
func createLogs(context.Context, receiver.Settings, component.Config, consumer.Logs) (receiver.Logs, error) {
return nopInstance, nil
}
func createProfiles(context.Context, receiver.Settings, component.Config, xconsumer.Profiles) (xreceiver.Profiles, error) {
return nopInstance, nil
}
var nopInstance = &nopReceiver{}
// nopReceiver acts as a receiver for testing purposes.
type nopReceiver struct {
component.StartFunc
component.ShutdownFunc
}
================================================
FILE: receiver/receivertest/nop_receiver_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package receivertest
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/receiver/xreceiver"
)
func TestNewNopFactory(t *testing.T) {
factory := NewNopFactory()
require.NotNil(t, factory)
assert.Equal(t, "nop", factory.Type().String())
cfg := factory.CreateDefaultConfig()
assert.Equal(t, &nopConfig{}, cfg)
traces, err := factory.CreateTraces(context.Background(), NewNopSettings(NopType), cfg, consumertest.NewNop())
require.NoError(t, err)
assert.NoError(t, traces.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, traces.Shutdown(context.Background()))
metrics, err := factory.CreateMetrics(context.Background(), NewNopSettings(NopType), cfg, consumertest.NewNop())
require.NoError(t, err)
assert.NoError(t, metrics.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, metrics.Shutdown(context.Background()))
logs, err := factory.CreateLogs(context.Background(), NewNopSettings(NopType), cfg, consumertest.NewNop())
require.NoError(t, err)
assert.NoError(t, logs.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, logs.Shutdown(context.Background()))
profiles, err := factory.(xreceiver.Factory).CreateProfiles(context.Background(), NewNopSettings(NopType), cfg, consumertest.NewNop())
require.NoError(t, err)
assert.NoError(t, profiles.Start(context.Background(), componenttest.NewNopHost()))
assert.NoError(t, profiles.Shutdown(context.Background()))
}
================================================
FILE: receiver/receivertest/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package receivertest
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: receiver/xreceiver/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: receiver/xreceiver/go.mod
================================================
module go.opentelemetry.io/collector/receiver/xreceiver
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/consumer/consumertest v0.148.0
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0
go.opentelemetry.io/collector/internal/componentalias v0.148.0
go.opentelemetry.io/collector/pipeline v1.54.0
go.opentelemetry.io/collector/receiver v1.54.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/collector/consumer v1.54.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/pdata v1.54.0 // indirect
go.opentelemetry.io/collector/pdata/pprofile v0.148.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/receiver => ../
replace go.opentelemetry.io/collector/consumer => ../../consumer
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer
replace go.opentelemetry.io/collector/pdata/testdata => ../../pdata/testdata
replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest
replace go.opentelemetry.io/collector/pipeline => ../../pipeline
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
================================================
FILE: receiver/xreceiver/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: receiver/xreceiver/metadata.yaml
================================================
type: xreceiver
github_project: open-telemetry/opentelemetry-collector
status:
class: pkg
codeowners:
active:
- mx-psi
- dmathieu
stability:
alpha: [profiles]
================================================
FILE: receiver/xreceiver/receiver.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xreceiver // import "go.opentelemetry.io/collector/receiver/xreceiver"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/internal/componentalias"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/receiver"
)
// Profiles receiver receives profiles.
// Its purpose is to translate data from any format to the collector's internal profile format.
// Profiles receiver feeds a xconsumer.Profiles with data.
//
// For example, it could be a pprof data source which translates pprof profiles into pprofile.Profiles.
type Profiles interface {
component.Component
}
// Factory is a factory interface for receivers.
//
// This interface cannot be directly implemented. Implementations must
// use the NewFactory to implement it.
type Factory interface {
receiver.Factory
// CreateProfiles creates a Profiles based on this config.
// If the receiver type does not support tracing or if the config is not valid
// an error will be returned instead. `next` is never nil.
CreateProfiles(ctx context.Context, set receiver.Settings, cfg component.Config, next xconsumer.Profiles) (Profiles, error)
// ProfilesStability gets the stability level of the Profiles receiver.
ProfilesStability() component.StabilityLevel
}
// CreateProfilesFunc is the equivalent of Factory.CreateProfiles.
type CreateProfilesFunc func(context.Context, receiver.Settings, component.Config, xconsumer.Profiles) (Profiles, error)
// FactoryOption apply changes to Factory.
type FactoryOption interface {
// applyOption applies the option.
applyOption(o *factory)
}
// factoryOptionFunc is a FactoryOption created through a function.
type factoryOptionFunc func(*factory)
func (f factoryOptionFunc) applyOption(o *factory) {
f(o)
}
type factory struct {
receiver.Factory
componentalias.TypeAliasHolder
opts []receiver.FactoryOption
createProfilesFunc CreateProfilesFunc
profilesStabilityLevel component.StabilityLevel
}
func (f *factory) ProfilesStability() component.StabilityLevel {
return f.profilesStabilityLevel
}
func (f *factory) CreateProfiles(ctx context.Context, set receiver.Settings, cfg component.Config, next xconsumer.Profiles) (Profiles, error) {
if f.createProfilesFunc == nil {
return nil, pipeline.ErrSignalNotSupported
}
if err := componentalias.ValidateComponentType(f.Factory, set.ID); err != nil {
return nil, err
}
return f.createProfilesFunc(ctx, set, cfg, next)
}
// WithTraces overrides the default "error not supported" implementation for Factory.CreateTraces and the default "undefined" stability level.
func WithTraces(createTraces receiver.CreateTracesFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.opts = append(o.opts, receiver.WithTraces(createTraces, sl))
})
}
// WithMetrics overrides the default "error not supported" implementation for Factory.CreateMetrics and the default "undefined" stability level.
func WithMetrics(createMetrics receiver.CreateMetricsFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.opts = append(o.opts, receiver.WithMetrics(createMetrics, sl))
})
}
// WithLogs overrides the default "error not supported" implementation for Factory.CreateLogs and the default "undefined" stability level.
func WithLogs(createLogs receiver.CreateLogsFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.opts = append(o.opts, receiver.WithLogs(createLogs, sl))
})
}
// WithProfiles overrides the default "error not supported" implementation for Factory.CreateProfiles and the default "undefined" stability level.
func WithProfiles(createProfiles CreateProfilesFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.profilesStabilityLevel = sl
o.createProfilesFunc = createProfiles
})
}
// WithDeprecatedTypeAlias configures a deprecated type alias for the receiver. Only one alias is supported per receiver.
// When the alias is used in configuration, a deprecation warning is automatically logged.
func WithDeprecatedTypeAlias(alias component.Type) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.SetDeprecatedAlias(alias)
})
}
// NewFactory creates a wrapped receiver.Factory with experimental capabilities.
func NewFactory(cfgType component.Type, createDefaultConfig component.CreateDefaultConfigFunc, options ...FactoryOption) Factory {
f := &factory{TypeAliasHolder: componentalias.NewTypeAliasHolder()}
for _, opt := range options {
opt.applyOption(f)
}
f.Factory = receiver.NewFactory(cfgType, createDefaultConfig, f.opts...)
f.Factory.(componentalias.TypeAliasHolder).SetDeprecatedAlias(f.DeprecatedAlias())
return f
}
================================================
FILE: receiver/xreceiver/receiver_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xreceiver
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/internal/componentalias"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/internal"
)
var testID = component.MustNewID("test")
func TestNewFactoryWithProfiles(t *testing.T) {
testType := component.MustNewType("test")
defaultCfg := struct{}{}
factory := NewFactory(
testType,
func() component.Config { return &defaultCfg },
WithProfiles(createProfiles, component.StabilityLevelAlpha),
)
assert.Equal(t, testType, factory.Type())
assert.EqualValues(t, &defaultCfg, factory.CreateDefaultConfig())
assert.Equal(t, component.StabilityLevelAlpha, factory.ProfilesStability())
_, err := factory.CreateProfiles(context.Background(), receiver.Settings{ID: testID}, &defaultCfg, nil)
require.NoError(t, err)
wrongID := component.MustNewID("wrong")
wrongIDErrStr := internal.ErrIDMismatch(wrongID, testType).Error()
_, err = factory.CreateProfiles(context.Background(), receiver.Settings{ID: wrongID}, &defaultCfg, nil)
assert.EqualError(t, err, wrongIDErrStr)
}
var nopInstance = &nopReceiver{
Consumer: consumertest.NewNop(),
}
// nopReceiver stores consumed traces and metrics for testing purposes.
type nopReceiver struct {
component.StartFunc
component.ShutdownFunc
consumertest.Consumer
}
func createProfiles(context.Context, receiver.Settings, component.Config, xconsumer.Profiles) (Profiles, error) {
return nopInstance, nil
}
func TestNewFactoryWithDeprecatedAlias(t *testing.T) {
testType := component.MustNewType("newname")
aliasType := component.MustNewType("oldname")
defaultCfg := struct{}{}
f := NewFactory(
testType,
func() component.Config { return &defaultCfg },
WithProfiles(createProfiles, component.StabilityLevelAlpha),
WithDeprecatedTypeAlias(aliasType),
)
assert.Equal(t, testType, f.Type())
assert.Equal(t, aliasType, f.(*factory).Factory.(componentalias.TypeAliasHolder).DeprecatedAlias())
assert.EqualValues(t, &defaultCfg, f.CreateDefaultConfig())
_, err := f.CreateProfiles(context.Background(), receiver.Settings{ID: component.MustNewID("newname")}, &defaultCfg, consumertest.NewNop())
require.NoError(t, err)
_, err = f.CreateProfiles(context.Background(), receiver.Settings{ID: component.MustNewID("oldname")}, &defaultCfg, consumertest.NewNop())
require.NoError(t, err)
_, err = f.CreateProfiles(context.Background(), receiver.Settings{ID: component.MustNewID("wrongname")}, &defaultCfg, consumertest.NewNop())
require.Error(t, err)
}
================================================
FILE: renovate.json
================================================
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"labels": [
"renovatebot",
"dependencies"
],
"constraints": {
"go": "1.25"
},
"extends": [
"config:recommended",
"helpers:pinGitHubActionDigests"
],
"schedule": [
"on tuesday"
],
"packageRules": [
{
"matchManagers": [
"gomod"
],
"matchUpdateTypes": [
"pin",
"pinDigest",
"digest",
"lockFileMaintenance",
"rollback",
"bump",
"replacement"
],
"enabled": false
},
{
"matchManagers": [
"gomod"
],
"matchUpdateTypes": [
"major"
],
"prBodyNotes": [
":warning: MAJOR VERSION UPDATE :warning: - please manually update this package"
],
"labels": [
"dependency-major-update"
]
},
{
"matchManagers": [
"dockerfile"
],
"groupName": "dockerfile deps"
},
{
"matchManagers": [
"docker-compose"
],
"groupName": "docker-compose deps"
},
{
"matchManagers": [
"github-actions"
],
"groupName": "github-actions deps"
},
{
"matchManagers": [
"gomod"
],
"matchSourceUrls": [
"https://github.com/open-telemetry/opentelemetry-go-contrib"
],
"groupName": "All opentelemetry-go-contrib packages"
},
{
"matchManagers": [
"gomod"
],
"groupName": "All go.opentelemetry.io/contrib packages",
"matchSourceUrls": [
"https://go.opentelemetry.io/otel{/,}**"
],
"allowedVersions": "!/v0.59.*/"
},
{
"matchManagers": [
"gomod"
],
"groupName": "All google.golang.org packages",
"matchPackageNames": [
"google.golang.org{/,}**"
]
},
{
"matchManagers": [
"gomod"
],
"groupName": "All golang.org/x packages",
"matchPackageNames": [
"golang.org/x{/,}**"
]
},
{
"matchManagers": [
"gomod"
],
"groupName": "All github.com/knadh/koanf packages",
"matchPackageNames": [
"github.com/knadh/koanf{/,}**"
]
},
{
"matchManagers": [
"gomod"
],
"groupName": "All go.opentelemetry.io/collector packages",
"matchPackageNames": [
"go.opentelemetry.io/collector{/,}**"
]
},
{
"matchManagers": [
"gomod"
],
"groupName": "All go.opentelemetry.io/build-tools packages",
"matchPackageNames": [
"go.opentelemetry.io/build-tools{/,}**"
]
},
{
"matchManagers": [
"gomod"
],
"matchDepTypes": [
"toolchain"
],
"enabled": false
}
],
"ignoreDeps": [
"github.com/mattn/go-ieproxy"
],
"prConcurrentLimit": 200,
"suppressNotifications": [
"prEditedNotification"
],
"postUpdateOptions": [
"gomodTidy"
]
}
================================================
FILE: reports/distributions/contrib.yaml
================================================
name: contrib
url: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
maintainers: []
components:
connector:
- forward
exporter:
- debug
- nop
- otlp_grpc
- otlp_http
extension:
- zpages
pkg:
- service
processor:
- batch
- memory_limiter
receiver:
- nop
- otlp
================================================
FILE: reports/distributions/core.yaml
================================================
name: core
url: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol
maintainers: []
components:
connector:
- forward
exporter:
- debug
- nop
- otlp_grpc
- otlp_http
extension:
- zpages
pkg:
- service
processor:
- batch
- memory_limiter
receiver:
- nop
- otlp
================================================
FILE: reports/distributions/k8s.yaml
================================================
name: k8s
url: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-k8s
maintainers: []
components:
connector:
- forward
exporter:
- debug
- nop
- otlp_grpc
- otlp_http
extension:
- zpages
processor:
- batch
- memory_limiter
receiver:
- otlp
================================================
FILE: reports/distributions/otlp.yaml
================================================
name: otlp
url: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-otlp
maintainers: []
components:
exporter:
- otlp_grpc
- otlp_http
receiver:
- otlp
================================================
FILE: scraper/Makefile
================================================
include ../Makefile.Common
================================================
FILE: scraper/README.md
================================================
# General Information
A scraper defines how to connect and scrape telemetry data from an external source.
| Status | |
| ------------- |-----------|
| Stability | [development]: metrics, logs |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Apkg%2F) [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aclosed+is%3Aissue+label%3Apkg%2F) |
[development]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#development
================================================
FILE: scraper/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:generate mdatagen metadata.yaml
// Package scraper allows to define pull based receivers that can be configured using the scraperreceiver.
package scraper // import "go.opentelemetry.io/collector/scraper"
================================================
FILE: scraper/factory.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package scraper // import "go.opentelemetry.io/collector/scraper"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pipeline"
)
// Settings configures scraper creators.
type Settings struct {
// ID returns the ID of the component that will be created.
ID component.ID
component.TelemetrySettings
// BuildInfo can be used by components for informational purposes.
BuildInfo component.BuildInfo
// prevent unkeyed literal initialization
_ struct{}
}
// Factory is factory interface for scrapers.
//
// This interface cannot be directly implemented. Implementations must
// use the NewFactory to implement it.
type Factory interface {
component.Factory
// CreateLogs creates a Logs scraper based on this config.
// If the scraper type does not support logs,
// this function returns the error [pipeline.ErrSignalNotSupported].
CreateLogs(ctx context.Context, set Settings, cfg component.Config) (Logs, error)
// CreateMetrics creates a Metrics scraper based on this config.
// If the scraper type does not support metrics,
// this function returns the error [pipeline.ErrSignalNotSupported].
CreateMetrics(ctx context.Context, set Settings, cfg component.Config) (Metrics, error)
// LogsStability gets the stability level of the Logs scraper.
LogsStability() component.StabilityLevel
// MetricsStability gets the stability level of the Metrics scraper.
MetricsStability() component.StabilityLevel
unexportedFactoryFunc()
}
// FactoryOption apply changes to Options.
type FactoryOption interface {
// applyOption applies the option.
applyOption(o *factory)
}
var _ FactoryOption = (*factoryOptionFunc)(nil)
// factoryOptionFunc is a FactoryOption created through a function.
type factoryOptionFunc func(*factory)
func (f factoryOptionFunc) applyOption(o *factory) {
f(o)
}
type factory struct {
cfgType component.Type
component.CreateDefaultConfigFunc
createLogsFunc CreateLogsFunc
createMetricsFunc CreateMetricsFunc
logsStabilityLevel component.StabilityLevel
metricsStabilityLevel component.StabilityLevel
}
func (f *factory) Type() component.Type {
return f.cfgType
}
func (f *factory) unexportedFactoryFunc() {}
func (f *factory) LogsStability() component.StabilityLevel {
return f.logsStabilityLevel
}
func (f *factory) MetricsStability() component.StabilityLevel {
return f.metricsStabilityLevel
}
func (f *factory) CreateLogs(ctx context.Context, set Settings, cfg component.Config) (Logs, error) {
if f.createLogsFunc == nil {
return nil, pipeline.ErrSignalNotSupported
}
return f.createLogsFunc(ctx, set, cfg)
}
func (f *factory) CreateMetrics(ctx context.Context, set Settings, cfg component.Config) (Metrics, error) {
if f.createMetricsFunc == nil {
return nil, pipeline.ErrSignalNotSupported
}
return f.createMetricsFunc(ctx, set, cfg)
}
// CreateLogsFunc is the equivalent of Factory.CreateLogs().
type CreateLogsFunc func(context.Context, Settings, component.Config) (Logs, error)
// CreateMetricsFunc is the equivalent of Factory.CreateMetrics().
type CreateMetricsFunc func(context.Context, Settings, component.Config) (Metrics, error)
// WithLogs overrides the default "error not supported" implementation for CreateLogs and the default "undefined" stability level.
func WithLogs(createLogs CreateLogsFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.logsStabilityLevel = sl
o.createLogsFunc = createLogs
})
}
// WithMetrics overrides the default "error not supported" implementation for CreateMetrics and the default "undefined" stability level.
func WithMetrics(createMetrics CreateMetricsFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.metricsStabilityLevel = sl
o.createMetricsFunc = createMetrics
})
}
// NewFactory returns a Factory.
func NewFactory(cfgType component.Type, createDefaultConfig component.CreateDefaultConfigFunc, options ...FactoryOption) Factory {
f := &factory{
cfgType: cfgType,
CreateDefaultConfigFunc: createDefaultConfig,
}
for _, opt := range options {
opt.applyOption(f)
}
return f
}
================================================
FILE: scraper/factory_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package scraper
import (
"context"
"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/pipeline"
)
var testType = component.MustNewType("test")
func nopSettings() Settings {
return Settings{
ID: component.NewID(testType),
TelemetrySettings: componenttest.NewNopTelemetrySettings(),
}
}
func TestNewFactory(t *testing.T) {
defaultCfg := struct{}{}
f := NewFactory(
testType,
func() component.Config { return &defaultCfg })
assert.Equal(t, testType, f.Type())
assert.EqualValues(t, &defaultCfg, f.CreateDefaultConfig())
_, err := f.CreateLogs(context.Background(), nopSettings(), &defaultCfg)
require.ErrorIs(t, err, pipeline.ErrSignalNotSupported)
_, err = f.CreateMetrics(context.Background(), nopSettings(), &defaultCfg)
require.ErrorIs(t, err, pipeline.ErrSignalNotSupported)
}
func TestNewFactoryWithOptions(t *testing.T) {
testType := component.MustNewType("test")
defaultCfg := struct{}{}
f := NewFactory(
testType,
func() component.Config { return &defaultCfg },
WithLogs(createLogs, component.StabilityLevelAlpha),
WithMetrics(createMetrics, component.StabilityLevelAlpha))
assert.Equal(t, testType, f.Type())
assert.EqualValues(t, &defaultCfg, f.CreateDefaultConfig())
assert.Equal(t, component.StabilityLevelAlpha, f.LogsStability())
_, err := f.CreateLogs(context.Background(), Settings{}, &defaultCfg)
require.NoError(t, err)
assert.Equal(t, component.StabilityLevelAlpha, f.MetricsStability())
_, err = f.CreateMetrics(context.Background(), Settings{}, &defaultCfg)
require.NoError(t, err)
}
func createLogs(context.Context, Settings, component.Config) (Logs, error) {
return NewLogs(newTestScrapeLogsFunc(nil))
}
func createMetrics(context.Context, Settings, component.Config) (Metrics, error) {
return NewMetrics(newTestScrapeMetricsFunc(nil))
}
================================================
FILE: scraper/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package scraper
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: scraper/go.mod
================================================
module go.opentelemetry.io/collector/scraper
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/collector/pipeline v1.54.0
go.uber.org/goleak v1.3.0
go.uber.org/multierr v1.11.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/zap v1.27.1 // indirect
golang.org/x/sys v0.41.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/pipeline => ../pipeline
replace go.opentelemetry.io/collector/pdata => ../pdata
replace go.opentelemetry.io/collector/component => ../component
replace go.opentelemetry.io/collector/component/componenttest => ../component/componenttest
replace go.opentelemetry.io/collector/featuregate => ../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../internal/testutil
================================================
FILE: scraper/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: scraper/logs.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package scraper // import "go.opentelemetry.io/collector/scraper"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/plog"
)
// Logs is the base interface for logs scrapers.
type Logs interface {
component.Component
// ScrapeLogs is the base interface to indicate that how should logs be scraped.
ScrapeLogs(context.Context) (plog.Logs, error)
}
// ScrapeLogsFunc is a helper function that is similar to Logs.ScrapeLogs.
type ScrapeLogsFunc ScrapeFunc[plog.Logs]
func (sf ScrapeLogsFunc) ScrapeLogs(ctx context.Context) (plog.Logs, error) {
return sf(ctx)
}
type logs struct {
baseScraper
ScrapeLogsFunc
}
// NewLogs creates a new Logs scraper.
func NewLogs(scrape ScrapeLogsFunc, options ...Option) (Logs, error) {
if scrape == nil {
return nil, errNilFunc
}
bs := &logs{
baseScraper: newBaseScraper(options),
ScrapeLogsFunc: scrape,
}
return bs, nil
}
================================================
FILE: scraper/logs_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package scraper
import (
"context"
"errors"
"sync"
"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/pdata/plog"
)
func TestNewLogs(t *testing.T) {
mp, err := NewLogs(newTestScrapeLogsFunc(nil))
require.NoError(t, err)
require.NoError(t, mp.Start(context.Background(), componenttest.NewNopHost()))
md, err := mp.ScrapeLogs(context.Background())
require.NoError(t, err)
assert.Equal(t, plog.NewLogs(), md)
require.NoError(t, mp.Shutdown(context.Background()))
}
func TestNewLogs_WithOptions(t *testing.T) {
want := errors.New("my_error")
mp, err := NewLogs(newTestScrapeLogsFunc(nil),
WithStart(func(context.Context, component.Host) error { return want }),
WithShutdown(func(context.Context) error { return want }))
require.NoError(t, err)
assert.Equal(t, want, mp.Start(context.Background(), componenttest.NewNopHost()))
assert.Equal(t, want, mp.Shutdown(context.Background()))
}
func TestNewLogs_NilRequiredFields(t *testing.T) {
_, err := NewLogs(nil)
require.Error(t, err)
}
func TestNewLogs_ProcessLogsError(t *testing.T) {
want := errors.New("my_error")
mp, err := NewLogs(newTestScrapeLogsFunc(want))
require.NoError(t, err)
_, err = mp.ScrapeLogs(context.Background())
require.ErrorIs(t, err, want)
}
func TestLogsConcurrency(t *testing.T) {
mp, err := NewLogs(newTestScrapeLogsFunc(nil))
require.NoError(t, err)
require.NoError(t, mp.Start(context.Background(), componenttest.NewNopHost()))
var wg sync.WaitGroup
for range 10 {
wg.Go(func() {
for range 10000 {
_, errScrape := mp.ScrapeLogs(context.Background())
assert.NoError(t, errScrape)
}
})
}
wg.Wait()
require.NoError(t, mp.Shutdown(context.Background()))
}
func newTestScrapeLogsFunc(retError error) ScrapeLogsFunc {
return func(_ context.Context) (plog.Logs, error) {
return plog.NewLogs(), retError
}
}
================================================
FILE: scraper/metadata.yaml
================================================
type: scraper
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
stability:
development: [metrics, logs]
================================================
FILE: scraper/metrics.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package scraper // import "go.opentelemetry.io/collector/scraper"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pmetric"
)
// Metrics is the base interface for metrics scrapers.
type Metrics interface {
component.Component
ScrapeMetrics(context.Context) (pmetric.Metrics, error)
}
// ScrapeMetricsFunc is a helper function that is similar to Metrics.ScrapeMetrics.
type ScrapeMetricsFunc ScrapeFunc[pmetric.Metrics]
func (sf ScrapeMetricsFunc) ScrapeMetrics(ctx context.Context) (pmetric.Metrics, error) {
return sf(ctx)
}
type metrics struct {
baseScraper
ScrapeMetricsFunc
}
// NewMetrics creates a new Metrics scraper.
func NewMetrics(scrape ScrapeMetricsFunc, options ...Option) (Metrics, error) {
if scrape == nil {
return nil, errNilFunc
}
bs := &metrics{
baseScraper: newBaseScraper(options),
ScrapeMetricsFunc: scrape,
}
return bs, nil
}
================================================
FILE: scraper/metrics_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package scraper
import (
"context"
"errors"
"sync"
"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/pdata/pmetric"
)
func TestNewMetrics(t *testing.T) {
mp, err := NewMetrics(newTestScrapeMetricsFunc(nil))
require.NoError(t, err)
require.NoError(t, mp.Start(context.Background(), componenttest.NewNopHost()))
md, err := mp.ScrapeMetrics(context.Background())
require.NoError(t, err)
assert.Equal(t, pmetric.NewMetrics(), md)
require.NoError(t, mp.Shutdown(context.Background()))
}
func TestNewMetrics_WithOptions(t *testing.T) {
want := errors.New("my_error")
mp, err := NewMetrics(newTestScrapeMetricsFunc(nil),
WithStart(func(context.Context, component.Host) error { return want }),
WithShutdown(func(context.Context) error { return want }))
require.NoError(t, err)
assert.Equal(t, want, mp.Start(context.Background(), componenttest.NewNopHost()))
assert.Equal(t, want, mp.Shutdown(context.Background()))
}
func TestNewMetrics_NilRequiredFields(t *testing.T) {
_, err := NewMetrics(nil)
require.Error(t, err)
}
func TestNewMetrics_ProcessMetricsError(t *testing.T) {
want := errors.New("my_error")
mp, err := NewMetrics(newTestScrapeMetricsFunc(want))
require.NoError(t, err)
_, err = mp.ScrapeMetrics(context.Background())
require.ErrorIs(t, err, want)
}
func TestMetricsConcurrency(t *testing.T) {
incomingMetrics := pmetric.NewMetrics()
dps := incomingMetrics.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty().SetEmptySum().DataPoints()
// Add 2 data points to the incoming
dps.AppendEmpty()
dps.AppendEmpty()
mp, err := NewMetrics(newTestScrapeMetricsFunc(nil))
require.NoError(t, err)
require.NoError(t, mp.Start(context.Background(), componenttest.NewNopHost()))
var wg sync.WaitGroup
for range 10 {
wg.Go(func() {
for range 10000 {
_, errScrape := mp.ScrapeMetrics(context.Background())
assert.NoError(t, errScrape)
}
})
}
wg.Wait()
require.NoError(t, mp.Shutdown(context.Background()))
}
func newTestScrapeMetricsFunc(retError error) ScrapeMetricsFunc {
return func(_ context.Context) (pmetric.Metrics, error) {
return pmetric.NewMetrics(), retError
}
}
================================================
FILE: scraper/scraper.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package scraper // import "go.opentelemetry.io/collector/scraper"
import (
"context"
"errors"
"go.opentelemetry.io/collector/component"
)
var errNilFunc = errors.New("nil scrape func")
// ScrapeFunc scrapes data.
type ScrapeFunc[T any] func(context.Context) (T, error)
// Option apply changes to internal options.
type Option interface {
apply(*baseScraper)
}
type scraperOptionFunc func(*baseScraper)
func (of scraperOptionFunc) apply(e *baseScraper) {
of(e)
}
// WithStart sets the function that will be called on startup.
func WithStart(start component.StartFunc) Option {
return scraperOptionFunc(func(o *baseScraper) {
o.StartFunc = start
})
}
// WithShutdown sets the function that will be called on shutdown.
func WithShutdown(shutdown component.ShutdownFunc) Option {
return scraperOptionFunc(func(o *baseScraper) {
o.ShutdownFunc = shutdown
})
}
type baseScraper struct {
component.StartFunc
component.ShutdownFunc
}
// newBaseScraper returns the internal settings starting from the default and applying all options.
func newBaseScraper(options []Option) baseScraper {
// Start from the default options:
bs := baseScraper{}
for _, op := range options {
op.apply(&bs)
}
return bs
}
================================================
FILE: scraper/scrapererror/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package scrapererror provides custom error types for scrapers.
package scrapererror // import "go.opentelemetry.io/collector/scraper/scrapererror"
================================================
FILE: scraper/scrapererror/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package scrapererror
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: scraper/scrapererror/partialscrapeerror.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package scrapererror // import "go.opentelemetry.io/collector/scraper/scrapererror"
import "errors"
// PartialScrapeError is an error to represent
// that a subset of data were failed to be scraped.
type PartialScrapeError struct {
error
Failed int
}
// NewPartialScrapeError creates PartialScrapeError for failed data.
// Use this error type only when a subset of data was failed to be scraped.
func NewPartialScrapeError(err error, failed int) PartialScrapeError {
return PartialScrapeError{
error: err,
Failed: failed,
}
}
// IsPartialScrapeError checks if an error was wrapped with PartialScrapeError.
func IsPartialScrapeError(err error) bool {
var partialScrapeErr PartialScrapeError
return errors.As(err, &partialScrapeErr)
}
================================================
FILE: scraper/scrapererror/partialscrapeerror_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package scrapererror
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestPartialScrapeError(t *testing.T) {
failed := 2
err := errors.New("some error")
partialErr := NewPartialScrapeError(err, failed)
require.EqualError(t, err, partialErr.Error())
assert.Equal(t, failed, partialErr.Failed)
}
func TestIsPartialScrapeError(t *testing.T) {
err := errors.New("testError")
require.False(t, IsPartialScrapeError(err))
err = NewPartialScrapeError(err, 2)
require.True(t, IsPartialScrapeError(err))
}
================================================
FILE: scraper/scrapererror/scrapeerror.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package scrapererror // import "go.opentelemetry.io/collector/scraper/scrapererror"
import (
"go.uber.org/multierr"
)
// ScrapeErrors contains multiple PartialScrapeErrors and can also contain generic errors.
type ScrapeErrors struct {
errs []error
failedScrapeCount int
}
// AddPartial adds a PartialScrapeError with the provided failed count and error.
func (s *ScrapeErrors) AddPartial(failed int, err error) {
s.errs = append(s.errs, NewPartialScrapeError(err, failed))
s.failedScrapeCount += failed
}
// Add adds a regular error.
func (s *ScrapeErrors) Add(err error) {
s.errs = append(s.errs, err)
}
// Combine converts a slice of errors into one error.
// It will return a PartialScrapeError if at least one error in the slice is a PartialScrapeError.
func (s *ScrapeErrors) Combine() error {
partialScrapeErr := false
for _, err := range s.errs {
if IsPartialScrapeError(err) {
partialScrapeErr = true
}
}
combined := multierr.Combine(s.errs...)
if !partialScrapeErr {
return combined
}
return NewPartialScrapeError(combined, s.failedScrapeCount)
}
================================================
FILE: scraper/scrapererror/scrapeerror_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package scrapererror
import (
"errors"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestScrapeErrorsAddPartial(t *testing.T) {
err1 := errors.New("err 1")
err2 := errors.New("err 2")
expected := []error{
PartialScrapeError{error: err1, Failed: 1},
PartialScrapeError{error: err2, Failed: 10},
}
var errs ScrapeErrors
errs.AddPartial(1, err1)
errs.AddPartial(10, err2)
assert.Equal(t, expected, errs.errs)
}
func TestScrapeErrorsAdd(t *testing.T) {
err1 := errors.New("err a")
err2 := errors.New("err b")
expected := []error{err1, err2}
var errs ScrapeErrors
errs.Add(err1)
errs.Add(err2)
assert.Equal(t, expected, errs.errs)
}
func TestScrapeErrorsCombine(t *testing.T) {
testCases := []struct {
errs func() ScrapeErrors
expectedErr string
expectedFailedCount int
expectNil bool
expectedScrape bool
}{
{
errs: func() ScrapeErrors {
var errs ScrapeErrors
return errs
},
expectNil: true,
},
{
errs: func() ScrapeErrors {
var errs ScrapeErrors
errs.AddPartial(10, errors.New("bad scrapes"))
errs.AddPartial(1, fmt.Errorf("err: %w", errors.New("bad scrape")))
return errs
},
expectedErr: "bad scrapes; err: bad scrape",
expectedFailedCount: 11,
expectedScrape: true,
},
{
errs: func() ScrapeErrors {
var errs ScrapeErrors
errs.Add(errors.New("bad regular"))
errs.Add(fmt.Errorf("err: %w", errors.New("bad reg")))
return errs
},
expectedErr: "bad regular; err: bad reg",
},
{
errs: func() ScrapeErrors {
var errs ScrapeErrors
errs.AddPartial(2, errors.New("bad two scrapes"))
errs.AddPartial(10, fmt.Errorf("%d scrapes failed: %w", 10, errors.New("bad things happened")))
errs.Add(errors.New("bad event"))
errs.Add(fmt.Errorf("event: %w", errors.New("something happened")))
return errs
},
expectedErr: "bad two scrapes; 10 scrapes failed: bad things happened; bad event; event: something happened",
expectedFailedCount: 12,
expectedScrape: true,
},
}
for _, tt := range testCases {
scrapeErrs := tt.errs()
if tt.expectNil {
require.NoError(t, scrapeErrs.Combine())
continue
}
require.EqualError(t, scrapeErrs.Combine(), tt.expectedErr)
if tt.expectedScrape {
var partialScrapeErr PartialScrapeError
if !errors.As(scrapeErrs.Combine(), &partialScrapeErr) {
t.Errorf("%+v.Combine() = %q. Want: PartialScrapeError", scrapeErrs, scrapeErrs.Combine())
} else if tt.expectedFailedCount != partialScrapeErr.Failed {
t.Errorf("%+v.Combine().Failed. Got %d Failed count. Want: %d", scrapeErrs, partialScrapeErr.Failed, tt.expectedFailedCount)
}
}
}
}
================================================
FILE: scraper/scraperhelper/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: scraper/scraperhelper/README.md
================================================
# General Information
A scraper defines how to connect and scrape telemetry data from an external source.
| Status | |
| ------------- |-----------|
| Stability | [beta]: metrics, logs |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Apkg%2Fscraperhelper) [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aclosed+is%3Aissue+label%3Apkg%2Fscraperhelper) |
[beta]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#beta
================================================
FILE: scraper/scraperhelper/config.schema.yaml
================================================
$defs:
controller_config:
description: ControllerConfig defines common settings for a scraper controller configuration. Scraper controller receivers can embed this struct, instead of receiver.Settings, and extend it with more fields if needed.
type: object
properties:
collection_interval:
description: CollectionInterval sets how frequently the scraper should be called and used as the context timeout to ensure that scrapers don't exceed the interval.
type: string
x-customType: time.Duration
format: duration
initial_delay:
description: InitialDelay sets the initial start delay for the scraper, any non positive value is assumed to be immediately.
type: string
x-customType: time.Duration
format: duration
timeout:
description: Timeout is an optional value used to set scraper's context deadline.
type: string
x-customType: time.Duration
format: duration
================================================
FILE: scraper/scraperhelper/controller.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package scraperhelper // import "go.opentelemetry.io/collector/scraper/scraperhelper"
import (
"context"
"time"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/scraper"
"go.opentelemetry.io/collector/scraper/scrapererror"
"go.opentelemetry.io/collector/scraper/scraperhelper/internal/controller"
)
type ControllerConfig = controller.ControllerConfig
// NewDefaultControllerConfig returns default scraper controller
// settings with a collection interval of one minute.
func NewDefaultControllerConfig() ControllerConfig {
return controller.NewDefaultControllerConfig()
}
// ControllerOption apply changes to internal options.
type ControllerOption interface {
apply(*controllerOptions)
}
type optionFunc func(*controllerOptions)
func (of optionFunc) apply(e *controllerOptions) {
of(e)
}
// AddMetricsScraper configures the scraper.Metrics 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(t component.Type, sc scraper.Metrics) ControllerOption {
f := scraper.NewFactory(t, nil,
scraper.WithMetrics(func(context.Context, scraper.Settings, component.Config) (scraper.Metrics, error) {
return sc, nil
}, component.StabilityLevelAlpha))
return AddFactoryWithConfig(f, nil)
}
// AddScraper configures the scraper.Metrics 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.
//
// Deprecated: [0.144.0] Use AddMetricsScraper instead.
func AddScraper(t component.Type, sc scraper.Metrics) ControllerOption {
return AddMetricsScraper(t, sc)
}
// AddFactoryWithConfig configures the scraper.Factory and associated config that
// will be used to create a new scraper. The created scraper will 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 AddFactoryWithConfig(f scraper.Factory, cfg component.Config) ControllerOption {
return optionFunc(func(o *controllerOptions) {
o.factoriesWithConfig = append(o.factoriesWithConfig, factoryWithConfig{f: f, cfg: cfg})
})
}
// WithTickerChannel allows you to override the scraper controller's ticker
// channel to specify when scrape is called. This is only expected to be
// used by tests.
func WithTickerChannel(tickerCh <-chan time.Time) ControllerOption {
return optionFunc(func(o *controllerOptions) {
o.tickerCh = tickerCh
})
}
type factoryWithConfig struct {
f scraper.Factory
cfg component.Config
}
type controllerOptions struct {
tickerCh <-chan time.Time
factoriesWithConfig []factoryWithConfig
}
// NewLogsController creates a receiver.Logs with the configured options, that can control multiple scraper.Logs.
func NewLogsController(cfg *ControllerConfig,
rSet receiver.Settings,
nextConsumer consumer.Logs,
options ...ControllerOption,
) (receiver.Logs, error) {
co := getOptions(options)
scrapers := make([]scraper.Logs, 0, len(co.factoriesWithConfig))
for _, fwc := range co.factoriesWithConfig {
set := controller.GetSettings(fwc.f.Type(), rSet)
s, err := fwc.f.CreateLogs(context.Background(), set, fwc.cfg)
if err != nil {
return nil, err
}
s, err = wrapObsLogs(s, rSet.ID, set.ID, set.TelemetrySettings)
if err != nil {
return nil, err
}
scrapers = append(scrapers, s)
}
return controller.NewController[scraper.Logs](
cfg, rSet, scrapers, func(c *controller.Controller[scraper.Logs]) { scrapeLogs(c, nextConsumer) }, co.tickerCh)
}
// NewMetricsController creates a receiver.Metrics with the configured options, that can control multiple scraper.Metrics.
func NewMetricsController(cfg *ControllerConfig,
rSet receiver.Settings,
nextConsumer consumer.Metrics,
options ...ControllerOption,
) (receiver.Metrics, error) {
co := getOptions(options)
scrapers := make([]scraper.Metrics, 0, len(co.factoriesWithConfig))
for _, fwc := range co.factoriesWithConfig {
set := controller.GetSettings(fwc.f.Type(), rSet)
s, err := fwc.f.CreateMetrics(context.Background(), set, fwc.cfg)
if err != nil {
return nil, err
}
s, err = wrapObsMetrics(s, rSet.ID, set.ID, set.TelemetrySettings)
if err != nil {
return nil, err
}
scrapers = append(scrapers, s)
}
return controller.NewController[scraper.Metrics](
cfg, rSet, scrapers, func(c *controller.Controller[scraper.Metrics]) { scrapeMetrics(c, nextConsumer) }, co.tickerCh)
}
func scrapeLogs(c *controller.Controller[scraper.Logs], nextConsumer consumer.Logs) {
ctx, done := controller.WithScrapeContext(c.Timeout)
defer done()
logs := plog.NewLogs()
for i := range c.Scrapers {
md, err := c.Scrapers[i].ScrapeLogs(ctx)
if err != nil && !scrapererror.IsPartialScrapeError(err) {
continue
}
md.ResourceLogs().MoveAndAppendTo(logs.ResourceLogs())
}
logRecordCount := logs.LogRecordCount()
ctx = c.Obsrecv.StartLogsOp(ctx)
err := nextConsumer.ConsumeLogs(ctx, logs)
c.Obsrecv.EndLogsOp(ctx, "", logRecordCount, err)
}
func scrapeMetrics(c *controller.Controller[scraper.Metrics], nextConsumer consumer.Metrics) {
ctx, done := controller.WithScrapeContext(c.Timeout)
defer done()
metrics := pmetric.NewMetrics()
for i := range c.Scrapers {
md, err := c.Scrapers[i].ScrapeMetrics(ctx)
if err != nil && !scrapererror.IsPartialScrapeError(err) {
continue
}
md.ResourceMetrics().MoveAndAppendTo(metrics.ResourceMetrics())
}
dataPointCount := metrics.DataPointCount()
ctx = c.Obsrecv.StartMetricsOp(ctx)
err := nextConsumer.ConsumeMetrics(ctx, metrics)
c.Obsrecv.EndMetricsOp(ctx, "", dataPointCount, err)
}
func getOptions(options []ControllerOption) controllerOptions {
co := controllerOptions{}
for _, op := range options {
op.apply(&co)
}
return co
}
================================================
FILE: scraper/scraperhelper/controller_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package scraperhelper
import (
"context"
"errors"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.uber.org/multierr"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/receivertest"
"go.opentelemetry.io/collector/scraper"
"go.opentelemetry.io/collector/scraper/scrapererror"
"go.opentelemetry.io/collector/scraper/scraperhelper/internal/controller"
"go.opentelemetry.io/collector/scraper/scraperhelper/internal/metadatatest"
"go.opentelemetry.io/collector/scraper/scraperhelper/internal/testhelper"
)
type testScrape struct {
ch chan int
timesScrapeCalled int
err error
}
func (ts *testScrape) scrapeLogs(context.Context) (plog.Logs, error) {
ts.timesScrapeCalled++
ts.ch <- ts.timesScrapeCalled
if ts.err != nil {
return plog.Logs{}, ts.err
}
md := plog.NewLogs()
md.ResourceLogs().AppendEmpty().ScopeLogs().AppendEmpty().LogRecords().AppendEmpty().Body().SetStr("")
return md, nil
}
func (ts *testScrape) scrapeMetrics(context.Context) (pmetric.Metrics, error) {
ts.timesScrapeCalled++
ts.ch <- ts.timesScrapeCalled
if ts.err != nil {
return pmetric.Metrics{}, ts.err
}
md := pmetric.NewMetrics()
md.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty().SetEmptyGauge().DataPoints().AppendEmpty()
return md, nil
}
func newTestNoDelaySettings() *ControllerConfig {
return &ControllerConfig{
CollectionInterval: time.Second,
InitialDelay: 0,
}
}
type scraperTestCase struct {
name string
scrapers int
scraperControllerSettings *ControllerConfig
scrapeErr error
expectScraped bool
initialize bool
close bool
initializeErr error
closeErr error
}
func TestLogsScrapeController(t *testing.T) {
testCases := []scraperTestCase{
{
name: "NoScrapers",
},
{
name: "AddLogsScrapersWithCollectionInterval",
scrapers: 2,
expectScraped: true,
},
{
name: "AddLogsScrapers_ScrapeError",
scrapers: 2,
scrapeErr: errors.New("err1"),
},
{
name: "AddLogsScrapersWithInitializeAndClose",
scrapers: 2,
initialize: true,
expectScraped: true,
close: true,
},
{
name: "AddLogsScrapersWithInitializeAndCloseErrors",
scrapers: 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) {
receiverID := component.MustNewID("receiver")
tel := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tel.Shutdown(context.Background())) })
set := tel.NewTelemetrySettings()
_, parentSpan := set.TracerProvider.Tracer("test").Start(context.Background(), t.Name())
defer parentSpan.End()
initializeChs := make([]chan bool, test.scrapers)
scrapeLogsChs := make([]chan int, test.scrapers)
closeChs := make([]chan bool, test.scrapers)
options := configureLogOptions(t, test, initializeChs, scrapeLogsChs, closeChs)
tickerCh := make(chan time.Time)
options = append(options, WithTickerChannel(tickerCh))
sink := new(consumertest.LogsSink)
cfg := newTestNoDelaySettings()
if test.scraperControllerSettings != nil {
cfg = test.scraperControllerSettings
}
mr, err := NewLogsController(cfg, receiver.Settings{ID: receiverID, TelemetrySettings: set, BuildInfo: component.NewDefaultBuildInfo()}, sink, options...)
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 {
testhelper.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 _, ch := range scrapeLogsChs {
<-ch
}
// Consume the initial scrapes on start
for range iterations {
tickerCh <- time.Now()
for _, ch := range scrapeLogsChs {
<-ch
}
}
// wait until all calls to scrape have completed
if test.scrapeErr == nil {
require.Eventually(t, func() bool {
return sink.LogRecordCount() == (1+iterations)*(test.scrapers)
}, time.Second, time.Millisecond)
}
if test.expectScraped {
assert.GreaterOrEqual(t, sink.LogRecordCount(), iterations)
}
spans := tel.SpanRecorder.Ended()
assertLogsReceiverSpan(t, spans)
testhelper.AssertScraperSpan(t, test.scrapeErr, spans, "scraper/scraper/ScrapeLogs")
assertLogsScraperObsMetrics(t, tel, receiverID, component.MustNewID("scraper"), test.scrapeErr, sink)
}
err = mr.Shutdown(context.Background())
expectedShutdownErr := getExpectedShutdownErr(test)
if expectedShutdownErr != nil {
assert.EqualError(t, err, expectedShutdownErr.Error())
} else if test.close {
testhelper.AssertChannelsCalled(t, closeChs, "shutdown was not called")
}
})
}
}
func TestMetricsScrapeController(t *testing.T) {
testCases := []scraperTestCase{
{
name: "NoScrapers",
},
{
name: "AddMetricsScrapersWithCollectionInterval",
scrapers: 2,
expectScraped: true,
},
{
name: "AddMetricsScrapers_ScrapeError",
scrapers: 2,
scrapeErr: errors.New("err1"),
},
{
name: "AddMetricsScrapersWithInitializeAndClose",
scrapers: 2,
initialize: true,
expectScraped: true,
close: true,
},
{
name: "AddMetricsScrapersWithInitializeAndCloseErrors",
scrapers: 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) {
receiverID := component.MustNewID("receiver")
tel := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tel.Shutdown(context.Background())) })
set := tel.NewTelemetrySettings()
_, parentSpan := set.TracerProvider.Tracer("test").Start(context.Background(), t.Name())
defer parentSpan.End()
initializeChs := make([]chan bool, test.scrapers)
scrapeMetricsChs := make([]chan int, test.scrapers)
closeChs := make([]chan bool, test.scrapers)
options := configureMetricOptions(t, test, initializeChs, scrapeMetricsChs, closeChs)
tickerCh := make(chan time.Time)
options = append(options, WithTickerChannel(tickerCh))
sink := new(consumertest.MetricsSink)
cfg := newTestNoDelaySettings()
if test.scraperControllerSettings != nil {
cfg = test.scraperControllerSettings
}
mr, err := NewMetricsController(cfg, receiver.Settings{ID: receiverID, TelemetrySettings: set, BuildInfo: component.NewDefaultBuildInfo()}, sink, options...)
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 {
testhelper.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 _, ch := range scrapeMetricsChs {
<-ch
}
// Consume the initial scrapes on start
for range iterations {
tickerCh <- time.Now()
for _, ch := range scrapeMetricsChs {
<-ch
}
}
// wait until all calls to scrape have completed
if test.scrapeErr == nil {
require.Eventually(t, func() bool {
return sink.DataPointCount() == (1+iterations)*(test.scrapers)
}, time.Second, time.Millisecond)
}
if test.expectScraped {
assert.GreaterOrEqual(t, sink.DataPointCount(), iterations)
}
spans := tel.SpanRecorder.Ended()
assertMetricsReceiverSpan(t, spans)
testhelper.AssertScraperSpan(t, test.scrapeErr, spans, "scraper/scraper/ScrapeMetrics")
assertMetricsScraperObsMetrics(t, tel, receiverID, component.MustNewID("scraper"), test.scrapeErr, sink)
}
err = mr.Shutdown(context.Background())
expectedShutdownErr := getExpectedShutdownErr(test)
if expectedShutdownErr != nil {
assert.EqualError(t, err, expectedShutdownErr.Error())
} else if test.close {
testhelper.AssertChannelsCalled(t, closeChs, "shutdown was not called")
}
})
}
}
func configureLogOptions(t *testing.T, test scraperTestCase, initializeChs []chan bool, scrapeLogsChs []chan int, closeChs []chan bool) []ControllerOption {
var logsOptions []ControllerOption
for i := 0; i < test.scrapers; i++ {
var scraperOptions []scraper.Option
if test.initialize {
initializeChs[i] = make(chan bool, 1)
ti := testhelper.NewTestInitialize(initializeChs[i], test.initializeErr)
scraperOptions = append(scraperOptions, scraper.WithStart(ti.Start))
}
if test.close {
closeChs[i] = make(chan bool, 1)
tc := testhelper.NewTestClose(closeChs[i], test.closeErr)
scraperOptions = append(scraperOptions, scraper.WithShutdown(tc.Shutdown))
}
scrapeLogsChs[i] = make(chan int)
ts := &testScrape{ch: scrapeLogsChs[i], err: test.scrapeErr}
scp, err := scraper.NewLogs(ts.scrapeLogs, scraperOptions...)
require.NoError(t, err)
logsOptions = append(logsOptions, addLogsScraper(component.MustNewType("scraper"), scp))
}
return logsOptions
}
func configureMetricOptions(t *testing.T, test scraperTestCase, initializeChs []chan bool, scrapeMetricsChs []chan int, closeChs []chan bool) []ControllerOption {
var metricOptions []ControllerOption
for i := 0; i < test.scrapers; i++ {
var scraperOptions []scraper.Option
if test.initialize {
initializeChs[i] = make(chan bool, 1)
ti := testhelper.NewTestInitialize(initializeChs[i], test.initializeErr)
scraperOptions = append(scraperOptions, scraper.WithStart(ti.Start))
}
if test.close {
closeChs[i] = make(chan bool, 1)
tc := testhelper.NewTestClose(closeChs[i], test.closeErr)
scraperOptions = append(scraperOptions, scraper.WithShutdown(tc.Shutdown))
}
scrapeMetricsChs[i] = make(chan int)
ts := &testScrape{ch: scrapeMetricsChs[i], err: test.scrapeErr}
scp, err := scraper.NewMetrics(ts.scrapeMetrics, scraperOptions...)
require.NoError(t, err)
metricOptions = append(metricOptions, AddMetricsScraper(component.MustNewType("scraper"), scp))
}
return metricOptions
}
func getExpectedStartErr(test scraperTestCase) error {
return test.initializeErr
}
func getExpectedShutdownErr(test scraperTestCase) error {
var errs error
if test.closeErr != nil {
for i := 0; i < test.scrapers; i++ {
errs = multierr.Append(errs, test.closeErr)
}
}
return errs
}
func assertMetricsReceiverSpan(t *testing.T, spans []sdktrace.ReadOnlySpan) {
receiverSpan := false
for _, span := range spans {
if span.Name() == "receiver/receiver/MetricsReceived" {
receiverSpan = true
break
}
}
assert.True(t, receiverSpan)
}
func assertLogsReceiverSpan(t *testing.T, spans []sdktrace.ReadOnlySpan) {
receiverSpan := false
for _, span := range spans {
if span.Name() == "receiver/receiver/LogsReceived" {
receiverSpan = true
break
}
}
assert.True(t, receiverSpan)
}
func assertLogsScraperObsMetrics(t *testing.T, tel *componenttest.Telemetry, receiver, scraper component.ID, expectedErr error, sink *consumertest.LogsSink) {
logRecordCounts := 0
for _, md := range sink.AllLogs() {
logRecordCounts += md.LogRecordCount()
}
expectedScraped := int64(sink.LogRecordCount())
expectedErrored := int64(0)
if expectedErr != nil {
var partialError scrapererror.PartialScrapeError
if errors.As(expectedErr, &partialError) {
expectedErrored = int64(partialError.Failed)
} else {
expectedScraped = int64(0)
expectedErrored = int64(sink.LogRecordCount())
}
}
metadatatest.AssertEqualScraperScrapedLogRecords(t, tel,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(receiverKey, receiver.String()),
attribute.String(scraperKey, scraper.String())),
Value: expectedScraped,
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
metadatatest.AssertEqualScraperErroredLogRecords(t, tel,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(receiverKey, receiver.String()),
attribute.String(scraperKey, scraper.String())),
Value: expectedErrored,
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
}
func assertMetricsScraperObsMetrics(t *testing.T, tel *componenttest.Telemetry, receiver, scraper component.ID, expectedErr error, sink *consumertest.MetricsSink) {
dataPointCounts := 0
for _, md := range sink.AllMetrics() {
dataPointCounts += md.DataPointCount()
}
expectedScraped := int64(sink.DataPointCount())
expectedErrored := int64(0)
if expectedErr != nil {
var partialError scrapererror.PartialScrapeError
if errors.As(expectedErr, &partialError) {
expectedErrored = int64(partialError.Failed)
} else {
expectedScraped = int64(0)
expectedErrored = int64(sink.DataPointCount())
}
}
metadatatest.AssertEqualScraperScrapedMetricPoints(t, tel,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(receiverKey, receiver.String()),
attribute.String(scraperKey, scraper.String())),
Value: expectedScraped,
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
metadatatest.AssertEqualScraperErroredMetricPoints(t, tel,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(receiverKey, receiver.String()),
attribute.String(scraperKey, scraper.String())),
Value: expectedErrored,
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
}
func TestSingleLogsScraperPerInterval(t *testing.T) {
scrapeCh := make(chan int, 10)
ts := &testScrape{ch: scrapeCh}
cfg := newTestNoDelaySettings()
tickerCh := make(chan time.Time)
scp, err := scraper.NewLogs(ts.scrapeLogs)
require.NoError(t, err)
recv, err := NewLogsController(
cfg,
receivertest.NewNopSettings(receivertest.NopType),
new(consumertest.LogsSink),
addLogsScraper(component.MustNewType("scraper"), scp),
WithTickerChannel(tickerCh),
)
require.NoError(t, err)
require.NoError(t, recv.Start(context.Background(), componenttest.NewNopHost()))
defer func() { require.NoError(t, recv.Shutdown(context.Background())) }()
tickerCh <- time.Now()
assert.Eventually(
t,
func() bool {
return <-scrapeCh == 2
},
300*time.Millisecond,
100*time.Millisecond,
"Make sure the scraper channel is called twice",
)
select {
case <-scrapeCh:
assert.Fail(t, "Scrape was called more than twice")
case <-time.After(100 * time.Millisecond):
return
}
}
func TestSingleMetricsScraperPerInterval(t *testing.T) {
scrapeCh := make(chan int, 10)
ts := &testScrape{ch: scrapeCh}
cfg := newTestNoDelaySettings()
tickerCh := make(chan time.Time)
scp, err := scraper.NewMetrics(ts.scrapeMetrics)
require.NoError(t, err)
recv, err := NewMetricsController(
cfg,
receivertest.NewNopSettings(receivertest.NopType),
new(consumertest.MetricsSink),
AddMetricsScraper(component.MustNewType("scraper"), scp),
WithTickerChannel(tickerCh),
)
require.NoError(t, err)
require.NoError(t, recv.Start(context.Background(), componenttest.NewNopHost()))
defer func() { require.NoError(t, recv.Shutdown(context.Background())) }()
tickerCh <- time.Now()
assert.Eventually(
t,
func() bool {
return <-scrapeCh == 2
},
300*time.Millisecond,
100*time.Millisecond,
"Make sure the scraper channel is called twice",
)
select {
case <-scrapeCh:
assert.Fail(t, "Scrape was called more than twice")
case <-time.After(100 * time.Millisecond):
return
}
}
func TestLogsScraperControllerStartsOnInit(t *testing.T) {
t.Parallel()
ts := &testScrape{
ch: make(chan int, 1),
}
scp, err := scraper.NewLogs(ts.scrapeLogs)
require.NoError(t, err, "Must not error when creating scraper")
r, err := NewLogsController(
&ControllerConfig{
CollectionInterval: time.Hour,
InitialDelay: 0,
},
receivertest.NewNopSettings(receivertest.NopType),
new(consumertest.LogsSink),
addLogsScraper(component.MustNewType("scraper"), scp),
)
require.NoError(t, err, "Must not error when creating scrape controller")
assert.NoError(t, r.Start(context.Background(), componenttest.NewNopHost()), "Must not error on start")
<-time.After(500 * time.Nanosecond)
require.NoError(t, r.Shutdown(context.Background()), "Must not have errored on shutdown")
assert.Equal(t, 1, ts.timesScrapeCalled, "Must have been called as soon as the controller started")
}
func TestMetricsScraperControllerStartsOnInit(t *testing.T) {
t.Parallel()
ts := &testScrape{
ch: make(chan int, 1),
}
scp, err := scraper.NewMetrics(ts.scrapeMetrics)
require.NoError(t, err, "Must not error when creating scraper")
r, err := NewMetricsController(
&ControllerConfig{
CollectionInterval: time.Hour,
InitialDelay: 0,
},
receivertest.NewNopSettings(receivertest.NopType),
new(consumertest.MetricsSink),
AddMetricsScraper(component.MustNewType("scraper"), scp),
)
require.NoError(t, err, "Must not error when creating scrape controller")
assert.NoError(t, r.Start(context.Background(), componenttest.NewNopHost()), "Must not error on start")
<-time.After(500 * time.Nanosecond)
require.NoError(t, r.Shutdown(context.Background()), "Must not have errored on shutdown")
assert.Equal(t, 1, ts.timesScrapeCalled, "Must have been called as soon as the controller started")
}
func TestLogsScraperControllerInitialDelay(t *testing.T) {
if testing.Short() {
t.Skip("This requires real time to pass, skipping")
return
}
t.Parallel()
var (
elapsed = make(chan time.Time, 1)
cfg = ControllerConfig{
CollectionInterval: time.Second,
InitialDelay: 300 * time.Millisecond,
}
)
scp, err := scraper.NewLogs(func(context.Context) (plog.Logs, error) {
elapsed <- time.Now()
return plog.NewLogs(), nil
})
require.NoError(t, err, "Must not error when creating scraper")
r, err := NewLogsController(
&cfg,
receivertest.NewNopSettings(receivertest.NopType),
new(consumertest.LogsSink),
addLogsScraper(component.MustNewType("scraper"), scp),
)
require.NoError(t, err, "Must not error when creating receiver")
t0 := time.Now()
require.NoError(t, r.Start(context.Background(), componenttest.NewNopHost()), "Must not error when starting")
t1 := <-elapsed
assert.GreaterOrEqual(t, t1.Sub(t0), 300*time.Millisecond, "Must have had 300ms pass as defined by initial delay")
assert.NoError(t, r.Shutdown(context.Background()), "Must not error closing down")
}
func TestMetricsScraperControllerInitialDelay(t *testing.T) {
if testing.Short() {
t.Skip("This requires real time to pass, skipping")
return
}
t.Parallel()
var (
elapsed = make(chan time.Time, 1)
cfg = ControllerConfig{
CollectionInterval: time.Second,
InitialDelay: 300 * time.Millisecond,
}
)
scp, err := scraper.NewMetrics(func(context.Context) (pmetric.Metrics, error) {
elapsed <- time.Now()
return pmetric.NewMetrics(), nil
})
require.NoError(t, err, "Must not error when creating scraper")
r, err := NewMetricsController(
&cfg,
receivertest.NewNopSettings(receivertest.NopType),
new(consumertest.MetricsSink),
AddMetricsScraper(component.MustNewType("scraper"), scp),
)
require.NoError(t, err, "Must not error when creating receiver")
t0 := time.Now()
require.NoError(t, r.Start(context.Background(), componenttest.NewNopHost()), "Must not error when starting")
t1 := <-elapsed
assert.GreaterOrEqual(t, t1.Sub(t0), 300*time.Millisecond, "Must have had 300ms pass as defined by initial delay")
assert.NoError(t, r.Shutdown(context.Background()), "Must not error closing down")
}
func TestLogsScraperShutdownBeforeScrapeCanStart(t *testing.T) {
cfg := ControllerConfig{
CollectionInterval: time.Second,
InitialDelay: 5 * time.Second,
}
scp, err := scraper.NewLogs(func(context.Context) (plog.Logs, error) {
// make the scraper wait for long enough it would disrupt a shutdown.
time.Sleep(30 * time.Second)
return plog.NewLogs(), nil
})
require.NoError(t, err, "Must not error when creating scraper")
r, err := NewLogsController(
&cfg,
receivertest.NewNopSettings(receivertest.NopType),
new(consumertest.LogsSink),
addLogsScraper(component.MustNewType("scraper"), scp),
)
require.NoError(t, err, "Must not error when creating receiver")
require.NoError(t, r.Start(context.Background(), componenttest.NewNopHost()))
shutdown := make(chan struct{}, 1)
go func() {
assert.NoError(t, r.Shutdown(context.Background()))
close(shutdown)
}()
timer := time.NewTicker(10 * time.Second)
select {
case <-timer.C:
require.Fail(t, "shutdown should not wait for scraping")
case <-shutdown:
}
}
func TestMetricsScraperShutdownBeforeScrapeCanStart(t *testing.T) {
cfg := ControllerConfig{
CollectionInterval: time.Second,
InitialDelay: 5 * time.Second,
}
scp, err := scraper.NewMetrics(func(context.Context) (pmetric.Metrics, error) {
// make the scraper wait for long enough it would disrupt a shutdown.
time.Sleep(30 * time.Second)
return pmetric.NewMetrics(), nil
})
require.NoError(t, err, "Must not error when creating scraper")
r, err := NewMetricsController(
&cfg,
receivertest.NewNopSettings(receivertest.NopType),
new(consumertest.MetricsSink),
AddMetricsScraper(component.MustNewType("scraper"), scp),
)
require.NoError(t, err, "Must not error when creating receiver")
require.NoError(t, r.Start(context.Background(), componenttest.NewNopHost()))
shutdown := make(chan struct{}, 1)
go func() {
assert.NoError(t, r.Shutdown(context.Background()))
close(shutdown)
}()
timer := time.NewTicker(10 * time.Second)
select {
case <-timer.C:
require.Fail(t, "shutdown should not wait for scraping")
case <-shutdown:
}
}
func addLogsScraper(t component.Type, sc scraper.Logs) ControllerOption {
f := scraper.NewFactory(t, nil,
scraper.WithLogs(func(context.Context, scraper.Settings, component.Config) (scraper.Logs, error) {
return sc, nil
}, component.StabilityLevelAlpha))
return AddFactoryWithConfig(f, nil)
}
func TestNewDefaultControllerConfig(t *testing.T) {
controllerConfig := NewDefaultControllerConfig()
intControllerConfig := controller.NewDefaultControllerConfig()
require.Equal(t, intControllerConfig, controllerConfig)
}
func TestNewMetricsController_ScraperIDInErrorLogs(t *testing.T) {
t.Parallel()
core, observedLogs := observer.New(zap.ErrorLevel)
tel := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tel.Shutdown(context.Background())) })
telset := tel.NewTelemetrySettings()
telset.Logger = zap.New(core)
receiverID := component.MustNewID("fakeReceiver")
scraperType := component.MustNewType("fakeScraper")
scrapeErr := errors.New("scrape error")
scrapeCh := make(chan int, 1)
ts := &testScrape{ch: scrapeCh, err: scrapeErr}
scp, err := scraper.NewMetrics(ts.scrapeMetrics)
require.NoError(t, err)
cfg := newTestNoDelaySettings()
tickerCh := make(chan time.Time)
recv, err := NewMetricsController(
cfg,
receiver.Settings{ID: receiverID, TelemetrySettings: telset, BuildInfo: component.NewDefaultBuildInfo()},
new(consumertest.MetricsSink),
AddMetricsScraper(scraperType, scp),
WithTickerChannel(tickerCh),
)
require.NoError(t, err)
require.NoError(t, recv.Start(context.Background(), componenttest.NewNopHost()))
defer func() { require.NoError(t, recv.Shutdown(context.Background())) }()
<-scrapeCh
require.Eventually(t, func() bool {
return observedLogs.Len() >= 1
}, time.Second, 10*time.Millisecond)
errorLogs := observedLogs.FilterLevelExact(zap.ErrorLevel).All()
require.Len(t, errorLogs, 1)
assert.Equal(t, "Error scraping metrics", errorLogs[0].Message)
assert.Equal(t, scraperType.String(), errorLogs[0].ContextMap()["scraper"])
assert.Equal(t, scrapeErr.Error(), errorLogs[0].ContextMap()["error"])
// Verify the original receiver telemetry settings logger was NOT mutated
// by logging something and checking it doesn't have the scraper field
telset.Logger.Error("test log from receiver")
allLogs := observedLogs.FilterLevelExact(zap.ErrorLevel).All()
require.Len(t, allLogs, 2)
receiverLog := allLogs[1]
assert.Equal(t, "test log from receiver", receiverLog.Message)
assert.NotContains(t, receiverLog.ContextMap(), "scraper")
}
================================================
FILE: scraper/scraperhelper/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:generate mdatagen metadata.yaml
// Package scraperhelper provides utilities for scrapers.
package scraperhelper // import "go.opentelemetry.io/collector/scraper/scraperhelper"
================================================
FILE: scraper/scraperhelper/documentation.md
================================================
[comment]: <> (Code generated by mdatagen. DO NOT EDIT.)
# scraperhelper
## Internal Telemetry
The following telemetry is emitted by this component.
### otelcol_scraper_errored_log_records
Number of log records that were unable to be scraped.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {datapoint} | Sum | Int | true | Alpha |
### otelcol_scraper_errored_metric_points
Number of metric points that were unable to be scraped.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {datapoint} | Sum | Int | true | Alpha |
### otelcol_scraper_scraped_log_records
Number of log records successfully scraped.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {datapoint} | Sum | Int | true | Alpha |
### otelcol_scraper_scraped_metric_points
Number of metric points successfully scraped.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {datapoint} | Sum | Int | true | Alpha |
================================================
FILE: scraper/scraperhelper/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package scraperhelper
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: scraper/scraperhelper/go.mod
================================================
module go.opentelemetry.io/collector/scraper/scraperhelper
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.opentelemetry.io/collector/consumer v1.54.0
go.opentelemetry.io/collector/consumer/consumertest v0.148.0
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/collector/pdata/testdata v0.148.0
go.opentelemetry.io/collector/pipeline v1.54.0
go.opentelemetry.io/collector/receiver v1.54.0
go.opentelemetry.io/collector/receiver/receiverhelper v0.148.0
go.opentelemetry.io/collector/receiver/receivertest v0.148.0
go.opentelemetry.io/collector/scraper v0.148.0
go.opentelemetry.io/otel v1.42.0
go.opentelemetry.io/otel/metric v1.42.0
go.opentelemetry.io/otel/sdk v1.42.0
go.opentelemetry.io/otel/sdk/metric v1.42.0
go.opentelemetry.io/otel/trace v1.42.0
go.uber.org/goleak v1.3.0
go.uber.org/multierr v1.11.0
go.uber.org/zap v1.27.1
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/consumer/consumererror v0.148.0 // indirect
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
go.opentelemetry.io/collector/pdata/pprofile v0.148.0 // indirect
go.opentelemetry.io/collector/pipeline/xpipeline v0.148.0 // indirect
go.opentelemetry.io/collector/receiver/xreceiver v0.148.0 // indirect
golang.org/x/sys v0.41.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/scraper => ../
replace go.opentelemetry.io/collector/receiver => ../../receiver
replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile
replace go.opentelemetry.io/collector/consumer/consumererror => ../../consumer/consumererror
replace go.opentelemetry.io/collector/receiver/receivertest => ../../receiver/receivertest
replace go.opentelemetry.io/collector/receiver/receiverhelper => ../../receiver/receiverhelper
replace go.opentelemetry.io/collector/consumer => ../../consumer
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest
replace go.opentelemetry.io/collector/receiver/xreceiver => ../../receiver/xreceiver
replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer
replace go.opentelemetry.io/collector/pipeline => ../../pipeline
replace go.opentelemetry.io/collector/pdata/testdata => ../../pdata/testdata
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/pipeline/xpipeline => ../../pipeline/xpipeline
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
================================================
FILE: scraper/scraperhelper/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: scraper/scraperhelper/internal/controller/config.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package controller // import "go.opentelemetry.io/collector/scraper/scraperhelper/internal/controller"
import (
"errors"
"fmt"
"time"
"go.uber.org/multierr"
)
var errNonPositiveInterval = errors.New("requires positive value")
// ControllerConfig defines common settings for a scraper controller
// configuration. Scraper controller receivers can embed this struct, instead
// of receiver.Settings, and extend it with more fields if needed.
type ControllerConfig struct {
// CollectionInterval sets how frequently the scraper
// should be called and used as the context timeout
// to ensure that scrapers don't exceed the interval.
CollectionInterval time.Duration `mapstructure:"collection_interval"`
// InitialDelay sets the initial start delay for the scraper,
// any non positive value is assumed to be immediately.
InitialDelay time.Duration `mapstructure:"initial_delay"`
// Timeout is an optional value used to set scraper's context deadline.
Timeout time.Duration `mapstructure:"timeout"`
// prevent unkeyed literal initialization
_ struct{}
}
// NewDefaultControllerConfig returns default scraper controller
// settings with a collection interval of one minute.
func NewDefaultControllerConfig() ControllerConfig {
return ControllerConfig{
CollectionInterval: time.Minute,
InitialDelay: time.Second,
Timeout: 0,
}
}
func (set *ControllerConfig) Validate() (errs error) {
if set.CollectionInterval <= 0 {
errs = multierr.Append(errs, fmt.Errorf(`"collection_interval": %w`, errNonPositiveInterval))
}
if set.Timeout < 0 {
errs = multierr.Append(errs, fmt.Errorf(`"timeout": %w`, errNonPositiveInterval))
}
return errs
}
================================================
FILE: scraper/scraperhelper/internal/controller/config_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package controller // import "go.opentelemetry.io/collector/scraper/scraperhelper/internal/controller"
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestScrapeControllerSettings(t *testing.T) {
t.Parallel()
for _, tc := range []struct {
name string
set ControllerConfig
errVal string
}{
{
name: "default configuration",
set: NewDefaultControllerConfig(),
errVal: "",
},
{
name: "zero value configuration",
set: ControllerConfig{},
errVal: `"collection_interval": requires positive value`,
},
{
name: "invalid timeout",
set: ControllerConfig{
CollectionInterval: time.Minute,
Timeout: -1 * time.Minute,
},
errVal: `"timeout": requires positive value`,
},
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
err := tc.set.Validate()
if tc.errVal == "" {
assert.NoError(t, err, "Must not error")
return
}
assert.EqualError(t, err, tc.errVal, "Must match the expected error")
})
}
}
================================================
FILE: scraper/scraperhelper/internal/controller/controller.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// package controller provides functionality used in scraperhelper and xscraperhelper.
package controller // import "go.opentelemetry.io/collector/scraper/scraperhelper/internal/controller"
import (
"context"
"sync"
"time"
"go.uber.org/multierr"
"go.uber.org/zap"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/receiverhelper"
"go.opentelemetry.io/collector/scraper"
)
type Controller[T component.Component] struct {
collectionInterval time.Duration
initialDelay time.Duration
Timeout time.Duration
Scrapers []T
scrapeFunc func(*Controller[T])
tickerCh <-chan time.Time
done chan struct{}
wg sync.WaitGroup
Obsrecv *receiverhelper.ObsReport
}
func NewController[T component.Component](
cfg *ControllerConfig,
rSet receiver.Settings,
scrapers []T,
scrapeFunc func(*Controller[T]),
tickerCh <-chan time.Time,
) (*Controller[T], error) {
obsrecv, err := receiverhelper.NewObsReport(receiverhelper.ObsReportSettings{
ReceiverID: rSet.ID,
Transport: "",
ReceiverCreateSettings: rSet,
})
if err != nil {
return nil, err
}
cs := &Controller[T]{
collectionInterval: cfg.CollectionInterval,
initialDelay: cfg.InitialDelay,
Timeout: cfg.Timeout,
Scrapers: scrapers,
scrapeFunc: scrapeFunc,
done: make(chan struct{}),
tickerCh: tickerCh,
Obsrecv: obsrecv,
}
return cs, nil
}
// Start the receiver, invoked during service start.
func (sc *Controller[T]) Start(ctx context.Context, host component.Host) error {
for _, scrp := range sc.Scrapers {
if err := scrp.Start(ctx, host); err != nil {
return err
}
}
sc.startScraping()
return nil
}
// Shutdown the receiver, invoked during service shutdown.
func (sc *Controller[T]) Shutdown(ctx context.Context) error {
// Signal the goroutine to stop.
close(sc.done)
sc.wg.Wait()
var errs error
for _, scrp := range sc.Scrapers {
errs = multierr.Append(errs, scrp.Shutdown(ctx))
}
return errs
}
// startScraping initiates a ticker that calls Scrape based on the configured
// collection interval.
func (sc *Controller[T]) startScraping() {
sc.wg.Go(func() {
if sc.initialDelay > 0 {
select {
case <-time.After(sc.initialDelay):
case <-sc.done:
return
}
}
if sc.tickerCh == nil {
ticker := time.NewTicker(sc.collectionInterval)
defer ticker.Stop()
sc.tickerCh = ticker.C
}
// Call scrape method during initialization to ensure
// that scrapers start from when the component starts
// instead of waiting for the full duration to start.
sc.scrapeFunc(sc)
for {
select {
case <-sc.tickerCh:
sc.scrapeFunc(sc)
case <-sc.done:
return
}
}
})
}
func GetSettings(sType component.Type, rSet receiver.Settings) scraper.Settings {
id := component.NewID(sType)
telemetry := rSet.TelemetrySettings
telemetry.Logger = telemetry.Logger.With(zap.String("scraper", id.String()))
return scraper.Settings{
ID: id,
TelemetrySettings: telemetry,
BuildInfo: rSet.BuildInfo,
}
}
// WithScrapeContext will return a context that has no deadline if timeout is 0
// which implies no explicit timeout had occurred, otherwise, a context
// with a deadline of the provided timeout is returned.
func WithScrapeContext(timeout time.Duration) (context.Context, context.CancelFunc) {
if timeout == 0 {
return context.WithCancel(context.Background())
}
return context.WithTimeout(context.Background(), timeout)
}
================================================
FILE: scraper/scraperhelper/internal/metadata/generated_telemetry.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"errors"
"sync"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/collector/component"
)
func Meter(settings component.TelemetrySettings) metric.Meter {
return settings.MeterProvider.Meter("go.opentelemetry.io/collector/scraper/scraperhelper")
}
func Tracer(settings component.TelemetrySettings) trace.Tracer {
return settings.TracerProvider.Tracer("go.opentelemetry.io/collector/scraper/scraperhelper")
}
// TelemetryBuilder provides an interface for components to report telemetry
// as defined in metadata and user config.
type TelemetryBuilder struct {
meter metric.Meter
mu sync.Mutex
registrations []metric.Registration
ScraperErroredLogRecords metric.Int64Counter
ScraperErroredMetricPoints metric.Int64Counter
ScraperScrapedLogRecords metric.Int64Counter
ScraperScrapedMetricPoints metric.Int64Counter
}
// TelemetryBuilderOption applies changes to default builder.
type TelemetryBuilderOption interface {
apply(*TelemetryBuilder)
}
type telemetryBuilderOptionFunc func(mb *TelemetryBuilder)
func (tbof telemetryBuilderOptionFunc) apply(mb *TelemetryBuilder) {
tbof(mb)
}
// Shutdown unregister all registered callbacks for async instruments.
func (builder *TelemetryBuilder) Shutdown() {
builder.mu.Lock()
defer builder.mu.Unlock()
for _, reg := range builder.registrations {
reg.Unregister()
}
}
// NewTelemetryBuilder provides a struct with methods to update all internal telemetry
// for a component
func NewTelemetryBuilder(settings component.TelemetrySettings, options ...TelemetryBuilderOption) (*TelemetryBuilder, error) {
builder := TelemetryBuilder{}
for _, op := range options {
op.apply(&builder)
}
builder.meter = Meter(settings)
var err, errs error
builder.ScraperErroredLogRecords, err = builder.meter.Int64Counter(
"otelcol_scraper_errored_log_records",
metric.WithDescription("Number of log records that were unable to be scraped. [Alpha]"),
metric.WithUnit("{datapoint}"),
)
errs = errors.Join(errs, err)
builder.ScraperErroredMetricPoints, err = builder.meter.Int64Counter(
"otelcol_scraper_errored_metric_points",
metric.WithDescription("Number of metric points that were unable to be scraped. [Alpha]"),
metric.WithUnit("{datapoint}"),
)
errs = errors.Join(errs, err)
builder.ScraperScrapedLogRecords, err = builder.meter.Int64Counter(
"otelcol_scraper_scraped_log_records",
metric.WithDescription("Number of log records successfully scraped. [Alpha]"),
metric.WithUnit("{datapoint}"),
)
errs = errors.Join(errs, err)
builder.ScraperScrapedMetricPoints, err = builder.meter.Int64Counter(
"otelcol_scraper_scraped_metric_points",
metric.WithDescription("Number of metric points successfully scraped. [Alpha]"),
metric.WithUnit("{datapoint}"),
)
errs = errors.Join(errs, err)
return &builder, errs
}
================================================
FILE: scraper/scraperhelper/internal/metadata/generated_telemetry_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/metric"
embeddedmetric "go.opentelemetry.io/otel/metric/embedded"
noopmetric "go.opentelemetry.io/otel/metric/noop"
"go.opentelemetry.io/otel/trace"
embeddedtrace "go.opentelemetry.io/otel/trace/embedded"
nooptrace "go.opentelemetry.io/otel/trace/noop"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
)
type mockMeter struct {
noopmetric.Meter
name string
}
type mockMeterProvider struct {
embeddedmetric.MeterProvider
}
func (m mockMeterProvider) Meter(name string, opts ...metric.MeterOption) metric.Meter {
return mockMeter{name: name}
}
type mockTracer struct {
nooptrace.Tracer
name string
}
type mockTracerProvider struct {
embeddedtrace.TracerProvider
}
func (m mockTracerProvider) Tracer(name string, opts ...trace.TracerOption) trace.Tracer {
return mockTracer{name: name}
}
func TestProviders(t *testing.T) {
set := component.TelemetrySettings{
MeterProvider: mockMeterProvider{},
TracerProvider: mockTracerProvider{},
}
meter := Meter(set)
if m, ok := meter.(mockMeter); ok {
require.Equal(t, "go.opentelemetry.io/collector/scraper/scraperhelper", m.name)
} else {
require.Fail(t, "returned Meter not mockMeter")
}
tracer := Tracer(set)
if m, ok := tracer.(mockTracer); ok {
require.Equal(t, "go.opentelemetry.io/collector/scraper/scraperhelper", m.name)
} else {
require.Fail(t, "returned Meter not mockTracer")
}
}
func TestNewTelemetryBuilder(t *testing.T) {
set := componenttest.NewNopTelemetrySettings()
applied := false
_, err := NewTelemetryBuilder(set, telemetryBuilderOptionFunc(func(b *TelemetryBuilder) {
applied = true
}))
require.NoError(t, err)
require.True(t, applied)
}
================================================
FILE: scraper/scraperhelper/internal/metadatatest/generated_telemetrytest.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadatatest
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"go.opentelemetry.io/collector/component/componenttest"
)
func AssertEqualScraperErroredLogRecords(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_scraper_errored_log_records",
Description: "Number of log records that were unable to be scraped. [Alpha]",
Unit: "{datapoint}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_scraper_errored_log_records")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualScraperErroredMetricPoints(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_scraper_errored_metric_points",
Description: "Number of metric points that were unable to be scraped. [Alpha]",
Unit: "{datapoint}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_scraper_errored_metric_points")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualScraperScrapedLogRecords(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_scraper_scraped_log_records",
Description: "Number of log records successfully scraped. [Alpha]",
Unit: "{datapoint}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_scraper_scraped_log_records")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualScraperScrapedMetricPoints(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_scraper_scraped_metric_points",
Description: "Number of metric points successfully scraped. [Alpha]",
Unit: "{datapoint}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_scraper_scraped_metric_points")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
================================================
FILE: scraper/scraperhelper/internal/metadatatest/generated_telemetrytest_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadatatest
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/scraper/scraperhelper/internal/metadata"
)
func TestSetupTelemetry(t *testing.T) {
testTel := componenttest.NewTelemetry()
tb, err := metadata.NewTelemetryBuilder(testTel.NewTelemetrySettings())
require.NoError(t, err)
defer tb.Shutdown()
tb.ScraperErroredLogRecords.Add(context.Background(), 1)
tb.ScraperErroredMetricPoints.Add(context.Background(), 1)
tb.ScraperScrapedLogRecords.Add(context.Background(), 1)
tb.ScraperScrapedMetricPoints.Add(context.Background(), 1)
AssertEqualScraperErroredLogRecords(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualScraperErroredMetricPoints(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualScraperScrapedLogRecords(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualScraperScrapedMetricPoints(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
require.NoError(t, testTel.Shutdown(context.Background()))
}
================================================
FILE: scraper/scraperhelper/internal/testhelper/helper.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// package testhelper provides functionality used in tests in scraperhelper and xscraperhelper.
package testhelper // import "go.opentelemetry.io/collector/scraper/scraperhelper/internal/testhelper"
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel/codes"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/collector/component"
)
type TestInitialize struct {
ch chan bool
err error
}
func NewTestInitialize(ch chan bool, err error) *TestInitialize {
return &TestInitialize{
ch: ch,
err: err,
}
}
func (ts *TestInitialize) Start(context.Context, component.Host) error {
ts.ch <- true
return ts.err
}
type TestClose struct {
ch chan bool
err error
}
func NewTestClose(ch chan bool, err error) *TestClose {
return &TestClose{
ch: ch,
err: err,
}
}
func (ts *TestClose) Shutdown(context.Context) error {
ts.ch <- true
return ts.err
}
func AssertChannelCalled(t *testing.T, ch chan bool, message string) {
select {
case <-ch:
default:
assert.Fail(t, message)
}
}
func AssertChannelsCalled(t *testing.T, chs []chan bool, message string) {
for _, ic := range chs {
AssertChannelCalled(t, ic, message)
}
}
func AssertScraperSpan(t *testing.T, expectedErr error, spans []sdktrace.ReadOnlySpan, expectedSpanName string) {
expectedStatusCode := codes.Unset
expectedStatusMessage := ""
if expectedErr != nil {
expectedStatusCode = codes.Error
expectedStatusMessage = expectedErr.Error()
}
scraperSpan := false
for _, span := range spans {
if span.Name() == expectedSpanName {
scraperSpan = true
assert.Equal(t, expectedStatusCode, span.Status().Code)
assert.Equal(t, expectedStatusMessage, span.Status().Description)
break
}
}
assert.True(t, scraperSpan)
}
================================================
FILE: scraper/scraperhelper/metadata.yaml
================================================
type: scraperhelper
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
stability:
beta: [metrics, logs]
telemetry:
metrics:
scraper_errored_log_records:
enabled: true
stability: alpha
description: Number of log records that were unable to be scraped.
unit: "{datapoint}"
sum:
value_type: int
monotonic: true
scraper_errored_metric_points:
enabled: true
stability: alpha
description: Number of metric points that were unable to be scraped.
unit: "{datapoint}"
sum:
value_type: int
monotonic: true
scraper_scraped_log_records:
enabled: true
stability: alpha
description: Number of log records successfully scraped.
unit: "{datapoint}"
sum:
value_type: int
monotonic: true
scraper_scraped_metric_points:
enabled: true
stability: alpha
description: Number of metric points successfully scraped.
unit: "{datapoint}"
sum:
value_type: int
monotonic: true
================================================
FILE: scraper/scraperhelper/obs_logs.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package scraperhelper // import "go.opentelemetry.io/collector/scraper/scraperhelper"
import (
"context"
"errors"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/metric"
"go.uber.org/zap"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/scraper"
"go.opentelemetry.io/collector/scraper/scrapererror"
"go.opentelemetry.io/collector/scraper/scraperhelper/internal/metadata"
)
const (
// scrapedLogRecordsKey used to identify log records scraped by the
// Collector.
scrapedLogRecordsKey = "scraped_log_records"
// erroredLogRecordsKey used to identify log records errored (i.e.
// unable to be scraped) by the Collector.
erroredLogRecordsKey = "errored_log_records"
)
func wrapObsLogs(sc scraper.Logs, receiverID, scraperID component.ID, set component.TelemetrySettings) (scraper.Logs, error) {
telemetryBuilder, errBuilder := metadata.NewTelemetryBuilder(set)
if errBuilder != nil {
return nil, errBuilder
}
tracer := metadata.Tracer(set)
spanName := scraperKey + spanNameSep + scraperID.String() + spanNameSep + "ScrapeLogs"
otelAttrs := metric.WithAttributeSet(attribute.NewSet(
attribute.String(receiverKey, receiverID.String()),
attribute.String(scraperKey, scraperID.String()),
))
scraperFuncs := func(ctx context.Context) (plog.Logs, error) {
ctx, span := tracer.Start(ctx, spanName)
defer span.End()
md, err := sc.ScrapeLogs(ctx)
numScrapedLogs := 0
numErroredLogs := 0
if err != nil {
set.Logger.Error("Error scraping logs", zap.Error(err))
var partialErr scrapererror.PartialScrapeError
if errors.As(err, &partialErr) {
numErroredLogs = partialErr.Failed
numScrapedLogs = md.LogRecordCount()
}
} else {
numScrapedLogs = md.LogRecordCount()
}
telemetryBuilder.ScraperScrapedLogRecords.Add(ctx, int64(numScrapedLogs), otelAttrs)
telemetryBuilder.ScraperErroredLogRecords.Add(ctx, int64(numErroredLogs), otelAttrs)
// end span according to errors
if span.IsRecording() {
span.SetAttributes(
attribute.String(formatKey, pipeline.SignalMetrics.String()),
attribute.Int64(scrapedLogRecordsKey, int64(numScrapedLogs)),
attribute.Int64(erroredLogRecordsKey, int64(numErroredLogs)),
)
if err != nil {
span.SetStatus(codes.Error, err.Error())
}
}
return md, err
}
return scraper.NewLogs(scraperFuncs, scraper.WithStart(sc.Start), scraper.WithShutdown(sc.Shutdown))
}
================================================
FILE: scraper/scraperhelper/obs_logs_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package scraperhelper
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/testdata"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/scraper"
"go.opentelemetry.io/collector/scraper/scraperhelper/internal/controller"
"go.opentelemetry.io/collector/scraper/scraperhelper/internal/metadatatest"
)
func TestScrapeLogsDataOp(t *testing.T) {
tel := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tel.Shutdown(context.Background())) })
set := tel.NewTelemetrySettings()
parentCtx, parentSpan := set.TracerProvider.Tracer("test").Start(context.Background(), t.Name())
defer parentSpan.End()
params := []testParams{
{items: 23, err: partialErrFake},
{items: 29, err: errFake},
{items: 15, err: nil},
}
for i := range params {
sm, err := scraper.NewLogs(func(context.Context) (plog.Logs, error) {
return testdata.GenerateLogs(params[i].items), params[i].err
})
require.NoError(t, err)
sf, err := wrapObsLogs(sm, receiverID, scraperID, set)
require.NoError(t, err)
_, err = sf.ScrapeLogs(parentCtx)
require.ErrorIs(t, err, params[i].err)
}
spans := tel.SpanRecorder.Ended()
require.Len(t, spans, len(params))
var scrapedLogRecords, erroredLogRecords int
for i, span := range spans {
assert.Equal(t, "scraper/"+scraperID.String()+"/ScrapeLogs", span.Name())
switch {
case params[i].err == nil:
scrapedLogRecords += params[i].items
require.Contains(t, span.Attributes(), attribute.Int64(scrapedLogRecordsKey, int64(params[i].items)))
require.Contains(t, span.Attributes(), attribute.Int64(erroredLogRecordsKey, 0))
assert.Equal(t, codes.Unset, span.Status().Code)
case errors.Is(params[i].err, errFake):
// Since we get an error, we cannot record any metrics because we don't know if the returned plog.Logs is valid instance.
require.Contains(t, span.Attributes(), attribute.Int64(scrapedLogRecordsKey, 0))
require.Contains(t, span.Attributes(), attribute.Int64(erroredLogRecordsKey, 0))
assert.Equal(t, codes.Error, span.Status().Code)
assert.Equal(t, params[i].err.Error(), span.Status().Description)
case errors.Is(params[i].err, partialErrFake):
scrapedLogRecords += params[i].items
erroredLogRecords += 2
require.Contains(t, span.Attributes(), attribute.Int64(scrapedLogRecordsKey, int64(params[i].items)))
require.Contains(t, span.Attributes(), attribute.Int64(erroredLogRecordsKey, 2))
assert.Equal(t, codes.Error, span.Status().Code)
assert.Equal(t, params[i].err.Error(), span.Status().Description)
default:
t.Fatalf("unexpected err param: %v", params[i].err)
}
}
checkScraperLogs(t, tel, receiverID, scraperID, int64(scrapedLogRecords), int64(erroredLogRecords))
}
func TestCheckScraperLogs(t *testing.T) {
tel := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tel.Shutdown(context.Background())) })
sm, err := scraper.NewLogs(func(context.Context) (plog.Logs, error) {
return testdata.GenerateLogs(7), nil
})
require.NoError(t, err)
sf, err := wrapObsLogs(sm, receiverID, scraperID, tel.NewTelemetrySettings())
require.NoError(t, err)
_, err = sf.ScrapeLogs(context.Background())
require.NoError(t, err)
checkScraperLogs(t, tel, receiverID, scraperID, 7, 0)
}
func TestScrapeLogsDataOp_LogsScraperID(t *testing.T) {
tel := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tel.Shutdown(context.Background())) })
core, observedLogs := observer.New(zap.ErrorLevel)
telset := tel.NewTelemetrySettings()
telset.Logger = zap.New(core)
rSet := receiver.Settings{
ID: receiverID,
TelemetrySettings: telset,
}
set := controller.GetSettings(scraperID.Type(), rSet)
sm, err := scraper.NewLogs(func(context.Context) (plog.Logs, error) {
return plog.NewLogs(), errFake
})
require.NoError(t, err)
sf, err := wrapObsLogs(sm, receiverID, scraperID, set.TelemetrySettings)
require.NoError(t, err)
_, err = sf.ScrapeLogs(context.Background())
require.ErrorIs(t, err, errFake)
errorLogs := observedLogs.FilterLevelExact(zap.ErrorLevel).All()
require.Len(t, errorLogs, 1)
assert.Equal(t, "Error scraping logs", errorLogs[0].Message)
assert.Equal(t, scraperID.String(), errorLogs[0].ContextMap()["scraper"])
assert.Equal(t, errFake.Error(), errorLogs[0].ContextMap()["error"])
}
func checkScraperLogs(t *testing.T, tel *componenttest.Telemetry, receiver, scraper component.ID, scrapedLogRecords, erroredLogRecords int64) {
metadatatest.AssertEqualScraperScrapedLogRecords(t, tel,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(receiverKey, receiver.String()),
attribute.String(scraperKey, scraper.String())),
Value: scrapedLogRecords,
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
metadatatest.AssertEqualScraperErroredLogRecords(t, tel,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(receiverKey, receiver.String()),
attribute.String(scraperKey, scraper.String())),
Value: erroredLogRecords,
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
}
================================================
FILE: scraper/scraperhelper/obs_metrics.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package scraperhelper // import "go.opentelemetry.io/collector/scraper/scraperhelper"
import (
"context"
"errors"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/metric"
"go.uber.org/zap"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/scraper"
"go.opentelemetry.io/collector/scraper/scrapererror"
"go.opentelemetry.io/collector/scraper/scraperhelper/internal/metadata"
)
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"
spanNameSep = "/"
// receiverKey used to identify receivers in metrics and traces.
receiverKey = "receiver"
// FormatKey used to identify the format of the data received.
formatKey = "format"
)
func wrapObsMetrics(sc scraper.Metrics, receiverID, scraperID component.ID, set component.TelemetrySettings) (scraper.Metrics, error) {
telemetryBuilder, errBuilder := metadata.NewTelemetryBuilder(set)
if errBuilder != nil {
return nil, errBuilder
}
tracer := metadata.Tracer(set)
spanName := scraperKey + spanNameSep + scraperID.String() + spanNameSep + "ScrapeMetrics"
otelAttrs := metric.WithAttributeSet(attribute.NewSet(
attribute.String(receiverKey, receiverID.String()),
attribute.String(scraperKey, scraperID.String()),
))
scraperFuncs := func(ctx context.Context) (pmetric.Metrics, error) {
ctx, span := tracer.Start(ctx, spanName)
defer span.End()
md, err := sc.ScrapeMetrics(ctx)
numScrapedMetrics := 0
numErroredMetrics := 0
if err != nil {
set.Logger.Error("Error scraping metrics", zap.Error(err))
var partialErr scrapererror.PartialScrapeError
if errors.As(err, &partialErr) {
numErroredMetrics = partialErr.Failed
numScrapedMetrics = md.MetricCount()
}
} else {
numScrapedMetrics = md.MetricCount()
}
telemetryBuilder.ScraperScrapedMetricPoints.Add(ctx, int64(numScrapedMetrics), otelAttrs)
telemetryBuilder.ScraperErroredMetricPoints.Add(ctx, int64(numErroredMetrics), otelAttrs)
// end span according to errors
if span.IsRecording() {
span.SetAttributes(
attribute.String(formatKey, pipeline.SignalMetrics.String()),
attribute.Int64(scrapedMetricPointsKey, int64(numScrapedMetrics)),
attribute.Int64(erroredMetricPointsKey, int64(numErroredMetrics)),
)
if err != nil {
span.SetStatus(codes.Error, err.Error())
}
}
return md, err
}
return scraper.NewMetrics(scraperFuncs, scraper.WithStart(sc.Start), scraper.WithShutdown(sc.Shutdown))
}
================================================
FILE: scraper/scraperhelper/obs_metrics_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package scraperhelper
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/testdata"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/scraper"
"go.opentelemetry.io/collector/scraper/scrapererror"
"go.opentelemetry.io/collector/scraper/scraperhelper/internal/controller"
"go.opentelemetry.io/collector/scraper/scraperhelper/internal/metadatatest"
)
var (
receiverID = component.MustNewID("fakeReceiver")
scraperID = component.MustNewID("fakeScraper")
errFake = errors.New("errFake")
partialErrFake = scrapererror.NewPartialScrapeError(errFake, 2)
)
type testParams struct {
items int
err error
}
func TestScrapeMetricsDataOp(t *testing.T) {
tel := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tel.Shutdown(context.Background())) })
set := tel.NewTelemetrySettings()
parentCtx, parentSpan := set.TracerProvider.Tracer("test").Start(context.Background(), t.Name())
defer parentSpan.End()
params := []testParams{
{items: 23, err: partialErrFake},
{items: 29, err: errFake},
{items: 15, err: nil},
}
for i := range params {
sm, err := scraper.NewMetrics(func(context.Context) (pmetric.Metrics, error) {
return testdata.GenerateMetrics(params[i].items), params[i].err
})
require.NoError(t, err)
sf, err := wrapObsMetrics(sm, receiverID, scraperID, set)
require.NoError(t, err)
_, err = sf.ScrapeMetrics(parentCtx)
require.ErrorIs(t, err, params[i].err)
}
spans := tel.SpanRecorder.Ended()
require.Len(t, spans, len(params))
var scrapedMetricPoints, erroredMetricPoints int
for i, span := range spans {
assert.Equal(t, "scraper/"+scraperID.String()+"/ScrapeMetrics", span.Name())
switch {
case params[i].err == nil:
scrapedMetricPoints += params[i].items
require.Contains(t, span.Attributes(), attribute.Int64(scrapedMetricPointsKey, int64(params[i].items)))
require.Contains(t, span.Attributes(), attribute.Int64(erroredMetricPointsKey, 0))
assert.Equal(t, codes.Unset, span.Status().Code)
case errors.Is(params[i].err, errFake):
// Since we get an error, we cannot record any metrics because we don't know if the returned pmetric.Metrics is valid instance.
require.Contains(t, span.Attributes(), attribute.Int64(scrapedMetricPointsKey, 0))
require.Contains(t, span.Attributes(), attribute.Int64(erroredMetricPointsKey, 0))
assert.Equal(t, codes.Error, span.Status().Code)
assert.Equal(t, params[i].err.Error(), span.Status().Description)
case errors.Is(params[i].err, partialErrFake):
scrapedMetricPoints += params[i].items
erroredMetricPoints += 2
require.Contains(t, span.Attributes(), attribute.Int64(scrapedMetricPointsKey, int64(params[i].items)))
require.Contains(t, span.Attributes(), attribute.Int64(erroredMetricPointsKey, 2))
assert.Equal(t, codes.Error, span.Status().Code)
assert.Equal(t, params[i].err.Error(), span.Status().Description)
default:
t.Fatalf("unexpected err param: %v", params[i].err)
}
}
checkScraperMetrics(t, tel, receiverID, scraperID, int64(scrapedMetricPoints), int64(erroredMetricPoints))
}
func TestCheckScraperMetrics(t *testing.T) {
tel := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tel.Shutdown(context.Background())) })
sm, err := scraper.NewMetrics(func(context.Context) (pmetric.Metrics, error) {
return testdata.GenerateMetrics(7), nil
})
require.NoError(t, err)
sf, err := wrapObsMetrics(sm, receiverID, scraperID, tel.NewTelemetrySettings())
require.NoError(t, err)
_, err = sf.ScrapeMetrics(context.Background())
require.NoError(t, err)
checkScraperMetrics(t, tel, receiverID, scraperID, 7, 0)
}
func TestScrapeMetricsDataOp_LogsScraperID(t *testing.T) {
tel := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tel.Shutdown(context.Background())) })
core, observedLogs := observer.New(zap.ErrorLevel)
telset := tel.NewTelemetrySettings()
telset.Logger = zap.New(core)
rSet := receiver.Settings{
ID: receiverID,
TelemetrySettings: telset,
}
set := controller.GetSettings(scraperID.Type(), rSet)
sm, err := scraper.NewMetrics(func(context.Context) (pmetric.Metrics, error) {
return pmetric.NewMetrics(), errFake
})
require.NoError(t, err)
sf, err := wrapObsMetrics(sm, receiverID, scraperID, set.TelemetrySettings)
require.NoError(t, err)
_, err = sf.ScrapeMetrics(context.Background())
require.ErrorIs(t, err, errFake)
errorLogs := observedLogs.FilterLevelExact(zap.ErrorLevel).All()
require.Len(t, errorLogs, 1)
assert.Equal(t, "Error scraping metrics", errorLogs[0].Message)
assert.Equal(t, scraperID.String(), errorLogs[0].ContextMap()["scraper"])
assert.Equal(t, errFake.Error(), errorLogs[0].ContextMap()["error"])
}
func checkScraperMetrics(t *testing.T, tt *componenttest.Telemetry, receiver, scraper component.ID, scrapedMetricPoints, erroredMetricPoints int64) {
metadatatest.AssertEqualScraperScrapedMetricPoints(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(receiverKey, receiver.String()),
attribute.String(scraperKey, scraper.String())),
Value: scrapedMetricPoints,
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
metadatatest.AssertEqualScraperErroredMetricPoints(t, tt,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(receiverKey, receiver.String()),
attribute.String(scraperKey, scraper.String())),
Value: erroredMetricPoints,
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
}
================================================
FILE: scraper/scraperhelper/xscraperhelper/Makefile
================================================
include ../../../Makefile.Common
================================================
FILE: scraper/scraperhelper/xscraperhelper/controller.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package xscraperhelper provides utilities for scrapers.
package xscraperhelper // import "go.opentelemetry.io/collector/scraper/scraperhelper/xscraperhelper"
import (
"context"
"time"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/xreceiver"
"go.opentelemetry.io/collector/scraper"
"go.opentelemetry.io/collector/scraper/scrapererror"
"go.opentelemetry.io/collector/scraper/scraperhelper"
"go.opentelemetry.io/collector/scraper/scraperhelper/internal/controller"
"go.opentelemetry.io/collector/scraper/xscraper"
)
const (
// scraperKey used to identify scrapers in metrics and traces.
scraperKey = "scraper"
spanNameSep = "/"
// receiverKey used to identify receivers in metrics and traces.
receiverKey = "receiver"
// FormatKey used to identify the format of the data received.
formatKey = "format"
)
type factoryWithConfig struct {
f xscraper.Factory
cfg component.Config
}
type controllerOptions struct {
tickerCh <-chan time.Time
factoriesWithConfig []factoryWithConfig
}
// ControllerOption apply changes to internal options.
type ControllerOption interface {
apply(*controllerOptions)
}
type optionFunc func(*controllerOptions)
func (of optionFunc) apply(e *controllerOptions) {
of(e)
}
// AddProfilesScraper configures the xscraper.Profiles to be called with the specified options,
// and at the specified collection interval.
//
// Observability information will be reported, and the scraped profiles
// will be passed to the next consumer.
func AddProfilesScraper(t component.Type, sc xscraper.Profiles) ControllerOption {
f := xscraper.NewFactory(t, nil,
xscraper.WithProfiles(func(context.Context, scraper.Settings, component.Config) (xscraper.Profiles, error) {
return sc, nil
}, component.StabilityLevelDevelopment))
return AddFactoryWithConfig(f, nil)
}
// AddFactoryWithConfig configures the scraper.Factory and associated config that
// will be used to create a new scraper. The created scraper will 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 AddFactoryWithConfig(f xscraper.Factory, cfg component.Config) ControllerOption {
return optionFunc(func(o *controllerOptions) {
o.factoriesWithConfig = append(o.factoriesWithConfig, factoryWithConfig{f: f, cfg: cfg})
})
}
// WithTickerChannel allows you to override the scraper controller's ticker
// channel to specify when scrape is called. This is only expected to be
// used by tests.
func WithTickerChannel(tickerCh <-chan time.Time) ControllerOption {
return optionFunc(func(o *controllerOptions) {
o.tickerCh = tickerCh
})
}
// NewProfilesController creates a receiver.Profiles with the configured options, that can control multiple xscraper.Profiles.
func NewProfilesController(cfg *scraperhelper.ControllerConfig,
rSet receiver.Settings,
nextConsumer xconsumer.Profiles,
options ...ControllerOption,
) (xreceiver.Profiles, error) {
co := getOptions(options)
scrapers := make([]xscraper.Profiles, 0, len(co.factoriesWithConfig))
for _, fwc := range co.factoriesWithConfig {
set := controller.GetSettings(fwc.f.Type(), rSet)
s, err := fwc.f.CreateProfiles(context.Background(), set, fwc.cfg)
if err != nil {
return nil, err
}
s, err = wrapObsProfiles(s, rSet.ID, set.ID, set.TelemetrySettings)
if err != nil {
return nil, err
}
scrapers = append(scrapers, s)
}
return controller.NewController[xscraper.Profiles](
cfg, rSet, scrapers, func(c *controller.Controller[xscraper.Profiles]) { scrapeProfiles(c, nextConsumer) }, co.tickerCh)
}
func getOptions(options []ControllerOption) controllerOptions {
co := controllerOptions{}
for _, op := range options {
op.apply(&co)
}
return co
}
func scrapeProfiles(c *controller.Controller[xscraper.Profiles], nextConsumer xconsumer.Profiles) {
ctx, done := controller.WithScrapeContext(c.Timeout)
defer done()
profiles := pprofile.NewProfiles()
for i := range c.Scrapers {
md, err := c.Scrapers[i].ScrapeProfiles(ctx)
if err != nil && !scrapererror.IsPartialScrapeError(err) {
continue
}
md.ResourceProfiles().MoveAndAppendTo(profiles.ResourceProfiles())
}
// TODO: Add proper receiver observability for profiles when receiverhelper supports it
// For now, we skip the obs report and just consume the profiles directly
_ = nextConsumer.ConsumeProfiles(ctx, profiles)
}
================================================
FILE: scraper/scraperhelper/xscraperhelper/controller_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package xscraperhelper provides utilities for scrapers.
package xscraperhelper // import "go.opentelemetry.io/collector/scraper/scraperhelper/xscraperhelper"
import (
"context"
"errors"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"go.uber.org/multierr"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/receivertest"
"go.opentelemetry.io/collector/scraper"
"go.opentelemetry.io/collector/scraper/scrapererror"
"go.opentelemetry.io/collector/scraper/scraperhelper"
"go.opentelemetry.io/collector/scraper/scraperhelper/internal/testhelper"
"go.opentelemetry.io/collector/scraper/scraperhelper/xscraperhelper/internal/metadatatest"
"go.opentelemetry.io/collector/scraper/xscraper"
)
type testScrape struct {
ch chan int
timesScrapeCalled int
err error
}
func (ts *testScrape) scrapeProfiles(context.Context) (pprofile.Profiles, error) {
ts.timesScrapeCalled++
ts.ch <- ts.timesScrapeCalled
if ts.err != nil {
return pprofile.Profiles{}, ts.err
}
md := pprofile.NewProfiles()
profile := md.ResourceProfiles().AppendEmpty().ScopeProfiles().AppendEmpty().Profiles().AppendEmpty()
profile.Samples().AppendEmpty()
return md, nil
}
func newTestNoDelaySettings() *scraperhelper.ControllerConfig {
return &scraperhelper.ControllerConfig{
CollectionInterval: time.Second,
InitialDelay: 0,
}
}
type scraperTestCase struct {
name string
scrapers int
scraperControllerSettings *scraperhelper.ControllerConfig
scrapeErr error
expectScraped bool
initialize bool
close bool
initializeErr error
closeErr error
}
func TestProfilesScrapeController(t *testing.T) {
testCases := []scraperTestCase{
{
name: "NoScrapers",
},
{
name: "AddProfilesScrapersWithCollectionInterval",
scrapers: 2,
expectScraped: true,
},
{
name: "AddProfilesScrapers_ScrapeError",
scrapers: 2,
scrapeErr: errors.New("err1"),
},
{
name: "AddProfilesScrapersWithInitializeAndClose",
scrapers: 2,
initialize: true,
expectScraped: true,
close: true,
},
{
name: "AddProfilesScrapersWithInitializeAndCloseErrors",
scrapers: 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) {
receiverID := component.MustNewID("receiver")
tel := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tel.Shutdown(context.Background())) })
set := tel.NewTelemetrySettings()
_, parentSpan := set.TracerProvider.Tracer("test").Start(context.Background(), t.Name())
defer parentSpan.End()
initializeChs := make([]chan bool, test.scrapers)
scrapeProfilesChs := make([]chan int, test.scrapers)
closeChs := make([]chan bool, test.scrapers)
options := configureProfilesOptions(t, test, initializeChs, scrapeProfilesChs, closeChs)
tickerCh := make(chan time.Time)
options = append(options, WithTickerChannel(tickerCh))
sink := new(consumertest.ProfilesSink)
cfg := newTestNoDelaySettings()
if test.scraperControllerSettings != nil {
cfg = test.scraperControllerSettings
}
mr, err := NewProfilesController(cfg, receiver.Settings{ID: receiverID, TelemetrySettings: set, BuildInfo: component.NewDefaultBuildInfo()}, sink, options...)
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 {
testhelper.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 _, ch := range scrapeProfilesChs {
<-ch
}
// Consume the initial scrapes on start
for range iterations {
tickerCh <- time.Now()
for _, ch := range scrapeProfilesChs {
<-ch
}
}
// wait until all calls to scrape have completed
if test.scrapeErr == nil {
require.Eventually(t, func() bool {
return sink.SampleCount() == (1+iterations)*(test.scrapers)
}, time.Second, time.Millisecond)
}
if test.expectScraped {
assert.GreaterOrEqual(t, sink.SampleCount(), iterations)
}
spans := tel.SpanRecorder.Ended()
testhelper.AssertScraperSpan(t, test.scrapeErr, spans, "scraper/scraper/ScrapeProfiles")
assertProfilesScraperObsMetrics(t, tel, receiverID, component.MustNewID("scraper"), test.scrapeErr, sink)
}
err = mr.Shutdown(context.Background())
expectedShutdownErr := getExpectedShutdownErr(test)
if expectedShutdownErr != nil {
assert.EqualError(t, err, expectedShutdownErr.Error())
} else if test.close {
testhelper.AssertChannelsCalled(t, closeChs, "shutdown was not called")
}
})
}
}
func getExpectedStartErr(test scraperTestCase) error {
return test.initializeErr
}
func getExpectedShutdownErr(test scraperTestCase) error {
var errs []error
if test.closeErr != nil {
for i := 0; i < test.scrapers; i++ {
errs = append(errs, test.closeErr)
}
}
return multierr.Combine(errs...)
}
func configureProfilesOptions(t *testing.T, test scraperTestCase, initializeChs []chan bool, scrapeProfilesChs []chan int, closeChs []chan bool) []ControllerOption {
var profilesOptions []ControllerOption
for i := 0; i < test.scrapers; i++ {
scrapeProfilesChs[i] = make(chan int)
ts := &testScrape{ch: scrapeProfilesChs[i], err: test.scrapeErr}
var xscraperOptions []xscraper.Option
if test.initialize {
initializeChs[i] = make(chan bool, 1)
ti := testhelper.NewTestInitialize(initializeChs[i], test.initializeErr)
xscraperOptions = append(xscraperOptions, xscraper.WithStart(ti.Start))
}
if test.close {
closeChs[i] = make(chan bool, 1)
tc := testhelper.NewTestClose(closeChs[i], test.closeErr)
xscraperOptions = append(xscraperOptions, xscraper.WithShutdown(tc.Shutdown))
}
scp, err := xscraper.NewProfiles(ts.scrapeProfiles, xscraperOptions...)
require.NoError(t, err)
profilesOptions = append(profilesOptions, AddProfilesScraper(component.MustNewType("scraper"), scp))
}
return profilesOptions
}
func TestSingleProfilesScraperPerInterval(t *testing.T) {
scrapeCh := make(chan int, 10)
ts := &testScrape{ch: scrapeCh}
cfg := newTestNoDelaySettings()
tickerCh := make(chan time.Time)
scp, err := xscraper.NewProfiles(ts.scrapeProfiles)
require.NoError(t, err)
recv, err := NewProfilesController(
cfg,
receivertest.NewNopSettings(receivertest.NopType),
new(consumertest.ProfilesSink),
AddProfilesScraper(component.MustNewType("scraper"), scp),
WithTickerChannel(tickerCh),
)
require.NoError(t, err)
require.NoError(t, recv.Start(context.Background(), componenttest.NewNopHost()))
defer func() { require.NoError(t, recv.Shutdown(context.Background())) }()
tickerCh <- time.Now()
assert.Eventually(
t,
func() bool {
return <-scrapeCh == 2
},
300*time.Millisecond,
100*time.Millisecond,
"Make sure the scraper channel is called twice",
)
select {
case <-scrapeCh:
assert.Fail(t, "Scrape was called more than twice")
case <-time.After(100 * time.Millisecond):
return
}
}
func TestProfilesScraperControllerStartsOnInit(t *testing.T) {
t.Parallel()
ts := &testScrape{
ch: make(chan int, 1),
}
scp, err := xscraper.NewProfiles(ts.scrapeProfiles)
require.NoError(t, err, "Must not error when creating scraper")
r, err := NewProfilesController(
&scraperhelper.ControllerConfig{
CollectionInterval: time.Hour,
InitialDelay: 0,
},
receivertest.NewNopSettings(receivertest.NopType),
new(consumertest.ProfilesSink),
AddProfilesScraper(component.MustNewType("scraper"), scp),
)
require.NoError(t, err, "Must not error when creating scrape controller")
assert.NoError(t, r.Start(context.Background(), componenttest.NewNopHost()), "Must not error on start")
<-time.After(500 * time.Nanosecond)
require.NoError(t, r.Shutdown(context.Background()), "Must not have errored on shutdown")
assert.Equal(t, 1, ts.timesScrapeCalled, "Must have been called as soon as the controller started")
}
func TestProfilesScraperControllerInitialDelay(t *testing.T) {
if testing.Short() {
t.Skip("This requires real time to pass, skipping")
return
}
t.Parallel()
var (
elapsed = make(chan time.Time, 1)
cfg = scraperhelper.ControllerConfig{
CollectionInterval: time.Second,
InitialDelay: 300 * time.Millisecond,
}
)
scp, err := xscraper.NewProfiles(func(context.Context) (pprofile.Profiles, error) {
elapsed <- time.Now()
return pprofile.NewProfiles(), nil
})
require.NoError(t, err, "Must not error when creating scraper")
r, err := NewProfilesController(
&cfg,
receivertest.NewNopSettings(receivertest.NopType),
new(consumertest.ProfilesSink),
AddProfilesScraper(component.MustNewType("scraper"), scp),
)
require.NoError(t, err, "Must not error when creating receiver")
t0 := time.Now()
require.NoError(t, r.Start(context.Background(), componenttest.NewNopHost()), "Must not error when starting")
t1 := <-elapsed
assert.GreaterOrEqual(t, t1.Sub(t0), 300*time.Millisecond, "Must have had 300ms pass as defined by initial delay")
assert.NoError(t, r.Shutdown(context.Background()), "Must not error closing down")
}
func TestProfilesScraperShutdownBeforeScrapeCanStart(t *testing.T) {
cfg := scraperhelper.ControllerConfig{
CollectionInterval: time.Second,
InitialDelay: 5 * time.Second,
}
scp, err := xscraper.NewProfiles(func(context.Context) (pprofile.Profiles, error) {
// make the scraper wait for long enough it would disrupt a shutdown.
time.Sleep(30 * time.Second)
return pprofile.NewProfiles(), nil
})
require.NoError(t, err, "Must not error when creating scraper")
r, err := NewProfilesController(
&cfg,
receivertest.NewNopSettings(receivertest.NopType),
new(consumertest.ProfilesSink),
AddProfilesScraper(component.MustNewType("scraper"), scp),
)
require.NoError(t, err, "Must not error when creating receiver")
require.NoError(t, r.Start(context.Background(), componenttest.NewNopHost()))
shutdown := make(chan struct{}, 1)
go func() {
assert.NoError(t, r.Shutdown(context.Background()))
close(shutdown)
}()
timer := time.NewTicker(10 * time.Second)
select {
case <-timer.C:
require.Fail(t, "shutdown should not wait for scraping")
case <-shutdown:
}
}
func assertProfilesScraperObsMetrics(t *testing.T, tel *componenttest.Telemetry, receiver, scraper component.ID, expectedErr error, sink *consumertest.ProfilesSink) {
sampleCounts := 0
for _, md := range sink.AllProfiles() {
sampleCounts += md.SampleCount()
}
expectedScraped := int64(sink.SampleCount())
expectedErrored := int64(0)
if expectedErr != nil {
var partialError scrapererror.PartialScrapeError
if errors.As(expectedErr, &partialError) {
expectedErrored = int64(partialError.Failed)
} else {
expectedScraped = int64(0)
expectedErrored = int64(sink.SampleCount())
}
}
metadatatest.AssertEqualScraperScrapedProfileRecords(t, tel,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(receiverKey, receiver.String()),
attribute.String(scraperKey, scraper.String())),
Value: expectedScraped,
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
metadatatest.AssertEqualScraperErroredProfileRecords(t, tel,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(receiverKey, receiver.String()),
attribute.String(scraperKey, scraper.String())),
Value: expectedErrored,
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
}
// TestNewProfilesControllerCreateError tests that NewProfilesController returns an error
// when the scraper factory's CreateProfiles method fails.
func TestNewProfilesControllerCreateError(t *testing.T) {
expectedErr := errors.New("create profiles error")
f := xscraper.NewFactory(component.MustNewType("scraper"), nil,
xscraper.WithProfiles(func(context.Context, scraper.Settings, component.Config) (xscraper.Profiles, error) {
return nil, expectedErr
}, component.StabilityLevelDevelopment))
cfg := newTestNoDelaySettings()
_, err := NewProfilesController(
cfg,
receivertest.NewNopSettings(receivertest.NopType),
new(consumertest.ProfilesSink),
AddFactoryWithConfig(f, nil),
)
require.Error(t, err)
assert.Equal(t, expectedErr, err)
}
// errorMeter is a meter that returns errors when creating instruments.
type errorMeter struct {
metric.Meter
}
func (errorMeter) Int64Counter(string, ...metric.Int64CounterOption) (metric.Int64Counter, error) {
return nil, errors.New("counter creation error")
}
// errorMeterProvider provides errorMeter instances.
type errorMeterProvider struct {
metric.MeterProvider
}
func (errorMeterProvider) Meter(string, ...metric.MeterOption) metric.Meter {
return errorMeter{}
}
// TestNewProfilesControllerTelemetryError tests that NewProfilesController returns an error
// when telemetry builder creation fails.
func TestNewProfilesControllerTelemetryError(t *testing.T) {
// Create a scraper that works
scp, err := xscraper.NewProfiles(func(context.Context) (pprofile.Profiles, error) {
return pprofile.NewProfiles(), nil
})
require.NoError(t, err)
f := xscraper.NewFactory(component.MustNewType("scraper"), nil,
xscraper.WithProfiles(func(context.Context, scraper.Settings, component.Config) (xscraper.Profiles, error) {
return scp, nil
}, component.StabilityLevelDevelopment))
// Create telemetry settings with a meter provider that fails
set := componenttest.NewNopTelemetrySettings()
set.MeterProvider = errorMeterProvider{}
cfg := newTestNoDelaySettings()
_, err = NewProfilesController(
cfg,
receiver.Settings{
ID: component.MustNewID("receiver"),
TelemetrySettings: set,
BuildInfo: component.NewDefaultBuildInfo(),
},
new(consumertest.ProfilesSink),
AddFactoryWithConfig(f, nil),
)
// The error should be from wrapObsProfiles failing due to telemetry builder creation
require.Error(t, err)
assert.Contains(t, err.Error(), "counter creation error")
}
================================================
FILE: scraper/scraperhelper/xscraperhelper/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:generate mdatagen metadata.yaml
// Package xscraperhelper provides utilities for scrapers.
package xscraperhelper // import "go.opentelemetry.io/collector/scraper/scraperhelper/xscraperhelper"
================================================
FILE: scraper/scraperhelper/xscraperhelper/documentation.md
================================================
[comment]: <> (Code generated by mdatagen. DO NOT EDIT.)
# xscraperhelper
## Internal Telemetry
The following telemetry is emitted by this component.
### otelcol_scraper_errored_profile_records
Number of profile records that were unable to be scraped.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {datapoint} | Sum | Int | true | Alpha |
### otelcol_scraper_scraped_profile_records
Number of profile records successfully scraped.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {datapoint} | Sum | Int | true | Alpha |
================================================
FILE: scraper/scraperhelper/xscraperhelper/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package xscraperhelper
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: scraper/scraperhelper/xscraperhelper/go.mod
================================================
module go.opentelemetry.io/collector/scraper/scraperhelper/xscraperhelper
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.opentelemetry.io/collector/consumer/consumertest v0.148.0
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0
go.opentelemetry.io/collector/pdata/pprofile v0.148.0
go.opentelemetry.io/collector/pdata/testdata v0.148.0
go.opentelemetry.io/collector/pipeline/xpipeline v0.148.0
go.opentelemetry.io/collector/receiver v1.54.0
go.opentelemetry.io/collector/receiver/receivertest v0.148.0
go.opentelemetry.io/collector/receiver/xreceiver v0.148.0
go.opentelemetry.io/collector/scraper v0.148.0
go.opentelemetry.io/collector/scraper/scraperhelper v0.148.0
go.opentelemetry.io/collector/scraper/xscraper v0.148.0
go.opentelemetry.io/otel v1.42.0
go.opentelemetry.io/otel/metric v1.42.0
go.opentelemetry.io/otel/sdk/metric v1.42.0
go.opentelemetry.io/otel/trace v1.42.0
go.uber.org/goleak v1.3.0
go.uber.org/multierr v1.11.0
go.uber.org/zap v1.27.1
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/consumer v1.54.0 // indirect
go.opentelemetry.io/collector/consumer/consumererror v0.148.0 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect
go.opentelemetry.io/collector/pdata v1.54.0 // indirect
go.opentelemetry.io/collector/pipeline v1.54.0 // indirect
go.opentelemetry.io/collector/receiver/receiverhelper v0.148.0 // indirect
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
golang.org/x/sys v0.41.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/scraper/scraperhelper => ..
replace go.opentelemetry.io/collector/internal/testutil => ../../../internal/testutil
replace go.opentelemetry.io/collector/featuregate => ../../../featuregate
replace go.opentelemetry.io/collector/receiver/receiverhelper => ../../../receiver/receiverhelper
replace go.opentelemetry.io/collector/pdata => ../../../pdata
replace go.opentelemetry.io/collector/scraper/xscraper => ../../xscraper
replace go.opentelemetry.io/collector/scraper => ../..
replace go.opentelemetry.io/collector/receiver => ../../../receiver
replace go.opentelemetry.io/collector/pdata/pprofile => ../../../pdata/pprofile
replace go.opentelemetry.io/collector/pipeline => ../../../pipeline
replace go.opentelemetry.io/collector/pdata/testdata => ../../../pdata/testdata
replace go.opentelemetry.io/collector/receiver/xreceiver => ../../../receiver/xreceiver
replace go.opentelemetry.io/collector/consumer/xconsumer => ../../../consumer/xconsumer
replace go.opentelemetry.io/collector/consumer/consumertest => ../../../consumer/consumertest
replace go.opentelemetry.io/collector/component/componenttest => ../../../component/componenttest
replace go.opentelemetry.io/collector/consumer/consumererror => ../../../consumer/consumererror
replace go.opentelemetry.io/collector/consumer => ../../../consumer
replace go.opentelemetry.io/collector/component => ../../../component
replace go.opentelemetry.io/collector/receiver/receivertest => ../../../receiver/receivertest
replace go.opentelemetry.io/collector/pipeline/xpipeline => ../../../pipeline/xpipeline
replace go.opentelemetry.io/collector/internal/componentalias => ../../../internal/componentalias
================================================
FILE: scraper/scraperhelper/xscraperhelper/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: scraper/scraperhelper/xscraperhelper/internal/metadata/generated_telemetry.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"errors"
"sync"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/collector/component"
)
func Meter(settings component.TelemetrySettings) metric.Meter {
return settings.MeterProvider.Meter("go.opentelemetry.io/collector/scraper/scraperhelper/xscraperhelper")
}
func Tracer(settings component.TelemetrySettings) trace.Tracer {
return settings.TracerProvider.Tracer("go.opentelemetry.io/collector/scraper/scraperhelper/xscraperhelper")
}
// TelemetryBuilder provides an interface for components to report telemetry
// as defined in metadata and user config.
type TelemetryBuilder struct {
meter metric.Meter
mu sync.Mutex
registrations []metric.Registration
ScraperErroredProfileRecords metric.Int64Counter
ScraperScrapedProfileRecords metric.Int64Counter
}
// TelemetryBuilderOption applies changes to default builder.
type TelemetryBuilderOption interface {
apply(*TelemetryBuilder)
}
type telemetryBuilderOptionFunc func(mb *TelemetryBuilder)
func (tbof telemetryBuilderOptionFunc) apply(mb *TelemetryBuilder) {
tbof(mb)
}
// Shutdown unregister all registered callbacks for async instruments.
func (builder *TelemetryBuilder) Shutdown() {
builder.mu.Lock()
defer builder.mu.Unlock()
for _, reg := range builder.registrations {
reg.Unregister()
}
}
// NewTelemetryBuilder provides a struct with methods to update all internal telemetry
// for a component
func NewTelemetryBuilder(settings component.TelemetrySettings, options ...TelemetryBuilderOption) (*TelemetryBuilder, error) {
builder := TelemetryBuilder{}
for _, op := range options {
op.apply(&builder)
}
builder.meter = Meter(settings)
var err, errs error
builder.ScraperErroredProfileRecords, err = builder.meter.Int64Counter(
"otelcol_scraper_errored_profile_records",
metric.WithDescription("Number of profile records that were unable to be scraped. [Alpha]"),
metric.WithUnit("{datapoint}"),
)
errs = errors.Join(errs, err)
builder.ScraperScrapedProfileRecords, err = builder.meter.Int64Counter(
"otelcol_scraper_scraped_profile_records",
metric.WithDescription("Number of profile records successfully scraped. [Alpha]"),
metric.WithUnit("{datapoint}"),
)
errs = errors.Join(errs, err)
return &builder, errs
}
================================================
FILE: scraper/scraperhelper/xscraperhelper/internal/metadata/generated_telemetry_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/metric"
embeddedmetric "go.opentelemetry.io/otel/metric/embedded"
noopmetric "go.opentelemetry.io/otel/metric/noop"
"go.opentelemetry.io/otel/trace"
embeddedtrace "go.opentelemetry.io/otel/trace/embedded"
nooptrace "go.opentelemetry.io/otel/trace/noop"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
)
type mockMeter struct {
noopmetric.Meter
name string
}
type mockMeterProvider struct {
embeddedmetric.MeterProvider
}
func (m mockMeterProvider) Meter(name string, opts ...metric.MeterOption) metric.Meter {
return mockMeter{name: name}
}
type mockTracer struct {
nooptrace.Tracer
name string
}
type mockTracerProvider struct {
embeddedtrace.TracerProvider
}
func (m mockTracerProvider) Tracer(name string, opts ...trace.TracerOption) trace.Tracer {
return mockTracer{name: name}
}
func TestProviders(t *testing.T) {
set := component.TelemetrySettings{
MeterProvider: mockMeterProvider{},
TracerProvider: mockTracerProvider{},
}
meter := Meter(set)
if m, ok := meter.(mockMeter); ok {
require.Equal(t, "go.opentelemetry.io/collector/scraper/scraperhelper/xscraperhelper", m.name)
} else {
require.Fail(t, "returned Meter not mockMeter")
}
tracer := Tracer(set)
if m, ok := tracer.(mockTracer); ok {
require.Equal(t, "go.opentelemetry.io/collector/scraper/scraperhelper/xscraperhelper", m.name)
} else {
require.Fail(t, "returned Meter not mockTracer")
}
}
func TestNewTelemetryBuilder(t *testing.T) {
set := componenttest.NewNopTelemetrySettings()
applied := false
_, err := NewTelemetryBuilder(set, telemetryBuilderOptionFunc(func(b *TelemetryBuilder) {
applied = true
}))
require.NoError(t, err)
require.True(t, applied)
}
================================================
FILE: scraper/scraperhelper/xscraperhelper/internal/metadatatest/generated_telemetrytest.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadatatest
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"go.opentelemetry.io/collector/component/componenttest"
)
func AssertEqualScraperErroredProfileRecords(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_scraper_errored_profile_records",
Description: "Number of profile records that were unable to be scraped. [Alpha]",
Unit: "{datapoint}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_scraper_errored_profile_records")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualScraperScrapedProfileRecords(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_scraper_scraped_profile_records",
Description: "Number of profile records successfully scraped. [Alpha]",
Unit: "{datapoint}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_scraper_scraped_profile_records")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
================================================
FILE: scraper/scraperhelper/xscraperhelper/internal/metadatatest/generated_telemetrytest_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadatatest
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/scraper/scraperhelper/xscraperhelper/internal/metadata"
)
func TestSetupTelemetry(t *testing.T) {
testTel := componenttest.NewTelemetry()
tb, err := metadata.NewTelemetryBuilder(testTel.NewTelemetrySettings())
require.NoError(t, err)
defer tb.Shutdown()
tb.ScraperErroredProfileRecords.Add(context.Background(), 1)
tb.ScraperScrapedProfileRecords.Add(context.Background(), 1)
AssertEqualScraperErroredProfileRecords(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualScraperScrapedProfileRecords(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
require.NoError(t, testTel.Shutdown(context.Background()))
}
================================================
FILE: scraper/scraperhelper/xscraperhelper/metadata.yaml
================================================
type: xscraperhelper
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
codeowners:
active:
- florianl
stability:
alpha: [profiles]
telemetry:
metrics:
scraper_errored_profile_records:
enabled: true
stability: alpha
description: Number of profile records that were unable to be scraped.
unit: "{datapoint}"
sum:
value_type: int
monotonic: true
scraper_scraped_profile_records:
enabled: true
stability: alpha
description: Number of profile records successfully scraped.
unit: "{datapoint}"
sum:
value_type: int
monotonic: true
================================================
FILE: scraper/scraperhelper/xscraperhelper/obs_profiles.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package xscraperhelper provides utilities for scrapers.
package xscraperhelper // import "go.opentelemetry.io/collector/scraper/scraperhelper/xscraperhelper"
import (
"context"
"errors"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/metric"
"go.uber.org/zap"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pipeline/xpipeline"
"go.opentelemetry.io/collector/scraper/scrapererror"
"go.opentelemetry.io/collector/scraper/scraperhelper/xscraperhelper/internal/metadata"
"go.opentelemetry.io/collector/scraper/xscraper"
)
const (
// scrapedProfileRecordsKey used to identify profile records scraped by the
// Collector.
scrapedProfileRecordsKey = "scraped_profile_records"
// erroredProfileRecordsKey used to identify profile records errored (i.e.
// unable to be scraped) by the Collector.
erroredProfileRecordsKey = "errored_profile_records"
)
func wrapObsProfiles(sc xscraper.Profiles, receiverID, scraperID component.ID, set component.TelemetrySettings) (xscraper.Profiles, error) {
telemetryBuilder, errBuilder := metadata.NewTelemetryBuilder(set)
if errBuilder != nil {
return nil, errBuilder
}
tracer := metadata.Tracer(set)
spanName := scraperKey + spanNameSep + scraperID.String() + spanNameSep + "ScrapeProfiles"
otelAttrs := metric.WithAttributeSet(attribute.NewSet(
attribute.String(receiverKey, receiverID.String()),
attribute.String(scraperKey, scraperID.String()),
))
scraperFuncs := func(ctx context.Context) (pprofile.Profiles, error) {
ctx, span := tracer.Start(ctx, spanName)
defer span.End()
md, err := sc.ScrapeProfiles(ctx)
numScrapedProfiles := 0
numErroredProfiles := 0
if err != nil {
set.Logger.Error("Error scraping profiles", zap.Error(err))
var partialErr scrapererror.PartialScrapeError
if errors.As(err, &partialErr) {
numErroredProfiles = partialErr.Failed
numScrapedProfiles = md.ProfileCount()
}
} else {
numScrapedProfiles = md.ProfileCount()
}
telemetryBuilder.ScraperScrapedProfileRecords.Add(ctx, int64(numScrapedProfiles), otelAttrs)
telemetryBuilder.ScraperErroredProfileRecords.Add(ctx, int64(numErroredProfiles), otelAttrs)
// end span according to errors
if span.IsRecording() {
span.SetAttributes(
attribute.String(formatKey, xpipeline.SignalProfiles.String()),
attribute.Int64(scrapedProfileRecordsKey, int64(numScrapedProfiles)),
attribute.Int64(erroredProfileRecordsKey, int64(numErroredProfiles)),
)
if err != nil {
span.SetStatus(codes.Error, err.Error())
}
}
return md, err
}
return xscraper.NewProfiles(scraperFuncs, xscraper.WithStart(sc.Start), xscraper.WithShutdown(sc.Shutdown))
}
================================================
FILE: scraper/scraperhelper/xscraperhelper/obs_profiles_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package xscraperhelper provides utilities for scrapers.
package xscraperhelper // import "go.opentelemetry.io/collector/scraper/scraperhelper/xscraperhelper"
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/testdata"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/scraper/scrapererror"
"go.opentelemetry.io/collector/scraper/scraperhelper/internal/controller"
"go.opentelemetry.io/collector/scraper/scraperhelper/xscraperhelper/internal/metadatatest"
"go.opentelemetry.io/collector/scraper/xscraper"
)
var (
receiverID = component.MustNewID("fakeReceiver")
scraperID = component.MustNewID("fakeScraper")
errFake = errors.New("errFake")
partialErrFake = scrapererror.NewPartialScrapeError(errFake, 2)
)
type testParams struct {
items int
err error
}
func TestScrapeProfilesDataOp(t *testing.T) {
tel := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tel.Shutdown(context.Background())) })
set := tel.NewTelemetrySettings()
parentCtx, parentSpan := set.TracerProvider.Tracer("test").Start(context.Background(), t.Name())
defer parentSpan.End()
params := []testParams{
{items: 23, err: partialErrFake},
{items: 29, err: errFake},
{items: 15, err: nil},
}
for i := range params {
sm, err := xscraper.NewProfiles(func(context.Context) (pprofile.Profiles, error) {
return testdata.GenerateProfiles(params[i].items), params[i].err
})
require.NoError(t, err)
sf, err := wrapObsProfiles(sm, receiverID, scraperID, set)
require.NoError(t, err)
_, err = sf.ScrapeProfiles(parentCtx)
require.ErrorIs(t, err, params[i].err)
}
spans := tel.SpanRecorder.Ended()
require.Len(t, spans, len(params))
var scrapedProfileRecords, erroredProfileRecords int
for i, span := range spans {
assert.Equal(t, "scraper/"+scraperID.String()+"/ScrapeProfiles", span.Name())
switch {
case params[i].err == nil:
scrapedProfileRecords += params[i].items
require.Contains(t, span.Attributes(), attribute.Int64(scrapedProfileRecordsKey, int64(params[i].items)))
require.Contains(t, span.Attributes(), attribute.Int64(erroredProfileRecordsKey, 0))
assert.Equal(t, codes.Unset, span.Status().Code)
case errors.Is(params[i].err, errFake):
// Since we get an error, we cannot record any metrics because we don't know if the returned pprofile.Profiles is valid instance.
require.Contains(t, span.Attributes(), attribute.Int64(scrapedProfileRecordsKey, 0))
require.Contains(t, span.Attributes(), attribute.Int64(erroredProfileRecordsKey, 0))
assert.Equal(t, codes.Error, span.Status().Code)
assert.Equal(t, params[i].err.Error(), span.Status().Description)
case errors.Is(params[i].err, partialErrFake):
scrapedProfileRecords += params[i].items
erroredProfileRecords += 2
require.Contains(t, span.Attributes(), attribute.Int64(scrapedProfileRecordsKey, int64(params[i].items)))
require.Contains(t, span.Attributes(), attribute.Int64(erroredProfileRecordsKey, 2))
assert.Equal(t, codes.Error, span.Status().Code)
assert.Equal(t, params[i].err.Error(), span.Status().Description)
default:
t.Fatalf("unexpected err param: %v", params[i].err)
}
}
checkScraperProfiles(t, tel, receiverID, scraperID, int64(scrapedProfileRecords), int64(erroredProfileRecords))
}
func TestCheckScraperProfiles(t *testing.T) {
tel := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tel.Shutdown(context.Background())) })
sm, err := xscraper.NewProfiles(func(context.Context) (pprofile.Profiles, error) {
return testdata.GenerateProfiles(7), nil
})
require.NoError(t, err)
sf, err := wrapObsProfiles(sm, receiverID, scraperID, tel.NewTelemetrySettings())
require.NoError(t, err)
_, err = sf.ScrapeProfiles(context.Background())
require.NoError(t, err)
checkScraperProfiles(t, tel, receiverID, scraperID, 7, 0)
}
func TestScrapeProfilesDataOp_LogsScraperID(t *testing.T) {
tel := componenttest.NewTelemetry()
t.Cleanup(func() { require.NoError(t, tel.Shutdown(context.Background())) })
core, observedLogs := observer.New(zap.ErrorLevel)
telset := tel.NewTelemetrySettings()
telset.Logger = zap.New(core)
rSet := receiver.Settings{
ID: receiverID,
TelemetrySettings: telset,
}
set := controller.GetSettings(scraperID.Type(), rSet)
sm, err := xscraper.NewProfiles(func(context.Context) (pprofile.Profiles, error) {
return pprofile.NewProfiles(), errFake
})
require.NoError(t, err)
sf, err := wrapObsProfiles(sm, receiverID, scraperID, set.TelemetrySettings)
require.NoError(t, err)
_, err = sf.ScrapeProfiles(context.Background())
require.ErrorIs(t, err, errFake)
errorLogs := observedLogs.FilterLevelExact(zap.ErrorLevel).All()
require.Len(t, errorLogs, 1)
assert.Equal(t, "Error scraping profiles", errorLogs[0].Message)
assert.Equal(t, scraperID.String(), errorLogs[0].ContextMap()["scraper"])
assert.Equal(t, errFake.Error(), errorLogs[0].ContextMap()["error"])
}
func checkScraperProfiles(t *testing.T, tel *componenttest.Telemetry, receiver, scraper component.ID, scrapedProfileRecords, erroredProfileRecords int64) {
metadatatest.AssertEqualScraperScrapedProfileRecords(t, tel,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(receiverKey, receiver.String()),
attribute.String(scraperKey, scraper.String())),
Value: scrapedProfileRecords,
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
metadatatest.AssertEqualScraperErroredProfileRecords(t, tel,
[]metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(
attribute.String(receiverKey, receiver.String()),
attribute.String(scraperKey, scraper.String())),
Value: erroredProfileRecords,
},
}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars())
}
================================================
FILE: scraper/scrapertest/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: scraper/scrapertest/go.mod
================================================
module go.opentelemetry.io/collector/scraper/scrapertest
go 1.25.0
require (
github.com/google/uuid v1.6.0
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.opentelemetry.io/collector/scraper v0.148.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/pdata v1.54.0 // indirect
go.opentelemetry.io/collector/pipeline v1.54.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
golang.org/x/sys v0.41.0 // indirect
)
replace go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/pipeline => ../../pipeline
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/scraper => ../
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
================================================
FILE: scraper/scrapertest/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: scraper/scrapertest/metadata.yaml
================================================
type: scraper/scrapertest
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: scraper/scrapertest/settings.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package scrapertest // import "go.opentelemetry.io/collector/scraper/scrapertest"
import (
"github.com/google/uuid"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/scraper"
)
var NopType = component.MustNewType("nop")
// NewNopSettings returns a new nop scraper.Settings with the given type.
func NewNopSettings(typ component.Type) scraper.Settings {
return scraper.Settings{
ID: component.NewIDWithName(typ, uuid.NewString()),
TelemetrySettings: componenttest.NewNopTelemetrySettings(),
BuildInfo: component.NewDefaultBuildInfo(),
}
}
================================================
FILE: scraper/xscraper/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: scraper/xscraper/doc.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:generate mdatagen metadata.yaml
// Package xscraper allows to define pull based receivers that can be configured using the scraperreceiver.
package xscraper // import "go.opentelemetry.io/collector/scraper/xscraper"
================================================
FILE: scraper/xscraper/factory.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xscraper // import "go.opentelemetry.io/collector/scraper/xscraper"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/scraper"
)
type Factory interface {
scraper.Factory
// CreateProfiles creates a Profiles scraper based on this config.
// If the scraper type does not support profiles,
// this function returns the error [pipeline.ErrSignalNotSupported].
CreateProfiles(ctx context.Context, set scraper.Settings, cfg component.Config) (Profiles, error)
// ProfilesStability gets the stability level of the Profiles scraper.
ProfilesStability() component.StabilityLevel
}
// FactoryOption apply changes to Options.
type FactoryOption interface {
// applyOption applies the option.
applyOption(o *factory)
}
var _ FactoryOption = (*factoryOptionFunc)(nil)
// factoryOptionFunc is a FactoryOption created through a function.
type factoryOptionFunc func(*factory)
func (f factoryOptionFunc) applyOption(o *factory) {
f(o)
}
type factory struct {
scraper.Factory
opts []scraper.FactoryOption
createProfilesFunc CreateProfilesFunc
profilesStabilityLevel component.StabilityLevel
}
func (f *factory) ProfilesStability() component.StabilityLevel {
return f.profilesStabilityLevel
}
func (f *factory) CreateProfiles(ctx context.Context, set scraper.Settings, cfg component.Config) (Profiles, error) {
if f.createProfilesFunc == nil {
return nil, pipeline.ErrSignalNotSupported
}
return f.createProfilesFunc(ctx, set, cfg)
}
// WithLogs overrides the default "error not supported" implementation for CreateLogs and the default "undefined" stability level.
func WithLogs(createLogs scraper.CreateLogsFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.opts = append(o.opts, scraper.WithLogs(createLogs, sl))
})
}
// WithMetrics overrides the default "error not supported" implementation for CreateMetrics and the default "undefined" stability level.
func WithMetrics(createMetrics scraper.CreateMetricsFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.opts = append(o.opts, scraper.WithMetrics(createMetrics, sl))
})
}
// CreateProfilesFunc is the equivalent of Factory.CreateProfiles().
type CreateProfilesFunc func(context.Context, scraper.Settings, component.Config) (Profiles, error)
// WithProfiles overrides the default "error not supported" implementation for CreateProfiles and the default "undefined" stability level.
func WithProfiles(createProfiles CreateProfilesFunc, sl component.StabilityLevel) FactoryOption {
return factoryOptionFunc(func(o *factory) {
o.profilesStabilityLevel = sl
o.createProfilesFunc = createProfiles
})
}
// NewFactory creates a Factory with experimental capabilities and wraps a scraper.Factory.
func NewFactory(cfgType component.Type, createDefaultConfig component.CreateDefaultConfigFunc, options ...FactoryOption) Factory {
f := &factory{}
for _, opt := range options {
opt.applyOption(f)
}
f.Factory = scraper.NewFactory(cfgType, createDefaultConfig, f.opts...)
return f
}
================================================
FILE: scraper/xscraper/factory_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xscraper
import (
"context"
"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/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/scraper"
)
var testType = component.MustNewType("test")
func nopSettings() scraper.Settings {
return scraper.Settings{
ID: component.NewID(testType),
TelemetrySettings: componenttest.NewNopTelemetrySettings(),
}
}
func TestNewFactory(t *testing.T) {
defaultCfg := struct{}{}
f := NewFactory(
testType,
func() component.Config { return &defaultCfg })
assert.Equal(t, testType, f.Type())
assert.EqualValues(t, &defaultCfg, f.CreateDefaultConfig())
_, err := f.CreateProfiles(context.Background(), nopSettings(), &defaultCfg)
require.ErrorIs(t, err, pipeline.ErrSignalNotSupported)
}
func TestNewFactoryWithOptions(t *testing.T) {
testType := component.MustNewType("test")
defaultCfg := struct{}{}
f := NewFactory(
testType,
func() component.Config { return &defaultCfg },
WithLogs(createLogs, component.StabilityLevelAlpha),
WithMetrics(createMetrics, component.StabilityLevelAlpha),
WithProfiles(createProfiles, component.StabilityLevelDevelopment))
assert.Equal(t, testType, f.Type())
assert.EqualValues(t, &defaultCfg, f.CreateDefaultConfig())
assert.Equal(t, component.StabilityLevelDevelopment, f.ProfilesStability())
_, err := f.CreateProfiles(context.Background(), scraper.Settings{}, &defaultCfg)
require.NoError(t, err)
assert.Equal(t, component.StabilityLevelAlpha, f.LogsStability())
_, err = f.CreateLogs(context.Background(), scraper.Settings{}, &defaultCfg)
require.NoError(t, err)
assert.Equal(t, component.StabilityLevelAlpha, f.MetricsStability())
_, err = f.CreateMetrics(context.Background(), scraper.Settings{}, &defaultCfg)
require.NoError(t, err)
}
func createProfiles(context.Context, scraper.Settings, component.Config) (Profiles, error) {
return NewProfiles(newTestScrapeProfilesFunc(nil))
}
func createLogs(context.Context, scraper.Settings, component.Config) (scraper.Logs, error) {
return scraper.NewLogs(newTestScrapeLogsFunc(nil))
}
func createMetrics(context.Context, scraper.Settings, component.Config) (scraper.Metrics, error) {
return scraper.NewMetrics(newTestScrapeMetricsFunc(nil))
}
func newTestScrapeLogsFunc(retError error) scraper.ScrapeLogsFunc {
return func(_ context.Context) (plog.Logs, error) {
return plog.NewLogs(), retError
}
}
func newTestScrapeMetricsFunc(retError error) scraper.ScrapeMetricsFunc {
return func(_ context.Context) (pmetric.Metrics, error) {
return pmetric.NewMetrics(), retError
}
}
================================================
FILE: scraper/xscraper/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package xscraper
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: scraper/xscraper/go.mod
================================================
module go.opentelemetry.io/collector/scraper/xscraper
go 1.25.0
require (
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/collector/pdata/pprofile v0.148.0
go.opentelemetry.io/collector/pipeline v1.54.0
go.opentelemetry.io/collector/scraper v0.148.0
go.uber.org/goleak v1.3.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
golang.org/x/sys v0.41.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/pipeline => ../../pipeline
replace go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile
replace go.opentelemetry.io/collector/component => ../../component
replace go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest
replace go.opentelemetry.io/collector/scraper => ../../scraper
replace go.opentelemetry.io/collector/featuregate => ../../featuregate
replace go.opentelemetry.io/collector/pdata => ../../pdata
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
================================================
FILE: scraper/xscraper/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: scraper/xscraper/metadata.yaml
================================================
type: xscraper
github_project: open-telemetry/opentelemetry-collector
status:
class: pkg
codeowners:
active:
- dmathieu
- florianl
stability:
alpha: [profiles]
================================================
FILE: scraper/xscraper/profiles.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xscraper // import "go.opentelemetry.io/collector/scraper/xscraper"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/scraper"
)
// Profiles is the base interface for profiles scrapers.
type Profiles interface {
component.Component
// ScrapeProfiles is the base interface to indicate that how should profiles be scraped.
ScrapeProfiles(context.Context) (pprofile.Profiles, error)
}
// ScrapeProfilesFunc is a helper function.
type ScrapeProfilesFunc scraper.ScrapeFunc[pprofile.Profiles]
func (sf ScrapeProfilesFunc) ScrapeProfiles(ctx context.Context) (pprofile.Profiles, error) {
return sf(ctx)
}
type profiles struct {
baseScraper
ScrapeProfilesFunc
}
// NewProfiles creates a new Profiles scraper.
func NewProfiles(scrape ScrapeProfilesFunc, options ...Option) (Profiles, error) {
if scrape == nil {
return nil, errNilFunc
}
bs := &profiles{
baseScraper: newBaseScraper(options),
ScrapeProfilesFunc: scrape,
}
return bs, nil
}
================================================
FILE: scraper/xscraper/profiles_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xscraper
import (
"context"
"errors"
"sync"
"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/pdata/pprofile"
)
func TestNewProfiles(t *testing.T) {
mp, err := NewProfiles(newTestScrapeProfilesFunc(nil))
require.NoError(t, err)
require.NoError(t, mp.Start(context.Background(), componenttest.NewNopHost()))
md, err := mp.ScrapeProfiles(context.Background())
require.NoError(t, err)
assert.Equal(t, pprofile.NewProfiles(), md)
require.NoError(t, mp.Shutdown(context.Background()))
}
func TestNewProfiles_WithOptions(t *testing.T) {
want := errors.New("my_error")
mp, err := NewProfiles(newTestScrapeProfilesFunc(nil),
WithStart(func(context.Context, component.Host) error { return want }),
WithShutdown(func(context.Context) error { return want }))
require.NoError(t, err)
assert.Equal(t, want, mp.Start(context.Background(), componenttest.NewNopHost()))
assert.Equal(t, want, mp.Shutdown(context.Background()))
}
func TestNewProfiles_NilRequiredFields(t *testing.T) {
_, err := NewProfiles(nil)
require.Error(t, err)
}
func TestNewProfiles_ProcessProfilesError(t *testing.T) {
want := errors.New("my_error")
mp, err := NewProfiles(newTestScrapeProfilesFunc(want))
require.NoError(t, err)
_, err = mp.ScrapeProfiles(context.Background())
require.ErrorIs(t, err, want)
}
func TestProfilesConcurrency(t *testing.T) {
mp, err := NewProfiles(newTestScrapeProfilesFunc(nil))
require.NoError(t, err)
require.NoError(t, mp.Start(context.Background(), componenttest.NewNopHost()))
var wg sync.WaitGroup
for range 10 {
wg.Go(func() {
for range 10000 {
_, errScrape := mp.ScrapeProfiles(context.Background())
assert.NoError(t, errScrape)
}
})
}
wg.Wait()
require.NoError(t, mp.Shutdown(context.Background()))
}
func newTestScrapeProfilesFunc(retError error) ScrapeProfilesFunc {
return func(_ context.Context) (pprofile.Profiles, error) {
return pprofile.NewProfiles(), retError
}
}
================================================
FILE: scraper/xscraper/scraper.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package xscraper // import "go.opentelemetry.io/collector/scraper/xscraper"
import (
"context"
"errors"
"go.opentelemetry.io/collector/component"
)
var errNilFunc = errors.New("nil scrape func")
// ScrapeFunc scrapes data.
type ScrapeFunc[T any] func(context.Context) (T, error)
// Option apply changes to internal options.
type Option interface {
apply(*baseScraper)
}
type scraperOptionFunc func(*baseScraper)
func (of scraperOptionFunc) apply(e *baseScraper) {
of(e)
}
// WithStart sets the function that will be called on startup.
func WithStart(start component.StartFunc) Option {
return scraperOptionFunc(func(o *baseScraper) {
o.StartFunc = start
})
}
// WithShutdown sets the function that will be called on shutdown.
func WithShutdown(shutdown component.ShutdownFunc) Option {
return scraperOptionFunc(func(o *baseScraper) {
o.ShutdownFunc = shutdown
})
}
type baseScraper struct {
component.StartFunc
component.ShutdownFunc
}
// newBaseScraper returns the internal settings starting from the default and applying all options.
func newBaseScraper(options []Option) baseScraper {
// Start from the default options:
bs := baseScraper{}
for _, op := range options {
op.apply(&bs)
}
return bs
}
================================================
FILE: service/Makefile
================================================
include ../Makefile.Common
================================================
FILE: service/README.md
================================================
# Service
| Status | |
| ------------- |-----------|
| Stability | [development]: traces, metrics, logs, profiles |
| Issues | [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Apkg%2Fservice) [](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aclosed+is%3Aissue+label%3Apkg%2Fservice) |
[development]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#development
[core]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
================================================
FILE: service/config.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package service // import "go.opentelemetry.io/collector/service"
import (
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/service/extensions"
"go.opentelemetry.io/collector/service/pipelines"
)
// Config defines the configurable components of the Service.
type Config struct {
// Telemetry is the configuration for collector's own telemetry.
Telemetry component.Config `mapstructure:"telemetry"`
// Extensions are the ordered list of extensions configured for the service.
Extensions extensions.Config `mapstructure:"extensions,omitempty"`
// Pipelines are the set of data pipelines configured for the service.
Pipelines pipelines.Config `mapstructure:"pipelines"`
// prevent unkeyed literal initialization
_ struct{}
}
================================================
FILE: service/config_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package service
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/xconfmap"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/service/extensions"
"go.opentelemetry.io/collector/service/pipelines"
)
func TestConfigValidate(t *testing.T) {
testCases := []struct {
name string // test case name (also file name containing config yaml)
cfgFn func() *Config
expected error
}{
{
name: "valid",
cfgFn: generateConfig,
expected: nil,
},
{
name: "valid-telemetry-config",
cfgFn: func() *Config {
cfg := generateConfig()
cfg.Telemetry = fakeTelemetryConfig{Invalid: false}
return cfg
},
expected: nil,
},
{
name: "duplicate-processor-reference",
cfgFn: func() *Config {
cfg := generateConfig()
pipe := cfg.Pipelines[pipeline.NewID(pipeline.SignalTraces)]
pipe.Processors = append(pipe.Processors, pipe.Processors...)
return cfg
},
expected: errors.New(`references processor "nop" multiple times`),
},
{
name: "invalid-telemetry-config",
cfgFn: func() *Config {
cfg := generateConfig()
cfg.Telemetry = fakeTelemetryConfig{Invalid: true}
return cfg
},
expected: errors.New("telemetry: invalid config"),
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
cfg := tt.cfgFn()
err := xconfmap.Validate(cfg)
if tt.expected != nil {
assert.ErrorContains(t, err, tt.expected.Error())
} else {
assert.NoError(t, err)
}
})
}
}
func TestConfmapMarshalConfig(t *testing.T) {
conf := confmap.New()
require.NoError(t, conf.Marshal(Config{
Telemetry: fakeTelemetryConfig{},
}))
assert.Equal(t, map[string]any{
"pipelines": map[string]any(nil),
"telemetry": map[string]any{"invalid": false},
}, conf.ToStringMap())
}
func generateConfig() *Config {
return &Config{
Extensions: extensions.Config{component.MustNewID("nop")},
Pipelines: pipelines.Config{
pipeline.NewID(pipeline.SignalTraces): {
Receivers: []component.ID{component.MustNewID("nop")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
}
}
type fakeTelemetryConfig struct {
Invalid bool `mapstructure:"invalid"`
}
func (cfg fakeTelemetryConfig) Validate() error {
if cfg.Invalid {
return errors.New("invalid config")
}
return nil
}
================================================
FILE: service/documentation.md
================================================
[comment]: <> (Code generated by mdatagen. DO NOT EDIT.)
# service
## Internal Telemetry
The following telemetry is emitted by this component.
### otelcol.connector.consumed.items
Number of items passed to the connector.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {item} | Sum | Int | true | Development |
### otelcol.connector.produced.items
Number of items emitted from the connector.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {item} | Sum | Int | true | Development |
### otelcol.exporter.consumed.items
Number of items passed to the exporter.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {item} | Sum | Int | true | Development |
### otelcol_process_cpu_seconds
Total CPU user and system time in seconds
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| s | Sum | Double | true | Alpha |
### otelcol_process_memory_rss
Total physical memory (resident set size)
| Unit | Metric Type | Value Type | Stability |
| ---- | ----------- | ---------- | --------- |
| By | Gauge | Int | Alpha |
### otelcol_process_runtime_heap_alloc_bytes
Bytes of allocated heap objects (see 'go doc runtime.MemStats.HeapAlloc')
| Unit | Metric Type | Value Type | Stability |
| ---- | ----------- | ---------- | --------- |
| By | Gauge | Int | Alpha |
### otelcol_process_runtime_total_alloc_bytes
Cumulative bytes allocated for heap objects (see 'go doc runtime.MemStats.TotalAlloc')
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| By | Sum | Int | true | Alpha |
### otelcol_process_runtime_total_sys_memory_bytes
Total bytes of memory obtained from the OS (see 'go doc runtime.MemStats.Sys')
| Unit | Metric Type | Value Type | Stability |
| ---- | ----------- | ---------- | --------- |
| By | Gauge | Int | Alpha |
### otelcol_process_uptime
Uptime of the process
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| s | Sum | Double | true | Alpha |
### otelcol.processor.consumed.items
Number of items passed to the processor.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {item} | Sum | Int | true | Development |
### otelcol.processor.produced.items
Number of items emitted from the processor.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {item} | Sum | Int | true | Development |
### otelcol.receiver.produced.items
Number of items emitted from the receiver.
| Unit | Metric Type | Value Type | Monotonic | Stability |
| ---- | ----------- | ---------- | --------- | --------- |
| {item} | Sum | Int | true | Development |
## Feature Gates
This component has the following feature gates:
| Feature Gate | Stage | Description | From Version | To Version | Reference |
| ------------ | ----- | ----------- | ------------ | ---------- | --------- |
| `service.AllowNoPipelines` | alpha | Allow starting the Collector without starting any pipelines. | v0.122.0 | N/A | [Link](https://github.com/open-telemetry/opentelemetry-collector/pull/12613) |
| `service.profilesSupport` | alpha | Controls whether profiles support can be enabled | v0.112.0 | N/A | [Link](https://github.com/open-telemetry/opentelemetry-collector/pull/11477) |
| `telemetry.UseLocalHostAsDefaultMetricsAddress` | beta | Controls whether default Prometheus metrics server use localhost as the default host for their endpoints | v0.111.0 | N/A | [Link](https://github.com/open-telemetry/opentelemetry-collector/pull/11251) |
| `telemetry.newPipelineTelemetry` | alpha | Injects component-identifying scope attributes in internal Collector metrics | v0.123.0 | N/A | [Link](https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/rfcs/component-universal-telemetry.md) |
For more information about feature gates, see the [Feature Gates](https://github.com/open-telemetry/opentelemetry-collector/blob/main/featuregate/README.md) documentation.
================================================
FILE: service/extensions/config.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package extensions // import "go.opentelemetry.io/collector/service/extensions"
import "go.opentelemetry.io/collector/component"
// Config represents the ordered list of extensions configured for the service.
type Config []component.ID
================================================
FILE: service/extensions/extensions.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package extensions // import "go.opentelemetry.io/collector/service/extensions"
import (
"context"
"fmt"
"net/http"
"sort"
"go.uber.org/multierr"
"go.uber.org/zap"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componentstatus"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/extension"
"go.opentelemetry.io/collector/extension/extensioncapabilities"
"go.opentelemetry.io/collector/service/internal/attribute"
"go.opentelemetry.io/collector/service/internal/builders"
"go.opentelemetry.io/collector/service/internal/componentattribute"
"go.opentelemetry.io/collector/service/internal/status"
"go.opentelemetry.io/collector/service/internal/zpages"
)
const zExtensionName = "zextensionname"
// Extensions is a map of extensions created from extension configs.
type Extensions struct {
telemetry component.TelemetrySettings
extMap map[component.ID]extension.Extension
instanceIDs map[component.ID]*componentstatus.InstanceID
extensionIDs []component.ID // start order (and reverse stop order)
reporter status.Reporter
}
// Start starts all extensions.
func (bes *Extensions) Start(ctx context.Context, host component.Host) error {
bes.telemetry.Logger.Info("Starting extensions...")
for _, extID := range bes.extensionIDs {
extLogger := componentattribute.LoggerWithAttributes(bes.telemetry.Logger,
attribute.Extension(extID).Set().ToSlice())
extLogger.Info("Extension is starting...")
instanceID := bes.instanceIDs[extID]
ext := bes.extMap[extID]
bes.reporter.ReportStatus(
instanceID,
componentstatus.NewEvent(componentstatus.StatusStarting),
)
if err := ext.Start(ctx, host); err != nil {
bes.reporter.ReportStatus(
instanceID,
componentstatus.NewPermanentErrorEvent(err),
)
// We log with zap.AddStacktrace(zap.DPanicLevel) to avoid adding the stack trace to the error log
extLogger.WithOptions(zap.AddStacktrace(zap.DPanicLevel)).Error("Failed to start extension", zap.Error(err))
return err
}
bes.reporter.ReportOKIfStarting(instanceID)
extLogger.Info("Extension started.")
}
return nil
}
// Shutdown stops all extensions.
func (bes *Extensions) Shutdown(ctx context.Context) error {
bes.telemetry.Logger.Info("Stopping extensions...")
var errs error
for i := len(bes.extensionIDs) - 1; i >= 0; i-- {
extID := bes.extensionIDs[i]
instanceID := bes.instanceIDs[extID]
ext := bes.extMap[extID]
bes.reporter.ReportStatus(
instanceID,
componentstatus.NewEvent(componentstatus.StatusStopping),
)
if err := ext.Shutdown(ctx); err != nil {
bes.reporter.ReportStatus(
instanceID,
componentstatus.NewPermanentErrorEvent(err),
)
errs = multierr.Append(errs, err)
continue
}
bes.reporter.ReportStatus(
instanceID,
componentstatus.NewEvent(componentstatus.StatusStopped),
)
}
return errs
}
func (bes *Extensions) NotifyPipelineReady() error {
for _, extID := range bes.extensionIDs {
ext := bes.extMap[extID]
if pw, ok := ext.(extensioncapabilities.PipelineWatcher); ok {
if err := pw.Ready(); err != nil {
return fmt.Errorf("failed to notify extension %q: %w", extID, err)
}
}
}
return nil
}
func (bes *Extensions) NotifyPipelineNotReady() error {
var errs error
for _, extID := range bes.extensionIDs {
ext := bes.extMap[extID]
if pw, ok := ext.(extensioncapabilities.PipelineWatcher); ok {
errs = multierr.Append(errs, pw.NotReady())
}
}
return errs
}
func (bes *Extensions) NotifyConfig(ctx context.Context, conf *confmap.Conf) error {
var errs error
for _, extID := range bes.extensionIDs {
ext := bes.extMap[extID]
if cw, ok := ext.(extensioncapabilities.ConfigWatcher); ok {
clonedConf := confmap.NewFromStringMap(conf.ToStringMap())
errs = multierr.Append(errs, cw.NotifyConfig(ctx, clonedConf))
}
}
return errs
}
func (bes *Extensions) NotifyComponentStatusChange(source *componentstatus.InstanceID, event *componentstatus.Event) {
for _, extID := range bes.extensionIDs {
ext := bes.extMap[extID]
if sw, ok := ext.(componentstatus.Watcher); ok {
sw.ComponentStatusChanged(source, event)
}
}
}
func (bes *Extensions) GetExtensions() map[component.ID]component.Component {
result := make(map[component.ID]component.Component, len(bes.extMap))
for extID, v := range bes.extMap {
result[extID] = v
}
return result
}
func (bes *Extensions) HandleZPages(w http.ResponseWriter, r *http.Request) {
extensionName := r.URL.Query().Get(zExtensionName)
w.Header().Set("Content-Type", "text/html; charset=utf-8")
zpages.WriteHTMLPageHeader(w, zpages.HeaderData{Title: "Extensions"})
data := zpages.SummaryExtensionsTableData{}
data.Rows = make([]zpages.SummaryExtensionsTableRowData, 0, len(bes.extMap))
for _, id := range bes.extensionIDs {
row := zpages.SummaryExtensionsTableRowData{FullName: id.String()}
data.Rows = append(data.Rows, row)
}
sort.Slice(data.Rows, func(i, j int) bool {
return data.Rows[i].FullName < data.Rows[j].FullName
})
zpages.WriteHTMLExtensionsSummaryTable(w, data)
if extensionName != "" {
zpages.WriteHTMLComponentHeader(w, zpages.ComponentHeaderData{
Name: extensionName,
})
// TODO: Add config + status info.
}
zpages.WriteHTMLPageFooter(w)
}
// Settings holds configuration for building Extensions.
type Settings struct {
Telemetry component.TelemetrySettings
BuildInfo component.BuildInfo
// Extensions builder for extensions.
Extensions builders.Extension
}
type Option interface {
apply(*Extensions)
}
type optionFunc func(*Extensions)
func (of optionFunc) apply(e *Extensions) {
of(e)
}
func WithReporter(reporter status.Reporter) Option {
return optionFunc(func(e *Extensions) {
e.reporter = reporter
})
}
// New creates a new Extensions from Config.
func New(ctx context.Context, set Settings, cfg Config, options ...Option) (*Extensions, error) {
exts := &Extensions{
telemetry: set.Telemetry,
extMap: make(map[component.ID]extension.Extension),
instanceIDs: make(map[component.ID]*componentstatus.InstanceID),
extensionIDs: make([]component.ID, 0, len(cfg)),
reporter: status.NewNopStatusReporter(),
}
for _, opt := range options {
opt.apply(exts)
}
for _, extID := range cfg {
instanceID := componentstatus.NewInstanceID(extID, component.KindExtension)
extSet := extension.Settings{
ID: extID,
TelemetrySettings: componentattribute.TelemetrySettingsWithAttributes(set.Telemetry, *attribute.Extension(extID).Set()),
BuildInfo: set.BuildInfo,
}
ext, err := set.Extensions.Create(ctx, extSet)
if err != nil {
return nil, fmt.Errorf("failed to create extension %q: %w", extID, err)
}
// Check if the factory really created the extension.
if ext == nil {
return nil, fmt.Errorf("factory for %q produced a nil extension", extID)
}
exts.extMap[extID] = ext
exts.instanceIDs[extID] = instanceID
}
order, err := computeOrder(exts)
if err != nil {
return nil, err
}
exts.extensionIDs = order
return exts, nil
}
================================================
FILE: service/extensions/extensions_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package extensions
import (
"context"
"errors"
"slices"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componentstatus"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/extension"
"go.opentelemetry.io/collector/extension/extensioncapabilities"
"go.opentelemetry.io/collector/extension/extensiontest"
"go.opentelemetry.io/collector/service/internal/builders"
"go.opentelemetry.io/collector/service/internal/status"
)
func TestBuildExtensions(t *testing.T) {
nopExtensionFactory := extensiontest.NewNopFactory()
nopExtensionConfig := nopExtensionFactory.CreateDefaultConfig()
errExtensionFactory := newCreateErrorExtensionFactory()
errExtensionConfig := errExtensionFactory.CreateDefaultConfig()
badExtensionFactory := newBadExtensionFactory()
badExtensionCfg := badExtensionFactory.CreateDefaultConfig()
tests := []struct {
name string
factories map[component.Type]extension.Factory
extensionsConfigs map[component.ID]component.Config
config Config
wantErrMsg string
}{
{
name: "extension_not_configured",
config: Config{
component.MustNewID("myextension"),
},
wantErrMsg: "failed to create extension \"myextension\": extension \"myextension\" is not configured",
},
{
name: "missing_extension_factory",
extensionsConfigs: map[component.ID]component.Config{
component.MustNewID("unknown"): nopExtensionConfig,
},
config: Config{
component.MustNewID("unknown"),
},
wantErrMsg: "failed to create extension \"unknown\": extension factory not available for: \"unknown\"",
},
{
name: "error_on_create_extension",
factories: map[component.Type]extension.Factory{
errExtensionFactory.Type(): errExtensionFactory,
},
extensionsConfigs: map[component.ID]component.Config{
component.NewID(errExtensionFactory.Type()): errExtensionConfig,
},
config: Config{
component.NewID(errExtensionFactory.Type()),
},
wantErrMsg: "failed to create extension \"err\": cannot create \"err\" extension type",
},
{
name: "bad_factory",
factories: map[component.Type]extension.Factory{
badExtensionFactory.Type(): badExtensionFactory,
},
extensionsConfigs: map[component.ID]component.Config{
component.NewID(badExtensionFactory.Type()): badExtensionCfg,
},
config: Config{
component.NewID(badExtensionFactory.Type()),
},
wantErrMsg: "factory for \"bf\" produced a nil extension",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := New(context.Background(), Settings{
Telemetry: componenttest.NewNopTelemetrySettings(),
BuildInfo: component.NewDefaultBuildInfo(),
Extensions: builders.NewExtension(tt.extensionsConfigs, tt.factories),
}, tt.config)
require.Error(t, err)
assert.EqualError(t, err, tt.wantErrMsg)
})
}
}
type testOrderExt struct {
name string
deps []string
}
type testOrderCase struct {
testName string
extensions []testOrderExt
order []string
err string
}
func TestOrdering(t *testing.T) {
tests := []testOrderCase{
{
testName: "no_deps",
extensions: []testOrderExt{{name: ""}, {name: "foo"}, {name: "bar"}},
order: nil, // no predictable order
},
{
testName: "deps",
extensions: []testOrderExt{
{name: "foo", deps: []string{"bar"}}, // foo -> bar
{name: "baz", deps: []string{"foo"}}, // baz -> foo
{name: "bar"},
},
// baz -> foo -> bar
order: []string{"recording/bar", "recording/foo", "recording/baz"},
},
{
testName: "deps_double",
extensions: []testOrderExt{
{name: "foo", deps: []string{"bar"}}, // foo -> bar
{name: "baz", deps: []string{"foo", "bar"}}, // baz -> {foo, bar}
{name: "bar"},
},
// baz -> foo -> bar
order: []string{"recording/bar", "recording/foo", "recording/baz"},
},
{
testName: "unknown_dep",
extensions: []testOrderExt{
{name: "foo", deps: []string{"BAZ"}},
{name: "bar"},
},
err: "unable to find extension",
},
{
testName: "circular",
extensions: []testOrderExt{
{name: "foo", deps: []string{"bar"}},
{name: "bar", deps: []string{"foo"}},
},
err: "unable to order extensions",
},
}
for _, testCase := range tests {
t.Run(testCase.testName, testCase.testOrdering)
}
}
func (tc testOrderCase) testOrdering(t *testing.T) {
var startOrder []string
var shutdownOrder []string
recordingExtensionFactory := newRecordingExtensionFactory(func(set extension.Settings, _ component.Host) error {
startOrder = append(startOrder, set.ID.String())
return nil
}, func(set extension.Settings) error {
shutdownOrder = append(shutdownOrder, set.ID.String())
return nil
})
extCfgs := make(map[component.ID]component.Config)
extIDs := make([]component.ID, len(tc.extensions))
for i, ext := range tc.extensions {
extID := component.NewIDWithName(recordingExtensionFactory.Type(), ext.name)
extIDs[i] = extID
extCfgs[extID] = recordingExtensionConfig{dependencies: ext.deps}
}
exts, err := New(context.Background(), Settings{
Telemetry: componenttest.NewNopTelemetrySettings(),
BuildInfo: component.NewDefaultBuildInfo(),
Extensions: builders.NewExtension(
extCfgs,
map[component.Type]extension.Factory{
recordingExtensionFactory.Type(): recordingExtensionFactory,
}),
}, Config(extIDs))
if tc.err != "" {
require.ErrorContains(t, err, tc.err)
return
}
require.NoError(t, err)
err = exts.Start(context.Background(), componenttest.NewNopHost())
require.NoError(t, err)
err = exts.Shutdown(context.Background())
require.NoError(t, err)
if len(tc.order) > 0 {
require.Equal(t, tc.order, startOrder)
slices.Reverse(shutdownOrder)
require.Equal(t, tc.order, shutdownOrder)
}
}
func TestNotifyConfig(t *testing.T) {
notificationError := errors.New("Error processing config")
nopExtensionFactory := extensiontest.NewNopFactory()
nopExtensionConfig := nopExtensionFactory.CreateDefaultConfig()
n1ExtensionFactory := newConfigWatcherExtensionFactory(component.MustNewType("notifiable1"), func() error { return nil })
n1ExtensionConfig := n1ExtensionFactory.CreateDefaultConfig()
n2ExtensionFactory := newConfigWatcherExtensionFactory(component.MustNewType("notifiable2"), func() error { return nil })
n2ExtensionConfig := n1ExtensionFactory.CreateDefaultConfig()
nErrExtensionFactory := newConfigWatcherExtensionFactory(component.MustNewType("notifiableErr"), func() error { return notificationError })
nErrExtensionConfig := nErrExtensionFactory.CreateDefaultConfig()
tests := []struct {
name string
factories map[component.Type]extension.Factory
extensionsConfigs map[component.ID]component.Config
serviceExtensions []component.ID
wantErrMsg string
want error
}{
{
name: "No notifiable extensions",
factories: map[component.Type]extension.Factory{
component.MustNewType("nop"): nopExtensionFactory,
},
extensionsConfigs: map[component.ID]component.Config{
component.MustNewID("nop"): nopExtensionConfig,
},
serviceExtensions: []component.ID{
component.MustNewID("nop"),
},
},
{
name: "One notifiable extension",
factories: map[component.Type]extension.Factory{
component.MustNewType("notifiable1"): n1ExtensionFactory,
},
extensionsConfigs: map[component.ID]component.Config{
component.MustNewID("notifiable1"): n1ExtensionConfig,
},
serviceExtensions: []component.ID{
component.MustNewID("notifiable1"),
},
},
{
name: "Multiple notifiable extensions",
factories: map[component.Type]extension.Factory{
component.MustNewType("notifiable1"): n1ExtensionFactory,
component.MustNewType("notifiable2"): n2ExtensionFactory,
},
extensionsConfigs: map[component.ID]component.Config{
component.MustNewID("notifiable1"): n1ExtensionConfig,
component.MustNewID("notifiable2"): n2ExtensionConfig,
},
serviceExtensions: []component.ID{
component.MustNewID("notifiable1"),
component.MustNewID("notifiable2"),
},
},
{
name: "Errors in extension notification",
factories: map[component.Type]extension.Factory{
component.MustNewType("notifiableErr"): nErrExtensionFactory,
},
extensionsConfigs: map[component.ID]component.Config{
component.MustNewID("notifiableErr"): nErrExtensionConfig,
},
serviceExtensions: []component.ID{
component.MustNewID("notifiableErr"),
},
want: notificationError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
extensions, err := New(context.Background(), Settings{
Telemetry: componenttest.NewNopTelemetrySettings(),
BuildInfo: component.NewDefaultBuildInfo(),
Extensions: builders.NewExtension(tt.extensionsConfigs, tt.factories),
}, tt.serviceExtensions)
require.NoError(t, err)
errs := extensions.NotifyConfig(context.Background(), confmap.NewFromStringMap(map[string]any{}))
assert.Equal(t, tt.want, errs)
})
}
}
type configWatcherExtension struct {
fn func() error
}
func (comp *configWatcherExtension) Start(context.Context, component.Host) error {
return comp.fn()
}
func (comp *configWatcherExtension) Shutdown(context.Context) error {
return comp.fn()
}
func (comp *configWatcherExtension) NotifyConfig(context.Context, *confmap.Conf) error {
return comp.fn()
}
func newConfigWatcherExtension(fn func() error) *configWatcherExtension {
comp := &configWatcherExtension{
fn: fn,
}
return comp
}
func newConfigWatcherExtensionFactory(name component.Type, fn func() error) extension.Factory {
return extension.NewFactory(
name,
func() component.Config {
return &struct{}{}
},
func(context.Context, extension.Settings, component.Config) (extension.Extension, error) {
return newConfigWatcherExtension(fn), nil
},
component.StabilityLevelDevelopment,
)
}
func newBadExtensionFactory() extension.Factory {
return extension.NewFactory(
component.MustNewType("bf"),
func() component.Config {
return &struct{}{}
},
func(context.Context, extension.Settings, component.Config) (extension.Extension, error) {
return nil, nil
},
component.StabilityLevelDevelopment,
)
}
func newCreateErrorExtensionFactory() extension.Factory {
return extension.NewFactory(
component.MustNewType("err"),
func() component.Config {
return &struct{}{}
},
func(context.Context, extension.Settings, component.Config) (extension.Extension, error) {
return nil, errors.New("cannot create \"err\" extension type")
},
component.StabilityLevelDevelopment,
)
}
func TestStatusReportedOnStartupShutdown(t *testing.T) {
// compare two slices of status events ignoring timestamp
assertEqualStatuses := func(t *testing.T, evts1, evts2 []*componentstatus.Event) {
assert.Len(t, evts2, len(evts1))
for i := range evts1 {
ev1 := evts1[i]
ev2 := evts2[i]
assert.Equal(t, ev1.Status(), ev2.Status())
assert.Equal(t, ev1.Err(), ev2.Err())
}
}
for _, tt := range []struct {
name string
expectedStatuses []*componentstatus.Event
startErr error
shutdownErr error
}{
{
name: "successful startup/shutdown",
expectedStatuses: []*componentstatus.Event{
componentstatus.NewEvent(componentstatus.StatusStarting),
componentstatus.NewEvent(componentstatus.StatusOK),
componentstatus.NewEvent(componentstatus.StatusStopping),
componentstatus.NewEvent(componentstatus.StatusStopped),
},
startErr: nil,
shutdownErr: nil,
},
{
name: "start error",
expectedStatuses: []*componentstatus.Event{
componentstatus.NewEvent(componentstatus.StatusStarting),
componentstatus.NewPermanentErrorEvent(assert.AnError),
},
startErr: assert.AnError,
shutdownErr: nil,
},
{
name: "shutdown error",
expectedStatuses: []*componentstatus.Event{
componentstatus.NewEvent(componentstatus.StatusStarting),
componentstatus.NewEvent(componentstatus.StatusOK),
componentstatus.NewEvent(componentstatus.StatusStopping),
componentstatus.NewPermanentErrorEvent(assert.AnError),
},
startErr: nil,
shutdownErr: assert.AnError,
},
} {
t.Run(tt.name, func(t *testing.T) {
statusType := component.MustNewType("statustest")
compID := component.NewID(statusType)
factory := newStatusTestExtensionFactory(statusType, tt.startErr, tt.shutdownErr)
config := factory.CreateDefaultConfig()
extensionsConfigs := map[component.ID]component.Config{
compID: config,
}
factories := map[component.Type]extension.Factory{
statusType: factory,
}
var actualStatuses []*componentstatus.Event
rep := status.NewReporter(func(_ *componentstatus.InstanceID, ev *componentstatus.Event) {
actualStatuses = append(actualStatuses, ev)
}, func(err error) {
require.NoError(t, err)
})
extensions, err := New(
context.Background(),
Settings{
Telemetry: componenttest.NewNopTelemetrySettings(),
BuildInfo: component.NewDefaultBuildInfo(),
Extensions: builders.NewExtension(extensionsConfigs, factories),
},
[]component.ID{compID},
WithReporter(rep),
)
require.NoError(t, err)
assert.Equal(t, tt.startErr, extensions.Start(context.Background(), componenttest.NewNopHost()))
if tt.startErr == nil {
assert.Equal(t, tt.shutdownErr, extensions.Shutdown(context.Background()))
}
assertEqualStatuses(t, tt.expectedStatuses, actualStatuses)
})
}
}
type statusTestExtension struct {
startErr error
shutdownErr error
}
func (ext *statusTestExtension) Start(context.Context, component.Host) error {
return ext.startErr
}
func (ext *statusTestExtension) Shutdown(context.Context) error {
return ext.shutdownErr
}
func newStatusTestExtension(startErr, shutdownErr error) *statusTestExtension {
return &statusTestExtension{
startErr: startErr,
shutdownErr: shutdownErr,
}
}
func newStatusTestExtensionFactory(name component.Type, startErr, shutdownErr error) extension.Factory {
return extension.NewFactory(
name,
func() component.Config {
return &struct{}{}
},
func(context.Context, extension.Settings, component.Config) (extension.Extension, error) {
return newStatusTestExtension(startErr, shutdownErr), nil
},
component.StabilityLevelDevelopment,
)
}
func newRecordingExtensionFactory(startCallback func(set extension.Settings, host component.Host) error, shutdownCallback func(set extension.Settings) error) extension.Factory {
return extension.NewFactory(
component.MustNewType("recording"),
func() component.Config {
return &recordingExtensionConfig{}
},
func(_ context.Context, set extension.Settings, cfg component.Config) (extension.Extension, error) {
return &recordingExtension{
config: cfg.(recordingExtensionConfig),
createSettings: set,
startCallback: startCallback,
shutdownCallback: shutdownCallback,
}, nil
},
component.StabilityLevelDevelopment,
)
}
type recordingExtensionConfig struct {
dependencies []string // names of dependencies of the same extension type
}
type recordingExtension struct {
config recordingExtensionConfig
startCallback func(set extension.Settings, host component.Host) error
shutdownCallback func(set extension.Settings) error
createSettings extension.Settings
}
var _ extensioncapabilities.Dependent = (*recordingExtension)(nil)
func (ext *recordingExtension) Dependencies() []component.ID {
if len(ext.config.dependencies) == 0 {
return nil
}
deps := make([]component.ID, len(ext.config.dependencies))
for i, dep := range ext.config.dependencies {
deps[i] = component.MustNewIDWithName("recording", dep)
}
return deps
}
func (ext *recordingExtension) Start(_ context.Context, host component.Host) error {
return ext.startCallback(ext.createSettings, host)
}
func (ext *recordingExtension) Shutdown(context.Context) error {
return ext.shutdownCallback(ext.createSettings)
}
================================================
FILE: service/extensions/graph.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package extensions // import "go.opentelemetry.io/collector/service/extensions"
import (
"errors"
"fmt"
"strings"
"gonum.org/v1/gonum/graph"
"gonum.org/v1/gonum/graph/simple"
"gonum.org/v1/gonum/graph/topo"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/extension/extensioncapabilities"
)
type node struct {
nodeID int64
extID component.ID
}
func (n node) ID() int64 {
return n.nodeID
}
func computeOrder(exts *Extensions) ([]component.ID, error) {
graph := simple.NewDirectedGraph()
nodes := make(map[component.ID]*node)
for extID := range exts.extMap {
n := &node{
nodeID: int64(len(nodes) + 1),
extID: extID,
}
graph.AddNode(n)
nodes[extID] = n
}
for extID, ext := range exts.extMap {
n := nodes[extID]
if dep, ok := ext.(extensioncapabilities.Dependent); ok {
for _, depID := range dep.Dependencies() {
d, ok := nodes[depID]
if !ok {
return nil, fmt.Errorf("unable to find extension %s on which extension %s depends", depID, extID)
}
graph.SetEdge(graph.NewEdge(d, n))
}
}
}
orderedNodes, err := topo.Sort(graph)
if err != nil {
return nil, cycleErr(err, topo.DirectedCyclesIn(graph))
}
order := make([]component.ID, len(orderedNodes))
for i, n := range orderedNodes {
order[i] = n.(*node).extID
}
return order, nil
}
func cycleErr(err error, cycles [][]graph.Node) error {
var topoErr topo.Unorderable
if !errors.As(err, &topoErr) || len(cycles) == 0 || len(cycles[0]) == 0 {
return err
}
cycle := cycles[0]
var names []string
for _, n := range cycle {
node := n.(*node)
names = append(names, node.extID.String())
}
cycleStr := "[" + strings.Join(names, " -> ") + "]"
return fmt.Errorf("unable to order extensions by dependencies, cycle found %s: %w", cycleStr, err)
}
================================================
FILE: service/extensions/graph_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package extensions // import "go.opentelemetry.io/collector/service/extensions"
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"gonum.org/v1/gonum/graph"
"gonum.org/v1/gonum/graph/topo"
)
func TestCycleErr(t *testing.T) {
err := errors.New("foo")
assert.Equal(t, err, cycleErr(err, nil), "cycleErr should return the error unchanged when it's unrecognized")
var topoErr topo.Unorderable = [][]graph.Node{{}}
assert.Equal(t, topoErr, cycleErr(topoErr, nil), "cycleErr should return topo.Unorderable error unchanged when no cycles are found")
}
================================================
FILE: service/extensions/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package extensions
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: service/generated_package_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package service
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: service/go.mod
================================================
module go.opentelemetry.io/collector/service
go 1.25.0
require (
github.com/google/uuid v1.6.0
github.com/prometheus/client_model v0.6.2
github.com/prometheus/common v0.67.5
github.com/shirou/gopsutil/v4 v4.26.2
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/component/componentstatus v0.148.0
go.opentelemetry.io/collector/component/componenttest v0.148.0
go.opentelemetry.io/collector/config/confighttp v0.148.0
go.opentelemetry.io/collector/config/confignet v1.54.0
go.opentelemetry.io/collector/config/configtelemetry v0.148.0
go.opentelemetry.io/collector/confmap v1.54.0
go.opentelemetry.io/collector/confmap/xconfmap v0.148.0
go.opentelemetry.io/collector/connector v0.148.0
go.opentelemetry.io/collector/connector/connectortest v0.148.0
go.opentelemetry.io/collector/connector/xconnector v0.148.0
go.opentelemetry.io/collector/consumer v1.54.0
go.opentelemetry.io/collector/consumer/consumererror v0.148.0
go.opentelemetry.io/collector/consumer/consumertest v0.148.0
go.opentelemetry.io/collector/consumer/xconsumer v0.148.0
go.opentelemetry.io/collector/exporter v1.54.0
go.opentelemetry.io/collector/exporter/exportertest v0.148.0
go.opentelemetry.io/collector/exporter/xexporter v0.148.0
go.opentelemetry.io/collector/extension v1.54.0
go.opentelemetry.io/collector/extension/extensioncapabilities v0.148.0
go.opentelemetry.io/collector/extension/extensiontest v0.148.0
go.opentelemetry.io/collector/extension/zpagesextension v0.148.0
go.opentelemetry.io/collector/featuregate v1.54.0
go.opentelemetry.io/collector/internal/componentalias v0.148.0
go.opentelemetry.io/collector/internal/fanoutconsumer v0.148.0
go.opentelemetry.io/collector/internal/telemetry v0.148.0
go.opentelemetry.io/collector/internal/testutil v0.148.0
go.opentelemetry.io/collector/otelcol v0.148.0
go.opentelemetry.io/collector/pdata v1.54.0
go.opentelemetry.io/collector/pdata/pprofile v0.148.0
go.opentelemetry.io/collector/pdata/testdata v0.148.0
go.opentelemetry.io/collector/pdata/xpdata v0.148.0
go.opentelemetry.io/collector/pipeline v1.54.0
go.opentelemetry.io/collector/pipeline/xpipeline v0.148.0
go.opentelemetry.io/collector/processor v1.54.0
go.opentelemetry.io/collector/processor/processortest v0.148.0
go.opentelemetry.io/collector/processor/xprocessor v0.148.0
go.opentelemetry.io/collector/receiver v1.54.0
go.opentelemetry.io/collector/receiver/receivertest v0.148.0
go.opentelemetry.io/collector/receiver/xreceiver v0.148.0
go.opentelemetry.io/collector/service/hostcapabilities v0.148.0
go.opentelemetry.io/collector/service/telemetry/telemetrytest v0.148.0
go.opentelemetry.io/contrib/bridges/otelzap v0.17.0
go.opentelemetry.io/contrib/otelconf v0.22.0
go.opentelemetry.io/contrib/propagators/b3 v1.42.0
go.opentelemetry.io/otel v1.42.0
go.opentelemetry.io/otel/log v0.18.0
go.opentelemetry.io/otel/metric v1.42.0
go.opentelemetry.io/otel/sdk v1.42.0
go.opentelemetry.io/otel/sdk/metric v1.42.0
go.opentelemetry.io/otel/trace v1.42.0
go.uber.org/goleak v1.3.0
go.uber.org/multierr v1.11.0
go.uber.org/zap v1.27.1
gonum.org/v1/gonum v0.17.0
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/ebitengine/purego v0.10.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/go-tpm v0.9.8 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.18.4 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.3.3 // indirect
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pierrec/lz4/v4 v4.1.26 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/otlptranslator v1.0.0 // indirect
github.com/prometheus/procfs v0.20.1 // indirect
github.com/rs/cors v1.11.1 // indirect
github.com/spf13/cobra v1.10.2 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.11.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/client v1.54.0 // indirect
go.opentelemetry.io/collector/config/configauth v1.54.0 // indirect
go.opentelemetry.io/collector/config/configcompression v1.54.0 // indirect
go.opentelemetry.io/collector/config/configmiddleware v1.54.0 // indirect
go.opentelemetry.io/collector/config/configopaque v1.54.0 // indirect
go.opentelemetry.io/collector/config/configoptional v1.54.0 // indirect
go.opentelemetry.io/collector/config/configtls v1.54.0 // indirect
go.opentelemetry.io/collector/extension/extensionauth v1.54.0 // indirect
go.opentelemetry.io/collector/extension/extensionmiddleware v0.148.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect
go.opentelemetry.io/contrib/zpages v0.67.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/prometheus v0.64.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 // indirect
go.opentelemetry.io/otel/sdk/log v0.18.0 // indirect
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.34.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace go.opentelemetry.io/collector/connector => ../connector
replace go.opentelemetry.io/collector/connector/connectortest => ../connector/connectortest
replace go.opentelemetry.io/collector/component => ../component
replace go.opentelemetry.io/collector/component/componenttest => ../component/componenttest
replace go.opentelemetry.io/collector/internal/telemetry => ../internal/telemetry/
replace go.opentelemetry.io/collector/component/componentstatus => ../component/componentstatus
replace go.opentelemetry.io/collector/pdata => ../pdata
replace go.opentelemetry.io/collector/pdata/testdata => ../pdata/testdata
replace go.opentelemetry.io/collector/extension/zpagesextension => ../extension/zpagesextension
replace go.opentelemetry.io/collector/extension => ../extension
replace go.opentelemetry.io/collector/exporter => ../exporter
replace go.opentelemetry.io/collector/confmap => ../confmap
replace go.opentelemetry.io/collector/confmap/xconfmap => ../confmap/xconfmap
replace go.opentelemetry.io/collector/config/configtelemetry => ../config/configtelemetry
replace go.opentelemetry.io/collector/pipeline => ../pipeline
replace go.opentelemetry.io/collector/processor => ../processor
replace go.opentelemetry.io/collector/processor/processortest => ../processor/processortest
replace go.opentelemetry.io/collector/consumer => ../consumer
replace go.opentelemetry.io/collector/service/hostcapabilities => ./hostcapabilities
replace go.opentelemetry.io/collector/service/telemetry/telemetrytest => ./telemetry/telemetrytest
replace go.opentelemetry.io/collector/receiver => ../receiver
replace go.opentelemetry.io/collector/featuregate => ../featuregate
replace go.opentelemetry.io/collector/config/configretry => ../config/configretry
replace go.opentelemetry.io/collector/extension/extensionauth => ../extension/extensionauth
replace go.opentelemetry.io/collector/extension/extensioncapabilities => ../extension/extensioncapabilities
replace go.opentelemetry.io/collector/config/configopaque => ../config/configopaque
replace go.opentelemetry.io/collector/config/confighttp => ../config/confighttp
replace go.opentelemetry.io/collector/config/configauth => ../config/configauth
replace go.opentelemetry.io/collector/config/configtls => ../config/configtls
replace go.opentelemetry.io/collector/config/configcompression => ../config/configcompression
replace go.opentelemetry.io/collector/pdata/pprofile => ../pdata/pprofile
replace go.opentelemetry.io/collector/consumer/xconsumer => ../consumer/xconsumer
replace go.opentelemetry.io/collector/consumer/consumertest => ../consumer/consumertest
replace go.opentelemetry.io/collector/client => ../client
replace go.opentelemetry.io/collector/receiver/xreceiver => ../receiver/xreceiver
replace go.opentelemetry.io/collector/receiver/receivertest => ../receiver/receivertest
replace go.opentelemetry.io/collector/processor/xprocessor => ../processor/xprocessor
replace go.opentelemetry.io/collector/exporter/xexporter => ../exporter/xexporter
replace go.opentelemetry.io/collector/pipeline/xpipeline => ../pipeline/xpipeline
replace go.opentelemetry.io/collector/exporter/exportertest => ../exporter/exportertest
replace go.opentelemetry.io/collector/consumer/consumererror => ../consumer/consumererror
replace go.opentelemetry.io/collector/connector/xconnector => ../connector/xconnector
replace go.opentelemetry.io/collector/internal/fanoutconsumer => ../internal/fanoutconsumer
replace go.opentelemetry.io/collector/extension/extensiontest => ../extension/extensiontest
replace go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest => ../extension/extensionauth/extensionauthtest
replace go.opentelemetry.io/collector/extension/xextension => ../extension/xextension
replace go.opentelemetry.io/collector/otelcol => ../otelcol
replace go.opentelemetry.io/collector/confmap/provider/fileprovider => ../confmap/provider/fileprovider
replace go.opentelemetry.io/collector/extension/extensionmiddleware => ../extension/extensionmiddleware
replace go.opentelemetry.io/collector/config/configmiddleware => ../config/configmiddleware
replace go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest => ../extension/extensionmiddleware/extensionmiddlewaretest
replace go.opentelemetry.io/collector/pdata/xpdata => ../pdata/xpdata
replace go.opentelemetry.io/collector/exporter/exporterhelper => ../exporter/exporterhelper
replace go.opentelemetry.io/collector/config/configoptional => ../config/configoptional
replace go.opentelemetry.io/collector/internal/testutil => ../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../internal/componentalias
replace go.opentelemetry.io/collector/config/confignet => ../config/confignet
================================================
FILE: service/go.sum
================================================
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/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f h1:RJ+BDPLSHQO7cSjKBqjPJSbi1qfk9WcsjQDtZiw3dZw=
github.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f/go.mod h1:VHbbch/X4roIY22jL1s3qRbZhCiRIgUAF/PdSUcx2io=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/go-tpm-tools v0.4.7 h1:J3ycC8umYxM9A4eF73EofRZu4BxY0jjQnUnkhIBbvws=
github.com/google/go-tpm-tools v0.4.7/go.mod h1:gSyXTZHe3fgbzb6WEGd90QucmsnT1SRdlye82gH8QjQ=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=
github.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
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 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY=
github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos=
github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM=
github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shirou/gopsutil/v4 v4.26.2 h1:X8i6sicvUFih4BmYIGT1m2wwgw2VG9YgrDTi7cIRGUI=
github.com/shirou/gopsutil/v4 v4.26.2/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/bridges/otelzap v0.17.0 h1:oCltVHJcblcth2z9B9dRTeZIZTe2Sf9Ad9h8bcc+s8M=
go.opentelemetry.io/contrib/bridges/otelzap v0.17.0/go.mod h1:G/VE1A/hRn6mEWdfC8rMvSdQVGM64KUPi4XilLkwcQw=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
go.opentelemetry.io/contrib/otelconf v0.22.0 h1:+kpcfczGOFM85zDZyqQCzWefhovegfn24D0WwmQz0n4=
go.opentelemetry.io/contrib/otelconf v0.22.0/go.mod h1:ojdbOukO+JRDJQmJY2PRIZEg0UYVzcOuZR59hp7xffc=
go.opentelemetry.io/contrib/propagators/b3 v1.42.0 h1:B2Pew5ufEtgkjLF+tSkXjgYZXQr9m7aCm1wLKB0URbU=
go.opentelemetry.io/contrib/propagators/b3 v1.42.0/go.mod h1:iPgUcSEF5DORW6+yNbdw/YevUy+QqJ508ncjhrRSCjc=
go.opentelemetry.io/contrib/zpages v0.67.0 h1:cIUwWSVDovuLEbDIKreptjdxMuIhGiqwq0uL8YNaq1c=
go.opentelemetry.io/contrib/zpages v0.67.0/go.mod h1:vK8fsYHgPYg4Z/XDbFSEvItSGZDbjWTvjBOu8+AiDhc=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 h1:deI9UQMoGFgrg5iLPgzueqFPHevDl+28YKfSpPTI6rY=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0/go.mod h1:PFx9NgpNUKXdf7J4Q3agRxMs3Y07QhTCVipKmLsMKnU=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0 h1:icqq3Z34UrEFk2u+HMhTtRsvo7Ues+eiJVjaJt62njs=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0/go.mod h1:W2m8P+d5Wn5kipj4/xmbt9uMqezEKfBjzVJadfABSBE=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 h1:MdKucPl/HbzckWWEisiNqMPhRrAOQX8r4jTuGr636gk=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0/go.mod h1:RolT8tWtfHcjajEH5wFIZ4Dgh5jpPdFXYV9pTAk/qjc=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0 h1:H7O6RlGOMTizyl3R08Kn5pdM06bnH8oscSj7o11tmLA=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0/go.mod h1:mBFWu/WOVDkWWsR7Tx7h6EpQB8wsv7P0Yrh0Pb7othc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 h1:uLXP+3mghfMf7XmV4PkGfFhFKuNWoCvvx5wP/wOXo0o=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0/go.mod h1:v0Tj04armyT59mnURNUJf7RCKcKzq+lgJs6QSjHjaTc=
go.opentelemetry.io/otel/exporters/prometheus v0.64.0 h1:g0LRDXMX/G1SEZtK8zl8Chm4K6GBwRkjPKE36LxiTYs=
go.opentelemetry.io/otel/exporters/prometheus v0.64.0/go.mod h1:UrgcjnarfdlBDP3GjDIJWe6HTprwSazNjwsI+Ru6hro=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 h1:KJVjPD3rcPb98rIs3HznyJlrfx9ge5oJvxxlGR+P/7s=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0/go.mod h1:K3kRa2ckmHWQaTWQdPRHc7qGXASuVuoEQXzrvlA98Ws=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 h1:lSZHgNHfbmQTPfuTmWVkEu8J8qXaQwuV30pjCcAUvP8=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0/go.mod h1:so9ounLcuoRDu033MW/E0AD4hhUjVqswrMF5FoZlBcw=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1JF26knRneorus8aOwVIDhvYx9WoDw=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs=
go.opentelemetry.io/otel/log v0.18.0 h1:XgeQIIBjZZrliksMEbcwMZefoOSMI1hdjiLEiiB0bAg=
go.opentelemetry.io/otel/log v0.18.0/go.mod h1:KEV1kad0NofR3ycsiDH4Yjcoj0+8206I6Ox2QYFSNgI=
go.opentelemetry.io/otel/log/logtest v0.18.0 h1:2QeyoKJdIgK2LJhG1yn78o/zmpXx1EditeyRDREqVS8=
go.opentelemetry.io/otel/log/logtest v0.18.0/go.mod h1:v1vh3PYR9zIa5MK6HwkH2lMrLBg/Y9Of6Qc+krlesX0=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/log v0.18.0 h1:n8OyZr7t7otkeTnPTbDNom6rW16TBYGtvyy2Gk6buQw=
go.opentelemetry.io/otel/sdk/log v0.18.0/go.mod h1:C0+wxkTwKpOCZLrlJ3pewPiiQwpzycPI/u6W0Z9fuYk=
go.opentelemetry.io/otel/sdk/log/logtest v0.18.0 h1:l3mYuPsuBx6UKE47BVcPrZoZ0q/KER57vbj2qkgDLXA=
go.opentelemetry.io/otel/sdk/log/logtest v0.18.0/go.mod h1:7cHtiVJpZebB3wybTa4NG+FUo5NPe3PROz1FqB0+qdw=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 h1:tu/dtnW1o3wfaxCOjSLn5IRX4YDcJrtlpzYkhHhGaC4=
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: service/hostcapabilities/Makefile
================================================
include ../../Makefile.Common
================================================
FILE: service/hostcapabilities/go.mod
================================================
module go.opentelemetry.io/collector/service/hostcapabilities
go 1.25.0
require (
go.opentelemetry.io/collector/component v1.54.0
go.opentelemetry.io/collector/pipeline v1.54.0
go.opentelemetry.io/collector/service v0.148.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
go.opentelemetry.io/collector/featuregate v1.54.0 // indirect
go.opentelemetry.io/collector/pdata v1.54.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
)
replace (
go.opentelemetry.io/collector/client => ../../client
go.opentelemetry.io/collector/component => ../../component
go.opentelemetry.io/collector/component/componentstatus => ../../component/componentstatus
go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest
go.opentelemetry.io/collector/config/configauth => ../../config/configauth
go.opentelemetry.io/collector/config/configcompression => ../../config/configcompression
go.opentelemetry.io/collector/config/confighttp => ../../config/confighttp
go.opentelemetry.io/collector/config/configopaque => ../../config/configopaque
go.opentelemetry.io/collector/config/configretry => ../../config/configretry
go.opentelemetry.io/collector/config/configtelemetry => ../../config/configtelemetry
go.opentelemetry.io/collector/config/configtls => ../../config/configtls
go.opentelemetry.io/collector/confmap => ../../confmap
go.opentelemetry.io/collector/confmap/xconfmap => ../../confmap/xconfmap
go.opentelemetry.io/collector/connector => ../../connector
go.opentelemetry.io/collector/connector/connectortest => ../../connector/connectortest
go.opentelemetry.io/collector/connector/xconnector => ../../connector/xconnector
go.opentelemetry.io/collector/consumer => ../../consumer
go.opentelemetry.io/collector/consumer/consumererror => ../../consumer/consumererror
go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest
go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer
go.opentelemetry.io/collector/exporter => ../../exporter
go.opentelemetry.io/collector/exporter/exportertest => ../../exporter/exportertest
go.opentelemetry.io/collector/exporter/xexporter => ../../exporter/xexporter
go.opentelemetry.io/collector/extension => ../../extension
go.opentelemetry.io/collector/extension/extensionauth => ../../extension/extensionauth
go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest => ../../extension/extensionauth/extensionauthtest
go.opentelemetry.io/collector/extension/extensioncapabilities => ../../extension/extensioncapabilities
go.opentelemetry.io/collector/extension/extensiontest => ../../extension/extensiontest
go.opentelemetry.io/collector/extension/xextension => ../../extension/xextension
go.opentelemetry.io/collector/extension/zpagesextension => ../../extension/zpagesextension
go.opentelemetry.io/collector/featuregate => ../../featuregate
go.opentelemetry.io/collector/internal/fanoutconsumer => ../../internal/fanoutconsumer
go.opentelemetry.io/collector/internal/telemetry => ../../internal/telemetry
go.opentelemetry.io/collector/pdata => ../../pdata
go.opentelemetry.io/collector/pdata/pprofile => ../../pdata/pprofile
go.opentelemetry.io/collector/pdata/testdata => ../../pdata/testdata
go.opentelemetry.io/collector/pipeline => ../../pipeline
go.opentelemetry.io/collector/pipeline/xpipeline => ../../pipeline/xpipeline
go.opentelemetry.io/collector/processor => ../../processor
go.opentelemetry.io/collector/processor/processortest => ../../processor/processortest
go.opentelemetry.io/collector/processor/xprocessor => ../../processor/xprocessor
go.opentelemetry.io/collector/receiver => ../../receiver
go.opentelemetry.io/collector/receiver/receivertest => ../../receiver/receivertest
go.opentelemetry.io/collector/receiver/xreceiver => ../../receiver/xreceiver
go.opentelemetry.io/collector/service => ..
)
replace go.opentelemetry.io/collector/otelcol => ../../otelcol
replace go.opentelemetry.io/collector/confmap/provider/fileprovider => ../../confmap/provider/fileprovider
replace go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest => ../../extension/extensionmiddleware/extensionmiddlewaretest
replace go.opentelemetry.io/collector/config/configmiddleware => ../../config/configmiddleware
replace go.opentelemetry.io/collector/extension/extensionmiddleware => ../../extension/extensionmiddleware
replace go.opentelemetry.io/collector/pdata/xpdata => ../../pdata/xpdata
replace go.opentelemetry.io/collector/exporter/exporterhelper => ../../exporter/exporterhelper
replace go.opentelemetry.io/collector/config/configoptional => ../../config/configoptional
replace go.opentelemetry.io/collector/service/telemetry/telemetrytest => ../telemetry/telemetrytest
replace go.opentelemetry.io/collector/internal/testutil => ../../internal/testutil
replace go.opentelemetry.io/collector/internal/componentalias => ../../internal/componentalias
replace go.opentelemetry.io/collector/config/confignet => ../../config/confignet
================================================
FILE: service/hostcapabilities/go.sum
================================================
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: service/hostcapabilities/interfaces.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package hostcapabilities provides interfaces that can be implemented by the host
// to provide additional capabilities.
package hostcapabilities // import "go.opentelemetry.io/collector/service/hostcapabilities"
import (
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/service/internal/moduleinfo"
)
// ModuleInfo is an interface that may be implemented by the host to provide
// information about modules that were used to build the host.
type ModuleInfo interface {
// GetModuleInfos returns the module information for the host
// i.e. Receivers, Processors, Exporters, Extensions, and Connectors
GetModuleInfos() moduleinfo.ModuleInfos
}
// ExposeExporters is an interface that may be implemented by the host to provide
// access to the exporters that were used to build the host.
//
// Deprecated: [v0.121.0] Will be removed in Service 1.0.
// See: https://github.com/open-telemetry/opentelemetry-collector/issues/7370 for service 1.0
type ExposeExporters interface {
GetExporters() map[pipeline.Signal]map[component.ID]component.Component
}
// ComponentFactory is an interface that may be implemented by the host to
// provide a component's factory
type ComponentFactory interface {
// GetFactory returns the component factory for the given
// component type
GetFactory(kind component.Kind, componentType component.Type) component.Factory
}
================================================
FILE: service/hostcapabilities/metadata.yaml
================================================
type: service/hostcapabilities
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: pkg
================================================
FILE: service/internal/attribute/attribute.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package attribute // import "go.opentelemetry.io/collector/service/internal/attribute"
import (
"hash/fnv"
"strings"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/internal/telemetry"
"go.opentelemetry.io/collector/pipeline"
)
const (
capabiltiesKind = "capabilities"
fanoutKind = "fanout"
)
type Attributes struct {
set attribute.Set
id int64
}
func newAttributes(attrs ...attribute.KeyValue) Attributes {
h := fnv.New64a()
for _, kv := range attrs {
h.Write([]byte("(" + string(kv.Key) + "|" + kv.Value.AsString() + ")"))
}
return Attributes{
set: attribute.NewSet(attrs...),
// The graph identifies nodes by an int64 ID, but fnv gives us a uint64.
// It is safe to cast because the meaning of the number is irrelevant.
// We only care that each node has a unique 64 bit ID, which is unaltered by this cast.
id: int64(h.Sum64()), // #nosec G115
}
}
func (a Attributes) Set() *attribute.Set {
return &a.set
}
func (a Attributes) ID() int64 {
return a.id
}
func Receiver(pipelineType pipeline.Signal, id component.ID) Attributes {
return newAttributes(
attribute.String(telemetry.ComponentKindKey, strings.ToLower(component.KindReceiver.String())),
attribute.String(telemetry.SignalKey, pipelineType.String()),
attribute.String(telemetry.ComponentIDKey, id.String()),
)
}
func Processor(pipelineID pipeline.ID, id component.ID) Attributes {
return newAttributes(
attribute.String(telemetry.ComponentKindKey, strings.ToLower(component.KindProcessor.String())),
attribute.String(telemetry.SignalKey, pipelineID.Signal().String()),
attribute.String(telemetry.PipelineIDKey, pipelineID.String()),
attribute.String(telemetry.ComponentIDKey, id.String()),
)
}
func Exporter(pipelineType pipeline.Signal, id component.ID) Attributes {
return newAttributes(
attribute.String(telemetry.ComponentKindKey, strings.ToLower(component.KindExporter.String())),
attribute.String(telemetry.SignalKey, pipelineType.String()),
attribute.String(telemetry.ComponentIDKey, id.String()),
)
}
func Connector(exprPipelineType, rcvrPipelineType pipeline.Signal, id component.ID) Attributes {
return newAttributes(
attribute.String(telemetry.ComponentKindKey, strings.ToLower(component.KindConnector.String())),
attribute.String(telemetry.SignalKey, exprPipelineType.String()),
attribute.String(telemetry.SignalOutputKey, rcvrPipelineType.String()),
attribute.String(telemetry.ComponentIDKey, id.String()),
)
}
func Extension(id component.ID) Attributes {
return newAttributes(
attribute.String(telemetry.ComponentKindKey, strings.ToLower(component.KindExtension.String())),
attribute.String(telemetry.ComponentIDKey, id.String()),
)
}
func Capabilities(pipelineID pipeline.ID) Attributes {
return newAttributes(
attribute.String(telemetry.ComponentKindKey, capabiltiesKind),
attribute.String(telemetry.PipelineIDKey, pipelineID.String()),
)
}
func Fanout(pipelineID pipeline.ID) Attributes {
return newAttributes(
attribute.String(telemetry.ComponentKindKey, fanoutKind),
attribute.String(telemetry.PipelineIDKey, pipelineID.String()),
)
}
================================================
FILE: service/internal/attribute/attribute_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package attribute_test
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/internal/telemetry"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/pipeline/xpipeline"
"go.opentelemetry.io/collector/service/internal/attribute"
)
var (
signals = []pipeline.Signal{
pipeline.SignalTraces,
pipeline.SignalMetrics,
pipeline.SignalLogs,
xpipeline.SignalProfiles,
}
cIDs = []component.ID{
component.MustNewID("foo"),
component.MustNewID("foo2"),
component.MustNewID("bar"),
}
pIDs = []pipeline.ID{
pipeline.NewID(pipeline.SignalTraces),
pipeline.NewIDWithName(pipeline.SignalTraces, "2"),
pipeline.NewID(pipeline.SignalMetrics),
pipeline.NewIDWithName(pipeline.SignalMetrics, "2"),
pipeline.NewID(pipeline.SignalLogs),
pipeline.NewIDWithName(pipeline.SignalLogs, "2"),
pipeline.NewID(xpipeline.SignalProfiles),
pipeline.NewIDWithName(xpipeline.SignalProfiles, "2"),
}
)
func TestReceiver(t *testing.T) {
for _, sig := range signals {
for _, id := range cIDs {
r := attribute.Receiver(sig, id)
componentKind, ok := r.Set().Value(telemetry.ComponentKindKey)
require.True(t, ok)
require.Equal(t, "receiver", componentKind.AsString())
signal, ok := r.Set().Value(telemetry.SignalKey)
require.True(t, ok)
require.Equal(t, sig.String(), signal.AsString())
componentID, ok := r.Set().Value(telemetry.ComponentIDKey)
require.True(t, ok)
require.Equal(t, id.String(), componentID.AsString())
}
}
}
func TestProcessor(t *testing.T) {
for _, pID := range pIDs {
for _, id := range cIDs {
p := attribute.Processor(pID, id)
componentKind, ok := p.Set().Value(telemetry.ComponentKindKey)
require.True(t, ok)
require.Equal(t, "processor", componentKind.AsString())
pipelineID, ok := p.Set().Value(telemetry.PipelineIDKey)
require.True(t, ok)
require.Equal(t, pID.String(), pipelineID.AsString())
componentID, ok := p.Set().Value(telemetry.ComponentIDKey)
require.True(t, ok)
require.Equal(t, id.String(), componentID.AsString())
}
}
}
func TestExporter(t *testing.T) {
for _, sig := range signals {
for _, id := range cIDs {
e := attribute.Exporter(sig, id)
componentKind, ok := e.Set().Value(telemetry.ComponentKindKey)
require.True(t, ok)
require.Equal(t, "exporter", componentKind.AsString())
signal, ok := e.Set().Value(telemetry.SignalKey)
require.True(t, ok)
require.Equal(t, sig.String(), signal.AsString())
componentID, ok := e.Set().Value(telemetry.ComponentIDKey)
require.True(t, ok)
require.Equal(t, id.String(), componentID.AsString())
}
}
}
func TestConnector(t *testing.T) {
for _, exprSig := range signals {
for _, rcvrSig := range signals {
for _, id := range cIDs {
c := attribute.Connector(exprSig, rcvrSig, id)
componentKind, ok := c.Set().Value(telemetry.ComponentKindKey)
require.True(t, ok)
require.Equal(t, "connector", componentKind.AsString())
signal, ok := c.Set().Value(telemetry.SignalKey)
require.True(t, ok)
require.Equal(t, exprSig.String(), signal.AsString())
signalOutput, ok := c.Set().Value(telemetry.SignalOutputKey)
require.True(t, ok)
require.Equal(t, rcvrSig.String(), signalOutput.AsString())
componentID, ok := c.Set().Value(telemetry.ComponentIDKey)
require.True(t, ok)
require.Equal(t, id.String(), componentID.AsString())
}
}
}
}
func TestExtension(t *testing.T) {
e := attribute.Extension(component.MustNewID("foo"))
componentKind, ok := e.Set().Value(telemetry.ComponentKindKey)
require.True(t, ok)
require.Equal(t, "extension", componentKind.AsString())
}
func TestSetEquality(t *testing.T) {
// The sets are created independently but should be exactly equivalent.
// We will ensure that corresponding elements are equal and that
// non-corresponding elements are not equal.
setI, setJ := createExampleSets(), createExampleSets()
for i, ei := range setI {
for j, ej := range setJ {
if i == j {
require.Equal(t, ei.ID(), ej.ID())
si, sj := ei.Set(), ej.Set()
require.True(t, si.Equals(sj))
} else {
require.NotEqual(t, ei.ID(), ej.ID())
si, sj := ei.Set(), ej.Set()
require.False(t, si.Equals(sj))
}
}
}
}
func createExampleSets() []attribute.Attributes {
sets := []attribute.Attributes{}
// Receiver examples.
for _, sig := range signals {
for _, id := range cIDs {
sets = append(sets, attribute.Receiver(sig, id))
}
}
// Processor examples.
for _, pID := range pIDs {
for _, cID := range cIDs {
sets = append(sets, attribute.Processor(pID, cID))
}
}
// Exporter examples.
for _, sig := range signals {
for _, id := range cIDs {
sets = append(sets, attribute.Exporter(sig, id))
}
}
// Connector examples.
for _, exprSig := range signals {
for _, rcvrSig := range signals {
for _, id := range cIDs {
sets = append(sets, attribute.Connector(exprSig, rcvrSig, id))
}
}
}
// Capabilities examples.
for _, pID := range pIDs {
sets = append(sets, attribute.Capabilities(pID))
}
// Fanout examples.
for _, pID := range pIDs {
sets = append(sets, attribute.Fanout(pID))
}
return sets
}
================================================
FILE: service/internal/builders/builders.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package builders // import "go.opentelemetry.io/collector/service/internal/builders"
import (
"errors"
"fmt"
"go.uber.org/zap"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/internal/componentalias"
)
var (
errNilNextConsumer = errors.New("nil next Consumer")
NopType = component.MustNewType("nop")
)
// logStabilityLevel logs the stability level of a component. The log level is set to info for
// undefined, unmaintained, deprecated and development. The log level is set to debug
// for alpha, beta and stable.
func logStabilityLevel(logger *zap.Logger, sl component.StabilityLevel) {
if sl >= component.StabilityLevelAlpha {
logger.Debug(sl.LogMessage())
} else {
logger.Info(sl.LogMessage())
}
}
// logDeprecatedTypeAlias checks if the provided type is a deprecated alias and logs a warning if so.
func logDeprecatedTypeAlias(logger *zap.Logger, factory component.Factory, usedType component.Type) {
tah, ok := factory.(componentalias.TypeAliasHolder)
if !ok {
return
}
alias := tah.DeprecatedAlias()
if alias.String() != "" && usedType == alias {
logger.Warn(fmt.Sprintf("%q alias is deprecated; use %q instead", alias.String(), factory.Type().String()))
}
}
================================================
FILE: service/internal/builders/builders_test/connector_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package builders
import (
"context"
"fmt"
"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/connector"
"go.opentelemetry.io/collector/connector/connectortest"
"go.opentelemetry.io/collector/connector/xconnector"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/otelcol"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/pipeline/xpipeline"
"go.opentelemetry.io/collector/service/internal/builders"
)
func TestConnectorBuilder(t *testing.T) {
defaultCfg := struct{}{}
factories, err := otelcol.MakeFactoryMap([]connector.Factory{
connector.NewFactory(component.MustNewType("err"), nil),
xconnector.NewFactory(
component.MustNewType("all"),
func() component.Config { return &defaultCfg },
xconnector.WithTracesToTraces(createConnectorTracesToTraces, component.StabilityLevelDevelopment),
xconnector.WithTracesToMetrics(createConnectorTracesToMetrics, component.StabilityLevelDevelopment),
xconnector.WithTracesToLogs(createConnectorTracesToLogs, component.StabilityLevelDevelopment),
xconnector.WithTracesToProfiles(createConnectorTracesToProfiles, component.StabilityLevelDevelopment),
xconnector.WithMetricsToTraces(createConnectorMetricsToTraces, component.StabilityLevelAlpha),
xconnector.WithMetricsToMetrics(createConnectorMetricsToMetrics, component.StabilityLevelAlpha),
xconnector.WithMetricsToLogs(createConnectorMetricsToLogs, component.StabilityLevelAlpha),
xconnector.WithMetricsToProfiles(createConnectorMetricsToProfiles, component.StabilityLevelAlpha),
xconnector.WithLogsToTraces(createConnectorLogsToTraces, component.StabilityLevelDeprecated),
xconnector.WithLogsToMetrics(createConnectorLogsToMetrics, component.StabilityLevelDeprecated),
xconnector.WithLogsToLogs(createConnectorLogsToLogs, component.StabilityLevelDeprecated),
xconnector.WithLogsToProfiles(createConnectorLogsToProfiles, component.StabilityLevelDeprecated),
xconnector.WithProfilesToTraces(createxconnectorToTraces, component.StabilityLevelDevelopment),
xconnector.WithProfilesToMetrics(createxconnectorToMetrics, component.StabilityLevelDevelopment),
xconnector.WithProfilesToLogs(createxconnectorToLogs, component.StabilityLevelDevelopment),
xconnector.WithProfilesToProfiles(createxconnectorToProfiles, component.StabilityLevelDevelopment),
),
}...)
require.NoError(t, err)
testCases := []struct {
name string
id component.ID
err func(pipeline.Signal, pipeline.Signal) string
nextTraces consumer.Traces
nextLogs consumer.Logs
nextMetrics consumer.Metrics
nextProfiles xconsumer.Profiles
}{
{
name: "unknown",
id: component.MustNewID("unknown"),
err: func(pipeline.Signal, pipeline.Signal) string {
return "connector factory not available for: \"unknown\""
},
nextTraces: consumertest.NewNop(),
nextLogs: consumertest.NewNop(),
nextMetrics: consumertest.NewNop(),
nextProfiles: consumertest.NewNop(),
},
{
name: "err",
id: component.MustNewID("err"),
err: func(expType, rcvType pipeline.Signal) string {
return fmt.Sprintf("connector \"err\" cannot connect from %s to %s: telemetry type is not supported", expType, rcvType)
},
nextTraces: consumertest.NewNop(),
nextLogs: consumertest.NewNop(),
nextMetrics: consumertest.NewNop(),
nextProfiles: consumertest.NewNop(),
},
{
name: "all",
id: component.MustNewID("all"),
err: func(pipeline.Signal, pipeline.Signal) string {
return ""
},
nextTraces: consumertest.NewNop(),
nextLogs: consumertest.NewNop(),
nextMetrics: consumertest.NewNop(),
nextProfiles: consumertest.NewNop(),
},
{
name: "all/named",
id: component.MustNewIDWithName("all", "named"),
err: func(pipeline.Signal, pipeline.Signal) string {
return ""
},
nextTraces: consumertest.NewNop(),
nextLogs: consumertest.NewNop(),
nextMetrics: consumertest.NewNop(),
nextProfiles: consumertest.NewNop(),
},
{
name: "no next consumer",
id: component.MustNewID("unknown"),
err: func(_, _ pipeline.Signal) string {
return "nil next Consumer"
},
nextTraces: nil,
nextLogs: nil,
nextMetrics: nil,
nextProfiles: nil,
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
cfgs := map[component.ID]component.Config{tt.id: defaultCfg}
b := builders.NewConnector(cfgs, factories)
t2t, err := b.CreateTracesToTraces(context.Background(), createConnectorSettings(tt.id), tt.nextTraces)
if expectedErr := tt.err(pipeline.SignalTraces, pipeline.SignalTraces); expectedErr != "" {
assert.EqualError(t, err, expectedErr)
assert.Nil(t, t2t)
} else {
assert.NoError(t, err)
assert.Equal(t, nopConnectorInstance, t2t)
}
t2m, err := b.CreateTracesToMetrics(context.Background(), createConnectorSettings(tt.id), tt.nextMetrics)
if expectedErr := tt.err(pipeline.SignalTraces, pipeline.SignalMetrics); expectedErr != "" {
assert.EqualError(t, err, expectedErr)
assert.Nil(t, t2m)
} else {
assert.NoError(t, err)
assert.Equal(t, nopConnectorInstance, t2m)
}
t2l, err := b.CreateTracesToLogs(context.Background(), createConnectorSettings(tt.id), tt.nextLogs)
if expectedErr := tt.err(pipeline.SignalTraces, pipeline.SignalLogs); expectedErr != "" {
assert.EqualError(t, err, expectedErr)
assert.Nil(t, t2l)
} else {
assert.NoError(t, err)
assert.Equal(t, nopConnectorInstance, t2l)
}
t2p, err := b.CreateTracesToProfiles(context.Background(), createConnectorSettings(tt.id), tt.nextProfiles)
if expectedErr := tt.err(pipeline.SignalTraces, xpipeline.SignalProfiles); expectedErr != "" {
assert.EqualError(t, err, expectedErr)
assert.Nil(t, t2p)
} else {
assert.NoError(t, err)
assert.Equal(t, nopConnectorInstance, t2p)
}
m2t, err := b.CreateMetricsToTraces(context.Background(), createConnectorSettings(tt.id), tt.nextTraces)
if expectedErr := tt.err(pipeline.SignalMetrics, pipeline.SignalTraces); expectedErr != "" {
assert.EqualError(t, err, expectedErr)
assert.Nil(t, m2t)
} else {
assert.NoError(t, err)
assert.Equal(t, nopConnectorInstance, m2t)
}
m2m, err := b.CreateMetricsToMetrics(context.Background(), createConnectorSettings(tt.id), tt.nextMetrics)
if expectedErr := tt.err(pipeline.SignalMetrics, pipeline.SignalMetrics); expectedErr != "" {
assert.EqualError(t, err, expectedErr)
assert.Nil(t, m2m)
} else {
assert.NoError(t, err)
assert.Equal(t, nopConnectorInstance, m2m)
}
m2l, err := b.CreateMetricsToLogs(context.Background(), createConnectorSettings(tt.id), tt.nextLogs)
if expectedErr := tt.err(pipeline.SignalMetrics, pipeline.SignalLogs); expectedErr != "" {
assert.EqualError(t, err, expectedErr)
assert.Nil(t, m2l)
} else {
assert.NoError(t, err)
assert.Equal(t, nopConnectorInstance, m2l)
}
m2p, err := b.CreateMetricsToProfiles(context.Background(), createConnectorSettings(tt.id), tt.nextProfiles)
if expectedErr := tt.err(pipeline.SignalMetrics, xpipeline.SignalProfiles); expectedErr != "" {
assert.EqualError(t, err, expectedErr)
assert.Nil(t, m2p)
} else {
assert.NoError(t, err)
assert.Equal(t, nopConnectorInstance, m2p)
}
l2t, err := b.CreateLogsToTraces(context.Background(), createConnectorSettings(tt.id), tt.nextTraces)
if expectedErr := tt.err(pipeline.SignalLogs, pipeline.SignalTraces); expectedErr != "" {
assert.EqualError(t, err, expectedErr)
assert.Nil(t, l2t)
} else {
assert.NoError(t, err)
assert.Equal(t, nopConnectorInstance, l2t)
}
l2m, err := b.CreateLogsToMetrics(context.Background(), createConnectorSettings(tt.id), tt.nextMetrics)
if expectedErr := tt.err(pipeline.SignalLogs, pipeline.SignalMetrics); expectedErr != "" {
assert.EqualError(t, err, expectedErr)
assert.Nil(t, l2m)
} else {
assert.NoError(t, err)
assert.Equal(t, nopConnectorInstance, l2m)
}
l2l, err := b.CreateLogsToLogs(context.Background(), createConnectorSettings(tt.id), tt.nextLogs)
if expectedErr := tt.err(pipeline.SignalLogs, pipeline.SignalLogs); expectedErr != "" {
assert.EqualError(t, err, expectedErr)
assert.Nil(t, l2l)
} else {
assert.NoError(t, err)
assert.Equal(t, nopConnectorInstance, l2l)
}
l2p, err := b.CreateLogsToProfiles(context.Background(), createConnectorSettings(tt.id), tt.nextProfiles)
if expectedErr := tt.err(pipeline.SignalLogs, xpipeline.SignalProfiles); expectedErr != "" {
assert.EqualError(t, err, expectedErr)
assert.Nil(t, l2p)
} else {
assert.NoError(t, err)
assert.Equal(t, nopConnectorInstance, l2p)
}
p2t, err := b.CreateProfilesToTraces(context.Background(), createConnectorSettings(tt.id), tt.nextTraces)
if expectedErr := tt.err(xpipeline.SignalProfiles, pipeline.SignalTraces); expectedErr != "" {
assert.EqualError(t, err, expectedErr)
assert.Nil(t, p2t)
} else {
assert.NoError(t, err)
assert.Equal(t, nopConnectorInstance, p2t)
}
p2m, err := b.CreateProfilesToMetrics(context.Background(), createConnectorSettings(tt.id), tt.nextMetrics)
if expectedErr := tt.err(xpipeline.SignalProfiles, pipeline.SignalMetrics); expectedErr != "" {
assert.EqualError(t, err, expectedErr)
assert.Nil(t, p2m)
} else {
assert.NoError(t, err)
assert.Equal(t, nopConnectorInstance, p2m)
}
p2l, err := b.CreateProfilesToLogs(context.Background(), createConnectorSettings(tt.id), tt.nextLogs)
if expectedErr := tt.err(xpipeline.SignalProfiles, pipeline.SignalLogs); expectedErr != "" {
assert.EqualError(t, err, expectedErr)
assert.Nil(t, p2l)
} else {
assert.NoError(t, err)
assert.Equal(t, nopConnectorInstance, p2l)
}
p2p, err := b.CreateProfilesToProfiles(context.Background(), createConnectorSettings(tt.id), tt.nextProfiles)
if expectedErr := tt.err(xpipeline.SignalProfiles, xpipeline.SignalProfiles); expectedErr != "" {
assert.EqualError(t, err, expectedErr)
assert.Nil(t, p2p)
} else {
assert.NoError(t, err)
assert.Equal(t, nopConnectorInstance, p2p)
}
})
}
}
func TestConnectorBuilderMissingConfig(t *testing.T) {
defaultCfg := struct{}{}
factories, err := otelcol.MakeFactoryMap([]connector.Factory{
xconnector.NewFactory(
component.MustNewType("all"),
func() component.Config { return &defaultCfg },
xconnector.WithTracesToTraces(createConnectorTracesToTraces, component.StabilityLevelDevelopment),
xconnector.WithTracesToMetrics(createConnectorTracesToMetrics, component.StabilityLevelDevelopment),
xconnector.WithTracesToLogs(createConnectorTracesToLogs, component.StabilityLevelDevelopment),
xconnector.WithTracesToProfiles(createConnectorTracesToProfiles, component.StabilityLevelDevelopment),
xconnector.WithMetricsToTraces(createConnectorMetricsToTraces, component.StabilityLevelAlpha),
xconnector.WithMetricsToMetrics(createConnectorMetricsToMetrics, component.StabilityLevelAlpha),
xconnector.WithMetricsToLogs(createConnectorMetricsToLogs, component.StabilityLevelAlpha),
xconnector.WithMetricsToProfiles(createConnectorMetricsToProfiles, component.StabilityLevelAlpha),
xconnector.WithLogsToTraces(createConnectorLogsToTraces, component.StabilityLevelDeprecated),
xconnector.WithLogsToMetrics(createConnectorLogsToMetrics, component.StabilityLevelDeprecated),
xconnector.WithLogsToLogs(createConnectorLogsToLogs, component.StabilityLevelDeprecated),
xconnector.WithLogsToProfiles(createConnectorLogsToProfiles, component.StabilityLevelDeprecated),
xconnector.WithProfilesToTraces(createxconnectorToTraces, component.StabilityLevelDevelopment),
xconnector.WithProfilesToMetrics(createxconnectorToMetrics, component.StabilityLevelDevelopment),
xconnector.WithProfilesToLogs(createxconnectorToLogs, component.StabilityLevelDevelopment),
xconnector.WithProfilesToProfiles(createxconnectorToProfiles, component.StabilityLevelDevelopment),
),
}...)
require.NoError(t, err)
bErr := builders.NewConnector(map[component.ID]component.Config{}, factories)
missingID := component.MustNewIDWithName("all", "missing")
t2t, err := bErr.CreateTracesToTraces(context.Background(), createConnectorSettings(missingID), consumertest.NewNop())
require.EqualError(t, err, "connector \"all/missing\" is not configured")
assert.Nil(t, t2t)
t2m, err := bErr.CreateTracesToMetrics(context.Background(), createConnectorSettings(missingID), consumertest.NewNop())
require.EqualError(t, err, "connector \"all/missing\" is not configured")
assert.Nil(t, t2m)
t2l, err := bErr.CreateTracesToLogs(context.Background(), createConnectorSettings(missingID), consumertest.NewNop())
require.EqualError(t, err, "connector \"all/missing\" is not configured")
assert.Nil(t, t2l)
t2p, err := bErr.CreateTracesToProfiles(context.Background(), createConnectorSettings(missingID), consumertest.NewNop())
require.EqualError(t, err, "connector \"all/missing\" is not configured")
assert.Nil(t, t2p)
m2t, err := bErr.CreateMetricsToTraces(context.Background(), createConnectorSettings(missingID), consumertest.NewNop())
require.EqualError(t, err, "connector \"all/missing\" is not configured")
assert.Nil(t, m2t)
m2m, err := bErr.CreateMetricsToMetrics(context.Background(), createConnectorSettings(missingID), consumertest.NewNop())
require.EqualError(t, err, "connector \"all/missing\" is not configured")
assert.Nil(t, m2m)
m2l, err := bErr.CreateMetricsToLogs(context.Background(), createConnectorSettings(missingID), consumertest.NewNop())
require.EqualError(t, err, "connector \"all/missing\" is not configured")
assert.Nil(t, m2l)
m2p, err := bErr.CreateMetricsToProfiles(context.Background(), createConnectorSettings(missingID), consumertest.NewNop())
require.EqualError(t, err, "connector \"all/missing\" is not configured")
assert.Nil(t, m2p)
l2t, err := bErr.CreateLogsToTraces(context.Background(), createConnectorSettings(missingID), consumertest.NewNop())
require.EqualError(t, err, "connector \"all/missing\" is not configured")
assert.Nil(t, l2t)
l2m, err := bErr.CreateLogsToMetrics(context.Background(), createConnectorSettings(missingID), consumertest.NewNop())
require.EqualError(t, err, "connector \"all/missing\" is not configured")
assert.Nil(t, l2m)
l2l, err := bErr.CreateLogsToLogs(context.Background(), createConnectorSettings(missingID), consumertest.NewNop())
require.EqualError(t, err, "connector \"all/missing\" is not configured")
assert.Nil(t, l2l)
l2p, err := bErr.CreateLogsToProfiles(context.Background(), createConnectorSettings(missingID), consumertest.NewNop())
require.EqualError(t, err, "connector \"all/missing\" is not configured")
assert.Nil(t, l2p)
p2t, err := bErr.CreateProfilesToTraces(context.Background(), createConnectorSettings(missingID), consumertest.NewNop())
require.EqualError(t, err, "connector \"all/missing\" is not configured")
assert.Nil(t, p2t)
p2m, err := bErr.CreateProfilesToMetrics(context.Background(), createConnectorSettings(missingID), consumertest.NewNop())
require.EqualError(t, err, "connector \"all/missing\" is not configured")
assert.Nil(t, p2m)
p2l, err := bErr.CreateProfilesToLogs(context.Background(), createConnectorSettings(missingID), consumertest.NewNop())
require.EqualError(t, err, "connector \"all/missing\" is not configured")
assert.Nil(t, p2l)
p2p, err := bErr.CreateProfilesToProfiles(context.Background(), createConnectorSettings(missingID), consumertest.NewNop())
require.EqualError(t, err, "connector \"all/missing\" is not configured")
assert.Nil(t, p2p)
}
func TestConnectorBuilderGetters(t *testing.T) {
factories, err := otelcol.MakeFactoryMap([]connector.Factory{connector.NewFactory(component.MustNewType("foo"), nil)}...)
require.NoError(t, err)
cfgs := map[component.ID]component.Config{component.MustNewID("foo"): struct{}{}}
b := builders.NewConnector(cfgs, factories)
assert.True(t, b.IsConfigured(component.MustNewID("foo")))
assert.False(t, b.IsConfigured(component.MustNewID("bar")))
assert.NotNil(t, b.Factory(component.MustNewID("foo").Type()))
assert.Nil(t, b.Factory(component.MustNewID("bar").Type()))
}
func TestNewNopConnectorConfigsAndFactories(t *testing.T) {
configs, factories := builders.NewNopConnectorConfigsAndFactories()
builder := builders.NewConnector(configs, factories)
require.NotNil(t, builder)
factory := connectortest.NewNopFactory()
cfg := factory.CreateDefaultConfig()
set := connectortest.NewNopSettings(factory.Type())
set.ID = component.NewIDWithName(builders.NopType, "conn")
tracesToTraces, err := factory.CreateTracesToTraces(context.Background(), set, cfg, consumertest.NewNop())
require.NoError(t, err)
bTracesToTraces, err := builder.CreateTracesToTraces(context.Background(), set, consumertest.NewNop())
require.NoError(t, err)
assert.IsType(t, tracesToTraces, bTracesToTraces)
tracesToMetrics, err := factory.CreateTracesToMetrics(context.Background(), set, cfg, consumertest.NewNop())
require.NoError(t, err)
bTracesToMetrics, err := builder.CreateTracesToMetrics(context.Background(), set, consumertest.NewNop())
require.NoError(t, err)
assert.IsType(t, tracesToMetrics, bTracesToMetrics)
tracesToLogs, err := factory.CreateTracesToLogs(context.Background(), set, cfg, consumertest.NewNop())
require.NoError(t, err)
bTracesToLogs, err := builder.CreateTracesToLogs(context.Background(), set, consumertest.NewNop())
require.NoError(t, err)
assert.IsType(t, tracesToLogs, bTracesToLogs)
tracesToProfiles, err := factory.(xconnector.Factory).CreateTracesToProfiles(context.Background(), set, cfg, consumertest.NewNop())
require.NoError(t, err)
bTracesToProfiles, err := builder.CreateTracesToProfiles(context.Background(), set, consumertest.NewNop())
require.NoError(t, err)
assert.IsType(t, tracesToProfiles, bTracesToProfiles)
metricsToTraces, err := factory.CreateMetricsToTraces(context.Background(), set, cfg, consumertest.NewNop())
require.NoError(t, err)
bMetricsToTraces, err := builder.CreateMetricsToTraces(context.Background(), set, consumertest.NewNop())
require.NoError(t, err)
assert.IsType(t, metricsToTraces, bMetricsToTraces)
metricsToMetrics, err := factory.CreateMetricsToMetrics(context.Background(), set, cfg, consumertest.NewNop())
require.NoError(t, err)
bMetricsToMetrics, err := builder.CreateMetricsToMetrics(context.Background(), set, consumertest.NewNop())
require.NoError(t, err)
assert.IsType(t, metricsToMetrics, bMetricsToMetrics)
metricsToLogs, err := factory.CreateMetricsToLogs(context.Background(), set, cfg, consumertest.NewNop())
require.NoError(t, err)
bMetricsToLogs, err := builder.CreateMetricsToLogs(context.Background(), set, consumertest.NewNop())
require.NoError(t, err)
assert.IsType(t, metricsToLogs, bMetricsToLogs)
metricsToProfiles, err := factory.(xconnector.Factory).CreateMetricsToProfiles(context.Background(), set, cfg, consumertest.NewNop())
require.NoError(t, err)
bMetricsToProfiles, err := builder.CreateMetricsToProfiles(context.Background(), set, consumertest.NewNop())
require.NoError(t, err)
assert.IsType(t, metricsToProfiles, bMetricsToProfiles)
logsToTraces, err := factory.CreateLogsToTraces(context.Background(), set, cfg, consumertest.NewNop())
require.NoError(t, err)
bLogsToTraces, err := builder.CreateLogsToTraces(context.Background(), set, consumertest.NewNop())
require.NoError(t, err)
assert.IsType(t, logsToTraces, bLogsToTraces)
logsToMetrics, err := factory.CreateLogsToMetrics(context.Background(), set, cfg, consumertest.NewNop())
require.NoError(t, err)
bLogsToMetrics, err := builder.CreateLogsToMetrics(context.Background(), set, consumertest.NewNop())
require.NoError(t, err)
assert.IsType(t, logsToMetrics, bLogsToMetrics)
logsToLogs, err := factory.CreateLogsToLogs(context.Background(), set, cfg, consumertest.NewNop())
require.NoError(t, err)
bLogsToLogs, err := builder.CreateLogsToLogs(context.Background(), set, consumertest.NewNop())
require.NoError(t, err)
assert.IsType(t, logsToLogs, bLogsToLogs)
logsToProfiles, err := factory.(xconnector.Factory).CreateLogsToProfiles(context.Background(), set, cfg, consumertest.NewNop())
require.NoError(t, err)
bLogsToProfiles, err := builder.CreateLogsToProfiles(context.Background(), set, consumertest.NewNop())
require.NoError(t, err)
assert.IsType(t, logsToProfiles, bLogsToProfiles)
profilesToTraces, err := factory.(xconnector.Factory).CreateProfilesToTraces(context.Background(), set, cfg, consumertest.NewNop())
require.NoError(t, err)
bProfilesToTraces, err := builder.CreateProfilesToTraces(context.Background(), set, consumertest.NewNop())
require.NoError(t, err)
assert.IsType(t, profilesToTraces, bProfilesToTraces)
profilesToMetrics, err := factory.(xconnector.Factory).CreateProfilesToMetrics(context.Background(), set, cfg, consumertest.NewNop())
require.NoError(t, err)
bProfilesToMetrics, err := builder.CreateProfilesToMetrics(context.Background(), set, consumertest.NewNop())
require.NoError(t, err)
assert.IsType(t, profilesToMetrics, bProfilesToMetrics)
profilesToLogs, err := factory.(xconnector.Factory).CreateProfilesToLogs(context.Background(), set, cfg, consumertest.NewNop())
require.NoError(t, err)
bProfilesToLogs, err := builder.CreateProfilesToLogs(context.Background(), set, consumertest.NewNop())
require.NoError(t, err)
assert.IsType(t, profilesToLogs, bProfilesToLogs)
profilesToProfiles, err := factory.(xconnector.Factory).CreateProfilesToProfiles(context.Background(), set, cfg, consumertest.NewNop())
require.NoError(t, err)
bProfilesToProfiles, err := builder.CreateProfilesToProfiles(context.Background(), set, consumertest.NewNop())
require.NoError(t, err)
assert.IsType(t, profilesToProfiles, bProfilesToProfiles)
}
var nopConnectorInstance = &nopConnector{
Consumer: consumertest.NewNop(),
}
// nopConnector stores consumed traces and metrics for testing purposes.
type nopConnector struct {
component.StartFunc
component.ShutdownFunc
consumertest.Consumer
}
func createConnectorTracesToTraces(context.Context, connector.Settings, component.Config, consumer.Traces) (connector.Traces, error) {
return nopConnectorInstance, nil
}
func createConnectorTracesToMetrics(context.Context, connector.Settings, component.Config, consumer.Metrics) (connector.Traces, error) {
return nopConnectorInstance, nil
}
func createConnectorTracesToLogs(context.Context, connector.Settings, component.Config, consumer.Logs) (connector.Traces, error) {
return nopConnectorInstance, nil
}
func createConnectorTracesToProfiles(context.Context, connector.Settings, component.Config, xconsumer.Profiles) (connector.Traces, error) {
return nopConnectorInstance, nil
}
func createConnectorMetricsToTraces(context.Context, connector.Settings, component.Config, consumer.Traces) (connector.Metrics, error) {
return nopConnectorInstance, nil
}
func createConnectorMetricsToMetrics(context.Context, connector.Settings, component.Config, consumer.Metrics) (connector.Metrics, error) {
return nopConnectorInstance, nil
}
func createConnectorMetricsToLogs(context.Context, connector.Settings, component.Config, consumer.Logs) (connector.Metrics, error) {
return nopConnectorInstance, nil
}
func createConnectorMetricsToProfiles(context.Context, connector.Settings, component.Config, xconsumer.Profiles) (connector.Metrics, error) {
return nopConnectorInstance, nil
}
func createConnectorLogsToTraces(context.Context, connector.Settings, component.Config, consumer.Traces) (connector.Logs, error) {
return nopConnectorInstance, nil
}
func createConnectorLogsToMetrics(context.Context, connector.Settings, component.Config, consumer.Metrics) (connector.Logs, error) {
return nopConnectorInstance, nil
}
func createConnectorLogsToLogs(context.Context, connector.Settings, component.Config, consumer.Logs) (connector.Logs, error) {
return nopConnectorInstance, nil
}
func createConnectorLogsToProfiles(context.Context, connector.Settings, component.Config, xconsumer.Profiles) (connector.Logs, error) {
return nopConnectorInstance, nil
}
func createxconnectorToTraces(context.Context, connector.Settings, component.Config, consumer.Traces) (xconnector.Profiles, error) {
return nopConnectorInstance, nil
}
func createxconnectorToMetrics(context.Context, connector.Settings, component.Config, consumer.Metrics) (xconnector.Profiles, error) {
return nopConnectorInstance, nil
}
func createxconnectorToLogs(context.Context, connector.Settings, component.Config, consumer.Logs) (xconnector.Profiles, error) {
return nopConnectorInstance, nil
}
func createxconnectorToProfiles(context.Context, connector.Settings, component.Config, xconsumer.Profiles) (xconnector.Profiles, error) {
return nopConnectorInstance, nil
}
func createConnectorSettings(id component.ID) connector.Settings {
return connector.Settings{
ID: id,
TelemetrySettings: componenttest.NewNopTelemetrySettings(),
BuildInfo: component.NewDefaultBuildInfo(),
}
}
================================================
FILE: service/internal/builders/builders_test/exporter_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package builders
import (
"context"
"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/consumer/consumertest"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/exporter/xexporter"
"go.opentelemetry.io/collector/otelcol"
"go.opentelemetry.io/collector/service/internal/builders"
)
func TestExporterBuilder(t *testing.T) {
defaultCfg := struct{}{}
factories, err := otelcol.MakeFactoryMap([]exporter.Factory{
exporter.NewFactory(component.MustNewType("err"), nil),
xexporter.NewFactory(
component.MustNewType("all"),
func() component.Config { return &defaultCfg },
xexporter.WithTraces(createExporterTraces, component.StabilityLevelDevelopment),
xexporter.WithMetrics(createExporterMetrics, component.StabilityLevelAlpha),
xexporter.WithLogs(createExporterLogs, component.StabilityLevelDeprecated),
xexporter.WithProfiles(createxexporter, component.StabilityLevelDevelopment),
),
}...)
require.NoError(t, err)
testCases := []struct {
name string
id component.ID
err string
}{
{
name: "unknown",
id: component.MustNewID("unknown"),
err: "exporter factory not available for: \"unknown\"",
},
{
name: "err",
id: component.MustNewID("err"),
err: "telemetry type is not supported",
},
{
name: "all",
id: component.MustNewID("all"),
},
{
name: "all/named",
id: component.MustNewIDWithName("all", "named"),
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
cfgs := map[component.ID]component.Config{tt.id: defaultCfg}
b := builders.NewExporter(cfgs, factories)
te, err := b.CreateTraces(context.Background(), createExporterSettings(tt.id))
if tt.err != "" {
require.EqualError(t, err, tt.err)
assert.Nil(t, te)
} else {
require.NoError(t, err)
assert.Equal(t, nopExporterInstance, te)
}
me, err := b.CreateMetrics(context.Background(), createExporterSettings(tt.id))
if tt.err != "" {
require.EqualError(t, err, tt.err)
assert.Nil(t, me)
} else {
require.NoError(t, err)
assert.Equal(t, nopExporterInstance, me)
}
le, err := b.CreateLogs(context.Background(), createExporterSettings(tt.id))
if tt.err != "" {
require.EqualError(t, err, tt.err)
assert.Nil(t, le)
} else {
require.NoError(t, err)
assert.Equal(t, nopExporterInstance, le)
}
pe, err := b.CreateProfiles(context.Background(), createExporterSettings(tt.id))
if tt.err != "" {
require.EqualError(t, err, tt.err)
assert.Nil(t, pe)
} else {
require.NoError(t, err)
assert.Equal(t, nopExporterInstance, pe)
}
})
}
}
func TestExporterBuilderMissingConfig(t *testing.T) {
defaultCfg := struct{}{}
factories, err := otelcol.MakeFactoryMap([]exporter.Factory{
xexporter.NewFactory(
component.MustNewType("all"),
func() component.Config { return &defaultCfg },
xexporter.WithTraces(createExporterTraces, component.StabilityLevelDevelopment),
xexporter.WithMetrics(createExporterMetrics, component.StabilityLevelAlpha),
xexporter.WithLogs(createExporterLogs, component.StabilityLevelDeprecated),
xexporter.WithProfiles(createxexporter, component.StabilityLevelDevelopment),
),
}...)
require.NoError(t, err)
bErr := builders.NewExporter(map[component.ID]component.Config{}, factories)
missingID := component.MustNewIDWithName("all", "missing")
te, err := bErr.CreateTraces(context.Background(), createExporterSettings(missingID))
require.EqualError(t, err, "exporter \"all/missing\" is not configured")
assert.Nil(t, te)
me, err := bErr.CreateMetrics(context.Background(), createExporterSettings(missingID))
require.EqualError(t, err, "exporter \"all/missing\" is not configured")
assert.Nil(t, me)
le, err := bErr.CreateLogs(context.Background(), createExporterSettings(missingID))
require.EqualError(t, err, "exporter \"all/missing\" is not configured")
assert.Nil(t, le)
pe, err := bErr.CreateProfiles(context.Background(), createExporterSettings(missingID))
require.EqualError(t, err, "exporter \"all/missing\" is not configured")
assert.Nil(t, pe)
}
func TestExporterBuilderFactory(t *testing.T) {
factories, err := otelcol.MakeFactoryMap([]exporter.Factory{exporter.NewFactory(component.MustNewType("foo"), nil)}...)
require.NoError(t, err)
cfgs := map[component.ID]component.Config{component.MustNewID("foo"): struct{}{}}
b := builders.NewExporter(cfgs, factories)
assert.NotNil(t, b.Factory(component.MustNewID("foo").Type()))
assert.Nil(t, b.Factory(component.MustNewID("bar").Type()))
}
func TestNewNopExporterConfigsAndFactories(t *testing.T) {
configs, factories := builders.NewNopExporterConfigsAndFactories()
builder := builders.NewExporter(configs, factories)
require.NotNil(t, builder)
factory := exportertest.NewNopFactory()
cfg := factory.CreateDefaultConfig()
set := exportertest.NewNopSettings(factory.Type())
set.ID = component.NewID(builders.NopType)
traces, err := factory.CreateTraces(context.Background(), set, cfg)
require.NoError(t, err)
bTraces, err := builder.CreateTraces(context.Background(), set)
require.NoError(t, err)
assert.IsType(t, traces, bTraces)
metrics, err := factory.CreateMetrics(context.Background(), set, cfg)
require.NoError(t, err)
bMetrics, err := builder.CreateMetrics(context.Background(), set)
require.NoError(t, err)
assert.IsType(t, metrics, bMetrics)
logs, err := factory.CreateLogs(context.Background(), set, cfg)
require.NoError(t, err)
bLogs, err := builder.CreateLogs(context.Background(), set)
require.NoError(t, err)
assert.IsType(t, logs, bLogs)
profiles, err := factory.(xexporter.Factory).CreateProfiles(context.Background(), set, cfg)
require.NoError(t, err)
bProfiles, err := builder.CreateProfiles(context.Background(), set)
require.NoError(t, err)
assert.IsType(t, profiles, bProfiles)
}
var nopExporterInstance = &nopExporter{
Consumer: consumertest.NewNop(),
}
// nopExporter stores consumed traces, metrics, logs and profiles for testing purposes.
type nopExporter struct {
component.StartFunc
component.ShutdownFunc
consumertest.Consumer
}
func createExporterTraces(context.Context, exporter.Settings, component.Config) (exporter.Traces, error) {
return nopExporterInstance, nil
}
func createExporterMetrics(context.Context, exporter.Settings, component.Config) (exporter.Metrics, error) {
return nopExporterInstance, nil
}
func createExporterLogs(context.Context, exporter.Settings, component.Config) (exporter.Logs, error) {
return nopExporterInstance, nil
}
func createxexporter(context.Context, exporter.Settings, component.Config) (xexporter.Profiles, error) {
return nopExporterInstance, nil
}
func createExporterSettings(id component.ID) exporter.Settings {
return exporter.Settings{
ID: id,
TelemetrySettings: componenttest.NewNopTelemetrySettings(),
BuildInfo: component.NewDefaultBuildInfo(),
}
}
================================================
FILE: service/internal/builders/builders_test/extension_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package builders
import (
"context"
"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/extension"
"go.opentelemetry.io/collector/extension/extensiontest"
"go.opentelemetry.io/collector/otelcol"
"go.opentelemetry.io/collector/service/internal/builders"
)
func TestExtensionBuilder(t *testing.T) {
testType := component.MustNewType("test")
defaultCfg := struct{}{}
testID := component.NewID(testType)
unknownID := component.MustNewID("unknown")
factories, err := otelcol.MakeFactoryMap([]extension.Factory{
extension.NewFactory(
testType,
func() component.Config { return &defaultCfg },
func(_ context.Context, settings extension.Settings, _ component.Config) (extension.Extension, error) {
return nopExtension{Settings: settings}, nil
},
component.StabilityLevelDevelopment),
}...)
require.NoError(t, err)
cfgs := map[component.ID]component.Config{testID: defaultCfg, unknownID: defaultCfg}
b := builders.NewExtension(cfgs, factories)
e, err := b.Create(context.Background(), createExtensionSettings(testID))
require.NoError(t, err)
assert.NotNil(t, e)
// Check that the extension has access to the resource attributes.
nop, ok := e.(nopExtension)
assert.True(t, ok)
assert.Equal(t, 0, nop.Settings.Resource.Attributes().Len())
missingType, err := b.Create(context.Background(), createExtensionSettings(unknownID))
require.EqualError(t, err, "extension factory not available for: \"unknown\"")
assert.Nil(t, missingType)
missingCfg, err := b.Create(context.Background(), createExtensionSettings(component.NewIDWithName(testType, "foo")))
require.EqualError(t, err, "extension \"test/foo\" is not configured")
assert.Nil(t, missingCfg)
}
func TestExtensionBuilderFactory(t *testing.T) {
factories, err := otelcol.MakeFactoryMap([]extension.Factory{extension.NewFactory(component.MustNewType("foo"), nil, nil, component.StabilityLevelDevelopment)}...)
require.NoError(t, err)
cfgs := map[component.ID]component.Config{component.MustNewID("foo"): struct{}{}}
b := builders.NewExtension(cfgs, factories)
assert.NotNil(t, b.Factory(component.MustNewID("foo").Type()))
assert.Nil(t, b.Factory(component.MustNewID("bar").Type()))
}
func TestNewNopExtensionConfigsAndFactories(t *testing.T) {
configs, factories := builders.NewNopExtensionConfigsAndFactories()
builder := builders.NewExtension(configs, factories)
require.NotNil(t, builder)
factory := extensiontest.NewNopFactory()
cfg := factory.CreateDefaultConfig()
set := extensiontest.NewNopSettings(factory.Type())
set.ID = component.NewID(builders.NopType)
ext, err := factory.Create(context.Background(), set, cfg)
require.NoError(t, err)
bExt, err := builder.Create(context.Background(), set)
require.NoError(t, err)
assert.IsType(t, ext, bExt)
}
type nopExtension struct {
component.StartFunc
component.ShutdownFunc
extension.Settings
}
func createExtensionSettings(id component.ID) extension.Settings {
return extension.Settings{
ID: id,
TelemetrySettings: componenttest.NewNopTelemetrySettings(),
BuildInfo: component.NewDefaultBuildInfo(),
}
}
================================================
FILE: service/internal/builders/builders_test/processor_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package builders
import (
"context"
"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/consumer"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/otelcol"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/processortest"
"go.opentelemetry.io/collector/processor/xprocessor"
"go.opentelemetry.io/collector/service/internal/builders"
)
func TestProcessorBuilder(t *testing.T) {
defaultCfg := struct{}{}
factories, err := otelcol.MakeFactoryMap([]processor.Factory{
processor.NewFactory(component.MustNewType("err"), nil),
xprocessor.NewFactory(
component.MustNewType("all"),
func() component.Config { return &defaultCfg },
xprocessor.WithTraces(createProcessorTraces, component.StabilityLevelDevelopment),
xprocessor.WithMetrics(createProcessorMetrics, component.StabilityLevelAlpha),
xprocessor.WithLogs(createProcessorLogs, component.StabilityLevelDeprecated),
xprocessor.WithProfiles(createxprocessor, component.StabilityLevelDevelopment),
),
}...)
require.NoError(t, err)
testCases := []struct {
name string
id component.ID
err string
nextTraces consumer.Traces
nextLogs consumer.Logs
nextMetrics consumer.Metrics
nextProfiles xconsumer.Profiles
}{
{
name: "unknown",
id: component.MustNewID("unknown"),
err: "processor factory not available for: \"unknown\"",
nextTraces: consumertest.NewNop(),
nextLogs: consumertest.NewNop(),
nextMetrics: consumertest.NewNop(),
nextProfiles: consumertest.NewNop(),
},
{
name: "err",
id: component.MustNewID("err"),
err: "telemetry type is not supported",
nextTraces: consumertest.NewNop(),
nextLogs: consumertest.NewNop(),
nextMetrics: consumertest.NewNop(),
nextProfiles: consumertest.NewNop(),
},
{
name: "all",
id: component.MustNewID("all"),
nextTraces: consumertest.NewNop(),
nextLogs: consumertest.NewNop(),
nextMetrics: consumertest.NewNop(),
nextProfiles: consumertest.NewNop(),
},
{
name: "all/named",
id: component.MustNewIDWithName("all", "named"),
nextTraces: consumertest.NewNop(),
nextLogs: consumertest.NewNop(),
nextMetrics: consumertest.NewNop(),
nextProfiles: consumertest.NewNop(),
},
{
name: "no next consumer",
id: component.MustNewID("unknown"),
err: "nil next Consumer",
nextTraces: nil,
nextLogs: nil,
nextMetrics: nil,
nextProfiles: nil,
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
cfgs := map[component.ID]component.Config{tt.id: defaultCfg}
b := builders.NewProcessor(cfgs, factories)
te, err := b.CreateTraces(context.Background(), createProcessorSettings(tt.id), tt.nextTraces)
if tt.err != "" {
require.EqualError(t, err, tt.err)
assert.Nil(t, te)
} else {
require.NoError(t, err)
assert.Equal(t, nopProcessorInstance, te)
}
me, err := b.CreateMetrics(context.Background(), createProcessorSettings(tt.id), tt.nextMetrics)
if tt.err != "" {
require.EqualError(t, err, tt.err)
assert.Nil(t, me)
} else {
require.NoError(t, err)
assert.Equal(t, nopProcessorInstance, me)
}
le, err := b.CreateLogs(context.Background(), createProcessorSettings(tt.id), tt.nextLogs)
if tt.err != "" {
require.EqualError(t, err, tt.err)
assert.Nil(t, le)
} else {
require.NoError(t, err)
assert.Equal(t, nopProcessorInstance, le)
}
pe, err := b.CreateProfiles(context.Background(), createProcessorSettings(tt.id), tt.nextProfiles)
if tt.err != "" {
require.EqualError(t, err, tt.err)
assert.Nil(t, pe)
} else {
require.NoError(t, err)
assert.Equal(t, nopProcessorInstance, pe)
}
})
}
}
func TestProcessorBuilderMissingConfig(t *testing.T) {
defaultCfg := struct{}{}
factories, err := otelcol.MakeFactoryMap([]processor.Factory{
xprocessor.NewFactory(
component.MustNewType("all"),
func() component.Config { return &defaultCfg },
xprocessor.WithTraces(createProcessorTraces, component.StabilityLevelDevelopment),
xprocessor.WithMetrics(createProcessorMetrics, component.StabilityLevelAlpha),
xprocessor.WithLogs(createProcessorLogs, component.StabilityLevelDeprecated),
xprocessor.WithProfiles(createxprocessor, component.StabilityLevelDevelopment),
),
}...)
require.NoError(t, err)
bErr := builders.NewProcessor(map[component.ID]component.Config{}, factories)
missingID := component.MustNewIDWithName("all", "missing")
te, err := bErr.CreateTraces(context.Background(), createProcessorSettings(missingID), consumertest.NewNop())
require.EqualError(t, err, "processor \"all/missing\" is not configured")
assert.Nil(t, te)
me, err := bErr.CreateMetrics(context.Background(), createProcessorSettings(missingID), consumertest.NewNop())
require.EqualError(t, err, "processor \"all/missing\" is not configured")
assert.Nil(t, me)
le, err := bErr.CreateLogs(context.Background(), createProcessorSettings(missingID), consumertest.NewNop())
require.EqualError(t, err, "processor \"all/missing\" is not configured")
assert.Nil(t, le)
pe, err := bErr.CreateProfiles(context.Background(), createProcessorSettings(missingID), consumertest.NewNop())
require.EqualError(t, err, "processor \"all/missing\" is not configured")
assert.Nil(t, pe)
}
func TestProcessorBuilderFactory(t *testing.T) {
factories, err := otelcol.MakeFactoryMap([]processor.Factory{processor.NewFactory(component.MustNewType("foo"), nil)}...)
require.NoError(t, err)
cfgs := map[component.ID]component.Config{component.MustNewID("foo"): struct{}{}}
b := builders.NewProcessor(cfgs, factories)
assert.NotNil(t, b.Factory(component.MustNewID("foo").Type()))
assert.Nil(t, b.Factory(component.MustNewID("bar").Type()))
}
func TestNewNopProcessorBuilder(t *testing.T) {
configs, factories := builders.NewNopProcessorConfigsAndFactories()
builder := builders.NewProcessor(configs, factories)
require.NotNil(t, builder)
factory := processortest.NewNopFactory()
cfg := factory.CreateDefaultConfig()
set := processortest.NewNopSettings(factory.Type())
set.ID = component.NewID(builders.NopType)
traces, err := factory.CreateTraces(context.Background(), set, cfg, consumertest.NewNop())
require.NoError(t, err)
bTraces, err := builder.CreateTraces(context.Background(), set, consumertest.NewNop())
require.NoError(t, err)
assert.IsType(t, traces, bTraces)
metrics, err := factory.CreateMetrics(context.Background(), set, cfg, consumertest.NewNop())
require.NoError(t, err)
bMetrics, err := builder.CreateMetrics(context.Background(), set, consumertest.NewNop())
require.NoError(t, err)
assert.IsType(t, metrics, bMetrics)
logs, err := factory.CreateLogs(context.Background(), set, cfg, consumertest.NewNop())
require.NoError(t, err)
bLogs, err := builder.CreateLogs(context.Background(), set, consumertest.NewNop())
require.NoError(t, err)
assert.IsType(t, logs, bLogs)
profiles, err := factory.(xprocessor.Factory).CreateProfiles(context.Background(), set, cfg, consumertest.NewNop())
require.NoError(t, err)
bProfiles, err := builder.CreateProfiles(context.Background(), set, consumertest.NewNop())
require.NoError(t, err)
assert.IsType(t, profiles, bProfiles)
}
var nopProcessorInstance = &nopProcessor{
Consumer: consumertest.NewNop(),
}
// nopProcessor stores consumed traces, metrics, logs and profiles for testing purposes.
type nopProcessor struct {
component.StartFunc
component.ShutdownFunc
consumertest.Consumer
}
func createProcessorTraces(context.Context, processor.Settings, component.Config, consumer.Traces) (processor.Traces, error) {
return nopProcessorInstance, nil
}
func createProcessorMetrics(context.Context, processor.Settings, component.Config, consumer.Metrics) (processor.Metrics, error) {
return nopProcessorInstance, nil
}
func createProcessorLogs(context.Context, processor.Settings, component.Config, consumer.Logs) (processor.Logs, error) {
return nopProcessorInstance, nil
}
func createxprocessor(context.Context, processor.Settings, component.Config, xconsumer.Profiles) (xprocessor.Profiles, error) {
return nopProcessorInstance, nil
}
func createProcessorSettings(id component.ID) processor.Settings {
return processor.Settings{
ID: id,
TelemetrySettings: componenttest.NewNopTelemetrySettings(),
BuildInfo: component.NewDefaultBuildInfo(),
}
}
================================================
FILE: service/internal/builders/builders_test/receiver_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package builders
import (
"context"
"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/consumer"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/otelcol"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/receivertest"
"go.opentelemetry.io/collector/receiver/xreceiver"
"go.opentelemetry.io/collector/service/internal/builders"
)
func TestReceiverBuilder(t *testing.T) {
defaultCfg := struct{}{}
factories, err := otelcol.MakeFactoryMap([]receiver.Factory{
receiver.NewFactory(component.MustNewType("err"), nil),
xreceiver.NewFactory(
component.MustNewType("all"),
func() component.Config { return &defaultCfg },
xreceiver.WithTraces(createReceiverTraces, component.StabilityLevelDevelopment),
xreceiver.WithMetrics(createReceiverMetrics, component.StabilityLevelAlpha),
xreceiver.WithLogs(createReceiverLogs, component.StabilityLevelDeprecated),
xreceiver.WithProfiles(createReceiverProfiles, component.StabilityLevelAlpha),
),
}...)
require.NoError(t, err)
testCases := []struct {
name string
id component.ID
err string
nextTraces consumer.Traces
nextLogs consumer.Logs
nextMetrics consumer.Metrics
nextProfiles xconsumer.Profiles
}{
{
name: "unknown",
id: component.MustNewID("unknown"),
err: "receiver factory not available for: \"unknown\"",
nextTraces: consumertest.NewNop(),
nextLogs: consumertest.NewNop(),
nextMetrics: consumertest.NewNop(),
nextProfiles: consumertest.NewNop(),
},
{
name: "err",
id: component.MustNewID("err"),
err: "telemetry type is not supported",
nextTraces: consumertest.NewNop(),
nextLogs: consumertest.NewNop(),
nextMetrics: consumertest.NewNop(),
nextProfiles: consumertest.NewNop(),
},
{
name: "all",
id: component.MustNewID("all"),
nextTraces: consumertest.NewNop(),
nextLogs: consumertest.NewNop(),
nextMetrics: consumertest.NewNop(),
nextProfiles: consumertest.NewNop(),
},
{
name: "all/named",
id: component.MustNewIDWithName("all", "named"),
nextTraces: consumertest.NewNop(),
nextLogs: consumertest.NewNop(),
nextMetrics: consumertest.NewNop(),
nextProfiles: consumertest.NewNop(),
},
{
name: "no next consumer",
id: component.MustNewID("unknown"),
err: "nil next Consumer",
nextTraces: nil,
nextLogs: nil,
nextMetrics: nil,
nextProfiles: nil,
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
cfgs := map[component.ID]component.Config{tt.id: defaultCfg}
b := builders.NewReceiver(cfgs, factories)
te, err := b.CreateTraces(context.Background(), settings(tt.id), tt.nextTraces)
if tt.err != "" {
require.EqualError(t, err, tt.err)
assert.Nil(t, te)
} else {
require.NoError(t, err)
assert.Equal(t, nopReceiverInstance, te)
}
me, err := b.CreateMetrics(context.Background(), settings(tt.id), tt.nextMetrics)
if tt.err != "" {
require.EqualError(t, err, tt.err)
assert.Nil(t, me)
} else {
require.NoError(t, err)
assert.Equal(t, nopReceiverInstance, me)
}
le, err := b.CreateLogs(context.Background(), settings(tt.id), tt.nextLogs)
if tt.err != "" {
require.EqualError(t, err, tt.err)
assert.Nil(t, le)
} else {
require.NoError(t, err)
assert.Equal(t, nopReceiverInstance, le)
}
pe, err := b.CreateProfiles(context.Background(), settings(tt.id), tt.nextProfiles)
if tt.err != "" {
require.EqualError(t, err, tt.err)
assert.Nil(t, pe)
} else {
require.NoError(t, err)
assert.Equal(t, nopReceiverInstance, pe)
}
})
}
}
func TestReceiverBuilderMissingConfig(t *testing.T) {
defaultCfg := struct{}{}
factories, err := otelcol.MakeFactoryMap([]receiver.Factory{
xreceiver.NewFactory(
component.MustNewType("all"),
func() component.Config { return &defaultCfg },
xreceiver.WithTraces(createReceiverTraces, component.StabilityLevelDevelopment),
xreceiver.WithMetrics(createReceiverMetrics, component.StabilityLevelAlpha),
xreceiver.WithLogs(createReceiverLogs, component.StabilityLevelDeprecated),
xreceiver.WithProfiles(createReceiverProfiles, component.StabilityLevelAlpha),
),
}...)
require.NoError(t, err)
bErr := builders.NewReceiver(map[component.ID]component.Config{}, factories)
missingID := component.MustNewIDWithName("all", "missing")
te, err := bErr.CreateTraces(context.Background(), settings(missingID), consumertest.NewNop())
require.EqualError(t, err, "receiver \"all/missing\" is not configured")
assert.Nil(t, te)
me, err := bErr.CreateMetrics(context.Background(), settings(missingID), consumertest.NewNop())
require.EqualError(t, err, "receiver \"all/missing\" is not configured")
assert.Nil(t, me)
le, err := bErr.CreateLogs(context.Background(), settings(missingID), consumertest.NewNop())
require.EqualError(t, err, "receiver \"all/missing\" is not configured")
assert.Nil(t, le)
pe, err := bErr.CreateProfiles(context.Background(), settings(missingID), consumertest.NewNop())
require.EqualError(t, err, "receiver \"all/missing\" is not configured")
assert.Nil(t, pe)
}
func TestReceiverBuilderFactory(t *testing.T) {
factories, err := otelcol.MakeFactoryMap([]receiver.Factory{receiver.NewFactory(component.MustNewType("foo"), nil)}...)
require.NoError(t, err)
cfgs := map[component.ID]component.Config{component.MustNewID("foo"): struct{}{}}
b := builders.NewReceiver(cfgs, factories)
assert.NotNil(t, b.Factory(component.MustNewID("foo").Type()))
assert.Nil(t, b.Factory(component.MustNewID("bar").Type()))
}
func TestNewNopReceiverConfigsAndFactories(t *testing.T) {
configs, factories := builders.NewNopReceiverConfigsAndFactories()
builder := builders.NewReceiver(configs, factories)
require.NotNil(t, builder)
factory := receivertest.NewNopFactory()
cfg := factory.CreateDefaultConfig()
set := receivertest.NewNopSettings(factory.Type())
set.ID = component.NewID(builders.NopType)
traces, err := factory.CreateTraces(context.Background(), set, cfg, consumertest.NewNop())
require.NoError(t, err)
bTraces, err := builder.CreateTraces(context.Background(), set, consumertest.NewNop())
require.NoError(t, err)
assert.IsType(t, traces, bTraces)
metrics, err := factory.CreateMetrics(context.Background(), set, cfg, consumertest.NewNop())
require.NoError(t, err)
bMetrics, err := builder.CreateMetrics(context.Background(), set, consumertest.NewNop())
require.NoError(t, err)
assert.IsType(t, metrics, bMetrics)
logs, err := factory.CreateLogs(context.Background(), set, cfg, consumertest.NewNop())
require.NoError(t, err)
bLogs, err := builder.CreateLogs(context.Background(), set, consumertest.NewNop())
require.NoError(t, err)
assert.IsType(t, logs, bLogs)
profiles, err := factory.(xreceiver.Factory).CreateProfiles(context.Background(), set, cfg, consumertest.NewNop())
require.NoError(t, err)
bProfiles, err := builder.CreateProfiles(context.Background(), set, consumertest.NewNop())
require.NoError(t, err)
assert.IsType(t, profiles, bProfiles)
}
func settings(id component.ID) receiver.Settings {
return receiver.Settings{
ID: id,
TelemetrySettings: componenttest.NewNopTelemetrySettings(),
BuildInfo: component.NewDefaultBuildInfo(),
}
}
var nopReceiverInstance = &nopReceiver{
Consumer: consumertest.NewNop(),
}
// nopReceiver stores consumed traces and metrics for testing purposes.
type nopReceiver struct {
component.StartFunc
component.ShutdownFunc
consumertest.Consumer
}
func createReceiverTraces(context.Context, receiver.Settings, component.Config, consumer.Traces) (receiver.Traces, error) {
return nopReceiverInstance, nil
}
func createReceiverMetrics(context.Context, receiver.Settings, component.Config, consumer.Metrics) (receiver.Metrics, error) {
return nopReceiverInstance, nil
}
func createReceiverLogs(context.Context, receiver.Settings, component.Config, consumer.Logs) (receiver.Logs, error) {
return nopReceiverInstance, nil
}
func createReceiverProfiles(context.Context, receiver.Settings, component.Config, xconsumer.Profiles) (xreceiver.Profiles, error) {
return nopReceiverInstance, nil
}
================================================
FILE: service/internal/builders/builders_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package builders
import (
"testing"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/internal/componentalias"
)
// mockFactory is a test factory that implements component.Factory
type mockFactory struct {
factoryType component.Type
}
func (m *mockFactory) Type() component.Type {
return m.factoryType
}
func (m *mockFactory) CreateDefaultConfig() component.Config {
return nil
}
// mockFactoryWithAlias is a test factory that implements both component.Factory and componentalias.TypeAliasHolder
type mockFactoryWithAlias struct {
factoryType component.Type
aliasHolder componentalias.TypeAliasHolder
}
func (m *mockFactoryWithAlias) Type() component.Type {
return m.factoryType
}
func (m *mockFactoryWithAlias) CreateDefaultConfig() component.Config {
return nil
}
func (m *mockFactoryWithAlias) DeprecatedAlias() component.Type {
return m.aliasHolder.DeprecatedAlias()
}
func (m *mockFactoryWithAlias) SetDeprecatedAlias(alias component.Type) {
m.aliasHolder.SetDeprecatedAlias(alias)
}
func TestLogDeprecatedTypeAlias(t *testing.T) {
tests := []struct {
name string
factory component.Factory
usedType component.Type
expectWarning bool
}{
{
name: "no_alias_holder",
factory: &mockFactory{factoryType: component.MustNewType("test")},
usedType: component.MustNewType("test"),
expectWarning: false,
},
{
name: "no_alias_set",
factory: &mockFactoryWithAlias{
factoryType: component.MustNewType("test"),
aliasHolder: componentalias.NewTypeAliasHolder(),
},
usedType: component.MustNewType("test"),
expectWarning: false,
},
{
name: "using_current_type",
factory: func() component.Factory {
f := &mockFactoryWithAlias{
factoryType: component.MustNewType("new"),
aliasHolder: componentalias.NewTypeAliasHolder(),
}
f.aliasHolder.SetDeprecatedAlias(component.MustNewType("old"))
return f
}(),
usedType: component.MustNewType("new"),
expectWarning: false,
},
{
name: "using_deprecated_alias",
factory: func() component.Factory {
f := &mockFactoryWithAlias{
factoryType: component.MustNewType("new"),
aliasHolder: componentalias.NewTypeAliasHolder(),
}
f.aliasHolder.SetDeprecatedAlias(component.MustNewType("old"))
return f
}(),
usedType: component.MustNewType("old"),
expectWarning: true,
},
{
name: "using_unrelated_type",
factory: func() component.Factory {
f := &mockFactoryWithAlias{
factoryType: component.MustNewType("new"),
aliasHolder: componentalias.NewTypeAliasHolder(),
}
f.aliasHolder.SetDeprecatedAlias(component.MustNewType("old"))
return f
}(),
usedType: component.MustNewType("other"),
expectWarning: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
core, logs := observer.New(zap.WarnLevel)
logger := zap.New(core)
logDeprecatedTypeAlias(logger, tt.factory, tt.usedType)
if tt.expectWarning && logs.Len() != 1 {
t.Errorf("expected 1 warning log but got %d", logs.Len())
} else if !tt.expectWarning && logs.Len() > 0 {
t.Errorf("expected no warning log but got %d", logs.Len())
}
})
}
}
================================================
FILE: service/internal/builders/connector.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package builders // import "go.opentelemetry.io/collector/service/internal/builders"
import (
"context"
"fmt"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/connector"
"go.opentelemetry.io/collector/connector/connectortest"
"go.opentelemetry.io/collector/connector/xconnector"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/pipeline/xpipeline"
)
func errDataTypes(id component.ID, from, to pipeline.Signal) error {
return fmt.Errorf("connector %q cannot connect from %s to %s: %w", id, from, to, pipeline.ErrSignalNotSupported)
}
// ConnectorBuilder is a helper struct that given a set of Configs and Factories helps with creating connectors.
type ConnectorBuilder struct {
cfgs map[component.ID]component.Config
factories map[component.Type]connector.Factory
}
// NewConnector creates a new ConnectorBuilder to help with creating components form a set of configs and factories.
func NewConnector(cfgs map[component.ID]component.Config, factories map[component.Type]connector.Factory) *ConnectorBuilder {
return &ConnectorBuilder{cfgs: cfgs, factories: factories}
}
// CreateTracesToTraces creates a Traces connector based on the settings and config.
func (b *ConnectorBuilder) CreateTracesToTraces(ctx context.Context, set connector.Settings, next consumer.Traces) (connector.Traces, error) {
if next == nil {
return nil, errNilNextConsumer
}
cfg, existsCfg := b.cfgs[set.ID]
if !existsCfg {
return nil, fmt.Errorf("connector %q is not configured", set.ID)
}
f, existsFactory := b.factories[set.ID.Type()]
if !existsFactory {
return nil, fmt.Errorf("connector factory not available for: %q", set.ID)
}
logDeprecatedTypeAlias(set.Logger, f, set.ID.Type())
logStabilityLevel(set.Logger, f.TracesToTracesStability())
return f.CreateTracesToTraces(ctx, set, cfg, next)
}
// CreateTracesToMetrics creates a Traces connector based on the settings and config.
func (b *ConnectorBuilder) CreateTracesToMetrics(ctx context.Context, set connector.Settings, next consumer.Metrics) (connector.Traces, error) {
if next == nil {
return nil, errNilNextConsumer
}
cfg, existsCfg := b.cfgs[set.ID]
if !existsCfg {
return nil, fmt.Errorf("connector %q is not configured", set.ID)
}
f, existsFactory := b.factories[set.ID.Type()]
if !existsFactory {
return nil, fmt.Errorf("connector factory not available for: %q", set.ID)
}
logDeprecatedTypeAlias(set.Logger, f, set.ID.Type())
logStabilityLevel(set.Logger, f.TracesToMetricsStability())
return f.CreateTracesToMetrics(ctx, set, cfg, next)
}
// CreateTracesToLogs creates a Traces connector based on the settings and config.
func (b *ConnectorBuilder) CreateTracesToLogs(ctx context.Context, set connector.Settings, next consumer.Logs) (connector.Traces, error) {
if next == nil {
return nil, errNilNextConsumer
}
cfg, existsCfg := b.cfgs[set.ID]
if !existsCfg {
return nil, fmt.Errorf("connector %q is not configured", set.ID)
}
f, existsFactory := b.factories[set.ID.Type()]
if !existsFactory {
return nil, fmt.Errorf("connector factory not available for: %q", set.ID)
}
logDeprecatedTypeAlias(set.Logger, f, set.ID.Type())
logStabilityLevel(set.Logger, f.TracesToLogsStability())
return f.CreateTracesToLogs(ctx, set, cfg, next)
}
// CreateTracesToProfiles creates a Traces connector based on the settings and config.
func (b *ConnectorBuilder) CreateTracesToProfiles(ctx context.Context, set connector.Settings, next xconsumer.Profiles) (connector.Traces, error) {
if next == nil {
return nil, errNilNextConsumer
}
cfg, existsCfg := b.cfgs[set.ID]
if !existsCfg {
return nil, fmt.Errorf("connector %q is not configured", set.ID)
}
connFact, existsFactory := b.factories[set.ID.Type()]
if !existsFactory {
return nil, fmt.Errorf("connector factory not available for: %q", set.ID)
}
f, ok := connFact.(xconnector.Factory)
if !ok {
return nil, errDataTypes(set.ID, pipeline.SignalTraces, xpipeline.SignalProfiles)
}
logDeprecatedTypeAlias(set.Logger, f, set.ID.Type())
logStabilityLevel(set.Logger, f.TracesToProfilesStability())
return f.CreateTracesToProfiles(ctx, set, cfg, next)
}
// CreateMetricsToTraces creates a Metrics connector based on the settings and config.
func (b *ConnectorBuilder) CreateMetricsToTraces(ctx context.Context, set connector.Settings, next consumer.Traces) (connector.Metrics, error) {
if next == nil {
return nil, errNilNextConsumer
}
cfg, existsCfg := b.cfgs[set.ID]
if !existsCfg {
return nil, fmt.Errorf("connector %q is not configured", set.ID)
}
f, existsFactory := b.factories[set.ID.Type()]
if !existsFactory {
return nil, fmt.Errorf("connector factory not available for: %q", set.ID)
}
logDeprecatedTypeAlias(set.Logger, f, set.ID.Type())
logStabilityLevel(set.Logger, f.MetricsToTracesStability())
return f.CreateMetricsToTraces(ctx, set, cfg, next)
}
// CreateMetricsToMetrics creates a Metrics connector based on the settings and config.
func (b *ConnectorBuilder) CreateMetricsToMetrics(ctx context.Context, set connector.Settings, next consumer.Metrics) (connector.Metrics, error) {
if next == nil {
return nil, errNilNextConsumer
}
cfg, existsCfg := b.cfgs[set.ID]
if !existsCfg {
return nil, fmt.Errorf("connector %q is not configured", set.ID)
}
f, existsFactory := b.factories[set.ID.Type()]
if !existsFactory {
return nil, fmt.Errorf("connector factory not available for: %q", set.ID)
}
logDeprecatedTypeAlias(set.Logger, f, set.ID.Type())
logStabilityLevel(set.Logger, f.MetricsToMetricsStability())
return f.CreateMetricsToMetrics(ctx, set, cfg, next)
}
// CreateMetricsToLogs creates a Metrics connector based on the settings and config.
func (b *ConnectorBuilder) CreateMetricsToLogs(ctx context.Context, set connector.Settings, next consumer.Logs) (connector.Metrics, error) {
if next == nil {
return nil, errNilNextConsumer
}
cfg, existsCfg := b.cfgs[set.ID]
if !existsCfg {
return nil, fmt.Errorf("connector %q is not configured", set.ID)
}
f, existsFactory := b.factories[set.ID.Type()]
if !existsFactory {
return nil, fmt.Errorf("connector factory not available for: %q", set.ID)
}
logDeprecatedTypeAlias(set.Logger, f, set.ID.Type())
logStabilityLevel(set.Logger, f.MetricsToLogsStability())
return f.CreateMetricsToLogs(ctx, set, cfg, next)
}
// CreateMetricsToProfiles creates a Metrics connector based on the settings and config.
func (b *ConnectorBuilder) CreateMetricsToProfiles(ctx context.Context, set connector.Settings, next xconsumer.Profiles) (connector.Metrics, error) {
if next == nil {
return nil, errNilNextConsumer
}
cfg, existsCfg := b.cfgs[set.ID]
if !existsCfg {
return nil, fmt.Errorf("connector %q is not configured", set.ID)
}
connFact, existsFactory := b.factories[set.ID.Type()]
if !existsFactory {
return nil, fmt.Errorf("connector factory not available for: %q", set.ID)
}
f, ok := connFact.(xconnector.Factory)
if !ok {
return nil, errDataTypes(set.ID, pipeline.SignalMetrics, xpipeline.SignalProfiles)
}
logDeprecatedTypeAlias(set.Logger, f, set.ID.Type())
logStabilityLevel(set.Logger, f.MetricsToProfilesStability())
return f.CreateMetricsToProfiles(ctx, set, cfg, next)
}
// CreateLogsToTraces creates a Logs connector based on the settings and config.
func (b *ConnectorBuilder) CreateLogsToTraces(ctx context.Context, set connector.Settings, next consumer.Traces) (connector.Logs, error) {
if next == nil {
return nil, errNilNextConsumer
}
cfg, existsCfg := b.cfgs[set.ID]
if !existsCfg {
return nil, fmt.Errorf("connector %q is not configured", set.ID)
}
f, existsFactory := b.factories[set.ID.Type()]
if !existsFactory {
return nil, fmt.Errorf("connector factory not available for: %q", set.ID)
}
logDeprecatedTypeAlias(set.Logger, f, set.ID.Type())
logStabilityLevel(set.Logger, f.LogsToTracesStability())
return f.CreateLogsToTraces(ctx, set, cfg, next)
}
// CreateLogsToMetrics creates a Logs connector based on the settings and config.
func (b *ConnectorBuilder) CreateLogsToMetrics(ctx context.Context, set connector.Settings, next consumer.Metrics) (connector.Logs, error) {
if next == nil {
return nil, errNilNextConsumer
}
cfg, existsCfg := b.cfgs[set.ID]
if !existsCfg {
return nil, fmt.Errorf("connector %q is not configured", set.ID)
}
f, existsFactory := b.factories[set.ID.Type()]
if !existsFactory {
return nil, fmt.Errorf("connector factory not available for: %q", set.ID)
}
logDeprecatedTypeAlias(set.Logger, f, set.ID.Type())
logStabilityLevel(set.Logger, f.LogsToMetricsStability())
return f.CreateLogsToMetrics(ctx, set, cfg, next)
}
// CreateLogsToLogs creates a Logs connector based on the settings and config.
func (b *ConnectorBuilder) CreateLogsToLogs(ctx context.Context, set connector.Settings, next consumer.Logs) (connector.Logs, error) {
if next == nil {
return nil, errNilNextConsumer
}
cfg, existsCfg := b.cfgs[set.ID]
if !existsCfg {
return nil, fmt.Errorf("connector %q is not configured", set.ID)
}
f, existsFactory := b.factories[set.ID.Type()]
if !existsFactory {
return nil, fmt.Errorf("connector factory not available for: %q", set.ID)
}
logDeprecatedTypeAlias(set.Logger, f, set.ID.Type())
logStabilityLevel(set.Logger, f.LogsToLogsStability())
return f.CreateLogsToLogs(ctx, set, cfg, next)
}
// CreateLogsToProfiles creates a Logs connector based on the settings and config.
func (b *ConnectorBuilder) CreateLogsToProfiles(ctx context.Context, set connector.Settings, next xconsumer.Profiles) (connector.Logs, error) {
if next == nil {
return nil, errNilNextConsumer
}
cfg, existsCfg := b.cfgs[set.ID]
if !existsCfg {
return nil, fmt.Errorf("connector %q is not configured", set.ID)
}
connFact, existsFactory := b.factories[set.ID.Type()]
if !existsFactory {
return nil, fmt.Errorf("connector factory not available for: %q", set.ID)
}
f, ok := connFact.(xconnector.Factory)
if !ok {
return nil, errDataTypes(set.ID, pipeline.SignalLogs, xpipeline.SignalProfiles)
}
logDeprecatedTypeAlias(set.Logger, f, set.ID.Type())
logStabilityLevel(set.Logger, f.LogsToProfilesStability())
return f.CreateLogsToProfiles(ctx, set, cfg, next)
}
// CreateProfilesToTraces creates a Profiles connector based on the settings and config.
func (b *ConnectorBuilder) CreateProfilesToTraces(ctx context.Context, set connector.Settings, next consumer.Traces) (xconnector.Profiles, error) {
if next == nil {
return nil, errNilNextConsumer
}
cfg, existsCfg := b.cfgs[set.ID]
if !existsCfg {
return nil, fmt.Errorf("connector %q is not configured", set.ID)
}
connFact, existsFactory := b.factories[set.ID.Type()]
if !existsFactory {
return nil, fmt.Errorf("connector factory not available for: %q", set.ID)
}
f, ok := connFact.(xconnector.Factory)
if !ok {
return nil, errDataTypes(set.ID, xpipeline.SignalProfiles, pipeline.SignalTraces)
}
logDeprecatedTypeAlias(set.Logger, f, set.ID.Type())
logStabilityLevel(set.Logger, f.ProfilesToTracesStability())
return f.CreateProfilesToTraces(ctx, set, cfg, next)
}
// CreateProfilesToMetrics creates a Profiles connector based on the settings and config.
func (b *ConnectorBuilder) CreateProfilesToMetrics(ctx context.Context, set connector.Settings, next consumer.Metrics) (xconnector.Profiles, error) {
if next == nil {
return nil, errNilNextConsumer
}
cfg, existsCfg := b.cfgs[set.ID]
if !existsCfg {
return nil, fmt.Errorf("connector %q is not configured", set.ID)
}
connFact, existsFactory := b.factories[set.ID.Type()]
if !existsFactory {
return nil, fmt.Errorf("connector factory not available for: %q", set.ID)
}
f, ok := connFact.(xconnector.Factory)
if !ok {
return nil, errDataTypes(set.ID, xpipeline.SignalProfiles, pipeline.SignalMetrics)
}
logDeprecatedTypeAlias(set.Logger, f, set.ID.Type())
logStabilityLevel(set.Logger, f.ProfilesToMetricsStability())
return f.CreateProfilesToMetrics(ctx, set, cfg, next)
}
// CreateProfilesToLogs creates a Profiles connector based on the settings and config.
func (b *ConnectorBuilder) CreateProfilesToLogs(ctx context.Context, set connector.Settings, next consumer.Logs) (xconnector.Profiles, error) {
if next == nil {
return nil, errNilNextConsumer
}
cfg, existsCfg := b.cfgs[set.ID]
if !existsCfg {
return nil, fmt.Errorf("connector %q is not configured", set.ID)
}
connFact, existsFactory := b.factories[set.ID.Type()]
if !existsFactory {
return nil, fmt.Errorf("connector factory not available for: %q", set.ID)
}
f, ok := connFact.(xconnector.Factory)
if !ok {
return nil, errDataTypes(set.ID, xpipeline.SignalProfiles, pipeline.SignalLogs)
}
logDeprecatedTypeAlias(set.Logger, f, set.ID.Type())
logStabilityLevel(set.Logger, f.ProfilesToLogsStability())
return f.CreateProfilesToLogs(ctx, set, cfg, next)
}
// CreateProfilesToProfiles creates a Profiles connector based on the settings and config.
func (b *ConnectorBuilder) CreateProfilesToProfiles(ctx context.Context, set connector.Settings, next xconsumer.Profiles) (xconnector.Profiles, error) {
if next == nil {
return nil, errNilNextConsumer
}
cfg, existsCfg := b.cfgs[set.ID]
if !existsCfg {
return nil, fmt.Errorf("connector %q is not configured", set.ID)
}
connFact, existsFactory := b.factories[set.ID.Type()]
if !existsFactory {
return nil, fmt.Errorf("connector factory not available for: %q", set.ID)
}
f, ok := connFact.(xconnector.Factory)
if !ok {
return nil, errDataTypes(set.ID, xpipeline.SignalProfiles, xpipeline.SignalProfiles)
}
logDeprecatedTypeAlias(set.Logger, f, set.ID.Type())
logStabilityLevel(set.Logger, f.ProfilesToProfilesStability())
return f.CreateProfilesToProfiles(ctx, set, cfg, next)
}
func (b *ConnectorBuilder) IsConfigured(componentID component.ID) bool {
_, ok := b.cfgs[componentID]
return ok
}
func (b *ConnectorBuilder) Factory(componentType component.Type) component.Factory {
return b.factories[componentType]
}
// NewNopConnectorConfigsAndFactories returns a configuration and factories that allows building a new nop connector.
func NewNopConnectorConfigsAndFactories() (map[component.ID]component.Config, map[component.Type]connector.Factory) {
nopFactory := connectortest.NewNopFactory()
// Use a different ID than receivertest and exportertest to avoid ambiguous
// configuration scenarios. Ambiguous IDs are detected in the 'otelcol' package,
// but lower level packages such as 'service' assume that IDs are disambiguated.
connID := component.NewIDWithName(NopType, "conn")
configs := map[component.ID]component.Config{
connID: nopFactory.CreateDefaultConfig(),
}
factories := map[component.Type]connector.Factory{
NopType: nopFactory,
}
return configs, factories
}
================================================
FILE: service/internal/builders/exporter.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package builders // import "go.opentelemetry.io/collector/service/internal/builders"
import (
"context"
"fmt"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/exporter/xexporter"
"go.opentelemetry.io/collector/pipeline"
)
// ExporterBuilder is a helper struct that given a set of Configs and Factories helps with creating exporters.
type ExporterBuilder struct {
cfgs map[component.ID]component.Config
factories map[component.Type]exporter.Factory
}
// NewExporter creates a new ExporterBuilder to help with creating components form a set of configs and factories.
func NewExporter(cfgs map[component.ID]component.Config, factories map[component.Type]exporter.Factory) *ExporterBuilder {
return &ExporterBuilder{cfgs: cfgs, factories: factories}
}
// CreateTraces creates a Traces exporter based on the settings and config.
func (b *ExporterBuilder) CreateTraces(ctx context.Context, set exporter.Settings) (exporter.Traces, error) {
cfg, existsCfg := b.cfgs[set.ID]
if !existsCfg {
return nil, fmt.Errorf("exporter %q is not configured", set.ID)
}
f, existsFactory := b.factories[set.ID.Type()]
if !existsFactory {
return nil, fmt.Errorf("exporter factory not available for: %q", set.ID)
}
logDeprecatedTypeAlias(set.Logger, f, set.ID.Type())
logStabilityLevel(set.Logger, f.TracesStability())
return f.CreateTraces(ctx, set, cfg)
}
// CreateMetrics creates a Metrics exporter based on the settings and config.
func (b *ExporterBuilder) CreateMetrics(ctx context.Context, set exporter.Settings) (exporter.Metrics, error) {
cfg, existsCfg := b.cfgs[set.ID]
if !existsCfg {
return nil, fmt.Errorf("exporter %q is not configured", set.ID)
}
f, existsFactory := b.factories[set.ID.Type()]
if !existsFactory {
return nil, fmt.Errorf("exporter factory not available for: %q", set.ID)
}
logDeprecatedTypeAlias(set.Logger, f, set.ID.Type())
logStabilityLevel(set.Logger, f.MetricsStability())
return f.CreateMetrics(ctx, set, cfg)
}
// CreateLogs creates a Logs exporter based on the settings and config.
func (b *ExporterBuilder) CreateLogs(ctx context.Context, set exporter.Settings) (exporter.Logs, error) {
cfg, existsCfg := b.cfgs[set.ID]
if !existsCfg {
return nil, fmt.Errorf("exporter %q is not configured", set.ID)
}
f, existsFactory := b.factories[set.ID.Type()]
if !existsFactory {
return nil, fmt.Errorf("exporter factory not available for: %q", set.ID)
}
logDeprecatedTypeAlias(set.Logger, f, set.ID.Type())
logStabilityLevel(set.Logger, f.LogsStability())
return f.CreateLogs(ctx, set, cfg)
}
// CreateProfiles creates a Profiles exporter based on the settings and config.
func (b *ExporterBuilder) CreateProfiles(ctx context.Context, set exporter.Settings) (xexporter.Profiles, error) {
cfg, existsCfg := b.cfgs[set.ID]
if !existsCfg {
return nil, fmt.Errorf("exporter %q is not configured", set.ID)
}
expFact, existsFactory := b.factories[set.ID.Type()]
if !existsFactory {
return nil, fmt.Errorf("exporter factory not available for: %q", set.ID)
}
f, ok := expFact.(xexporter.Factory)
if !ok {
return nil, pipeline.ErrSignalNotSupported
}
logDeprecatedTypeAlias(set.Logger, f, set.ID.Type())
logStabilityLevel(set.Logger, f.ProfilesStability())
return f.CreateProfiles(ctx, set, cfg)
}
func (b *ExporterBuilder) Factory(componentType component.Type) component.Factory {
return b.factories[componentType]
}
// NewNopExporterConfigsAndFactories returns a configuration and factories that allows building a new nop exporter.
func NewNopExporterConfigsAndFactories() (map[component.ID]component.Config, map[component.Type]exporter.Factory) {
nopFactory := exportertest.NewNopFactory()
configs := map[component.ID]component.Config{
component.NewID(NopType): nopFactory.CreateDefaultConfig(),
}
factories := map[component.Type]exporter.Factory{
NopType: nopFactory,
}
return configs, factories
}
================================================
FILE: service/internal/builders/extension.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package builders // import "go.opentelemetry.io/collector/service/internal/builders"
import (
"context"
"fmt"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/extension"
"go.opentelemetry.io/collector/extension/extensiontest"
)
// Extension is an interface that allows using implementations of the builder
// from different packages.
type Extension interface {
Create(context.Context, extension.Settings) (extension.Extension, error)
Factory(component.Type) component.Factory
}
// ExtensionBuilder is a helper struct that given a set of Configs and Factories helps with creating extensions.
type ExtensionBuilder struct {
cfgs map[component.ID]component.Config
factories map[component.Type]extension.Factory
}
// NewExtension creates a new ExtensionBuilder to help with creating
// components form a set of configs and factories.
func NewExtension(cfgs map[component.ID]component.Config, factories map[component.Type]extension.Factory) *ExtensionBuilder {
return &ExtensionBuilder{cfgs: cfgs, factories: factories}
}
// Create creates an extension based on the settings and configs available.
func (b *ExtensionBuilder) Create(ctx context.Context, set extension.Settings) (extension.Extension, error) {
cfg, existsCfg := b.cfgs[set.ID]
if !existsCfg {
return nil, fmt.Errorf("extension %q is not configured", set.ID)
}
f, existsFactory := b.factories[set.ID.Type()]
if !existsFactory {
return nil, fmt.Errorf("extension factory not available for: %q", set.ID)
}
logDeprecatedTypeAlias(set.Logger, f, set.ID.Type())
logStabilityLevel(set.Logger, f.Stability())
return f.Create(ctx, set, cfg)
}
func (b *ExtensionBuilder) Factory(componentType component.Type) component.Factory {
return b.factories[componentType]
}
// NewNopExtensionConfigsAndFactories returns a configuration and factories that allows building a new nop processor.
func NewNopExtensionConfigsAndFactories() (map[component.ID]component.Config, map[component.Type]extension.Factory) {
nopFactory := extensiontest.NewNopFactory()
configs := map[component.ID]component.Config{
component.NewID(NopType): nopFactory.CreateDefaultConfig(),
}
factories := map[component.Type]extension.Factory{
NopType: nopFactory,
}
return configs, factories
}
================================================
FILE: service/internal/builders/processor.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package builders // import "go.opentelemetry.io/collector/service/internal/builders"
import (
"context"
"fmt"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/processortest"
"go.opentelemetry.io/collector/processor/xprocessor"
)
// ProcessorBuilder processor is a helper struct that given a set of Configs
// and Factories helps with creating processors.
type ProcessorBuilder struct {
cfgs map[component.ID]component.Config
factories map[component.Type]processor.Factory
}
// NewProcessor creates a new ProcessorBuilder to help with creating components form a set of configs and factories.
func NewProcessor(cfgs map[component.ID]component.Config, factories map[component.Type]processor.Factory) *ProcessorBuilder {
return &ProcessorBuilder{cfgs: cfgs, factories: factories}
}
// CreateTraces creates a Traces processor based on the settings and config.
func (b *ProcessorBuilder) CreateTraces(ctx context.Context, set processor.Settings, next consumer.Traces) (processor.Traces, error) {
if next == nil {
return nil, errNilNextConsumer
}
cfg, existsCfg := b.cfgs[set.ID]
if !existsCfg {
return nil, fmt.Errorf("processor %q is not configured", set.ID)
}
f, existsFactory := b.factories[set.ID.Type()]
if !existsFactory {
return nil, fmt.Errorf("processor factory not available for: %q", set.ID)
}
logDeprecatedTypeAlias(set.Logger, f, set.ID.Type())
logStabilityLevel(set.Logger, f.TracesStability())
return f.CreateTraces(ctx, set, cfg, next)
}
// CreateMetrics creates a Metrics processor based on the settings and config.
func (b *ProcessorBuilder) CreateMetrics(ctx context.Context, set processor.Settings, next consumer.Metrics) (processor.Metrics, error) {
if next == nil {
return nil, errNilNextConsumer
}
cfg, existsCfg := b.cfgs[set.ID]
if !existsCfg {
return nil, fmt.Errorf("processor %q is not configured", set.ID)
}
f, existsFactory := b.factories[set.ID.Type()]
if !existsFactory {
return nil, fmt.Errorf("processor factory not available for: %q", set.ID)
}
logDeprecatedTypeAlias(set.Logger, f, set.ID.Type())
logStabilityLevel(set.Logger, f.MetricsStability())
return f.CreateMetrics(ctx, set, cfg, next)
}
// CreateLogs creates a Logs processor based on the settings and config.
func (b *ProcessorBuilder) CreateLogs(ctx context.Context, set processor.Settings, next consumer.Logs) (processor.Logs, error) {
if next == nil {
return nil, errNilNextConsumer
}
cfg, existsCfg := b.cfgs[set.ID]
if !existsCfg {
return nil, fmt.Errorf("processor %q is not configured", set.ID)
}
f, existsFactory := b.factories[set.ID.Type()]
if !existsFactory {
return nil, fmt.Errorf("processor factory not available for: %q", set.ID)
}
logDeprecatedTypeAlias(set.Logger, f, set.ID.Type())
logStabilityLevel(set.Logger, f.LogsStability())
return f.CreateLogs(ctx, set, cfg, next)
}
// CreateProfiles creates a Profiles processor based on the settings and config.
func (b *ProcessorBuilder) CreateProfiles(ctx context.Context, set processor.Settings, next xconsumer.Profiles) (xprocessor.Profiles, error) {
if next == nil {
return nil, errNilNextConsumer
}
cfg, existsCfg := b.cfgs[set.ID]
if !existsCfg {
return nil, fmt.Errorf("processor %q is not configured", set.ID)
}
procFact, existsFactory := b.factories[set.ID.Type()]
if !existsFactory {
return nil, fmt.Errorf("processor factory not available for: %q", set.ID)
}
f, ok := procFact.(xprocessor.Factory)
if !ok {
return nil, pipeline.ErrSignalNotSupported
}
logDeprecatedTypeAlias(set.Logger, f, set.ID.Type())
logStabilityLevel(set.Logger, f.ProfilesStability())
return f.CreateProfiles(ctx, set, cfg, next)
}
func (b *ProcessorBuilder) Factory(componentType component.Type) component.Factory {
return b.factories[componentType]
}
// NewNopProcessorConfigsAndFactories returns a configuration and factories that allows building a new nop processor.
func NewNopProcessorConfigsAndFactories() (map[component.ID]component.Config, map[component.Type]processor.Factory) {
nopFactory := processortest.NewNopFactory()
configs := map[component.ID]component.Config{
component.NewID(NopType): nopFactory.CreateDefaultConfig(),
}
factories := map[component.Type]processor.Factory{
NopType: nopFactory,
}
return configs, factories
}
================================================
FILE: service/internal/builders/receiver.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package builders // import "go.opentelemetry.io/collector/service/internal/builders"
import (
"context"
"fmt"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/receivertest"
"go.opentelemetry.io/collector/receiver/xreceiver"
)
// ReceiverBuilder receiver is a helper struct that given a set of Configs and
// Factories helps with creating receivers.
type ReceiverBuilder struct {
cfgs map[component.ID]component.Config
factories map[component.Type]receiver.Factory
}
// NewReceiver creates a new ReceiverBuilder to help with creating
// components form a set of configs and factories.
func NewReceiver(cfgs map[component.ID]component.Config, factories map[component.Type]receiver.Factory) *ReceiverBuilder {
return &ReceiverBuilder{cfgs: cfgs, factories: factories}
}
// CreateTraces creates a Traces receiver based on the settings and config.
func (b *ReceiverBuilder) CreateTraces(ctx context.Context, set receiver.Settings, next consumer.Traces) (receiver.Traces, error) {
if next == nil {
return nil, errNilNextConsumer
}
cfg, existsCfg := b.cfgs[set.ID]
if !existsCfg {
return nil, fmt.Errorf("receiver %q is not configured", set.ID)
}
f, existsFactory := b.factories[set.ID.Type()]
if !existsFactory {
return nil, fmt.Errorf("receiver factory not available for: %q", set.ID)
}
logDeprecatedTypeAlias(set.Logger, f, set.ID.Type())
logStabilityLevel(set.Logger, f.TracesStability())
return f.CreateTraces(ctx, set, cfg, next)
}
// CreateMetrics creates a Metrics receiver based on the settings and config.
func (b *ReceiverBuilder) CreateMetrics(ctx context.Context, set receiver.Settings, next consumer.Metrics) (receiver.Metrics, error) {
if next == nil {
return nil, errNilNextConsumer
}
cfg, existsCfg := b.cfgs[set.ID]
if !existsCfg {
return nil, fmt.Errorf("receiver %q is not configured", set.ID)
}
f, existsFactory := b.factories[set.ID.Type()]
if !existsFactory {
return nil, fmt.Errorf("receiver factory not available for: %q", set.ID)
}
logDeprecatedTypeAlias(set.Logger, f, set.ID.Type())
logStabilityLevel(set.Logger, f.MetricsStability())
return f.CreateMetrics(ctx, set, cfg, next)
}
// CreateLogs creates a Logs receiver based on the settings and config.
func (b *ReceiverBuilder) CreateLogs(ctx context.Context, set receiver.Settings, next consumer.Logs) (receiver.Logs, error) {
if next == nil {
return nil, errNilNextConsumer
}
cfg, existsCfg := b.cfgs[set.ID]
if !existsCfg {
return nil, fmt.Errorf("receiver %q is not configured", set.ID)
}
f, existsFactory := b.factories[set.ID.Type()]
if !existsFactory {
return nil, fmt.Errorf("receiver factory not available for: %q", set.ID)
}
logDeprecatedTypeAlias(set.Logger, f, set.ID.Type())
logStabilityLevel(set.Logger, f.LogsStability())
return f.CreateLogs(ctx, set, cfg, next)
}
// CreateProfiles creates a Profiles receiver based on the settings and config.
func (b *ReceiverBuilder) CreateProfiles(ctx context.Context, set receiver.Settings, next xconsumer.Profiles) (xreceiver.Profiles, error) {
if next == nil {
return nil, errNilNextConsumer
}
cfg, existsCfg := b.cfgs[set.ID]
if !existsCfg {
return nil, fmt.Errorf("receiver %q is not configured", set.ID)
}
recvFact, existsFactory := b.factories[set.ID.Type()]
if !existsFactory {
return nil, fmt.Errorf("receiver factory not available for: %q", set.ID)
}
f, ok := recvFact.(xreceiver.Factory)
if !ok {
return nil, pipeline.ErrSignalNotSupported
}
logDeprecatedTypeAlias(set.Logger, f, set.ID.Type())
logStabilityLevel(set.Logger, f.ProfilesStability())
return f.CreateProfiles(ctx, set, cfg, next)
}
func (b *ReceiverBuilder) Factory(componentType component.Type) component.Factory {
return b.factories[componentType]
}
// NewNopReceiverConfigsAndFactories returns a configuration and factories that allows building a new nop receiver.
func NewNopReceiverConfigsAndFactories() (map[component.ID]component.Config, map[component.Type]receiver.Factory) {
nopFactory := receivertest.NewNopFactory()
configs := map[component.ID]component.Config{
component.NewID(NopType): nopFactory.CreateDefaultConfig(),
}
factories := map[component.Type]receiver.Factory{
NopType: nopFactory,
}
return configs, factories
}
================================================
FILE: service/internal/capabilityconsumer/capabilities.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package capabilityconsumer // import "go.opentelemetry.io/collector/service/internal/capabilityconsumer"
import (
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/xconsumer"
)
func NewLogs(logs consumer.Logs, capabilities consumer.Capabilities) consumer.Logs {
if logs.Capabilities() == capabilities {
return logs
}
return capLogs{Logs: logs, cap: capabilities}
}
type capLogs struct {
consumer.Logs
cap consumer.Capabilities
}
func (mts capLogs) Capabilities() consumer.Capabilities {
return mts.cap
}
func NewMetrics(metrics consumer.Metrics, capabilities consumer.Capabilities) consumer.Metrics {
if metrics.Capabilities() == capabilities {
return metrics
}
return capMetrics{Metrics: metrics, cap: capabilities}
}
type capMetrics struct {
consumer.Metrics
cap consumer.Capabilities
}
func (mts capMetrics) Capabilities() consumer.Capabilities {
return mts.cap
}
func NewTraces(traces consumer.Traces, capabilities consumer.Capabilities) consumer.Traces {
if traces.Capabilities() == capabilities {
return traces
}
return capTraces{Traces: traces, cap: capabilities}
}
type capTraces struct {
consumer.Traces
cap consumer.Capabilities
}
func (mts capTraces) Capabilities() consumer.Capabilities {
return mts.cap
}
func NewProfiles(profiles xconsumer.Profiles, capabilities consumer.Capabilities) xconsumer.Profiles {
if profiles.Capabilities() == capabilities {
return profiles
}
return capProfiles{Profiles: profiles, cap: capabilities}
}
type capProfiles struct {
xconsumer.Profiles
cap consumer.Capabilities
}
func (mts capProfiles) Capabilities() consumer.Capabilities {
return mts.cap
}
================================================
FILE: service/internal/capabilityconsumer/capabilities_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package capabilityconsumer
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/pdata/testdata"
)
func TestLogs(t *testing.T) {
sink := &consumertest.LogsSink{}
require.Equal(t, consumer.Capabilities{MutatesData: false}, sink.Capabilities())
same := NewLogs(sink, consumer.Capabilities{MutatesData: false})
assert.Same(t, sink, same)
wrap := NewLogs(sink, consumer.Capabilities{MutatesData: true})
assert.Equal(t, consumer.Capabilities{MutatesData: true}, wrap.Capabilities())
require.NoError(t, wrap.ConsumeLogs(context.Background(), testdata.GenerateLogs(1)))
assert.Len(t, sink.AllLogs(), 1)
assert.Equal(t, testdata.GenerateLogs(1), sink.AllLogs()[0])
}
func TestMetrics(t *testing.T) {
sink := &consumertest.MetricsSink{}
require.Equal(t, consumer.Capabilities{MutatesData: false}, sink.Capabilities())
same := NewMetrics(sink, consumer.Capabilities{MutatesData: false})
assert.Same(t, sink, same)
wrap := NewMetrics(sink, consumer.Capabilities{MutatesData: true})
assert.Equal(t, consumer.Capabilities{MutatesData: true}, wrap.Capabilities())
require.NoError(t, wrap.ConsumeMetrics(context.Background(), testdata.GenerateMetrics(1)))
assert.Len(t, sink.AllMetrics(), 1)
assert.Equal(t, testdata.GenerateMetrics(1), sink.AllMetrics()[0])
}
func TestTraces(t *testing.T) {
sink := &consumertest.TracesSink{}
require.Equal(t, consumer.Capabilities{MutatesData: false}, sink.Capabilities())
same := NewTraces(sink, consumer.Capabilities{MutatesData: false})
assert.Same(t, sink, same)
wrap := NewTraces(sink, consumer.Capabilities{MutatesData: true})
assert.Equal(t, consumer.Capabilities{MutatesData: true}, wrap.Capabilities())
require.NoError(t, wrap.ConsumeTraces(context.Background(), testdata.GenerateTraces(1)))
assert.Len(t, sink.AllTraces(), 1)
assert.Equal(t, testdata.GenerateTraces(1), sink.AllTraces()[0])
}
func TestProfiles(t *testing.T) {
sink := &consumertest.ProfilesSink{}
require.Equal(t, consumer.Capabilities{MutatesData: false}, sink.Capabilities())
same := NewProfiles(sink, consumer.Capabilities{MutatesData: false})
assert.Same(t, sink, same)
wrap := NewProfiles(sink, consumer.Capabilities{MutatesData: true})
assert.Equal(t, consumer.Capabilities{MutatesData: true}, wrap.Capabilities())
require.NoError(t, wrap.ConsumeProfiles(context.Background(), testdata.GenerateProfiles(1)))
assert.Len(t, sink.AllProfiles(), 1)
assert.Equal(t, testdata.GenerateProfiles(1), sink.AllProfiles()[0])
}
================================================
FILE: service/internal/capabilityconsumer/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package capabilityconsumer
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: service/internal/componentattribute/logger_zap.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package componentattribute // import "go.opentelemetry.io/collector/service/internal/componentattribute"
import (
"slices"
"go.opentelemetry.io/otel/attribute"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"go.opentelemetry.io/collector/internal/telemetry"
)
// This wrapper around zapcore.Field tells the Zap -> OTel bridge that the field
// should be turned into an instrumentation scope instead of a set of log record attributes.
type scopeAttributesField struct {
fields []zapcore.Field
attrs []attribute.KeyValue
}
var _ zapcore.ObjectMarshaler = scopeAttributesField{}
func (saf scopeAttributesField) MarshalLogObject(enc zapcore.ObjectEncoder) error {
for _, field := range saf.fields {
field.AddTo(enc)
}
return nil
}
func makeScopeField(attrs []attribute.KeyValue) zap.Field {
return zap.Inline(scopeAttributesField{
fields: telemetry.ToZapFields(attrs),
attrs: attrs,
})
}
func ExtractLogScopeAttributes(field zap.Field) ([]attribute.KeyValue, bool) {
if field.Type != zapcore.InlineMarshalerType {
return nil, false
}
saf, ok := field.Interface.(scopeAttributesField)
if !ok {
return nil, false
}
return saf.attrs, true
}
type coreWithAttributes struct {
zapcore.Core
sourceCore zapcore.Core
attrs []attribute.KeyValue
withFields []zap.Field
}
var _ zapcore.Core = coreWithAttributes{}
func (cwa coreWithAttributes) With(fields []zapcore.Field) zapcore.Core {
cwa.withFields = append(cwa.withFields, fields...)
cwa.Core = cwa.Core.With(fields)
return cwa
}
func LoggerWithAttributes(logger *zap.Logger, attrs []attribute.KeyValue) *zap.Logger {
return logger.WithOptions(zap.WrapCore(func(c zapcore.Core) zapcore.Core {
return coreWithAttributes{
Core: c.With([]zap.Field{makeScopeField(attrs)}),
sourceCore: c,
attrs: attrs,
}
}))
}
func (cwa coreWithAttributes) DropInjectedAttributes(droppedAttrs ...string) zapcore.Core {
cwa.attrs = slices.DeleteFunc(slices.Clone(cwa.attrs), func(kv attribute.KeyValue) bool {
return slices.Contains(droppedAttrs, string(kv.Key))
})
cwa.Core = cwa.sourceCore.With(append([]zap.Field{makeScopeField(cwa.attrs)}, cwa.withFields...))
return cwa
}
================================================
FILE: service/internal/componentattribute/meter_provider.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package componentattribute // import "go.opentelemetry.io/collector/service/internal/componentattribute"
import (
"slices"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
)
type meterProviderWithAttributes struct {
metric.MeterProvider
attrs []attribute.KeyValue
}
func (mpwa meterProviderWithAttributes) Meter(name string, opts ...metric.MeterOption) metric.Meter {
conf := metric.NewMeterConfig(opts...)
attrSet := conf.InstrumentationAttributes()
// prepend our attributes so they can be overwritten
newAttrs := append(slices.Clone(mpwa.attrs), attrSet.ToSlice()...)
// append our attribute set option to overwrite the old one
opts = append(opts, metric.WithInstrumentationAttributes(newAttrs...))
return mpwa.MeterProvider.Meter(name, opts...)
}
func (mpwa meterProviderWithAttributes) DropInjectedAttributes(droppedAttrs ...string) metric.MeterProvider {
return meterProviderWithAttributes{
MeterProvider: mpwa.MeterProvider,
attrs: slices.DeleteFunc(slices.Clone(mpwa.attrs), func(kv attribute.KeyValue) bool {
return slices.Contains(droppedAttrs, string(kv.Key))
}),
}
}
================================================
FILE: service/internal/componentattribute/telemetry.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package componentattribute // import "go.opentelemetry.io/collector/service/internal/componentattribute"
import (
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/service/internal/metadata"
)
func TelemetrySettingsWithAttributes(ts component.TelemetrySettings, attrSet attribute.Set) component.TelemetrySettings {
attrs := attrSet.ToSlice()
ts.Logger = LoggerWithAttributes(ts.Logger, attrs)
ts.TracerProvider = tracerProviderWithAttributes{
TracerProvider: ts.TracerProvider,
attrs: attrs,
}
if metadata.TelemetryNewPipelineTelemetryFeatureGate.IsEnabled() {
ts.MeterProvider = meterProviderWithAttributes{
MeterProvider: ts.MeterProvider,
attrs: attrs,
}
}
return ts
}
================================================
FILE: service/internal/componentattribute/telemetry_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package componentattribute_test
import (
"context"
"encoding/json"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
metricSdk "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
traceSdk "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/sdk/trace/tracetest"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"go.uber.org/zap/zaptest/observer"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/internal/telemetry"
"go.opentelemetry.io/collector/service/internal/componentattribute"
"go.opentelemetry.io/collector/service/internal/metadata"
)
func findScopeAttributesField(context []zap.Field) ([]attribute.KeyValue, bool) {
for _, field := range context {
scope, ok := componentattribute.ExtractLogScopeAttributes(field)
if ok {
return scope, true
}
}
return nil, false
}
func attributeSetJSON(t *testing.T, set attribute.Set) string {
scopeBuf, err := json.Marshal(set.MarshalLog())
require.NoError(t, err)
return string(scopeBuf)
}
func getLogScopeAndFields(t *testing.T, logObs *observer.ObservedLogs) (string, string) {
logs := logObs.TakeAll()
require.Len(t, logs, 1)
log := logs[0]
require.Equal(t, "test", log.Message)
scope, ok := findScopeAttributesField(log.Context)
require.True(t, ok, "Failed to find ScopeAttributesField field")
scopeStr := attributeSetJSON(t, attribute.NewSet(scope...))
enc := zapcore.NewJSONEncoder(zapcore.EncoderConfig{})
fieldsBuf, err := enc.EncodeEntry(log.Entry, log.Context)
require.NoError(t, err)
fieldsStr := strings.TrimSuffix(fieldsBuf.String(), "\n")
return scopeStr, fieldsStr
}
func getSpanScope(t *testing.T, spanObs *tracetest.InMemoryExporter) string {
spans := spanObs.GetSpans().Snapshots()
spanObs.Reset()
require.Len(t, spans, 1)
span := spans[0]
require.Equal(t, "test", span.Name())
return attributeSetJSON(t, span.InstrumentationScope().Attributes)
}
func getMetricScope(t *testing.T, metricObs *metricSdk.ManualReader) string {
rm := metricdata.ResourceMetrics{}
err := metricObs.Collect(t.Context(), &rm)
require.NoError(t, err)
require.Len(t, rm.ScopeMetrics, 1)
return attributeSetJSON(t, rm.ScopeMetrics[0].Scope.Attributes)
}
type TestResults struct {
LogScope string
LogFields string
SpanScope string
MetricScope string
}
func getScopes(t *testing.T, tswa component.TelemetrySettings, logObs *observer.ObservedLogs, spanObs *tracetest.InMemoryExporter, metricObs *metricSdk.ManualReader) TestResults {
// Create new tracer, meter, and metric instrument
tracer := tswa.TracerProvider.Tracer("test", trace.WithInstrumentationAttributes(attribute.String("after", "val")))
meter := tswa.MeterProvider.Meter("test", metric.WithInstrumentationAttributes(attribute.String("after", "val")))
gauge, err := meter.Int64Gauge("test")
require.NoError(t, err)
// Emit a log, a span, and a metric point
tswa.Logger.Info("test", zap.String("manual", "val"))
logScope, logFields := getLogScopeAndFields(t, logObs)
_, span := tracer.Start(t.Context(), "test")
span.End()
gauge.Record(t.Context(), 1)
// Check resulting scope attributes
return TestResults{
LogScope: logScope,
LogFields: logFields,
SpanScope: getSpanScope(t, spanObs),
MetricScope: getMetricScope(t, metricObs),
}
}
type tracerProviderWrapper struct {
trace.TracerProvider
}
func testTelemetryWithAttributes(t *testing.T, useTraceSdk bool) {
prevState := metadata.TelemetryNewPipelineTelemetryFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.TelemetryNewPipelineTelemetryFeatureGate.ID(), true))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.TelemetryNewPipelineTelemetryFeatureGate.ID(), prevState))
}()
// Setup mock TelemetrySettings
core, logObs := observer.New(zap.DebugLevel)
logger := zap.New(core)
logger = logger.With(zap.String("before", "val"))
spanObs := tracetest.NewInMemoryExporter()
var tracerProvider trace.TracerProvider = traceSdk.NewTracerProvider(traceSdk.WithSpanProcessor(traceSdk.NewSimpleSpanProcessor(spanObs)))
if !useTraceSdk {
tracerProvider = tracerProviderWrapper{TracerProvider: tracerProvider}
}
// Use delta temporality so points from the first step are no longer exported in the second step
metricObs := metricSdk.NewManualReader(metricSdk.WithTemporalitySelector(func(metricSdk.InstrumentKind) metricdata.Temporality {
return metricdata.DeltaTemporality
}))
meterProvider := metricSdk.NewMeterProvider(metricSdk.WithReader(metricObs))
ts := component.TelemetrySettings{
Logger: logger,
TracerProvider: tracerProvider,
MeterProvider: meterProvider,
}
// Inject attributes
tswa := componentattribute.TelemetrySettingsWithAttributes(ts, attribute.NewSet(
attribute.String("injected1", "val"),
attribute.String("injected2", "val"),
))
// Check that SDK-only methods are accessible through Unwrap
wrapped, ok := tswa.TracerProvider.(interface {
Unwrap() trace.TracerProvider
})
if assert.True(t, ok) {
_, ok := wrapped.Unwrap().(interface {
ForceFlush(ctx context.Context) error
})
assert.Equal(t, useTraceSdk, ok)
}
// Add extra log attribute
tswa.Logger = tswa.Logger.With(zap.String("after", "val"))
assert.Equal(t, TestResults{
LogScope: `{"injected1":"val","injected2":"val"}`,
LogFields: `{"before":"val","injected1":"val","injected2":"val","after":"val","manual":"val"}`,
SpanScope: `{"after":"val","injected1":"val","injected2":"val"}`,
MetricScope: `{"after":"val","injected1":"val","injected2":"val"}`,
}, getScopes(t, tswa, logObs, spanObs, metricObs))
// Drop one injected attribute
tswa = telemetry.DropInjectedAttributes(tswa, "injected1")
// Check scopes again
assert.Equal(t, TestResults{
LogScope: `{"injected2":"val"}`,
LogFields: `{"before":"val","injected2":"val","after":"val","manual":"val"}`,
SpanScope: `{"after":"val","injected2":"val"}`,
MetricScope: `{"after":"val","injected2":"val"}`,
}, getScopes(t, tswa, logObs, spanObs, metricObs))
}
func TestTelemetryWithAttributes(t *testing.T) {
t.Run("sdk", func(t *testing.T) {
testTelemetryWithAttributes(t, true)
})
t.Run("generic", func(t *testing.T) {
testTelemetryWithAttributes(t, false)
})
}
================================================
FILE: service/internal/componentattribute/tracer_provider.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package componentattribute // import "go.opentelemetry.io/collector/service/internal/componentattribute"
import (
"slices"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)
type tracerProviderWithAttributes struct {
trace.TracerProvider
attrs []attribute.KeyValue
}
func (tpwa tracerProviderWithAttributes) Tracer(name string, options ...trace.TracerOption) trace.Tracer {
conf := trace.NewTracerConfig(options...)
attrSet := conf.InstrumentationAttributes()
// prepend our attributes so they can be overwritten
newAttrs := append(slices.Clone(tpwa.attrs), attrSet.ToSlice()...)
// append our attribute set option to overwrite the old one
options = append(options, trace.WithInstrumentationAttributes(newAttrs...))
return tpwa.TracerProvider.Tracer(name, options...)
}
func (tpwa tracerProviderWithAttributes) Unwrap() trace.TracerProvider {
return tpwa.TracerProvider
}
func (tpwa tracerProviderWithAttributes) DropInjectedAttributes(droppedAttrs ...string) trace.TracerProvider {
return tracerProviderWithAttributes{
TracerProvider: tpwa.TracerProvider,
attrs: slices.DeleteFunc(slices.Clone(tpwa.attrs), func(kv attribute.KeyValue) bool {
return slices.Contains(droppedAttrs, string(kv.Key))
}),
}
}
================================================
FILE: service/internal/graph/capabilities.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package graph // import "go.opentelemetry.io/collector/service/internal/graph"
import (
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/service/internal/attribute"
)
var _ consumerNode = (*capabilitiesNode)(nil)
// Every pipeline has a "virtual" capabilities node immediately after the receiver(s).
// There are two purposes for this node:
// 1. Present aggregated capabilities to receivers, such as whether the pipeline mutates data.
// 2. Present a consistent "first consumer" for each pipeline.
// The nodeID is derived from "pipeline ID".
type capabilitiesNode struct {
attribute.Attributes
pipelineID pipeline.ID
baseConsumer
consumer.ConsumeTracesFunc
consumer.ConsumeMetricsFunc
consumer.ConsumeLogsFunc
xconsumer.ConsumeProfilesFunc
}
func newCapabilitiesNode(pipelineID pipeline.ID) *capabilitiesNode {
return &capabilitiesNode{
Attributes: attribute.Capabilities(pipelineID),
pipelineID: pipelineID,
}
}
func (n *capabilitiesNode) getConsumer() baseConsumer {
return n
}
================================================
FILE: service/internal/graph/connector.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package graph // import "go.opentelemetry.io/collector/service/internal/graph"
import (
"context"
otelattr "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/connector"
"go.opentelemetry.io/collector/connector/xconnector"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/pipeline/xpipeline"
"go.opentelemetry.io/collector/service/internal/attribute"
"go.opentelemetry.io/collector/service/internal/builders"
"go.opentelemetry.io/collector/service/internal/capabilityconsumer"
"go.opentelemetry.io/collector/service/internal/componentattribute"
"go.opentelemetry.io/collector/service/internal/metadata"
"go.opentelemetry.io/collector/service/internal/obsconsumer"
"go.opentelemetry.io/collector/service/internal/refconsumer"
)
const pipelineIDAttrKey = "otelcol.pipeline.id"
var _ consumerNode = (*connectorNode)(nil)
type connectorNode struct {
attribute.Attributes
componentID component.ID
exprPipelineType pipeline.Signal
rcvrPipelineType pipeline.Signal
component.Component
consumer baseConsumer
}
func newConnectorNode(exprPipelineType, rcvrPipelineType pipeline.Signal, connID component.ID) *connectorNode {
return &connectorNode{
Attributes: attribute.Connector(exprPipelineType, rcvrPipelineType, connID),
componentID: connID,
exprPipelineType: exprPipelineType,
rcvrPipelineType: rcvrPipelineType,
}
}
func (n *connectorNode) getConsumer() baseConsumer {
return n.consumer
}
func (n *connectorNode) buildComponent(
ctx context.Context,
tel component.TelemetrySettings,
info component.BuildInfo,
builder *builders.ConnectorBuilder,
nexts []baseConsumer,
) error {
set := connector.Settings{
ID: n.componentID,
TelemetrySettings: componentattribute.TelemetrySettingsWithAttributes(tel, *n.Set()),
BuildInfo: info,
}
switch n.rcvrPipelineType {
case pipeline.SignalTraces:
return n.buildTraces(ctx, set, builder, nexts)
case pipeline.SignalMetrics:
return n.buildMetrics(ctx, set, builder, nexts)
case pipeline.SignalLogs:
return n.buildLogs(ctx, set, builder, nexts)
case xpipeline.SignalProfiles:
return n.buildProfiles(ctx, set, builder, nexts)
}
return nil
}
func (n *connectorNode) buildTraces(
ctx context.Context,
set connector.Settings,
builder *builders.ConnectorBuilder,
nexts []baseConsumer,
) error {
tb, err := metadata.NewTelemetryBuilder(set.TelemetrySettings)
if err != nil {
return err
}
producedSettings := obsconsumer.Settings{
ItemCounter: tb.ConnectorProducedItems,
SizeCounter: tb.ConnectorProducedSize,
Logger: set.Logger,
}
consumedSettings := obsconsumer.Settings{
ItemCounter: tb.ConnectorConsumedItems,
SizeCounter: tb.ConnectorConsumedSize,
Logger: set.Logger,
}
consumers := make(map[pipeline.ID]consumer.Traces, len(nexts))
for _, next := range nexts {
consumers[next.(*capabilitiesNode).pipelineID] = obsconsumer.NewTraces(
next.(consumer.Traces),
producedSettings,
obsconsumer.WithStaticDataPointAttribute(
otelattr.String(
pipelineIDAttrKey,
next.(*capabilitiesNode).pipelineID.String(),
),
),
)
}
next := connector.NewTracesRouter(consumers)
switch n.exprPipelineType {
case pipeline.SignalTraces:
n.Component, err = builder.CreateTracesToTraces(ctx, set, next)
if err != nil {
return err
}
// Connectors which might pass along data must inherit capabilities of all nexts
n.consumer = obsconsumer.NewTraces(
capabilityconsumer.NewTraces(
n.Component.(consumer.Traces),
aggregateCap(n.Component.(consumer.Traces), nexts),
),
consumedSettings,
)
n.consumer = refconsumer.NewTraces(n.consumer.(consumer.Traces))
case pipeline.SignalMetrics:
n.Component, err = builder.CreateMetricsToTraces(ctx, set, next)
if err != nil {
return err
}
n.consumer = obsconsumer.NewMetrics(n.Component.(consumer.Metrics), consumedSettings)
n.consumer = refconsumer.NewMetrics(n.consumer.(consumer.Metrics))
case pipeline.SignalLogs:
n.Component, err = builder.CreateLogsToTraces(ctx, set, next)
if err != nil {
return err
}
n.consumer = obsconsumer.NewLogs(n.Component.(consumer.Logs), consumedSettings)
n.consumer = refconsumer.NewLogs(n.consumer.(consumer.Logs))
case xpipeline.SignalProfiles:
n.Component, err = builder.CreateProfilesToTraces(ctx, set, next)
if err != nil {
return err
}
n.consumer = obsconsumer.NewProfiles(n.Component.(xconsumer.Profiles), consumedSettings)
n.consumer = refconsumer.NewProfiles(n.consumer.(xconsumer.Profiles))
}
return nil
}
func (n *connectorNode) buildMetrics(
ctx context.Context,
set connector.Settings,
builder *builders.ConnectorBuilder,
nexts []baseConsumer,
) error {
tb, err := metadata.NewTelemetryBuilder(set.TelemetrySettings)
if err != nil {
return err
}
producedSettings := obsconsumer.Settings{
ItemCounter: tb.ConnectorProducedItems,
SizeCounter: tb.ConnectorProducedSize,
Logger: set.Logger,
}
consumedSettings := obsconsumer.Settings{
ItemCounter: tb.ConnectorConsumedItems,
SizeCounter: tb.ConnectorConsumedSize,
Logger: set.Logger,
}
consumers := make(map[pipeline.ID]consumer.Metrics, len(nexts))
for _, next := range nexts {
consumers[next.(*capabilitiesNode).pipelineID] = obsconsumer.NewMetrics(
next.(consumer.Metrics),
producedSettings,
obsconsumer.WithStaticDataPointAttribute(
otelattr.String(
pipelineIDAttrKey,
next.(*capabilitiesNode).pipelineID.String(),
),
),
)
}
next := connector.NewMetricsRouter(consumers)
switch n.exprPipelineType {
case pipeline.SignalMetrics:
n.Component, err = builder.CreateMetricsToMetrics(ctx, set, next)
if err != nil {
return err
}
// Connectors which might pass along data must inherit capabilities of all nexts
n.consumer = obsconsumer.NewMetrics(
capabilityconsumer.NewMetrics(
n.Component.(consumer.Metrics),
aggregateCap(n.Component.(consumer.Metrics), nexts),
),
consumedSettings,
)
n.consumer = refconsumer.NewMetrics(n.consumer.(consumer.Metrics))
case pipeline.SignalTraces:
n.Component, err = builder.CreateTracesToMetrics(ctx, set, next)
if err != nil {
return err
}
n.consumer = obsconsumer.NewTraces(n.Component.(consumer.Traces), consumedSettings)
n.consumer = refconsumer.NewTraces(n.consumer.(consumer.Traces))
case pipeline.SignalLogs:
n.Component, err = builder.CreateLogsToMetrics(ctx, set, next)
if err != nil {
return err
}
n.consumer = obsconsumer.NewLogs(n.Component.(consumer.Logs), consumedSettings)
n.consumer = refconsumer.NewLogs(n.consumer.(consumer.Logs))
case xpipeline.SignalProfiles:
n.Component, err = builder.CreateProfilesToMetrics(ctx, set, next)
if err != nil {
return err
}
n.consumer = obsconsumer.NewProfiles(n.Component.(xconsumer.Profiles), consumedSettings)
n.consumer = refconsumer.NewProfiles(n.consumer.(xconsumer.Profiles))
}
return nil
}
func (n *connectorNode) buildLogs(
ctx context.Context,
set connector.Settings,
builder *builders.ConnectorBuilder,
nexts []baseConsumer,
) error {
tb, err := metadata.NewTelemetryBuilder(set.TelemetrySettings)
if err != nil {
return err
}
producedSettings := obsconsumer.Settings{
ItemCounter: tb.ConnectorProducedItems,
SizeCounter: tb.ConnectorProducedSize,
Logger: set.Logger,
}
consumedSettings := obsconsumer.Settings{
ItemCounter: tb.ConnectorConsumedItems,
SizeCounter: tb.ConnectorConsumedSize,
Logger: set.Logger,
}
consumers := make(map[pipeline.ID]consumer.Logs, len(nexts))
for _, next := range nexts {
consumers[next.(*capabilitiesNode).pipelineID] = obsconsumer.NewLogs(
next.(consumer.Logs),
producedSettings,
obsconsumer.WithStaticDataPointAttribute(
otelattr.String(
pipelineIDAttrKey,
next.(*capabilitiesNode).pipelineID.String(),
),
),
)
}
next := connector.NewLogsRouter(consumers)
switch n.exprPipelineType {
case pipeline.SignalLogs:
n.Component, err = builder.CreateLogsToLogs(ctx, set, next)
if err != nil {
return err
}
// Connectors which might pass along data must inherit capabilities of all nexts
n.consumer = obsconsumer.NewLogs(
capabilityconsumer.NewLogs(
n.Component.(consumer.Logs),
aggregateCap(n.Component.(consumer.Logs), nexts),
),
consumedSettings,
)
n.consumer = refconsumer.NewLogs(n.consumer.(consumer.Logs))
case pipeline.SignalTraces:
n.Component, err = builder.CreateTracesToLogs(ctx, set, next)
if err != nil {
return err
}
n.consumer = obsconsumer.NewTraces(n.Component.(consumer.Traces), consumedSettings)
n.consumer = refconsumer.NewTraces(n.consumer.(consumer.Traces))
case pipeline.SignalMetrics:
n.Component, err = builder.CreateMetricsToLogs(ctx, set, next)
if err != nil {
return err
}
n.consumer = obsconsumer.NewMetrics(n.Component.(consumer.Metrics), consumedSettings)
n.consumer = refconsumer.NewMetrics(n.consumer.(consumer.Metrics))
case xpipeline.SignalProfiles:
n.Component, err = builder.CreateProfilesToLogs(ctx, set, next)
if err != nil {
return err
}
n.consumer = obsconsumer.NewProfiles(n.Component.(xconsumer.Profiles), consumedSettings)
n.consumer = refconsumer.NewProfiles(n.consumer.(xconsumer.Profiles))
}
return nil
}
func (n *connectorNode) buildProfiles(
ctx context.Context,
set connector.Settings,
builder *builders.ConnectorBuilder,
nexts []baseConsumer,
) error {
tb, err := metadata.NewTelemetryBuilder(set.TelemetrySettings)
if err != nil {
return err
}
producedSettings := obsconsumer.Settings{
ItemCounter: tb.ConnectorProducedItems,
SizeCounter: tb.ConnectorProducedSize,
Logger: set.Logger,
}
consumedSettings := obsconsumer.Settings{
ItemCounter: tb.ConnectorConsumedItems,
SizeCounter: tb.ConnectorConsumedSize,
Logger: set.Logger,
}
consumers := make(map[pipeline.ID]xconsumer.Profiles, len(nexts))
for _, next := range nexts {
consumers[next.(*capabilitiesNode).pipelineID] = obsconsumer.NewProfiles(
next.(xconsumer.Profiles),
producedSettings,
obsconsumer.WithStaticDataPointAttribute(
otelattr.String(
pipelineIDAttrKey,
next.(*capabilitiesNode).pipelineID.String(),
),
),
)
}
next := xconnector.NewProfilesRouter(consumers)
switch n.exprPipelineType {
case xpipeline.SignalProfiles:
n.Component, err = builder.CreateProfilesToProfiles(ctx, set, next)
if err != nil {
return err
}
// Connectors which might pass along data must inherit capabilities of all nexts
n.consumer = obsconsumer.NewProfiles(
capabilityconsumer.NewProfiles(
n.Component.(xconsumer.Profiles),
aggregateCap(n.Component.(xconsumer.Profiles), nexts),
),
consumedSettings,
)
n.consumer = refconsumer.NewProfiles(n.consumer.(xconsumer.Profiles))
case pipeline.SignalTraces:
n.Component, err = builder.CreateTracesToProfiles(ctx, set, next)
if err != nil {
return err
}
n.consumer = obsconsumer.NewTraces(n.Component.(consumer.Traces), consumedSettings)
n.consumer = refconsumer.NewTraces(n.consumer.(consumer.Traces))
case pipeline.SignalMetrics:
n.Component, err = builder.CreateMetricsToProfiles(ctx, set, next)
if err != nil {
return err
}
n.consumer = obsconsumer.NewMetrics(n.Component.(consumer.Metrics), consumedSettings)
n.consumer = refconsumer.NewMetrics(n.consumer.(consumer.Metrics))
case pipeline.SignalLogs:
n.Component, err = builder.CreateLogsToProfiles(ctx, set, next)
if err != nil {
return err
}
n.consumer = obsconsumer.NewLogs(n.Component.(consumer.Logs), consumedSettings)
n.consumer = refconsumer.NewLogs(n.consumer.(consumer.Logs))
}
return nil
}
// When connecting pipelines of the same data type, the connector must
// inherit the capabilities of pipelines in which it is acting as a receiver.
// Since the incoming and outgoing data types are the same, we must also consider
// that the connector itself may mutate the data and pass it along.
func aggregateCap(base baseConsumer, nexts []baseConsumer) consumer.Capabilities {
capabilities := base.Capabilities()
for _, next := range nexts {
capabilities.MutatesData = capabilities.MutatesData || next.Capabilities().MutatesData
}
return capabilities
}
================================================
FILE: service/internal/graph/consumer.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package graph // import "go.opentelemetry.io/collector/service/internal/graph"
import (
"go.opentelemetry.io/collector/consumer"
)
// baseConsumer redeclared here since not public in consumer package. May consider to make that public.
type baseConsumer interface {
Capabilities() consumer.Capabilities
}
type consumerNode interface {
getConsumer() baseConsumer
}
================================================
FILE: service/internal/graph/exporter.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package graph // import "go.opentelemetry.io/collector/service/internal/graph"
import (
"context"
"fmt"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/pipeline/xpipeline"
"go.opentelemetry.io/collector/service/internal/attribute"
"go.opentelemetry.io/collector/service/internal/builders"
"go.opentelemetry.io/collector/service/internal/componentattribute"
"go.opentelemetry.io/collector/service/internal/metadata"
"go.opentelemetry.io/collector/service/internal/obsconsumer"
"go.opentelemetry.io/collector/service/internal/refconsumer"
)
var _ consumerNode = (*exporterNode)(nil)
// An exporter instance can be shared by multiple pipelines of the same type.
// Therefore, nodeID is derived from "pipeline type" and "component ID".
type exporterNode struct {
attribute.Attributes
componentID component.ID
pipelineType pipeline.Signal
component.Component
consumer baseConsumer
}
func newExporterNode(pipelineType pipeline.Signal, exprID component.ID) *exporterNode {
return &exporterNode{
Attributes: attribute.Exporter(pipelineType, exprID),
componentID: exprID,
pipelineType: pipelineType,
}
}
func (n *exporterNode) getConsumer() baseConsumer {
return n.consumer
}
func (n *exporterNode) buildComponent(
ctx context.Context,
tel component.TelemetrySettings,
info component.BuildInfo,
builder *builders.ExporterBuilder,
) error {
set := exporter.Settings{
ID: n.componentID,
TelemetrySettings: componentattribute.TelemetrySettingsWithAttributes(tel, *n.Set()),
BuildInfo: info,
}
tb, err := metadata.NewTelemetryBuilder(set.TelemetrySettings)
if err != nil {
return err
}
consumedSettings := obsconsumer.Settings{
ItemCounter: tb.ExporterConsumedItems,
SizeCounter: tb.ExporterConsumedSize,
Logger: set.Logger,
}
switch n.pipelineType {
case pipeline.SignalTraces:
n.Component, err = builder.CreateTraces(ctx, set)
if err != nil {
return fmt.Errorf("failed to create %q exporter for data type %q: %w", set.ID, n.pipelineType, err)
}
n.consumer = obsconsumer.NewTraces(n.Component.(consumer.Traces), consumedSettings)
n.consumer = refconsumer.NewTraces(n.consumer.(consumer.Traces))
case pipeline.SignalMetrics:
n.Component, err = builder.CreateMetrics(ctx, set)
if err != nil {
return fmt.Errorf("failed to create %q exporter for data type %q: %w", set.ID, n.pipelineType, err)
}
n.consumer = obsconsumer.NewMetrics(n.Component.(consumer.Metrics), consumedSettings)
n.consumer = refconsumer.NewMetrics(n.consumer.(consumer.Metrics))
case pipeline.SignalLogs:
n.Component, err = builder.CreateLogs(ctx, set)
if err != nil {
return fmt.Errorf("failed to create %q exporter for data type %q: %w", set.ID, n.pipelineType, err)
}
n.consumer = obsconsumer.NewLogs(n.Component.(consumer.Logs), consumedSettings)
n.consumer = refconsumer.NewLogs(n.consumer.(consumer.Logs))
case xpipeline.SignalProfiles:
n.Component, err = builder.CreateProfiles(ctx, set)
if err != nil {
return fmt.Errorf("failed to create %q exporter for data type %q: %w", set.ID, n.pipelineType, err)
}
n.consumer = obsconsumer.NewProfiles(n.Component.(xconsumer.Profiles), consumedSettings)
n.consumer = refconsumer.NewProfiles(n.consumer.(xconsumer.Profiles))
default:
return fmt.Errorf("error creating exporter %q for data type %q is not supported", set.ID, n.pipelineType)
}
return nil
}
================================================
FILE: service/internal/graph/fanout.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package graph // import "go.opentelemetry.io/collector/service/internal/graph"
import (
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/service/internal/attribute"
)
var _ consumerNode = (*fanOutNode)(nil)
// Each pipeline has one fan-out node before exporters.
// Therefore, nodeID is derived from "pipeline ID".
type fanOutNode struct {
attribute.Attributes
pipelineID pipeline.ID
baseConsumer
}
func newFanOutNode(pipelineID pipeline.ID) *fanOutNode {
return &fanOutNode{
Attributes: attribute.Fanout(pipelineID),
pipelineID: pipelineID,
}
}
func (n *fanOutNode) getConsumer() baseConsumer {
return n.baseConsumer
}
================================================
FILE: service/internal/graph/graph.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package graph contains the internal graph representation of the pipelines.
//
// [Build] is the constructor for a [Graph] object. The method calls out to helpers that transform the graph from a config
// to a DAG of components. The configuration undergoes additional validation here as well, and is used to instantiate
// the components of the pipeline.
//
// [Graph.StartAll] starts all components in each pipeline.
//
// [Graph.ShutdownAll] stops all components in each pipeline.
package graph // import "go.opentelemetry.io/collector/service/internal/graph"
import (
"context"
"errors"
"fmt"
"strings"
"go.uber.org/multierr"
"go.uber.org/zap"
"gonum.org/v1/gonum/graph"
"gonum.org/v1/gonum/graph/simple"
"gonum.org/v1/gonum/graph/topo"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componentstatus"
"go.opentelemetry.io/collector/connector"
"go.opentelemetry.io/collector/connector/xconnector"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/internal/fanoutconsumer"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/pipeline/xpipeline"
"go.opentelemetry.io/collector/service/hostcapabilities"
"go.opentelemetry.io/collector/service/internal/builders"
"go.opentelemetry.io/collector/service/internal/capabilityconsumer"
"go.opentelemetry.io/collector/service/internal/status"
"go.opentelemetry.io/collector/service/pipelines"
)
// Settings holds configuration for building builtPipelines.
type Settings struct {
Telemetry component.TelemetrySettings
BuildInfo component.BuildInfo
ReceiverBuilder *builders.ReceiverBuilder
ProcessorBuilder *builders.ProcessorBuilder
ExporterBuilder *builders.ExporterBuilder
ConnectorBuilder *builders.ConnectorBuilder
// PipelineConfigs is a map of component.ID to PipelineConfig.
PipelineConfigs pipelines.Config
ReportStatus status.ServiceStatusFunc
}
type Graph struct {
// All component instances represented as nodes, with directed edges indicating data flow.
componentGraph *simple.DirectedGraph
// Keep track of how nodes relate to pipelines, so we can declare edges in the graph.
pipelines map[pipeline.ID]*pipelineNodes
// Keep track of status source per node
instanceIDs map[int64]*componentstatus.InstanceID
telemetry component.TelemetrySettings
}
// Build builds a full pipeline graph.
// Build also validates the configuration of the pipelines and does the actual initialization of each Component in the Graph.
func Build(ctx context.Context, set Settings) (*Graph, error) {
pipelines := &Graph{
componentGraph: simple.NewDirectedGraph(),
pipelines: make(map[pipeline.ID]*pipelineNodes, len(set.PipelineConfigs)),
instanceIDs: make(map[int64]*componentstatus.InstanceID),
telemetry: set.Telemetry,
}
for pipelineID := range set.PipelineConfigs {
pipelines.pipelines[pipelineID] = &pipelineNodes{
receivers: make(map[int64]graph.Node),
exporters: make(map[int64]graph.Node),
}
}
if err := pipelines.createNodes(set); err != nil {
return nil, err
}
pipelines.createEdges()
err := pipelines.buildComponents(ctx, set)
return pipelines, err
}
// Creates a node for each instance of a component and adds it to the graph.
// Validates that connectors are configured to export and receive correctly.
func (g *Graph) createNodes(set Settings) error {
// Build a list of all connectors for easy reference.
connectors := make(map[component.ID]struct{})
// Keep track of connectors and where they are used. (map[connectorID][]pipelineID).
connectorsAsExporter := make(map[component.ID][]pipeline.ID)
connectorsAsReceiver := make(map[component.ID][]pipeline.ID)
// Build each pipelineNodes struct for each pipeline by parsing the pipelineCfg.
// Also populates the connectors, connectorsAsExporter and connectorsAsReceiver maps.
for pipelineID, pipelineCfg := range set.PipelineConfigs {
pipe := g.pipelines[pipelineID]
for _, recvID := range pipelineCfg.Receivers {
// Checks if this receiver is a connector or a regular receiver.
if set.ConnectorBuilder.IsConfigured(recvID) {
connectors[recvID] = struct{}{}
connectorsAsReceiver[recvID] = append(connectorsAsReceiver[recvID], pipelineID)
continue
}
rcvrNode := g.createReceiver(pipelineID, recvID)
pipe.receivers[rcvrNode.ID()] = rcvrNode
}
pipe.capabilitiesNode = newCapabilitiesNode(pipelineID)
for _, procID := range pipelineCfg.Processors {
procNode := g.createProcessor(pipelineID, procID)
pipe.processors = append(pipe.processors, procNode)
}
pipe.fanOutNode = newFanOutNode(pipelineID)
for _, exprID := range pipelineCfg.Exporters {
if set.ConnectorBuilder.IsConfigured(exprID) {
connectors[exprID] = struct{}{}
connectorsAsExporter[exprID] = append(connectorsAsExporter[exprID], pipelineID)
continue
}
expNode := g.createExporter(pipelineID, exprID)
pipe.exporters[expNode.ID()] = expNode
}
}
for connID := range connectors {
factory := set.ConnectorBuilder.Factory(connID.Type())
if factory == nil {
return fmt.Errorf("connector factory not available for: %q", connID.Type())
}
connFactory := factory.(connector.Factory)
expTypes := make(map[pipeline.Signal]bool)
for _, pipelineID := range connectorsAsExporter[connID] {
// The presence of each key indicates how the connector is used as an exporter.
// The value is initially set to false. Later we will set the value to true *if* we
// confirm that there is a supported corresponding use as a receiver.
expTypes[pipelineID.Signal()] = false
}
recTypes := make(map[pipeline.Signal]bool)
for _, pipelineID := range connectorsAsReceiver[connID] {
// The presence of each key indicates how the connector is used as a receiver.
// The value is initially set to false. Later we will set the value to true *if* we
// confirm that there is a supported corresponding use as an exporter.
recTypes[pipelineID.Signal()] = false
}
for expType := range expTypes {
for recType := range recTypes {
// Typechecks the connector's receiving and exporting datatypes.
if connectorStability(connFactory, expType, recType) != component.StabilityLevelUndefined {
expTypes[expType] = true
recTypes[recType] = true
}
}
}
for expType, supportedUse := range expTypes {
if supportedUse {
continue
}
return fmt.Errorf("connector %q used as exporter in %v pipeline but not used in any supported receiver pipeline", connID, formatPipelineNamesWithSignal(connectorsAsExporter[connID], expType))
}
for recType, supportedUse := range recTypes {
if supportedUse {
continue
}
return fmt.Errorf("connector %q used as receiver in %v pipeline but not used in any supported exporter pipeline", connID, formatPipelineNamesWithSignal(connectorsAsReceiver[connID], recType))
}
for _, eID := range connectorsAsExporter[connID] {
for _, rID := range connectorsAsReceiver[connID] {
if connectorStability(connFactory, eID.Signal(), rID.Signal()) == component.StabilityLevelUndefined {
// Connector is not supported for this combination, but we know it is used correctly elsewhere
continue
}
connNode := g.createConnector(eID, rID, connID)
g.pipelines[eID].exporters[connNode.ID()] = connNode
g.pipelines[rID].receivers[connNode.ID()] = connNode
}
}
}
return nil
}
// formatPipelineNamesWithSignal formats pipeline name with signal as "signal[/name]" format.
func formatPipelineNamesWithSignal(pipelineIDs []pipeline.ID, signal pipeline.Signal) []string {
var formatted []string
for _, pid := range pipelineIDs {
if pid.Signal() == signal {
formatted = append(formatted, pid.String())
}
}
return formatted
}
func (g *Graph) createReceiver(pipelineID pipeline.ID, recvID component.ID) *receiverNode {
rcvrNode := newReceiverNode(pipelineID.Signal(), recvID)
if node := g.componentGraph.Node(rcvrNode.ID()); node != nil {
instanceID := g.instanceIDs[node.ID()]
g.instanceIDs[node.ID()] = instanceID.WithPipelines(pipelineID)
return node.(*receiverNode)
}
g.componentGraph.AddNode(rcvrNode)
g.instanceIDs[rcvrNode.ID()] = componentstatus.NewInstanceID(
recvID, component.KindReceiver, pipelineID,
)
return rcvrNode
}
func (g *Graph) createProcessor(pipelineID pipeline.ID, procID component.ID) *processorNode {
procNode := newProcessorNode(pipelineID, procID)
g.componentGraph.AddNode(procNode)
g.instanceIDs[procNode.ID()] = componentstatus.NewInstanceID(
procID, component.KindProcessor, pipelineID,
)
return procNode
}
func (g *Graph) createExporter(pipelineID pipeline.ID, exprID component.ID) *exporterNode {
expNode := newExporterNode(pipelineID.Signal(), exprID)
if node := g.componentGraph.Node(expNode.ID()); node != nil {
instanceID := g.instanceIDs[expNode.ID()]
g.instanceIDs[expNode.ID()] = instanceID.WithPipelines(pipelineID)
return node.(*exporterNode)
}
g.componentGraph.AddNode(expNode)
g.instanceIDs[expNode.ID()] = componentstatus.NewInstanceID(
expNode.componentID, component.KindExporter, pipelineID,
)
return expNode
}
func (g *Graph) createConnector(exprPipelineID, rcvrPipelineID pipeline.ID, connID component.ID) *connectorNode {
connNode := newConnectorNode(exprPipelineID.Signal(), rcvrPipelineID.Signal(), connID)
if node := g.componentGraph.Node(connNode.ID()); node != nil {
instanceID := g.instanceIDs[connNode.ID()]
g.instanceIDs[connNode.ID()] = instanceID.WithPipelines(exprPipelineID, rcvrPipelineID)
return node.(*connectorNode)
}
g.componentGraph.AddNode(connNode)
g.instanceIDs[connNode.ID()] = componentstatus.NewInstanceID(
connNode.componentID, component.KindConnector, exprPipelineID, rcvrPipelineID,
)
return connNode
}
// Iterates through the pipelines and creates edges between components.
func (g *Graph) createEdges() {
for _, pg := range g.pipelines {
// Draw edges from each receiver to the capability node.
for _, receiver := range pg.receivers {
g.componentGraph.SetEdge(g.componentGraph.NewEdge(receiver, pg.capabilitiesNode))
}
// Iterates through processors, chaining them together. starts with the capabilities node.
var from, to graph.Node
from = pg.capabilitiesNode
for _, processor := range pg.processors {
to = processor
g.componentGraph.SetEdge(g.componentGraph.NewEdge(from, to))
from = processor
}
// Always inserts a fanout node before any exporters. If there is only one
// exporter, the fanout node is still created and acts as a noop.
to = pg.fanOutNode
g.componentGraph.SetEdge(g.componentGraph.NewEdge(from, to))
for _, exporter := range pg.exporters {
g.componentGraph.SetEdge(g.componentGraph.NewEdge(pg.fanOutNode, exporter))
}
}
}
// Uses the already built graph g to instantiate the actual components for each component of each pipeline.
// Handles calling the factories for each component - and hooking up each component to the next.
// Also calculates whether each pipeline mutates data so the receiver can know whether it needs to clone the data.
func (g *Graph) buildComponents(ctx context.Context, set Settings) error {
nodes, err := topo.Sort(g.componentGraph)
if err != nil {
return cycleErr(err, topo.DirectedCyclesIn(g.componentGraph))
}
for i := len(nodes) - 1; i >= 0; i-- {
node := nodes[i]
switch n := node.(type) {
case *receiverNode:
err = n.buildComponent(ctx, set.Telemetry, set.BuildInfo, set.ReceiverBuilder, g.nextConsumers(n.ID()))
case *processorNode:
// nextConsumers is guaranteed to be length 1. Either it is the next processor or it is the fanout node for the exporters.
err = n.buildComponent(ctx, set.Telemetry, set.BuildInfo, set.ProcessorBuilder, g.nextConsumers(n.ID())[0])
case *exporterNode:
err = n.buildComponent(ctx, set.Telemetry, set.BuildInfo, set.ExporterBuilder)
case *connectorNode:
err = n.buildComponent(ctx, set.Telemetry, set.BuildInfo, set.ConnectorBuilder, g.nextConsumers(n.ID()))
case *capabilitiesNode:
capability := consumer.Capabilities{
// The fanOutNode represents the aggregate capabilities of the exporters in the pipeline.
MutatesData: g.pipelines[n.pipelineID].fanOutNode.getConsumer().Capabilities().MutatesData,
}
for _, proc := range g.pipelines[n.pipelineID].processors {
capability.MutatesData = capability.MutatesData || proc.(*processorNode).getConsumer().Capabilities().MutatesData
}
next := g.nextConsumers(n.ID())[0]
switch n.pipelineID.Signal() {
case pipeline.SignalTraces:
cc := capabilityconsumer.NewTraces(next.(consumer.Traces), capability)
n.baseConsumer = cc
n.ConsumeTracesFunc = cc.ConsumeTraces
case pipeline.SignalMetrics:
cc := capabilityconsumer.NewMetrics(next.(consumer.Metrics), capability)
n.baseConsumer = cc
n.ConsumeMetricsFunc = cc.ConsumeMetrics
case pipeline.SignalLogs:
cc := capabilityconsumer.NewLogs(next.(consumer.Logs), capability)
n.baseConsumer = cc
n.ConsumeLogsFunc = cc.ConsumeLogs
case xpipeline.SignalProfiles:
cc := capabilityconsumer.NewProfiles(next.(xconsumer.Profiles), capability)
n.baseConsumer = cc
n.ConsumeProfilesFunc = cc.ConsumeProfiles
}
case *fanOutNode:
nexts := g.nextConsumers(n.ID())
switch n.pipelineID.Signal() {
case pipeline.SignalTraces:
consumers := make([]consumer.Traces, 0, len(nexts))
for _, next := range nexts {
consumers = append(consumers, next.(consumer.Traces))
}
n.baseConsumer = fanoutconsumer.NewTraces(consumers)
case pipeline.SignalMetrics:
consumers := make([]consumer.Metrics, 0, len(nexts))
for _, next := range nexts {
consumers = append(consumers, next.(consumer.Metrics))
}
n.baseConsumer = fanoutconsumer.NewMetrics(consumers)
case pipeline.SignalLogs:
consumers := make([]consumer.Logs, 0, len(nexts))
for _, next := range nexts {
consumers = append(consumers, next.(consumer.Logs))
}
n.baseConsumer = fanoutconsumer.NewLogs(consumers)
case xpipeline.SignalProfiles:
consumers := make([]xconsumer.Profiles, 0, len(nexts))
for _, next := range nexts {
consumers = append(consumers, next.(xconsumer.Profiles))
}
n.baseConsumer = fanoutconsumer.NewProfiles(consumers)
}
}
if err != nil {
return err
}
}
return nil
}
// Find all nodes
func (g *Graph) nextConsumers(nodeID int64) []baseConsumer {
nextNodes := g.componentGraph.From(nodeID)
nexts := make([]baseConsumer, 0, nextNodes.Len())
for nextNodes.Next() {
nexts = append(nexts, nextNodes.Node().(consumerNode).getConsumer())
}
return nexts
}
// A node-based representation of a pipeline configuration.
type pipelineNodes struct {
// Use map to assist with deduplication of connector instances.
receivers map[int64]graph.Node
// The node to which receivers emit. Passes through to processors.
// Easily accessible as the first node in a pipeline.
*capabilitiesNode
// The order of processors is very important. Therefore use a slice for processors.
processors []graph.Node
// Emits to exporters.
*fanOutNode
// Use map to assist with deduplication of connector instances.
exporters map[int64]graph.Node
}
func (g *Graph) StartAll(ctx context.Context, host *Host) error {
if host == nil {
return errors.New("host cannot be nil")
}
nodes, err := topo.Sort(g.componentGraph)
if err != nil {
return err
}
// Start in reverse topological order so that downstream components
// are started before upstream components. This ensures that each
// component's consumer is ready to consume.
for i := len(nodes) - 1; i >= 0; i-- {
node := nodes[i]
comp, ok := node.(component.Component)
if !ok {
// Skip capabilities/fanout nodes
continue
}
instanceID := g.instanceIDs[node.ID()]
host.Reporter.ReportStatus(
instanceID,
componentstatus.NewEvent(componentstatus.StatusStarting),
)
if compErr := comp.Start(ctx, &HostWrapper{Host: host, InstanceID: instanceID}); compErr != nil {
host.Reporter.ReportStatus(
instanceID,
componentstatus.NewPermanentErrorEvent(compErr),
)
// We log with zap.AddStacktrace(zap.DPanicLevel) to avoid adding the stack trace to the error log
g.telemetry.Logger.WithOptions(zap.AddStacktrace(zap.DPanicLevel)).
Error("Failed to start component",
zap.Error(compErr),
zap.String("type", instanceID.Kind().String()),
zap.String("id", instanceID.ComponentID().String()),
)
return fmt.Errorf("failed to start %q %s: %w", instanceID.ComponentID().String(), strings.ToLower(instanceID.Kind().String()), compErr)
}
host.Reporter.ReportOKIfStarting(instanceID)
}
return nil
}
func (g *Graph) ShutdownAll(ctx context.Context, reporter status.Reporter) error {
nodes, err := topo.Sort(g.componentGraph)
if err != nil {
return err
}
// Stop in topological order so that upstream components
// are stopped before downstream components. This ensures
// that each component has a chance to drain to its consumer
// before the consumer is stopped.
var errs error
for i := range nodes {
node := nodes[i]
comp, ok := node.(component.Component)
if !ok {
// Skip capabilities/fanout nodes
continue
}
instanceID := g.instanceIDs[node.ID()]
reporter.ReportStatus(
instanceID,
componentstatus.NewEvent(componentstatus.StatusStopping),
)
if compErr := comp.Shutdown(ctx); compErr != nil {
errs = multierr.Append(errs, compErr)
reporter.ReportStatus(
instanceID,
componentstatus.NewPermanentErrorEvent(compErr),
)
continue
}
reporter.ReportStatus(
instanceID,
componentstatus.NewEvent(componentstatus.StatusStopped),
)
}
return errs
}
func (g *Graph) GetExporters() map[pipeline.Signal]map[component.ID]component.Component {
exporters := make(map[pipeline.Signal]map[component.ID]component.Component)
exporters[pipeline.SignalTraces] = make(map[component.ID]component.Component)
exporters[pipeline.SignalMetrics] = make(map[component.ID]component.Component)
exporters[pipeline.SignalLogs] = make(map[component.ID]component.Component)
exporters[xpipeline.SignalProfiles] = make(map[component.ID]component.Component)
for _, pg := range g.pipelines {
for _, expNode := range pg.exporters {
// Skip connectors, otherwise individual components can introduce cycles
if expNode, ok := g.componentGraph.Node(expNode.ID()).(*exporterNode); ok {
exporters[expNode.pipelineType][expNode.componentID] = expNode.Component
}
}
}
return exporters
}
func cycleErr(err error, cycles [][]graph.Node) error {
var topoErr topo.Unorderable
if !errors.As(err, &topoErr) || len(cycles) == 0 || len(cycles[0]) == 0 {
return err
}
// There may be multiple cycles, but report only the first one.
cycle := cycles[0]
// The last node is a duplicate of the first node.
// Remove it because we may start from a different node.
cycle = cycle[:len(cycle)-1]
// A cycle always contains a connector. For the sake of consistent
// error messages report the cycle starting from a connector.
for i := 0; i < len(cycle); i++ {
if _, ok := cycle[i].(*connectorNode); ok {
cycle = append(cycle[i:], cycle[:i]...)
break
}
}
// Repeat the first node at the end to clarify the cycle
cycle = append(cycle, cycle[0])
// Build the error message
componentDetails := make([]string, 0, len(cycle))
for _, node := range cycle {
switch n := node.(type) {
case *processorNode:
componentDetails = append(componentDetails, fmt.Sprintf("processor %q in pipeline %q", n.componentID, n.pipelineID.String()))
case *connectorNode:
componentDetails = append(componentDetails, fmt.Sprintf("connector %q (%s to %s)", n.componentID, n.exprPipelineType, n.rcvrPipelineType))
default:
continue // skip capabilities/fanout nodes
}
}
return fmt.Errorf("cycle detected: %s", strings.Join(componentDetails, " -> "))
}
func connectorStability(f connector.Factory, expType, recType pipeline.Signal) component.StabilityLevel {
switch expType {
case pipeline.SignalTraces:
switch recType {
case pipeline.SignalTraces:
return f.TracesToTracesStability()
case pipeline.SignalMetrics:
return f.TracesToMetricsStability()
case pipeline.SignalLogs:
return f.TracesToLogsStability()
case xpipeline.SignalProfiles:
fprof, ok := f.(xconnector.Factory)
if !ok {
return component.StabilityLevelUndefined
}
return fprof.TracesToProfilesStability()
}
case pipeline.SignalMetrics:
switch recType {
case pipeline.SignalTraces:
return f.MetricsToTracesStability()
case pipeline.SignalMetrics:
return f.MetricsToMetricsStability()
case pipeline.SignalLogs:
return f.MetricsToLogsStability()
case xpipeline.SignalProfiles:
fprof, ok := f.(xconnector.Factory)
if !ok {
return component.StabilityLevelUndefined
}
return fprof.MetricsToProfilesStability()
}
case pipeline.SignalLogs:
switch recType {
case pipeline.SignalTraces:
return f.LogsToTracesStability()
case pipeline.SignalMetrics:
return f.LogsToMetricsStability()
case pipeline.SignalLogs:
return f.LogsToLogsStability()
case xpipeline.SignalProfiles:
fprof, ok := f.(xconnector.Factory)
if !ok {
return component.StabilityLevelUndefined
}
return fprof.LogsToProfilesStability()
}
case xpipeline.SignalProfiles:
fprof, ok := f.(xconnector.Factory)
if !ok {
return component.StabilityLevelUndefined
}
switch recType {
case pipeline.SignalTraces:
return fprof.ProfilesToTracesStability()
case pipeline.SignalMetrics:
return fprof.ProfilesToMetricsStability()
case pipeline.SignalLogs:
return fprof.ProfilesToLogsStability()
case xpipeline.SignalProfiles:
return fprof.ProfilesToProfilesStability()
}
}
return component.StabilityLevelUndefined
}
var (
_ component.Host = (*HostWrapper)(nil)
_ componentstatus.Reporter = (*HostWrapper)(nil)
_ hostcapabilities.ExposeExporters = (*HostWrapper)(nil) //nolint:staticcheck // SA1019
)
type HostWrapper struct {
*Host
InstanceID *componentstatus.InstanceID
}
func (host *HostWrapper) Report(event *componentstatus.Event) {
host.Reporter.ReportStatus(host.InstanceID, event)
}
================================================
FILE: service/internal/graph/graph_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package graph
import (
"context"
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componentstatus"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/connector"
"go.opentelemetry.io/collector/connector/connectortest"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/pdata/testdata"
"go.opentelemetry.io/collector/pdata/xpdata/pref"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/pipeline/xpipeline"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/processortest"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/receivertest"
"go.opentelemetry.io/collector/service/internal/builders"
"go.opentelemetry.io/collector/service/internal/status"
"go.opentelemetry.io/collector/service/internal/testcomponents"
"go.opentelemetry.io/collector/service/pipelines"
)
func TestConnectorPipelinesGraph(t *testing.T) {
t.Run("with_internal_telemetry", func(t *testing.T) {
setObsConsumerGateForTest(t, true)
testConnectorPipelinesGraph(t)
})
t.Run("without_internal_telemetry", func(t *testing.T) {
setObsConsumerGateForTest(t, false)
testConnectorPipelinesGraph(t)
})
}
func testConnectorPipelinesGraph(t *testing.T) {
tests := []struct {
name string
pipelineConfigs pipelines.Config
expectedPerExporter int // requires symmetry in Pipelines
}{
{
name: "pipelines_simple.yaml",
pipelineConfigs: pipelines.Config{
pipeline.NewID(pipeline.SignalTraces): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewID(pipeline.SignalMetrics): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewID(pipeline.SignalLogs): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewID(xpipeline.SignalProfiles): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
},
expectedPerExporter: 1,
},
{
name: "pipelines_simple_mutate.yaml",
pipelineConfigs: pipelines.Config{
pipeline.NewID(pipeline.SignalTraces): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewIDWithName("exampleprocessor", "mutate")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewID(pipeline.SignalMetrics): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewIDWithName("exampleprocessor", "mutate")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewID(pipeline.SignalLogs): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewIDWithName("exampleprocessor", "mutate")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewID(xpipeline.SignalProfiles): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewIDWithName("exampleprocessor", "mutate")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
},
expectedPerExporter: 1,
},
{
name: "pipelines_simple_multi_proc.yaml",
pipelineConfigs: pipelines.Config{
pipeline.NewID(pipeline.SignalTraces): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor"), component.MustNewIDWithName("exampleprocessor", "mutate")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewID(pipeline.SignalMetrics): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor"), component.MustNewIDWithName("exampleprocessor", "mutate")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewID(pipeline.SignalLogs): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor"), component.MustNewIDWithName("exampleprocessor", "mutate")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewID(xpipeline.SignalProfiles): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor"), component.MustNewIDWithName("exampleprocessor", "mutate")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
},
expectedPerExporter: 1,
},
{
name: "pipelines_simple_no_proc.yaml",
pipelineConfigs: pipelines.Config{
pipeline.NewID(pipeline.SignalTraces): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewID(pipeline.SignalMetrics): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewID(pipeline.SignalLogs): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewID(xpipeline.SignalProfiles): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
},
expectedPerExporter: 1,
},
{
name: "pipelines_multi.yaml",
pipelineConfigs: pipelines.Config{
pipeline.NewID(pipeline.SignalTraces): {
Receivers: []component.ID{component.MustNewID("examplereceiver"), component.MustNewIDWithName("examplereceiver", "1")},
Processors: []component.ID{component.MustNewIDWithName("exampleprocessor", "mutate"), component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter"), component.MustNewIDWithName("exampleexporter", "1")},
},
pipeline.NewID(pipeline.SignalMetrics): {
Receivers: []component.ID{component.MustNewID("examplereceiver"), component.MustNewIDWithName("examplereceiver", "1")},
Processors: []component.ID{component.MustNewIDWithName("exampleprocessor", "mutate"), component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter"), component.MustNewIDWithName("exampleexporter", "1")},
},
pipeline.NewID(pipeline.SignalLogs): {
Receivers: []component.ID{component.MustNewID("examplereceiver"), component.MustNewIDWithName("examplereceiver", "1")},
Processors: []component.ID{component.MustNewIDWithName("exampleprocessor", "mutate"), component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter"), component.MustNewIDWithName("exampleexporter", "1")},
},
pipeline.NewID(xpipeline.SignalProfiles): {
Receivers: []component.ID{component.MustNewID("examplereceiver"), component.MustNewIDWithName("examplereceiver", "1")},
Processors: []component.ID{component.MustNewIDWithName("exampleprocessor", "mutate"), component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter"), component.MustNewIDWithName("exampleexporter", "1")},
},
},
expectedPerExporter: 2,
},
{
name: "pipelines_multi_no_proc.yaml",
pipelineConfigs: pipelines.Config{
pipeline.NewID(pipeline.SignalTraces): {
Receivers: []component.ID{component.MustNewID("examplereceiver"), component.MustNewIDWithName("examplereceiver", "1")},
Exporters: []component.ID{component.MustNewID("exampleexporter"), component.MustNewIDWithName("exampleexporter", "1")},
},
pipeline.NewID(pipeline.SignalMetrics): {
Receivers: []component.ID{component.MustNewID("examplereceiver"), component.MustNewIDWithName("examplereceiver", "1")},
Exporters: []component.ID{component.MustNewID("exampleexporter"), component.MustNewIDWithName("exampleexporter", "1")},
},
pipeline.NewID(pipeline.SignalLogs): {
Receivers: []component.ID{component.MustNewID("examplereceiver"), component.MustNewIDWithName("examplereceiver", "1")},
Exporters: []component.ID{component.MustNewID("exampleexporter"), component.MustNewIDWithName("exampleexporter", "1")},
},
pipeline.NewID(xpipeline.SignalProfiles): {
Receivers: []component.ID{component.MustNewID("examplereceiver"), component.MustNewIDWithName("examplereceiver", "1")},
Exporters: []component.ID{component.MustNewID("exampleexporter"), component.MustNewIDWithName("exampleexporter", "1")},
},
},
expectedPerExporter: 2,
},
{
name: "multi_pipeline_receivers_and_exporters.yaml",
pipelineConfigs: pipelines.Config{
pipeline.NewID(pipeline.SignalTraces): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewIDWithName("exampleprocessor", "mutate")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewIDWithName(pipeline.SignalTraces, "1"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewID(pipeline.SignalMetrics): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewIDWithName("exampleprocessor", "mutate")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewIDWithName(pipeline.SignalMetrics, "1"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewID(pipeline.SignalLogs): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewIDWithName("exampleprocessor", "mutate")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewIDWithName(pipeline.SignalLogs, "1"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewID(xpipeline.SignalProfiles): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewIDWithName("exampleprocessor", "mutate")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewIDWithName(xpipeline.SignalProfiles, "1"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
},
expectedPerExporter: 2,
},
{
name: "pipelines_conn_simple_traces.yaml",
pipelineConfigs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalTraces, "in"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
},
pipeline.NewIDWithName(pipeline.SignalTraces, "out"): {
Receivers: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
Processors: []component.ID{component.MustNewIDWithName("exampleprocessor", "mutate")}, // mutate propagates upstream to connector
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
},
expectedPerExporter: 1,
},
{
name: "pipelines_conn_simple_metrics.yaml",
pipelineConfigs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalMetrics, "in"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
},
pipeline.NewIDWithName(pipeline.SignalMetrics, "out"): {
Receivers: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
Processors: []component.ID{component.MustNewIDWithName("exampleprocessor", "mutate")}, // mutate propagates upstream to connector
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
},
expectedPerExporter: 1,
},
{
name: "pipelines_conn_simple_logs.yaml",
pipelineConfigs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalLogs, "in"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
},
pipeline.NewIDWithName(pipeline.SignalLogs, "out"): {
Receivers: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
Processors: []component.ID{component.MustNewIDWithName("exampleprocessor", "mutate")}, // mutate propagates upstream to connector
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
},
expectedPerExporter: 1,
},
{
name: "pipelines_conn_simple_profiles.yaml",
pipelineConfigs: pipelines.Config{
pipeline.NewIDWithName(xpipeline.SignalProfiles, "in"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
},
pipeline.NewIDWithName(xpipeline.SignalProfiles, "out"): {
Receivers: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
Processors: []component.ID{component.MustNewIDWithName("exampleprocessor", "mutate")}, // mutate propagates upstream to connector
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
},
expectedPerExporter: 1,
},
{
name: "pipelines_conn_fork_merge_traces.yaml",
pipelineConfigs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalTraces, "in"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
},
pipeline.NewIDWithName(pipeline.SignalTraces, "type0"): {
Receivers: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewIDWithName("exampleconnector", "merge")},
},
pipeline.NewIDWithName(pipeline.SignalTraces, "type1"): {
Receivers: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
Processors: []component.ID{component.MustNewIDWithName("exampleprocessor", "mutate")}, // mutate propagates upstream to connector
Exporters: []component.ID{component.MustNewIDWithName("exampleconnector", "merge")},
},
pipeline.NewIDWithName(pipeline.SignalTraces, "out"): {
Receivers: []component.ID{component.MustNewIDWithName("exampleconnector", "merge")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
},
expectedPerExporter: 2,
},
{
name: "pipelines_conn_fork_merge_metrics.yaml",
pipelineConfigs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalMetrics, "in"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
},
pipeline.NewIDWithName(pipeline.SignalMetrics, "type0"): {
Receivers: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewIDWithName("exampleconnector", "merge")},
},
pipeline.NewIDWithName(pipeline.SignalMetrics, "type1"): {
Receivers: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
Processors: []component.ID{component.MustNewIDWithName("exampleprocessor", "mutate")}, // mutate propagates upstream to connector
Exporters: []component.ID{component.MustNewIDWithName("exampleconnector", "merge")},
},
pipeline.NewIDWithName(pipeline.SignalMetrics, "out"): {
Receivers: []component.ID{component.MustNewIDWithName("exampleconnector", "merge")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
},
expectedPerExporter: 2,
},
{
name: "pipelines_conn_fork_merge_logs.yaml",
pipelineConfigs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalLogs, "in"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
},
pipeline.NewIDWithName(pipeline.SignalLogs, "type0"): {
Receivers: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewIDWithName("exampleconnector", "merge")},
},
pipeline.NewIDWithName(pipeline.SignalLogs, "type1"): {
Receivers: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
Processors: []component.ID{component.MustNewIDWithName("exampleprocessor", "mutate")}, // mutate propagates upstream to connector
Exporters: []component.ID{component.MustNewIDWithName("exampleconnector", "merge")},
},
pipeline.NewIDWithName(pipeline.SignalLogs, "out"): {
Receivers: []component.ID{component.MustNewIDWithName("exampleconnector", "merge")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
},
expectedPerExporter: 2,
},
{
name: "pipelines_conn_fork_merge_profiles.yaml",
pipelineConfigs: pipelines.Config{
pipeline.NewIDWithName(xpipeline.SignalProfiles, "in"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
},
pipeline.NewIDWithName(xpipeline.SignalProfiles, "type0"): {
Receivers: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewIDWithName("exampleconnector", "merge")},
},
pipeline.NewIDWithName(xpipeline.SignalProfiles, "type1"): {
Receivers: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
Processors: []component.ID{component.MustNewIDWithName("exampleprocessor", "mutate")}, // mutate propagates upstream to connector
Exporters: []component.ID{component.MustNewIDWithName("exampleconnector", "merge")},
},
pipeline.NewIDWithName(xpipeline.SignalProfiles, "out"): {
Receivers: []component.ID{component.MustNewIDWithName("exampleconnector", "merge")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
},
expectedPerExporter: 2,
},
{
name: "pipelines_conn_translate_from_traces.yaml",
pipelineConfigs: pipelines.Config{
pipeline.NewID(pipeline.SignalTraces): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleconnector")},
},
pipeline.NewID(pipeline.SignalMetrics): {
Receivers: []component.ID{component.MustNewID("exampleconnector")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewID(pipeline.SignalLogs): {
Receivers: []component.ID{component.MustNewID("exampleconnector")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewID(xpipeline.SignalProfiles): {
Receivers: []component.ID{component.MustNewID("exampleconnector")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
},
expectedPerExporter: 1,
},
{
name: "pipelines_conn_translate_from_metrics.yaml",
pipelineConfigs: pipelines.Config{
pipeline.NewID(pipeline.SignalMetrics): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleconnector")},
},
pipeline.NewID(pipeline.SignalTraces): {
Receivers: []component.ID{component.MustNewID("exampleconnector")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewID(pipeline.SignalLogs): {
Receivers: []component.ID{component.MustNewID("exampleconnector")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewID(xpipeline.SignalProfiles): {
Receivers: []component.ID{component.MustNewID("exampleconnector")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
},
expectedPerExporter: 1,
},
{
name: "pipelines_conn_translate_from_logs.yaml",
pipelineConfigs: pipelines.Config{
pipeline.NewID(pipeline.SignalLogs): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleconnector")},
},
pipeline.NewID(pipeline.SignalTraces): {
Receivers: []component.ID{component.MustNewID("exampleconnector")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewID(pipeline.SignalMetrics): {
Receivers: []component.ID{component.MustNewID("exampleconnector")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewID(xpipeline.SignalProfiles): {
Receivers: []component.ID{component.MustNewID("exampleconnector")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
},
expectedPerExporter: 1,
},
{
name: "pipelines_conn_translate_from_profiles.yaml",
pipelineConfigs: pipelines.Config{
pipeline.NewID(pipeline.SignalTraces): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleconnector")},
},
pipeline.NewID(pipeline.SignalMetrics): {
Receivers: []component.ID{component.MustNewID("exampleconnector")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewID(pipeline.SignalLogs): {
Receivers: []component.ID{component.MustNewID("exampleconnector")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewID(xpipeline.SignalProfiles): {
Receivers: []component.ID{component.MustNewID("exampleconnector")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
},
expectedPerExporter: 1,
},
{
name: "pipelines_conn_matrix_immutable.yaml",
pipelineConfigs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalTraces, "in"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleconnector")},
},
pipeline.NewIDWithName(pipeline.SignalMetrics, "in"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleconnector")},
},
pipeline.NewIDWithName(pipeline.SignalLogs, "in"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleconnector")},
},
pipeline.NewIDWithName(xpipeline.SignalProfiles, "in"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleconnector")},
},
pipeline.NewIDWithName(pipeline.SignalTraces, "out"): {
Receivers: []component.ID{component.MustNewID("exampleconnector")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewIDWithName(pipeline.SignalMetrics, "out"): {
Receivers: []component.ID{component.MustNewID("exampleconnector")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewIDWithName(pipeline.SignalLogs, "out"): {
Receivers: []component.ID{component.MustNewID("exampleconnector")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewIDWithName(xpipeline.SignalProfiles, "out"): {
Receivers: []component.ID{component.MustNewID("exampleconnector")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
},
expectedPerExporter: 4,
},
{
name: "pipelines_conn_matrix_mutable.yaml",
pipelineConfigs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalTraces, "in"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewIDWithName("exampleprocessor", "mutate")},
Exporters: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
},
pipeline.NewIDWithName(pipeline.SignalMetrics, "in"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewIDWithName("exampleprocessor", "mutate")},
Exporters: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
},
pipeline.NewIDWithName(pipeline.SignalLogs, "in"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewIDWithName("exampleprocessor", "mutate")},
Exporters: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
},
pipeline.NewIDWithName(xpipeline.SignalProfiles, "in"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewIDWithName("exampleprocessor", "mutate")},
Exporters: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
},
pipeline.NewIDWithName(pipeline.SignalTraces, "out"): {
Receivers: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
Processors: []component.ID{component.MustNewIDWithName("exampleprocessor", "mutate")}, // mutate propagates upstream to connector
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewIDWithName(pipeline.SignalMetrics, "out"): {
Receivers: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
Processors: []component.ID{component.MustNewIDWithName("exampleprocessor", "mutate")}, // mutate propagates upstream to connector
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewIDWithName(pipeline.SignalLogs, "out"): {
Receivers: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
Processors: []component.ID{component.MustNewIDWithName("exampleprocessor", "mutate")}, // mutate propagates upstream to connector
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewIDWithName(xpipeline.SignalProfiles, "out"): {
Receivers: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
Processors: []component.ID{component.MustNewIDWithName("exampleprocessor", "mutate")}, // mutate propagates upstream to connector
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
},
expectedPerExporter: 4,
},
{
name: "pipelines_conn_lanes.yaml",
pipelineConfigs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalTraces, "in"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Exporters: []component.ID{component.MustNewID("mockforward")},
},
pipeline.NewIDWithName(pipeline.SignalTraces, "out"): {
Receivers: []component.ID{component.MustNewID("mockforward")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewIDWithName(pipeline.SignalMetrics, "in"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Exporters: []component.ID{component.MustNewID("mockforward")},
},
pipeline.NewIDWithName(pipeline.SignalMetrics, "out"): {
Receivers: []component.ID{component.MustNewID("mockforward")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewIDWithName(pipeline.SignalLogs, "in"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Exporters: []component.ID{component.MustNewID("mockforward")},
},
pipeline.NewIDWithName(pipeline.SignalLogs, "out"): {
Receivers: []component.ID{component.MustNewID("mockforward")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewIDWithName(xpipeline.SignalProfiles, "in"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Exporters: []component.ID{component.MustNewID("mockforward")},
},
pipeline.NewIDWithName(xpipeline.SignalProfiles, "out"): {
Receivers: []component.ID{component.MustNewID("mockforward")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
},
expectedPerExporter: 1,
},
{
name: "pipelines_conn_mutate_traces.yaml",
pipelineConfigs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalTraces, "in"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
},
pipeline.NewIDWithName(pipeline.SignalTraces, "out0"): {
Receivers: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewIDWithName(pipeline.SignalTraces, "middle"): {
Receivers: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewIDWithName("exampleconnector", "mutate")},
},
pipeline.NewIDWithName(pipeline.SignalTraces, "out1"): {
Receivers: []component.ID{component.MustNewIDWithName("exampleconnector", "mutate")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
},
expectedPerExporter: 2,
},
{
name: "pipelines_conn_mutate_metrics.yaml",
pipelineConfigs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalMetrics, "in"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
},
pipeline.NewIDWithName(pipeline.SignalMetrics, "out0"): {
Receivers: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewIDWithName(pipeline.SignalMetrics, "middle"): {
Receivers: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewIDWithName("exampleconnector", "mutate")},
},
pipeline.NewIDWithName(pipeline.SignalMetrics, "out1"): {
Receivers: []component.ID{component.MustNewIDWithName("exampleconnector", "mutate")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
},
expectedPerExporter: 2,
},
{
name: "pipelines_conn_mutate_logs.yaml",
pipelineConfigs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalLogs, "in"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
},
pipeline.NewIDWithName(pipeline.SignalLogs, "out0"): {
Receivers: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewIDWithName(pipeline.SignalLogs, "middle"): {
Receivers: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewIDWithName("exampleconnector", "mutate")},
},
pipeline.NewIDWithName(pipeline.SignalLogs, "out1"): {
Receivers: []component.ID{component.MustNewIDWithName("exampleconnector", "mutate")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
},
expectedPerExporter: 2,
},
{
name: "pipelines_conn_mutate_profiles.yaml",
pipelineConfigs: pipelines.Config{
pipeline.NewIDWithName(xpipeline.SignalProfiles, "in"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
},
pipeline.NewIDWithName(xpipeline.SignalProfiles, "out0"): {
Receivers: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewIDWithName(xpipeline.SignalProfiles, "middle"): {
Receivers: []component.ID{component.MustNewIDWithName("exampleconnector", "inherit_mutate")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewIDWithName("exampleconnector", "mutate")},
},
pipeline.NewIDWithName(xpipeline.SignalProfiles, "out1"): {
Receivers: []component.ID{component.MustNewIDWithName("exampleconnector", "mutate")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
},
expectedPerExporter: 2,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Build the pipeline
set := Settings{
Telemetry: componenttest.NewNopTelemetrySettings(),
BuildInfo: component.NewDefaultBuildInfo(),
ReceiverBuilder: builders.NewReceiver(
map[component.ID]component.Config{
component.MustNewID("examplereceiver"): testcomponents.ExampleReceiverFactory.CreateDefaultConfig(),
component.MustNewIDWithName("examplereceiver", "1"): testcomponents.ExampleReceiverFactory.CreateDefaultConfig(),
},
map[component.Type]receiver.Factory{
testcomponents.ExampleReceiverFactory.Type(): testcomponents.ExampleReceiverFactory,
},
),
ProcessorBuilder: builders.NewProcessor(
map[component.ID]component.Config{
component.MustNewID("exampleprocessor"): testcomponents.ExampleProcessorFactory.CreateDefaultConfig(),
component.MustNewIDWithName("exampleprocessor", "mutate"): testcomponents.ExampleProcessorFactory.CreateDefaultConfig(),
},
map[component.Type]processor.Factory{
testcomponents.ExampleProcessorFactory.Type(): testcomponents.ExampleProcessorFactory,
},
),
ExporterBuilder: builders.NewExporter(
map[component.ID]component.Config{
component.MustNewID("exampleexporter"): testcomponents.ExampleExporterFactory.CreateDefaultConfig(),
component.MustNewIDWithName("exampleexporter", "1"): testcomponents.ExampleExporterFactory.CreateDefaultConfig(),
},
map[component.Type]exporter.Factory{
testcomponents.ExampleExporterFactory.Type(): testcomponents.ExampleExporterFactory,
},
),
ConnectorBuilder: builders.NewConnector(
map[component.ID]component.Config{
component.MustNewID("exampleconnector"): testcomponents.ExampleConnectorFactory.CreateDefaultConfig(),
component.MustNewIDWithName("exampleconnector", "merge"): testcomponents.ExampleConnectorFactory.CreateDefaultConfig(),
component.MustNewIDWithName("exampleconnector", "mutate"): testcomponents.ExampleConnectorFactory.CreateDefaultConfig(),
component.MustNewIDWithName("exampleconnector", "inherit_mutate"): testcomponents.ExampleConnectorFactory.CreateDefaultConfig(),
component.MustNewID("mockforward"): testcomponents.MockForwardConnectorFactory.CreateDefaultConfig(),
},
map[component.Type]connector.Factory{
testcomponents.ExampleConnectorFactory.Type(): testcomponents.ExampleConnectorFactory,
testcomponents.MockForwardConnectorFactory.Type(): testcomponents.MockForwardConnectorFactory,
},
),
PipelineConfigs: tt.pipelineConfigs,
}
pg, err := Build(context.Background(), set)
require.NoError(t, err)
assert.Len(t, pg.pipelines, len(tt.pipelineConfigs))
require.NoError(t, pg.StartAll(context.Background(), &Host{Reporter: status.NewReporter(func(*componentstatus.InstanceID, *componentstatus.Event) {}, func(error) {})}))
mutatingPipelines := make(map[pipeline.ID]bool, len(tt.pipelineConfigs))
// Check each pipeline individually, ensuring that all components are started
// and that they have observed no signals yet.
for pipelineID, pipelineCfg := range tt.pipelineConfigs {
pl, ok := pg.pipelines[pipelineID]
require.True(t, ok, "expected to find pipeline: %s", pipelineID.String())
// Determine independently if the capabilities node should report MutateData as true
var expectMutatesData bool
for _, expr := range pipelineCfg.Exporters {
if strings.Contains(expr.Name(), "mutate") {
expectMutatesData = true
}
}
for _, proc := range pipelineCfg.Processors {
if proc.Name() == "mutate" {
expectMutatesData = true
}
}
assert.Equal(t, expectMutatesData, pl.capabilitiesNode.getConsumer().Capabilities().MutatesData)
mutatingPipelines[pipelineID] = expectMutatesData
expectedReceivers, expectedExporters := expectedInstances(tt.pipelineConfigs, pipelineID)
require.Len(t, pl.receivers, expectedReceivers)
require.Len(t, pl.processors, len(pipelineCfg.Processors))
require.Len(t, pl.exporters, expectedExporters)
for _, n := range pl.exporters {
switch c := n.(type) {
case *exporterNode:
e := c.Component.(*testcomponents.ExampleExporter)
require.True(t, e.Started())
require.Empty(t, e.Traces)
require.Empty(t, e.Metrics)
require.Empty(t, e.Logs)
require.Empty(t, e.Profiles)
case *connectorNode:
require.True(t, c.Component.(*testcomponents.ExampleConnector).Started())
default:
require.Fail(t, fmt.Sprintf("unexpected type %T", c))
}
}
for _, n := range pl.processors {
require.True(t, n.(*processorNode).Component.(*testcomponents.ExampleProcessor).Started())
}
for _, n := range pl.receivers {
switch c := n.(type) {
case *receiverNode:
require.True(t, c.Component.(*testcomponents.ExampleReceiver).Started())
case *connectorNode:
require.True(t, c.Component.(*testcomponents.ExampleConnector).Started())
default:
require.Fail(t, fmt.Sprintf("unexpected type %T", c))
}
}
}
// Check that Connectors are correctly inheriting mutability from downstream Pipelines
for expPipelineID, expPipeline := range pg.pipelines {
for _, exp := range expPipeline.exporters {
expConn, ok := exp.(*connectorNode)
if !ok {
continue
}
if expConn.getConsumer().Capabilities().MutatesData {
continue
}
// find all the Pipelines of the same type where this connector is a receiver
var inheritMutatesData bool
for recPipelineID, recPipeline := range pg.pipelines {
if recPipelineID == expPipelineID || recPipelineID.Signal() != expPipelineID.Signal() {
continue
}
for _, rec := range recPipeline.receivers {
recConn, ok := rec.(*connectorNode)
if !ok || recConn.ID() != expConn.ID() {
continue
}
inheritMutatesData = inheritMutatesData || mutatingPipelines[recPipelineID]
}
}
assert.Equal(t, inheritMutatesData, expConn.getConsumer().Capabilities().MutatesData)
}
}
// Push data into the Pipelines. The list of Receivers is retrieved directly from the overall
// component graph because we do not want to duplicate signal inputs to Receivers that are
// shared between Pipelines. The `allReceivers` function also excludes Connectors, which we do
// not want to directly inject with signals.
allReceivers := pg.getReceivers()
for _, c := range allReceivers[pipeline.SignalTraces] {
tracesReceiver := c.(*testcomponents.ExampleReceiver)
require.NoError(t, tracesReceiver.ConsumeTraces(context.Background(), testdata.GenerateTraces(1)))
}
for _, c := range allReceivers[pipeline.SignalMetrics] {
metricsReceiver := c.(*testcomponents.ExampleReceiver)
require.NoError(t, metricsReceiver.ConsumeMetrics(context.Background(), testdata.GenerateMetrics(1)))
}
for _, c := range allReceivers[pipeline.SignalLogs] {
logsReceiver := c.(*testcomponents.ExampleReceiver)
require.NoError(t, logsReceiver.ConsumeLogs(context.Background(), testdata.GenerateLogs(1)))
}
for _, c := range allReceivers[xpipeline.SignalProfiles] {
profilesReceiver := c.(*testcomponents.ExampleReceiver)
require.NoError(t, profilesReceiver.ConsumeProfiles(context.Background(), testdata.GenerateProfiles(1)))
}
// Shut down the entire component graph
require.NoError(t, pg.ShutdownAll(context.Background(), status.NewNopStatusReporter()))
// Check each pipeline individually, ensuring that all components are stopped.
for pipelineID := range tt.pipelineConfigs {
pl, ok := pg.pipelines[pipelineID]
require.True(t, ok, "expected to find pipeline: %s", pipelineID.String())
for _, n := range pl.receivers {
switch c := n.(type) {
case *receiverNode:
require.True(t, c.Component.(*testcomponents.ExampleReceiver).Stopped())
case *connectorNode:
require.True(t, c.Component.(*testcomponents.ExampleConnector).Stopped())
default:
require.Fail(t, fmt.Sprintf("unexpected type %T", c))
}
}
for _, n := range pl.processors {
require.True(t, n.(*processorNode).Component.(*testcomponents.ExampleProcessor).Stopped())
}
for _, n := range pl.exporters {
switch c := n.(type) {
case *exporterNode:
require.True(t, c.Component.(*testcomponents.ExampleExporter).Stopped())
case *connectorNode:
require.True(t, c.Component.(*testcomponents.ExampleConnector).Stopped())
default:
require.Fail(t, fmt.Sprintf("unexpected type %T", c))
}
}
}
// Get the list of Exporters directly from the overall component graph. Like Receivers,
// exclude Connectors and validate each exporter once regardless of sharing between Pipelines.
allExporters := pg.GetExporters()
for _, e := range allExporters[pipeline.SignalTraces] {
tracesExporter := e.(consumer.Traces).(*testcomponents.ExampleExporter)
assert.Len(t, tracesExporter.Traces, tt.expectedPerExporter)
expected := testdata.GenerateTraces(1)
for i := 0; i < tt.expectedPerExporter; i++ {
assert.True(t, pref.EqualTraces(expected, tracesExporter.Traces[i]))
}
}
for _, e := range allExporters[pipeline.SignalMetrics] {
metricsExporter := e.(consumer.Metrics).(*testcomponents.ExampleExporter)
assert.Len(t, metricsExporter.Metrics, tt.expectedPerExporter)
expected := testdata.GenerateMetrics(1)
for i := 0; i < tt.expectedPerExporter; i++ {
assert.True(t, pref.EqualMetrics(expected, metricsExporter.Metrics[i]))
}
}
for _, e := range allExporters[pipeline.SignalLogs] {
logsExporter := e.(consumer.Logs).(*testcomponents.ExampleExporter)
assert.Len(t, logsExporter.Logs, tt.expectedPerExporter)
expected := testdata.GenerateLogs(1)
for i := 0; i < tt.expectedPerExporter; i++ {
assert.True(t, pref.EqualLogs(expected, logsExporter.Logs[i]))
}
}
for _, e := range allExporters[xpipeline.SignalProfiles] {
profilesExporter := e.(xconsumer.Profiles).(*testcomponents.ExampleExporter)
assert.Len(t, profilesExporter.Profiles, tt.expectedPerExporter)
expected := testdata.GenerateProfiles(1)
for i := 0; i < tt.expectedPerExporter; i++ {
assert.True(t, pref.EqualProfiles(expected, profilesExporter.Profiles[i]))
}
}
})
}
}
func TestInstances(t *testing.T) {
t.Run("with_internal_telemetry", func(t *testing.T) {
setObsConsumerGateForTest(t, true)
testInstances(t)
})
t.Run("without_internal_telemetry", func(t *testing.T) {
setObsConsumerGateForTest(t, false)
testInstances(t)
})
}
func testInstances(t *testing.T) {
tests := []struct {
name string
pipelineConfigs pipelines.Config
expectInstances map[component.ID]int
}{
{
name: "one_pipeline_each_signal",
pipelineConfigs: pipelines.Config{
pipeline.NewID(pipeline.SignalTraces): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewID(pipeline.SignalMetrics): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewID(pipeline.SignalLogs): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewID(xpipeline.SignalProfiles): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
},
expectInstances: map[component.ID]int{
component.MustNewID("examplereceiver"): 4, // one per signal
component.MustNewID("exampleprocessor"): 4, // one per pipeline
component.MustNewID("exampleexporter"): 4, // one per signal
},
},
{
name: "shared_by_signals",
pipelineConfigs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalTraces, "1"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewIDWithName(pipeline.SignalTraces, "2"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewIDWithName(pipeline.SignalMetrics, "1"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewIDWithName(pipeline.SignalMetrics, "2"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewIDWithName(pipeline.SignalLogs, "1"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewIDWithName(pipeline.SignalLogs, "2"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewIDWithName(xpipeline.SignalProfiles, "1"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
pipeline.NewIDWithName(xpipeline.SignalProfiles, "2"): {
Receivers: []component.ID{component.MustNewID("examplereceiver")},
Processors: []component.ID{component.MustNewID("exampleprocessor")},
Exporters: []component.ID{component.MustNewID("exampleexporter")},
},
},
expectInstances: map[component.ID]int{
component.MustNewID("examplereceiver"): 4, // one per signal
component.MustNewID("exampleprocessor"): 8, // one per pipeline
component.MustNewID("exampleexporter"): 4, // one per signal
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
set := Settings{
Telemetry: componenttest.NewNopTelemetrySettings(),
BuildInfo: component.NewDefaultBuildInfo(),
ReceiverBuilder: builders.NewReceiver(
map[component.ID]component.Config{
component.MustNewID("examplereceiver"): testcomponents.ExampleReceiverFactory.CreateDefaultConfig(),
},
map[component.Type]receiver.Factory{
testcomponents.ExampleReceiverFactory.Type(): testcomponents.ExampleReceiverFactory,
},
),
ProcessorBuilder: builders.NewProcessor(
map[component.ID]component.Config{
component.MustNewID("exampleprocessor"): testcomponents.ExampleProcessorFactory.CreateDefaultConfig(),
},
map[component.Type]processor.Factory{
testcomponents.ExampleProcessorFactory.Type(): testcomponents.ExampleProcessorFactory,
},
),
ExporterBuilder: builders.NewExporter(
map[component.ID]component.Config{
component.MustNewID("exampleexporter"): testcomponents.ExampleExporterFactory.CreateDefaultConfig(),
},
map[component.Type]exporter.Factory{
testcomponents.ExampleExporterFactory.Type(): testcomponents.ExampleExporterFactory,
},
),
ConnectorBuilder: builders.NewConnector(map[component.ID]component.Config{}, map[component.Type]connector.Factory{}),
PipelineConfigs: tt.pipelineConfigs,
}
pg, err := Build(context.Background(), set)
require.NoError(t, err)
require.Len(t, pg.pipelines, len(set.PipelineConfigs))
// For each component id, build a map of the instances of that component.
// Use graph.Node.ID() as the key to determine uniqueness of instances.
componentInstances := map[component.ID]map[int64]struct{}{}
for _, pipeline := range pg.pipelines {
for _, n := range pipeline.receivers {
r := n.(*receiverNode)
if _, ok := componentInstances[r.componentID]; !ok {
componentInstances[r.componentID] = map[int64]struct{}{}
}
componentInstances[r.componentID][n.ID()] = struct{}{}
}
for _, n := range pipeline.processors {
p := n.(*processorNode)
if _, ok := componentInstances[p.componentID]; !ok {
componentInstances[p.componentID] = map[int64]struct{}{}
}
componentInstances[p.componentID][n.ID()] = struct{}{}
}
for _, n := range pipeline.exporters {
e := n.(*exporterNode)
if _, ok := componentInstances[e.componentID]; !ok {
componentInstances[e.componentID] = map[int64]struct{}{}
}
componentInstances[e.componentID][n.ID()] = struct{}{}
}
}
var totalExpected int
for id, instances := range componentInstances {
totalExpected += tt.expectInstances[id]
require.Len(t, instances, tt.expectInstances[id], id.String())
}
totalExpected += len(tt.pipelineConfigs) * 2 // one fanout & one capabilities node per pipeline
require.Equal(t, totalExpected, pg.componentGraph.Nodes().Len())
})
}
}
func TestConnectorRouter(t *testing.T) {
t.Run("with_internal_telemetry", func(t *testing.T) {
setObsConsumerGateForTest(t, true)
testConnectorRouter(t)
})
t.Run("without_internal_telemetry", func(t *testing.T) {
setObsConsumerGateForTest(t, false)
testConnectorRouter(t)
})
}
func testConnectorRouter(t *testing.T) {
rcvrID := component.MustNewID("examplereceiver")
routeTracesID := component.MustNewIDWithName("examplerouter", "traces")
routeMetricsID := component.MustNewIDWithName("examplerouter", "metrics")
routeLogsID := component.MustNewIDWithName("examplerouter", "logs")
routeProfilesID := component.MustNewIDWithName("examplerouter", "profiles")
expRightID := component.MustNewIDWithName("exampleexporter", "right")
expLeftID := component.MustNewIDWithName("exampleexporter", "left")
tracesInID := pipeline.NewIDWithName(pipeline.SignalTraces, "in")
tracesRightID := pipeline.NewIDWithName(pipeline.SignalTraces, "right")
tracesLeftID := pipeline.NewIDWithName(pipeline.SignalTraces, "left")
metricsInID := pipeline.NewIDWithName(pipeline.SignalMetrics, "in")
metricsRightID := pipeline.NewIDWithName(pipeline.SignalMetrics, "right")
metricsLeftID := pipeline.NewIDWithName(pipeline.SignalMetrics, "left")
logsInID := pipeline.NewIDWithName(pipeline.SignalLogs, "in")
logsRightID := pipeline.NewIDWithName(pipeline.SignalLogs, "right")
logsLeftID := pipeline.NewIDWithName(pipeline.SignalLogs, "left")
profilesInID := pipeline.NewIDWithName(xpipeline.SignalProfiles, "in")
profilesRightID := pipeline.NewIDWithName(xpipeline.SignalProfiles, "right")
profilesLeftID := pipeline.NewIDWithName(xpipeline.SignalProfiles, "left")
ctx := context.Background()
set := Settings{
Telemetry: componenttest.NewNopTelemetrySettings(),
BuildInfo: component.NewDefaultBuildInfo(),
ReceiverBuilder: builders.NewReceiver(
map[component.ID]component.Config{
rcvrID: testcomponents.ExampleReceiverFactory.CreateDefaultConfig(),
},
map[component.Type]receiver.Factory{
testcomponents.ExampleReceiverFactory.Type(): testcomponents.ExampleReceiverFactory,
},
),
ExporterBuilder: builders.NewExporter(
map[component.ID]component.Config{
expRightID: testcomponents.ExampleExporterFactory.CreateDefaultConfig(),
expLeftID: testcomponents.ExampleExporterFactory.CreateDefaultConfig(),
},
map[component.Type]exporter.Factory{
testcomponents.ExampleExporterFactory.Type(): testcomponents.ExampleExporterFactory,
},
),
ConnectorBuilder: builders.NewConnector(
map[component.ID]component.Config{
routeTracesID: testcomponents.ExampleRouterConfig{
Traces: &testcomponents.LeftRightConfig{
Right: tracesRightID,
Left: tracesLeftID,
},
},
routeMetricsID: testcomponents.ExampleRouterConfig{
Metrics: &testcomponents.LeftRightConfig{
Right: metricsRightID,
Left: metricsLeftID,
},
},
routeLogsID: testcomponents.ExampleRouterConfig{
Logs: &testcomponents.LeftRightConfig{
Right: logsRightID,
Left: logsLeftID,
},
},
routeProfilesID: testcomponents.ExampleRouterConfig{
Profiles: &testcomponents.LeftRightConfig{
Right: profilesRightID,
Left: profilesLeftID,
},
},
},
map[component.Type]connector.Factory{
testcomponents.ExampleRouterFactory.Type(): testcomponents.ExampleRouterFactory,
},
),
PipelineConfigs: pipelines.Config{
tracesInID: {
Receivers: []component.ID{rcvrID},
Exporters: []component.ID{routeTracesID},
},
tracesRightID: {
Receivers: []component.ID{routeTracesID},
Exporters: []component.ID{expRightID},
},
tracesLeftID: {
Receivers: []component.ID{routeTracesID},
Exporters: []component.ID{expLeftID},
},
metricsInID: {
Receivers: []component.ID{rcvrID},
Exporters: []component.ID{routeMetricsID},
},
metricsRightID: {
Receivers: []component.ID{routeMetricsID},
Exporters: []component.ID{expRightID},
},
metricsLeftID: {
Receivers: []component.ID{routeMetricsID},
Exporters: []component.ID{expLeftID},
},
logsInID: {
Receivers: []component.ID{rcvrID},
Exporters: []component.ID{routeLogsID},
},
logsRightID: {
Receivers: []component.ID{routeLogsID},
Exporters: []component.ID{expRightID},
},
logsLeftID: {
Receivers: []component.ID{routeLogsID},
Exporters: []component.ID{expLeftID},
},
profilesInID: {
Receivers: []component.ID{rcvrID},
Exporters: []component.ID{routeProfilesID},
},
profilesRightID: {
Receivers: []component.ID{routeProfilesID},
Exporters: []component.ID{expRightID},
},
profilesLeftID: {
Receivers: []component.ID{routeProfilesID},
Exporters: []component.ID{expLeftID},
},
},
}
pg, err := Build(ctx, set)
require.NoError(t, err)
allReceivers := pg.getReceivers()
allExporters := pg.GetExporters()
assert.Len(t, pg.pipelines, len(set.PipelineConfigs))
// Get a handle for the traces receiver and both Exporters
tracesReceiver := allReceivers[pipeline.SignalTraces][rcvrID].(*testcomponents.ExampleReceiver)
tracesRight := allExporters[pipeline.SignalTraces][expRightID].(*testcomponents.ExampleExporter)
tracesLeft := allExporters[pipeline.SignalTraces][expLeftID].(*testcomponents.ExampleExporter)
// Consume 1, validate it went right
require.NoError(t, tracesReceiver.ConsumeTraces(ctx, testdata.GenerateTraces(1)))
assert.Len(t, tracesRight.Traces, 1)
assert.Empty(t, tracesLeft.Traces)
// Consume 1, validate it went left
require.NoError(t, tracesReceiver.ConsumeTraces(ctx, testdata.GenerateTraces(1)))
assert.Len(t, tracesRight.Traces, 1)
assert.Len(t, tracesLeft.Traces, 1)
// Consume 3, validate 2 went right, 1 went left
assert.NoError(t, tracesReceiver.ConsumeTraces(ctx, testdata.GenerateTraces(1)))
assert.NoError(t, tracesReceiver.ConsumeTraces(ctx, testdata.GenerateTraces(1)))
assert.NoError(t, tracesReceiver.ConsumeTraces(ctx, testdata.GenerateTraces(1)))
assert.Len(t, tracesRight.Traces, 3)
assert.Len(t, tracesLeft.Traces, 2)
// Get a handle for the metrics receiver and both Exporters
metricsReceiver := allReceivers[pipeline.SignalMetrics][rcvrID].(*testcomponents.ExampleReceiver)
metricsRight := allExporters[pipeline.SignalMetrics][expRightID].(*testcomponents.ExampleExporter)
metricsLeft := allExporters[pipeline.SignalMetrics][expLeftID].(*testcomponents.ExampleExporter)
// Consume 1, validate it went right
require.NoError(t, metricsReceiver.ConsumeMetrics(ctx, testdata.GenerateMetrics(1)))
assert.Len(t, metricsRight.Metrics, 1)
assert.Empty(t, metricsLeft.Metrics)
// Consume 1, validate it went left
require.NoError(t, metricsReceiver.ConsumeMetrics(ctx, testdata.GenerateMetrics(1)))
assert.Len(t, metricsRight.Metrics, 1)
assert.Len(t, metricsLeft.Metrics, 1)
// Consume 3, validate 2 went right, 1 went left
assert.NoError(t, metricsReceiver.ConsumeMetrics(ctx, testdata.GenerateMetrics(1)))
assert.NoError(t, metricsReceiver.ConsumeMetrics(ctx, testdata.GenerateMetrics(1)))
assert.NoError(t, metricsReceiver.ConsumeMetrics(ctx, testdata.GenerateMetrics(1)))
assert.Len(t, metricsRight.Metrics, 3)
assert.Len(t, metricsLeft.Metrics, 2)
// Get a handle for the logs receiver and both Exporters
logsReceiver := allReceivers[pipeline.SignalLogs][rcvrID].(*testcomponents.ExampleReceiver)
logsRight := allExporters[pipeline.SignalLogs][expRightID].(*testcomponents.ExampleExporter)
logsLeft := allExporters[pipeline.SignalLogs][expLeftID].(*testcomponents.ExampleExporter)
// Consume 1, validate it went right
require.NoError(t, logsReceiver.ConsumeLogs(ctx, testdata.GenerateLogs(1)))
assert.Len(t, logsRight.Logs, 1)
assert.Empty(t, logsLeft.Logs)
// Consume 1, validate it went left
require.NoError(t, logsReceiver.ConsumeLogs(ctx, testdata.GenerateLogs(1)))
assert.Len(t, logsRight.Logs, 1)
assert.Len(t, logsLeft.Logs, 1)
// Consume 3, validate 2 went right, 1 went left
assert.NoError(t, logsReceiver.ConsumeLogs(ctx, testdata.GenerateLogs(1)))
assert.NoError(t, logsReceiver.ConsumeLogs(ctx, testdata.GenerateLogs(1)))
assert.NoError(t, logsReceiver.ConsumeLogs(ctx, testdata.GenerateLogs(1)))
assert.Len(t, logsRight.Logs, 3)
assert.Len(t, logsLeft.Logs, 2)
// Get a handle for the profiles receiver and both Exporters
profilesReceiver := allReceivers[xpipeline.SignalProfiles][rcvrID].(*testcomponents.ExampleReceiver)
profilesRight := allExporters[xpipeline.SignalProfiles][expRightID].(*testcomponents.ExampleExporter)
profilesLeft := allExporters[xpipeline.SignalProfiles][expLeftID].(*testcomponents.ExampleExporter)
// Consume 1, validate it went right
require.NoError(t, profilesReceiver.ConsumeProfiles(ctx, testdata.GenerateProfiles(1)))
assert.Len(t, profilesRight.Profiles, 1)
assert.Empty(t, profilesLeft.Profiles)
// Consume 1, validate it went left
require.NoError(t, profilesReceiver.ConsumeProfiles(ctx, testdata.GenerateProfiles(1)))
assert.Len(t, profilesRight.Profiles, 1)
assert.Len(t, profilesLeft.Profiles, 1)
// Consume 3, validate 2 went right, 1 went left
assert.NoError(t, profilesReceiver.ConsumeProfiles(ctx, testdata.GenerateProfiles(1)))
assert.NoError(t, profilesReceiver.ConsumeProfiles(ctx, testdata.GenerateProfiles(1)))
assert.NoError(t, profilesReceiver.ConsumeProfiles(ctx, testdata.GenerateProfiles(1)))
assert.Len(t, profilesRight.Profiles, 3)
assert.Len(t, profilesLeft.Profiles, 2)
}
func TestGraphBuildErrors(t *testing.T) {
t.Run("with_internal_telemetry", func(t *testing.T) {
setObsConsumerGateForTest(t, true)
testGraphBuildErrors(t)
})
t.Run("without_internal_telemetry", func(t *testing.T) {
setObsConsumerGateForTest(t, false)
testGraphBuildErrors(t)
})
}
func testGraphBuildErrors(t *testing.T) {
nopReceiverFactory := receivertest.NewNopFactory()
nopProcessorFactory := processortest.NewNopFactory()
nopExporterFactory := exportertest.NewNopFactory()
nopConnectorFactory := connectortest.NewNopFactory()
mfConnectorFactory := testcomponents.MockForwardConnectorFactory
badReceiverFactory := newBadReceiverFactory()
badProcessorFactory := newBadProcessorFactory()
badExporterFactory := newBadExporterFactory()
badConnectorFactory := newBadConnectorFactory()
tests := []struct {
name string
receiverCfgs map[component.ID]component.Config
processorCfgs map[component.ID]component.Config
exporterCfgs map[component.ID]component.Config
connectorCfgs map[component.ID]component.Config
pipelineCfgs pipelines.Config
expected string
}{
{
name: "not_supported_exporter_logs",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("bf"): badExporterFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewID(pipeline.SignalLogs): {
Receivers: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("bf")},
},
},
expected: "failed to create \"bf\" exporter for data type \"logs\": telemetry type is not supported",
},
{
name: "not_supported_exporter_metrics",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("bf"): badExporterFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewID(pipeline.SignalMetrics): {
Receivers: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("bf")},
},
},
expected: "failed to create \"bf\" exporter for data type \"metrics\": telemetry type is not supported",
},
{
name: "not_supported_exporter_traces",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("bf"): badExporterFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewID(pipeline.SignalTraces): {
Receivers: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("bf")},
},
},
expected: "failed to create \"bf\" exporter for data type \"traces\": telemetry type is not supported",
},
{
name: "not_supported_exporter_profiles",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("bf"): badExporterFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewID(xpipeline.SignalProfiles): {
Receivers: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("bf")},
},
},
expected: "failed to create \"bf\" exporter for data type \"profiles\": telemetry type is not supported",
},
{
name: "not_supported_processor_logs",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
processorCfgs: map[component.ID]component.Config{
component.MustNewID("bf"): badProcessorFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewID(pipeline.SignalLogs): {
Receivers: []component.ID{component.MustNewID("nop")},
Processors: []component.ID{component.MustNewID("bf")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: "failed to create \"bf\" processor, in pipeline \"logs\": telemetry type is not supported",
},
{
name: "not_supported_processor_metrics",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
processorCfgs: map[component.ID]component.Config{
component.MustNewID("bf"): badProcessorFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewID(pipeline.SignalMetrics): {
Receivers: []component.ID{component.MustNewID("nop")},
Processors: []component.ID{component.MustNewID("bf")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: "failed to create \"bf\" processor, in pipeline \"metrics\": telemetry type is not supported",
},
{
name: "not_supported_processor_traces",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
processorCfgs: map[component.ID]component.Config{
component.MustNewID("bf"): badProcessorFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewID(pipeline.SignalTraces): {
Receivers: []component.ID{component.MustNewID("nop")},
Processors: []component.ID{component.MustNewID("bf")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: "failed to create \"bf\" processor, in pipeline \"traces\": telemetry type is not supported",
},
{
name: "not_supported_processor_profiles",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
processorCfgs: map[component.ID]component.Config{
component.MustNewID("bf"): badProcessorFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewID(xpipeline.SignalProfiles): {
Receivers: []component.ID{component.MustNewID("nop")},
Processors: []component.ID{component.MustNewID("bf")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: "failed to create \"bf\" processor, in pipeline \"profiles\": telemetry type is not supported",
},
{
name: "not_supported_receiver_logs",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("bf"): badReceiverFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewID(pipeline.SignalLogs): {
Receivers: []component.ID{component.MustNewID("bf")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: "failed to create \"bf\" receiver for data type \"logs\": telemetry type is not supported",
},
{
name: "not_supported_receiver_metrics",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("bf"): badReceiverFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewID(pipeline.SignalMetrics): {
Receivers: []component.ID{component.MustNewID("bf")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: "failed to create \"bf\" receiver for data type \"metrics\": telemetry type is not supported",
},
{
name: "not_supported_receiver_traces",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("bf"): badReceiverFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewID(pipeline.SignalTraces): {
Receivers: []component.ID{component.MustNewID("bf")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: "failed to create \"bf\" receiver for data type \"traces\": telemetry type is not supported",
},
{
name: "not_supported_receiver_profiles",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("bf"): badReceiverFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewID(xpipeline.SignalProfiles): {
Receivers: []component.ID{component.MustNewID("bf")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: "failed to create \"bf\" receiver for data type \"profiles\": telemetry type is not supported",
},
{
name: "not_supported_connector_traces_traces.yaml",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopExporterFactory.CreateDefaultConfig(),
},
connectorCfgs: map[component.ID]component.Config{
component.MustNewID("bf"): nopConnectorFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalTraces, "in"): {
Receivers: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("bf")},
},
pipeline.NewIDWithName(pipeline.SignalTraces, "out"): {
Receivers: []component.ID{component.MustNewID("bf")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: "connector \"bf\" used as exporter in [traces/in] pipeline but not used in any supported receiver pipeline",
},
{
name: "not_supported_connector_traces_metrics.yaml",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopExporterFactory.CreateDefaultConfig(),
},
connectorCfgs: map[component.ID]component.Config{
component.MustNewID("bf"): nopConnectorFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalTraces, "in"): {
Receivers: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("bf")},
},
pipeline.NewIDWithName(pipeline.SignalMetrics, "out"): {
Receivers: []component.ID{component.MustNewID("bf")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: "connector \"bf\" used as exporter in [traces/in] pipeline but not used in any supported receiver pipeline",
},
{
name: "not_supported_connector_traces_logs.yaml",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopExporterFactory.CreateDefaultConfig(),
},
connectorCfgs: map[component.ID]component.Config{
component.MustNewID("bf"): nopConnectorFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalTraces, "in"): {
Receivers: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("bf")},
},
pipeline.NewIDWithName(pipeline.SignalLogs, "out"): {
Receivers: []component.ID{component.MustNewID("bf")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: "connector \"bf\" used as exporter in [traces/in] pipeline but not used in any supported receiver pipeline",
},
{
name: "not_supported_connector_traces_profiles.yaml",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopExporterFactory.CreateDefaultConfig(),
},
connectorCfgs: map[component.ID]component.Config{
component.MustNewID("bf"): nopConnectorFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalTraces, "in"): {
Receivers: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("bf")},
},
pipeline.NewIDWithName(xpipeline.SignalProfiles, "out"): {
Receivers: []component.ID{component.MustNewID("bf")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: "connector \"bf\" used as exporter in [traces/in] pipeline but not used in any supported receiver pipeline",
},
{
name: "not_supported_connector_metrics_traces.yaml",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopExporterFactory.CreateDefaultConfig(),
},
connectorCfgs: map[component.ID]component.Config{
component.MustNewID("bf"): nopConnectorFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalMetrics, "in"): {
Receivers: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("bf")},
},
pipeline.NewIDWithName(pipeline.SignalTraces, "out"): {
Receivers: []component.ID{component.MustNewID("bf")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: "connector \"bf\" used as exporter in [metrics/in] pipeline but not used in any supported receiver pipeline",
},
{
name: "not_supported_connector_metrics_metrics.yaml",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopExporterFactory.CreateDefaultConfig(),
},
connectorCfgs: map[component.ID]component.Config{
component.MustNewID("bf"): nopConnectorFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalMetrics, "in"): {
Receivers: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("bf")},
},
pipeline.NewIDWithName(pipeline.SignalMetrics, "out"): {
Receivers: []component.ID{component.MustNewID("bf")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: "connector \"bf\" used as exporter in [metrics/in] pipeline but not used in any supported receiver pipeline",
},
{
name: "not_supported_connector_metrics_logs.yaml",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopExporterFactory.CreateDefaultConfig(),
},
connectorCfgs: map[component.ID]component.Config{
component.MustNewID("bf"): nopConnectorFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalMetrics, "in"): {
Receivers: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("bf")},
},
pipeline.NewIDWithName(pipeline.SignalLogs, "out"): {
Receivers: []component.ID{component.MustNewID("bf")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: "connector \"bf\" used as exporter in [metrics/in] pipeline but not used in any supported receiver pipeline",
},
{
name: "not_supported_connector_metrics_profiles.yaml",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopExporterFactory.CreateDefaultConfig(),
},
connectorCfgs: map[component.ID]component.Config{
component.MustNewID("bf"): nopConnectorFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalMetrics, "in"): {
Receivers: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("bf")},
},
pipeline.NewIDWithName(xpipeline.SignalProfiles, "out"): {
Receivers: []component.ID{component.MustNewID("bf")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: "connector \"bf\" used as exporter in [metrics/in] pipeline but not used in any supported receiver pipeline",
},
{
name: "not_supported_connector_logs_traces.yaml",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopExporterFactory.CreateDefaultConfig(),
},
connectorCfgs: map[component.ID]component.Config{
component.MustNewID("bf"): nopConnectorFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalLogs, "in"): {
Receivers: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("bf")},
},
pipeline.NewIDWithName(pipeline.SignalTraces, "out"): {
Receivers: []component.ID{component.MustNewID("bf")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: "connector \"bf\" used as exporter in [logs/in] pipeline but not used in any supported receiver pipeline",
},
{
name: "not_supported_connector_logs_metrics.yaml",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopExporterFactory.CreateDefaultConfig(),
},
connectorCfgs: map[component.ID]component.Config{
component.MustNewID("bf"): nopConnectorFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalLogs, "in"): {
Receivers: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("bf")},
},
pipeline.NewIDWithName(pipeline.SignalMetrics, "out"): {
Receivers: []component.ID{component.MustNewID("bf")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: "connector \"bf\" used as exporter in [logs/in] pipeline but not used in any supported receiver pipeline",
},
{
name: "not_supported_connector_logs_logs.yaml",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopExporterFactory.CreateDefaultConfig(),
},
connectorCfgs: map[component.ID]component.Config{
component.MustNewID("bf"): nopConnectorFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalLogs, "in"): {
Receivers: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("bf")},
},
pipeline.NewIDWithName(pipeline.SignalLogs, "out"): {
Receivers: []component.ID{component.MustNewID("bf")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: "connector \"bf\" used as exporter in [logs/in] pipeline but not used in any supported receiver pipeline",
},
{
name: "not_supported_connector_logs_profiles.yaml",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopExporterFactory.CreateDefaultConfig(),
},
connectorCfgs: map[component.ID]component.Config{
component.MustNewID("bf"): nopConnectorFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalLogs, "in"): {
Receivers: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("bf")},
},
pipeline.NewIDWithName(xpipeline.SignalProfiles, "out"): {
Receivers: []component.ID{component.MustNewID("bf")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: "connector \"bf\" used as exporter in [logs/in] pipeline but not used in any supported receiver pipeline",
},
{
name: "not_supported_connector_profiles_traces.yaml",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopExporterFactory.CreateDefaultConfig(),
},
connectorCfgs: map[component.ID]component.Config{
component.MustNewID("bf"): nopConnectorFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewIDWithName(xpipeline.SignalProfiles, "in"): {
Receivers: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("bf")},
},
pipeline.NewIDWithName(pipeline.SignalTraces, "out"): {
Receivers: []component.ID{component.MustNewID("bf")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: "connector \"bf\" used as exporter in [profiles/in] pipeline but not used in any supported receiver pipeline",
},
{
name: "not_supported_connector_profiles_metrics.yaml",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopExporterFactory.CreateDefaultConfig(),
},
connectorCfgs: map[component.ID]component.Config{
component.MustNewID("bf"): nopConnectorFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewIDWithName(xpipeline.SignalProfiles, "in"): {
Receivers: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("bf")},
},
pipeline.NewIDWithName(pipeline.SignalMetrics, "out"): {
Receivers: []component.ID{component.MustNewID("bf")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: "connector \"bf\" used as exporter in [profiles/in] pipeline but not used in any supported receiver pipeline",
},
{
name: "not_supported_connector_profiles_logs.yaml",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopExporterFactory.CreateDefaultConfig(),
},
connectorCfgs: map[component.ID]component.Config{
component.MustNewID("bf"): nopConnectorFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewIDWithName(xpipeline.SignalProfiles, "in"): {
Receivers: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("bf")},
},
pipeline.NewIDWithName(pipeline.SignalLogs, "out"): {
Receivers: []component.ID{component.MustNewID("bf")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: "connector \"bf\" used as exporter in [profiles/in] pipeline but not used in any supported receiver pipeline",
},
{
name: "not_supported_connector_profiles_profiles.yaml",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopExporterFactory.CreateDefaultConfig(),
},
connectorCfgs: map[component.ID]component.Config{
component.MustNewID("bf"): nopConnectorFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewIDWithName(xpipeline.SignalProfiles, "in"): {
Receivers: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("bf")},
},
pipeline.NewIDWithName(xpipeline.SignalProfiles, "out"): {
Receivers: []component.ID{component.MustNewID("bf")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: "connector \"bf\" used as exporter in [profiles/in] pipeline but not used in any supported receiver pipeline",
},
{
name: "orphaned-connector-use-as-exporter",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopExporterFactory.CreateDefaultConfig(),
},
connectorCfgs: map[component.ID]component.Config{
component.MustNewIDWithName("nop", "conn"): nopConnectorFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalMetrics, "in"): {
Receivers: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewIDWithName("nop", "conn")},
},
},
expected: `connector "nop/conn" used as exporter in [metrics/in] pipeline but not used in any supported receiver pipeline`,
},
{
name: "orphaned-connector-use-as-receiver",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopExporterFactory.CreateDefaultConfig(),
},
connectorCfgs: map[component.ID]component.Config{
component.MustNewIDWithName("nop", "conn"): nopConnectorFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalTraces, "out"): {
Receivers: []component.ID{component.MustNewIDWithName("nop", "conn")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: `connector "nop/conn" used as receiver in [traces/out] pipeline but not used in any supported exporter pipeline`,
},
{
name: "partially-orphaned-connector-use-as-exporter",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopExporterFactory.CreateDefaultConfig(),
},
connectorCfgs: map[component.ID]component.Config{
component.MustNewID("mockforward"): mfConnectorFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalTraces, "in"): {
Receivers: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("mockforward")},
},
pipeline.NewIDWithName(pipeline.SignalTraces, "out"): {
Receivers: []component.ID{component.MustNewID("mockforward")},
Exporters: []component.ID{component.MustNewID("nop")},
},
pipeline.NewIDWithName(pipeline.SignalMetrics, "in"): {
Receivers: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("mockforward")},
},
},
expected: `connector "mockforward" used as exporter in [metrics/in] pipeline but not used in any supported receiver pipeline`,
},
{
name: "partially-orphaned-connector-use-as-receiver",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopExporterFactory.CreateDefaultConfig(),
},
connectorCfgs: map[component.ID]component.Config{
component.MustNewID("mockforward"): mfConnectorFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalMetrics, "in"): {
Receivers: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("mockforward")},
},
pipeline.NewIDWithName(pipeline.SignalMetrics, "out"): {
Receivers: []component.ID{component.MustNewID("mockforward")},
Exporters: []component.ID{component.MustNewID("nop")},
},
pipeline.NewIDWithName(pipeline.SignalTraces, "out"): {
Receivers: []component.ID{component.MustNewID("mockforward")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: `connector "mockforward" used as receiver in [traces/out] pipeline but not used in any supported exporter pipeline`,
},
{
name: "not_allowed_simple_cycle_traces.yaml",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
processorCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopProcessorFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopExporterFactory.CreateDefaultConfig(),
},
connectorCfgs: map[component.ID]component.Config{
component.MustNewIDWithName("nop", "conn"): nopConnectorFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewID(pipeline.SignalTraces): {
Receivers: []component.ID{component.MustNewIDWithName("nop", "conn")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewIDWithName("nop", "conn")},
},
},
expected: `cycle detected: ` +
`connector "nop/conn" (traces to traces) -> ` +
`processor "nop" in pipeline "traces" -> ` +
`connector "nop/conn" (traces to traces)`,
},
{
name: "not_allowed_simple_cycle_metrics.yaml",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
processorCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopProcessorFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopExporterFactory.CreateDefaultConfig(),
},
connectorCfgs: map[component.ID]component.Config{
component.MustNewIDWithName("nop", "conn"): nopConnectorFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewID(pipeline.SignalMetrics): {
Receivers: []component.ID{component.MustNewIDWithName("nop", "conn")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewIDWithName("nop", "conn")},
},
},
expected: `cycle detected: ` +
`connector "nop/conn" (metrics to metrics) -> ` +
`processor "nop" in pipeline "metrics" -> ` +
`connector "nop/conn" (metrics to metrics)`,
},
{
name: "not_allowed_simple_cycle_logs.yaml",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
processorCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopProcessorFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopExporterFactory.CreateDefaultConfig(),
},
connectorCfgs: map[component.ID]component.Config{
component.MustNewIDWithName("nop", "conn"): nopConnectorFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewID(pipeline.SignalLogs): {
Receivers: []component.ID{component.MustNewIDWithName("nop", "conn")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewIDWithName("nop", "conn")},
},
},
expected: `cycle detected: ` +
`connector "nop/conn" (logs to logs) -> ` +
`processor "nop" in pipeline "logs" -> ` +
`connector "nop/conn" (logs to logs)`,
},
{
name: "not_allowed_simple_cycle_profiles.yaml",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
processorCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopProcessorFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopExporterFactory.CreateDefaultConfig(),
},
connectorCfgs: map[component.ID]component.Config{
component.MustNewIDWithName("nop", "conn"): nopConnectorFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewID(xpipeline.SignalProfiles): {
Receivers: []component.ID{component.MustNewIDWithName("nop", "conn")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewIDWithName("nop", "conn")},
},
},
expected: `cycle detected: ` +
`connector "nop/conn" (profiles to profiles) -> ` +
`processor "nop" in pipeline "profiles" -> ` +
`connector "nop/conn" (profiles to profiles)`,
},
{
name: "not_allowed_deep_cycle_traces.yaml",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
processorCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopProcessorFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopExporterFactory.CreateDefaultConfig(),
},
connectorCfgs: map[component.ID]component.Config{
component.MustNewIDWithName("nop", "conn"): nopConnectorFactory.CreateDefaultConfig(),
component.MustNewIDWithName("nop", "conn1"): nopConnectorFactory.CreateDefaultConfig(),
component.MustNewIDWithName("nop", "conn2"): nopConnectorFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalTraces, "in"): {
Receivers: []component.ID{component.MustNewID("nop")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewIDWithName("nop", "conn")},
},
pipeline.NewIDWithName(pipeline.SignalTraces, "1"): {
Receivers: []component.ID{component.MustNewIDWithName("nop", "conn")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewIDWithName("nop", "conn1")},
},
pipeline.NewIDWithName(pipeline.SignalTraces, "2"): {
Receivers: []component.ID{component.MustNewIDWithName("nop", "conn1")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewIDWithName("nop", "conn2"), component.MustNewIDWithName("nop", "conn")},
},
pipeline.NewIDWithName(pipeline.SignalTraces, "out"): {
Receivers: []component.ID{component.MustNewIDWithName("nop", "conn2")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: `cycle detected: ` +
`connector "nop/conn1" (traces to traces) -> ` +
`processor "nop" in pipeline "traces/2" -> ` +
`connector "nop/conn" (traces to traces) -> ` +
`processor "nop" in pipeline "traces/1" -> ` +
`connector "nop/conn1" (traces to traces)`,
},
{
name: "not_allowed_deep_cycle_metrics.yaml",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
processorCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopProcessorFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopExporterFactory.CreateDefaultConfig(),
},
connectorCfgs: map[component.ID]component.Config{
component.MustNewIDWithName("nop", "conn"): nopConnectorFactory.CreateDefaultConfig(),
component.MustNewIDWithName("nop", "conn1"): nopConnectorFactory.CreateDefaultConfig(),
component.MustNewIDWithName("nop", "conn2"): nopConnectorFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalMetrics, "in"): {
Receivers: []component.ID{component.MustNewID("nop")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewIDWithName("nop", "conn")},
},
pipeline.NewIDWithName(pipeline.SignalMetrics, "1"): {
Receivers: []component.ID{component.MustNewIDWithName("nop", "conn")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewIDWithName("nop", "conn1")},
},
pipeline.NewIDWithName(pipeline.SignalMetrics, "2"): {
Receivers: []component.ID{component.MustNewIDWithName("nop", "conn1")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewIDWithName("nop", "conn2"), component.MustNewIDWithName("nop", "conn")},
},
pipeline.NewIDWithName(pipeline.SignalMetrics, "out"): {
Receivers: []component.ID{component.MustNewIDWithName("nop", "conn2")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: `cycle detected: ` +
`connector "nop/conn1" (metrics to metrics) -> ` +
`processor "nop" in pipeline "metrics/2" -> ` +
`connector "nop/conn" (metrics to metrics) -> ` +
`processor "nop" in pipeline "metrics/1" -> ` +
`connector "nop/conn1" (metrics to metrics)`,
},
{
name: "not_allowed_deep_cycle_logs.yaml",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
processorCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopProcessorFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopExporterFactory.CreateDefaultConfig(),
},
connectorCfgs: map[component.ID]component.Config{
component.MustNewIDWithName("nop", "conn"): nopConnectorFactory.CreateDefaultConfig(),
component.MustNewIDWithName("nop", "conn1"): nopConnectorFactory.CreateDefaultConfig(),
component.MustNewIDWithName("nop", "conn2"): nopConnectorFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalLogs, "in"): {
Receivers: []component.ID{component.MustNewID("nop")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewIDWithName("nop", "conn")},
},
pipeline.NewIDWithName(pipeline.SignalLogs, "1"): {
Receivers: []component.ID{component.MustNewIDWithName("nop", "conn")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewIDWithName("nop", "conn1")},
},
pipeline.NewIDWithName(pipeline.SignalLogs, "2"): {
Receivers: []component.ID{component.MustNewIDWithName("nop", "conn1")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewIDWithName("nop", "conn2"), component.MustNewIDWithName("nop", "conn")},
},
pipeline.NewIDWithName(pipeline.SignalLogs, "out"): {
Receivers: []component.ID{component.MustNewIDWithName("nop", "conn2")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: `cycle detected: ` +
`connector "nop/conn" (logs to logs) -> ` +
`processor "nop" in pipeline "logs/1" -> ` +
`connector "nop/conn1" (logs to logs) -> ` +
`processor "nop" in pipeline "logs/2" -> ` +
`connector "nop/conn" (logs to logs)`,
},
{
name: "not_allowed_deep_cycle_profiles.yaml",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
processorCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopProcessorFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopExporterFactory.CreateDefaultConfig(),
},
connectorCfgs: map[component.ID]component.Config{
component.MustNewIDWithName("nop", "conn"): nopConnectorFactory.CreateDefaultConfig(),
component.MustNewIDWithName("nop", "conn1"): nopConnectorFactory.CreateDefaultConfig(),
component.MustNewIDWithName("nop", "conn2"): nopConnectorFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewIDWithName(xpipeline.SignalProfiles, "in"): {
Receivers: []component.ID{component.MustNewID("nop")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewIDWithName("nop", "conn")},
},
pipeline.NewIDWithName(xpipeline.SignalProfiles, "1"): {
Receivers: []component.ID{component.MustNewIDWithName("nop", "conn")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewIDWithName("nop", "conn1")},
},
pipeline.NewIDWithName(xpipeline.SignalProfiles, "2"): {
Receivers: []component.ID{component.MustNewIDWithName("nop", "conn1")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewIDWithName("nop", "conn2"), component.MustNewIDWithName("nop", "conn")},
},
pipeline.NewIDWithName(xpipeline.SignalProfiles, "out"): {
Receivers: []component.ID{component.MustNewIDWithName("nop", "conn2")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: `cycle detected: ` +
`connector "nop/conn1" (profiles to profiles) -> ` +
`processor "nop" in pipeline "profiles/2" -> ` +
`connector "nop/conn" (profiles to profiles) -> ` +
`processor "nop" in pipeline "profiles/1" -> ` +
`connector "nop/conn1" (profiles to profiles)`,
},
{
name: "not_allowed_deep_cycle_multi_signal.yaml",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
processorCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopProcessorFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopExporterFactory.CreateDefaultConfig(),
},
connectorCfgs: map[component.ID]component.Config{
component.MustNewIDWithName("nop", "fork"): nopConnectorFactory.CreateDefaultConfig(),
component.MustNewIDWithName("nop", "count"): nopConnectorFactory.CreateDefaultConfig(),
component.MustNewIDWithName("nop", "forkagain"): nopConnectorFactory.CreateDefaultConfig(),
component.MustNewIDWithName("nop", "rawlog"): nopConnectorFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalTraces, "in"): {
Receivers: []component.ID{component.MustNewID("nop")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewIDWithName("nop", "fork")},
},
pipeline.NewIDWithName(pipeline.SignalTraces, "copy1"): {
Receivers: []component.ID{component.MustNewIDWithName("nop", "fork")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewIDWithName("nop", "count")},
},
pipeline.NewIDWithName(pipeline.SignalTraces, "copy2"): {
Receivers: []component.ID{component.MustNewIDWithName("nop", "fork")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewIDWithName("nop", "forkagain")},
},
pipeline.NewIDWithName(pipeline.SignalTraces, "copy2a"): {
Receivers: []component.ID{component.MustNewIDWithName("nop", "forkagain")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewIDWithName("nop", "count")},
},
pipeline.NewIDWithName(pipeline.SignalTraces, "copy2b"): {
Receivers: []component.ID{component.MustNewIDWithName("nop", "forkagain")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewIDWithName("nop", "rawlog")},
},
pipeline.NewIDWithName(pipeline.SignalMetrics, "count"): {
Receivers: []component.ID{component.MustNewIDWithName("nop", "count")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("nop")},
},
pipeline.NewIDWithName(pipeline.SignalLogs, "raw"): {
Receivers: []component.ID{component.MustNewIDWithName("nop", "rawlog")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewIDWithName("nop", "fork")}, // cannot loop back to "nop/fork"
},
},
expected: `cycle detected: ` +
`connector "nop/rawlog" (traces to logs) -> ` +
`processor "nop" in pipeline "logs/raw" -> ` +
`connector "nop/fork" (logs to traces) -> ` +
`processor "nop" in pipeline "traces/copy2" -> ` +
`connector "nop/forkagain" (traces to traces) -> ` +
`processor "nop" in pipeline "traces/copy2b" -> ` +
`connector "nop/rawlog" (traces to logs)`,
},
{
name: "unknown_exporter_config",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewID(pipeline.SignalTraces): {
Receivers: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("nop"), component.MustNewIDWithName("nop", "1")},
},
},
expected: "failed to create \"nop/1\" exporter for data type \"traces\": exporter \"nop/1\" is not configured",
},
{
name: "unknown_exporter_factory",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("unknown"): nopReceiverFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewID(pipeline.SignalTraces): {
Receivers: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("unknown")},
},
},
expected: "failed to create \"unknown\" exporter for data type \"traces\": exporter factory not available for: \"unknown\"",
},
{
name: "unknown_processor_config",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
processorCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopProcessorFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewID(pipeline.SignalMetrics): {
Receivers: []component.ID{component.MustNewID("nop")},
Processors: []component.ID{component.MustNewID("nop"), component.MustNewIDWithName("nop", "1")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: "failed to create \"nop/1\" processor, in pipeline \"metrics\": processor \"nop/1\" is not configured",
},
{
name: "unknown_processor_factory",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
processorCfgs: map[component.ID]component.Config{
component.MustNewID("unknown"): nopProcessorFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewID(pipeline.SignalMetrics): {
Receivers: []component.ID{component.MustNewID("nop")},
Processors: []component.ID{component.MustNewID("unknown")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: "failed to create \"unknown\" processor, in pipeline \"metrics\": processor factory not available for: \"unknown\"",
},
{
name: "unknown_receiver_config",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewID(pipeline.SignalLogs): {
Receivers: []component.ID{component.MustNewID("nop"), component.MustNewIDWithName("nop", "1")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: "failed to create \"nop/1\" receiver for data type \"logs\": receiver \"nop/1\" is not configured",
},
{
name: "unknown_receiver_factory",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("unknown"): nopReceiverFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewID(pipeline.SignalLogs): {
Receivers: []component.ID{component.MustNewID("unknown")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: "failed to create \"unknown\" receiver for data type \"logs\": receiver factory not available for: \"unknown\"",
},
{
name: "unknown_connector_factory",
receiverCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
exporterCfgs: map[component.ID]component.Config{
component.MustNewID("nop"): nopReceiverFactory.CreateDefaultConfig(),
},
connectorCfgs: map[component.ID]component.Config{
component.MustNewID("unknown"): nopConnectorFactory.CreateDefaultConfig(),
},
pipelineCfgs: pipelines.Config{
pipeline.NewIDWithName(pipeline.SignalTraces, "in"): {
Receivers: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("unknown")},
},
pipeline.NewIDWithName(pipeline.SignalTraces, "out"): {
Receivers: []component.ID{component.MustNewID("unknown")},
Exporters: []component.ID{component.MustNewID("nop")},
},
},
expected: "connector factory not available for: \"unknown\"",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
set := Settings{
BuildInfo: component.NewDefaultBuildInfo(),
Telemetry: componenttest.NewNopTelemetrySettings(),
ReceiverBuilder: builders.NewReceiver(
tt.receiverCfgs,
map[component.Type]receiver.Factory{
nopReceiverFactory.Type(): nopReceiverFactory,
badReceiverFactory.Type(): badReceiverFactory,
}),
ProcessorBuilder: builders.NewProcessor(
tt.processorCfgs,
map[component.Type]processor.Factory{
nopProcessorFactory.Type(): nopProcessorFactory,
badProcessorFactory.Type(): badProcessorFactory,
}),
ExporterBuilder: builders.NewExporter(
tt.exporterCfgs,
map[component.Type]exporter.Factory{
nopExporterFactory.Type(): nopExporterFactory,
badExporterFactory.Type(): badExporterFactory,
}),
ConnectorBuilder: builders.NewConnector(
tt.connectorCfgs,
map[component.Type]connector.Factory{
nopConnectorFactory.Type(): nopConnectorFactory,
badConnectorFactory.Type(): badConnectorFactory,
mfConnectorFactory.Type(): mfConnectorFactory,
}),
PipelineConfigs: tt.pipelineCfgs,
}
_, err := Build(context.Background(), set)
assert.EqualError(t, err, tt.expected)
})
}
}
================================================
FILE: service/internal/graph/host.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package graph // import "go.opentelemetry.io/collector/service/internal/graph"
import (
"net/http"
"path"
"runtime"
"time"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componentstatus"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/service/extensions"
"go.opentelemetry.io/collector/service/hostcapabilities"
"go.opentelemetry.io/collector/service/internal/builders"
"go.opentelemetry.io/collector/service/internal/moduleinfo"
"go.opentelemetry.io/collector/service/internal/status"
"go.opentelemetry.io/collector/service/internal/zpages"
)
var (
_ component.Host = (*Host)(nil)
_ hostcapabilities.ModuleInfo = (*Host)(nil)
_ hostcapabilities.ExposeExporters = (*Host)(nil) //nolint:staticcheck // SA1019
_ hostcapabilities.ComponentFactory = (*Host)(nil)
)
type Host struct {
AsyncErrorChannel chan error
Receivers *builders.ReceiverBuilder
Processors *builders.ProcessorBuilder
Exporters *builders.ExporterBuilder
Connectors *builders.ConnectorBuilder
Extensions *builders.ExtensionBuilder
ModuleInfos moduleinfo.ModuleInfos
BuildInfo component.BuildInfo
Pipelines *Graph
ServiceExtensions *extensions.Extensions
Reporter status.Reporter
}
func (host *Host) GetFactory(kind component.Kind, componentType component.Type) component.Factory {
switch kind {
case component.KindReceiver:
return host.Receivers.Factory(componentType)
case component.KindProcessor:
return host.Processors.Factory(componentType)
case component.KindExporter:
return host.Exporters.Factory(componentType)
case component.KindConnector:
return host.Connectors.Factory(componentType)
case component.KindExtension:
return host.Extensions.Factory(componentType)
}
return nil
}
func (host *Host) GetExtensions() map[component.ID]component.Component {
return host.ServiceExtensions.GetExtensions()
}
func (host *Host) GetModuleInfos() moduleinfo.ModuleInfos {
return host.ModuleInfos
}
// Deprecated: [0.79.0] This function will be removed in the future.
// Several components in the contrib repository use this function so it cannot be removed
// before those cases are removed. In most cases, use of this function can be replaced by a
// connector. See https://github.com/open-telemetry/opentelemetry-collector/issues/7370 and
// https://github.com/open-telemetry/opentelemetry-collector/pull/7390#issuecomment-1483710184
// for additional information.
func (host *Host) GetExporters() map[pipeline.Signal]map[component.ID]component.Component {
return host.Pipelines.GetExporters()
}
func (host *Host) NotifyComponentStatusChange(source *componentstatus.InstanceID, event *componentstatus.Event) {
host.ServiceExtensions.NotifyComponentStatusChange(source, event)
if event.Status() == componentstatus.StatusFatalError {
host.AsyncErrorChannel <- event.Err()
}
}
const (
// Paths
zServicePath = "servicez"
zPipelinePath = "pipelinez"
zExtensionPath = "extensionz"
zFeaturePath = "featurez"
)
// InfoVar is a singleton instance of the Info struct.
var runtimeInfoVar [][2]string
func init() {
runtimeInfoVar = [][2]string{
{"StartTimestamp", time.Now().String()},
{"Go", runtime.Version()},
{"OS", runtime.GOOS},
{"Arch", runtime.GOARCH},
// Add other valuable runtime information here.
}
}
func (host *Host) RegisterZPages(mux *http.ServeMux, pathPrefix string) {
mux.HandleFunc(path.Join(pathPrefix, zServicePath), host.zPagesRequest)
mux.HandleFunc(path.Join(pathPrefix, zPipelinePath), host.Pipelines.HandleZPages)
mux.HandleFunc(path.Join(pathPrefix, zExtensionPath), host.ServiceExtensions.HandleZPages)
mux.HandleFunc(path.Join(pathPrefix, zFeaturePath), handleFeaturezRequest)
}
func (host *Host) zPagesRequest(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
zpages.WriteHTMLPageHeader(w, zpages.HeaderData{Title: "Service " + host.BuildInfo.Command})
zpages.WriteHTMLPropertiesTable(w, zpages.PropertiesTableData{Name: "Build Info", Properties: getBuildInfoProperties(host.BuildInfo)})
zpages.WriteHTMLPropertiesTable(w, zpages.PropertiesTableData{Name: "Runtime Info", Properties: runtimeInfoVar})
zpages.WriteHTMLComponentHeader(w, zpages.ComponentHeaderData{
Name: "Pipelines",
ComponentEndpoint: zPipelinePath,
Link: true,
})
zpages.WriteHTMLComponentHeader(w, zpages.ComponentHeaderData{
Name: "Extensions",
ComponentEndpoint: zExtensionPath,
Link: true,
})
zpages.WriteHTMLComponentHeader(w, zpages.ComponentHeaderData{
Name: "Features",
ComponentEndpoint: zFeaturePath,
Link: true,
})
zpages.WriteHTMLPageFooter(w)
}
func handleFeaturezRequest(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
zpages.WriteHTMLPageHeader(w, zpages.HeaderData{Title: "Feature Gates"})
zpages.WriteHTMLFeaturesTable(w, getFeaturesTableData())
zpages.WriteHTMLPageFooter(w)
}
func getFeaturesTableData() zpages.FeatureGateTableData {
data := zpages.FeatureGateTableData{}
featuregate.GlobalRegistry().VisitAll(func(gate *featuregate.Gate) {
data.Rows = append(data.Rows, zpages.FeatureGateTableRowData{
ID: gate.ID(),
Enabled: gate.IsEnabled(),
Description: gate.Description(),
Stage: gate.Stage().String(),
FromVersion: gate.FromVersion(),
ToVersion: gate.ToVersion(),
ReferenceURL: gate.ReferenceURL(),
})
})
return data
}
func getBuildInfoProperties(buildInfo component.BuildInfo) [][2]string {
return [][2]string{
{"Command", buildInfo.Command},
{"Description", buildInfo.Description},
{"Version", buildInfo.Version},
}
}
================================================
FILE: service/internal/graph/lifecycle_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package graph
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gonum.org/v1/gonum/graph/simple"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componentstatus"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/connector"
"go.opentelemetry.io/collector/connector/connectortest"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exportertest"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/processortest"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/receivertest"
"go.opentelemetry.io/collector/service/internal/builders"
"go.opentelemetry.io/collector/service/internal/status"
"go.opentelemetry.io/collector/service/pipelines"
)
func TestGraphStartStop(t *testing.T) {
t.Run("with_internal_telemetry", func(t *testing.T) {
setObsConsumerGateForTest(t, true)
testGraphStartStop(t)
})
t.Run("without_internal_telemetry", func(t *testing.T) {
setObsConsumerGateForTest(t, false)
testGraphStartStop(t)
})
}
func testGraphStartStop(t *testing.T) {
testCases := []struct {
name string
edges [][2]component.ID
}{
{
name: "single",
edges: [][2]component.ID{
{component.MustNewIDWithName("r", "1"), component.MustNewIDWithName("p", "1")},
{component.MustNewIDWithName("r", "2"), component.MustNewIDWithName("p", "1")},
{component.MustNewIDWithName("p", "1"), component.MustNewIDWithName("p", "2")},
{component.MustNewIDWithName("p", "2"), component.MustNewIDWithName("e", "1")},
{component.MustNewIDWithName("p", "1"), component.MustNewIDWithName("e", "2")},
},
},
{
name: "multi",
edges: [][2]component.ID{
// Pipeline 1
{component.MustNewIDWithName("r", "1"), component.MustNewIDWithName("p", "1")},
{component.MustNewIDWithName("r", "2"), component.MustNewIDWithName("p", "1")},
{component.MustNewIDWithName("p", "1"), component.MustNewIDWithName("p", "2")},
{component.MustNewIDWithName("p", "2"), component.MustNewIDWithName("e", "1")},
{component.MustNewIDWithName("p", "1"), component.MustNewIDWithName("e", "2")},
// Pipeline 2, shares r1 and e2
{component.MustNewIDWithName("r", "1"), component.MustNewIDWithName("p", "3")},
{component.MustNewIDWithName("p", "3"), component.MustNewIDWithName("e", "2")},
},
},
{
name: "connected",
edges: [][2]component.ID{
// Pipeline 1
{component.MustNewIDWithName("r", "1"), component.MustNewIDWithName("p", "1")},
{component.MustNewIDWithName("r", "2"), component.MustNewIDWithName("p", "1")},
{component.MustNewIDWithName("p", "1"), component.MustNewIDWithName("p", "2")},
{component.MustNewIDWithName("p", "2"), component.MustNewIDWithName("e", "1")},
{component.MustNewIDWithName("p", "1"), component.MustNewIDWithName("c", "1")},
// Pipeline 2, shares r1 and c1
{component.MustNewIDWithName("r", "1"), component.MustNewIDWithName("p", "3")},
{component.MustNewIDWithName("p", "3"), component.MustNewIDWithName("c", "1")},
// Pipeline 3, emits to e2 and c2
{component.MustNewIDWithName("c", "1"), component.MustNewIDWithName("e", "2")},
{component.MustNewIDWithName("c", "1"), component.MustNewIDWithName("c", "2")},
// Pipeline 4, also emits to e2
{component.MustNewIDWithName("c", "2"), component.MustNewIDWithName("e", "2")},
},
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
ctx := &contextWithOrder{
Context: context.Background(),
order: map[component.ID]int{},
}
pg := &Graph{componentGraph: simple.NewDirectedGraph()}
pg.telemetry = componenttest.NewNopTelemetrySettings()
pg.instanceIDs = make(map[int64]*componentstatus.InstanceID)
for _, edge := range tt.edges {
f, t := &testNode{id: edge[0]}, &testNode{id: edge[1]}
pg.instanceIDs[f.ID()] = &componentstatus.InstanceID{}
pg.instanceIDs[t.ID()] = &componentstatus.InstanceID{}
pg.componentGraph.SetEdge(simple.Edge{F: f, T: t})
}
require.NoError(t, pg.StartAll(ctx, &Host{Reporter: status.NewReporter(func(*componentstatus.InstanceID, *componentstatus.Event) {}, func(error) {})}))
for _, edge := range tt.edges {
assert.Greater(t, ctx.order[edge[0]], ctx.order[edge[1]])
}
ctx.order = map[component.ID]int{}
require.NoError(t, pg.ShutdownAll(ctx, status.NewNopStatusReporter()))
for _, edge := range tt.edges {
assert.Less(t, ctx.order[edge[0]], ctx.order[edge[1]])
}
})
}
}
func TestGraphStartStopCycle(t *testing.T) {
t.Run("with_internal_telemetry", func(t *testing.T) {
setObsConsumerGateForTest(t, true)
testGraphStartStopCycle(t)
})
t.Run("without_internal_telemetry", func(t *testing.T) {
setObsConsumerGateForTest(t, false)
testGraphStartStopCycle(t)
})
}
func testGraphStartStopCycle(t *testing.T) {
pg := &Graph{componentGraph: simple.NewDirectedGraph()}
r1 := &testNode{id: component.MustNewIDWithName("r", "1")}
p1 := &testNode{id: component.MustNewIDWithName("p", "1")}
c1 := &testNode{id: component.MustNewIDWithName("c", "1")}
e1 := &testNode{id: component.MustNewIDWithName("e", "1")}
pg.instanceIDs = map[int64]*componentstatus.InstanceID{
r1.ID(): {},
p1.ID(): {},
c1.ID(): {},
e1.ID(): {},
}
pg.componentGraph.SetEdge(simple.Edge{F: r1, T: p1})
pg.componentGraph.SetEdge(simple.Edge{F: p1, T: c1})
pg.componentGraph.SetEdge(simple.Edge{F: c1, T: e1})
pg.componentGraph.SetEdge(simple.Edge{F: c1, T: p1}) // loop back
err := pg.StartAll(context.Background(), &Host{Reporter: status.NewReporter(func(*componentstatus.InstanceID, *componentstatus.Event) {}, func(error) {})})
require.ErrorContains(t, err, `topo: no topological ordering: cyclic components`)
err = pg.ShutdownAll(context.Background(), status.NewNopStatusReporter())
assert.ErrorContains(t, err, `topo: no topological ordering: cyclic components`)
}
func TestGraphStartStopComponentError(t *testing.T) {
t.Run("with_internal_telemetry", func(t *testing.T) {
setObsConsumerGateForTest(t, true)
testGraphStartStopComponentError(t)
})
t.Run("without_internal_telemetry", func(t *testing.T) {
setObsConsumerGateForTest(t, false)
testGraphStartStopComponentError(t)
})
}
func testGraphStartStopComponentError(t *testing.T) {
pg := &Graph{componentGraph: simple.NewDirectedGraph()}
pg.telemetry = componenttest.NewNopTelemetrySettings()
r1 := &testNode{
id: component.MustNewIDWithName("r", "1"),
startErr: errors.New("foo"),
}
e1 := &testNode{
id: component.MustNewIDWithName("e", "1"),
shutdownErr: errors.New("bar"),
}
pg.instanceIDs = map[int64]*componentstatus.InstanceID{
r1.ID(): {},
e1.ID(): {},
}
pg.componentGraph.SetEdge(simple.Edge{
F: r1,
T: e1,
})
require.ErrorIs(t, pg.StartAll(context.Background(), &Host{Reporter: status.NewReporter(func(*componentstatus.InstanceID, *componentstatus.Event) {}, func(error) {})}), r1.startErr)
assert.EqualError(t, pg.ShutdownAll(context.Background(), status.NewNopStatusReporter()), "bar")
}
// This includes all tests from the previous implementation, plus a new one
// relevant only to the new graph-based implementation.
func TestGraphFailToStartAndShutdown(t *testing.T) {
t.Run("with_internal_telemetry", func(t *testing.T) {
setObsConsumerGateForTest(t, true)
testGraphFailToStartAndShutdown(t)
})
t.Run("without_internal_telemetry", func(t *testing.T) {
setObsConsumerGateForTest(t, false)
testGraphFailToStartAndShutdown(t)
})
}
func testGraphFailToStartAndShutdown(t *testing.T) {
errReceiverFactory := newErrReceiverFactory()
errProcessorFactory := newErrProcessorFactory()
errExporterFactory := newErrExporterFactory()
errConnectorFactory := newErrConnectorFactory()
nopReceiverFactory := receivertest.NewNopFactory()
nopProcessorFactory := processortest.NewNopFactory()
nopExporterFactory := exportertest.NewNopFactory()
nopConnectorFactory := connectortest.NewNopFactory()
set := Settings{
Telemetry: componenttest.NewNopTelemetrySettings(),
BuildInfo: component.NewDefaultBuildInfo(),
ReceiverBuilder: builders.NewReceiver(
map[component.ID]component.Config{
component.NewID(nopReceiverFactory.Type()): nopReceiverFactory.CreateDefaultConfig(),
component.NewID(errReceiverFactory.Type()): errReceiverFactory.CreateDefaultConfig(),
},
map[component.Type]receiver.Factory{
nopReceiverFactory.Type(): nopReceiverFactory,
errReceiverFactory.Type(): errReceiverFactory,
}),
ProcessorBuilder: builders.NewProcessor(
map[component.ID]component.Config{
component.NewID(nopProcessorFactory.Type()): nopProcessorFactory.CreateDefaultConfig(),
component.NewID(errProcessorFactory.Type()): errProcessorFactory.CreateDefaultConfig(),
},
map[component.Type]processor.Factory{
nopProcessorFactory.Type(): nopProcessorFactory,
errProcessorFactory.Type(): errProcessorFactory,
}),
ExporterBuilder: builders.NewExporter(
map[component.ID]component.Config{
component.NewID(nopExporterFactory.Type()): nopExporterFactory.CreateDefaultConfig(),
component.NewID(errExporterFactory.Type()): errExporterFactory.CreateDefaultConfig(),
},
map[component.Type]exporter.Factory{
nopExporterFactory.Type(): nopExporterFactory,
errExporterFactory.Type(): errExporterFactory,
}),
ConnectorBuilder: builders.NewConnector(
map[component.ID]component.Config{
component.NewIDWithName(nopConnectorFactory.Type(), "conn"): nopConnectorFactory.CreateDefaultConfig(),
component.NewIDWithName(errConnectorFactory.Type(), "conn"): errConnectorFactory.CreateDefaultConfig(),
},
map[component.Type]connector.Factory{
nopConnectorFactory.Type(): nopConnectorFactory,
errConnectorFactory.Type(): errConnectorFactory,
}),
}
dataTypes := []pipeline.Signal{pipeline.SignalTraces, pipeline.SignalMetrics, pipeline.SignalLogs}
for _, dt := range dataTypes {
t.Run(dt.String()+"/receiver", func(t *testing.T) {
set.PipelineConfigs = pipelines.Config{
pipeline.NewID(dt): {
Receivers: []component.ID{component.MustNewID("nop"), component.MustNewID("err")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("nop")},
},
}
pipelines, err := Build(context.Background(), set)
require.NoError(t, err)
require.Error(t, pipelines.StartAll(context.Background(), &Host{Reporter: status.NewReporter(func(*componentstatus.InstanceID, *componentstatus.Event) {}, func(error) {})}))
assert.Error(t, pipelines.ShutdownAll(context.Background(), status.NewNopStatusReporter()))
})
t.Run(dt.String()+"/processor", func(t *testing.T) {
set.PipelineConfigs = pipelines.Config{
pipeline.NewID(dt): {
Receivers: []component.ID{component.MustNewID("nop")},
Processors: []component.ID{component.MustNewID("nop"), component.MustNewID("err")},
Exporters: []component.ID{component.MustNewID("nop")},
},
}
pipelines, err := Build(context.Background(), set)
require.NoError(t, err)
require.Error(t, pipelines.StartAll(context.Background(), &Host{Reporter: status.NewReporter(func(*componentstatus.InstanceID, *componentstatus.Event) {}, func(error) {})}))
assert.Error(t, pipelines.ShutdownAll(context.Background(), status.NewNopStatusReporter()))
})
t.Run(dt.String()+"/exporter", func(t *testing.T) {
set.PipelineConfigs = pipelines.Config{
pipeline.NewID(dt): {
Receivers: []component.ID{component.MustNewID("nop")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("nop"), component.MustNewID("err")},
},
}
pipelines, err := Build(context.Background(), set)
require.NoError(t, err)
require.Error(t, pipelines.StartAll(context.Background(), &Host{Reporter: status.NewReporter(func(*componentstatus.InstanceID, *componentstatus.Event) {}, func(error) {})}))
assert.Error(t, pipelines.ShutdownAll(context.Background(), status.NewNopStatusReporter()))
})
for _, dt2 := range dataTypes {
t.Run(dt.String()+"/"+dt2.String()+"/connector", func(t *testing.T) {
set.PipelineConfigs = pipelines.Config{
pipeline.NewIDWithName(dt, "in"): {
Receivers: []component.ID{component.MustNewID("nop")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("nop"), component.MustNewIDWithName("err", "conn")},
},
pipeline.NewIDWithName(dt2, "out"): {
Receivers: []component.ID{component.MustNewID("nop"), component.MustNewIDWithName("err", "conn")},
Processors: []component.ID{component.MustNewID("nop")},
Exporters: []component.ID{component.MustNewID("nop")},
},
}
pipelines, err := Build(context.Background(), set)
require.NoError(t, err)
require.Error(t, pipelines.StartAll(context.Background(), &Host{Reporter: status.NewReporter(func(*componentstatus.InstanceID, *componentstatus.Event) {}, func(error) {})}))
assert.Error(t, pipelines.ShutdownAll(context.Background(), status.NewNopStatusReporter()))
})
}
}
}
func TestStatusReportedOnStartupShutdown(t *testing.T) {
t.Run("with_internal_telemetry", func(t *testing.T) {
setObsConsumerGateForTest(t, true)
testStatusReportedOnStartupShutdown(t)
})
t.Run("without_internal_telemetry", func(t *testing.T) {
setObsConsumerGateForTest(t, false)
testStatusReportedOnStartupShutdown(t)
})
}
func testStatusReportedOnStartupShutdown(t *testing.T) {
rNoErr := &testNode{id: component.MustNewIDWithName("r_no_err", "1")}
rStErr := &testNode{id: component.MustNewIDWithName("r_st_err", "1"), startErr: assert.AnError}
rSdErr := &testNode{id: component.MustNewIDWithName("r_sd_err", "1"), shutdownErr: assert.AnError}
eNoErr := &testNode{id: component.MustNewIDWithName("e_no_err", "1")}
eStErr := &testNode{id: component.MustNewIDWithName("e_st_err", "1"), startErr: assert.AnError}
eSdErr := &testNode{id: component.MustNewIDWithName("e_sd_err", "1"), shutdownErr: assert.AnError}
instanceIDs := map[*testNode]*componentstatus.InstanceID{
rNoErr: componentstatus.NewInstanceID(rNoErr.id, component.KindReceiver),
rStErr: componentstatus.NewInstanceID(rStErr.id, component.KindReceiver),
rSdErr: componentstatus.NewInstanceID(rSdErr.id, component.KindReceiver),
eNoErr: componentstatus.NewInstanceID(eNoErr.id, component.KindExporter),
eStErr: componentstatus.NewInstanceID(eStErr.id, component.KindExporter),
eSdErr: componentstatus.NewInstanceID(eSdErr.id, component.KindExporter),
}
// compare two maps of status events ignoring timestamp
assertEqualStatuses := func(t *testing.T, evMap1, evMap2 map[*componentstatus.InstanceID][]*componentstatus.Event) {
assert.Len(t, evMap2, len(evMap1))
for id, evts1 := range evMap1 {
evts2 := evMap2[id]
assert.Len(t, evts2, len(evts1))
for i := range evts1 {
ev1 := evts1[i]
ev2 := evts2[i]
assert.Equal(t, ev1.Status(), ev2.Status())
assert.Equal(t, ev1.Err(), ev2.Err())
}
}
}
for _, tt := range []struct {
name string
edge [2]*testNode
expectedStatuses map[*componentstatus.InstanceID][]*componentstatus.Event
startupErr error
shutdownErr error
}{
{
name: "successful startup/shutdown",
edge: [2]*testNode{rNoErr, eNoErr},
expectedStatuses: map[*componentstatus.InstanceID][]*componentstatus.Event{
instanceIDs[rNoErr]: {
componentstatus.NewEvent(componentstatus.StatusStarting),
componentstatus.NewEvent(componentstatus.StatusOK),
componentstatus.NewEvent(componentstatus.StatusStopping),
componentstatus.NewEvent(componentstatus.StatusStopped),
},
instanceIDs[eNoErr]: {
componentstatus.NewEvent(componentstatus.StatusStarting),
componentstatus.NewEvent(componentstatus.StatusOK),
componentstatus.NewEvent(componentstatus.StatusStopping),
componentstatus.NewEvent(componentstatus.StatusStopped),
},
},
},
{
name: "early startup error",
edge: [2]*testNode{rNoErr, eStErr},
expectedStatuses: map[*componentstatus.InstanceID][]*componentstatus.Event{
instanceIDs[eStErr]: {
componentstatus.NewEvent(componentstatus.StatusStarting),
componentstatus.NewPermanentErrorEvent(assert.AnError),
componentstatus.NewEvent(componentstatus.StatusStopping),
componentstatus.NewEvent(componentstatus.StatusStopped),
},
},
startupErr: assert.AnError,
},
{
name: "late startup error",
edge: [2]*testNode{rStErr, eNoErr},
expectedStatuses: map[*componentstatus.InstanceID][]*componentstatus.Event{
instanceIDs[rStErr]: {
componentstatus.NewEvent(componentstatus.StatusStarting),
componentstatus.NewPermanentErrorEvent(assert.AnError),
componentstatus.NewEvent(componentstatus.StatusStopping),
componentstatus.NewEvent(componentstatus.StatusStopped),
},
instanceIDs[eNoErr]: {
componentstatus.NewEvent(componentstatus.StatusStarting),
componentstatus.NewEvent(componentstatus.StatusOK),
componentstatus.NewEvent(componentstatus.StatusStopping),
componentstatus.NewEvent(componentstatus.StatusStopped),
},
},
startupErr: assert.AnError,
},
{
name: "early shutdown error",
edge: [2]*testNode{rSdErr, eNoErr},
expectedStatuses: map[*componentstatus.InstanceID][]*componentstatus.Event{
instanceIDs[rSdErr]: {
componentstatus.NewEvent(componentstatus.StatusStarting),
componentstatus.NewEvent(componentstatus.StatusOK),
componentstatus.NewEvent(componentstatus.StatusStopping),
componentstatus.NewPermanentErrorEvent(assert.AnError),
},
instanceIDs[eNoErr]: {
componentstatus.NewEvent(componentstatus.StatusStarting),
componentstatus.NewEvent(componentstatus.StatusOK),
componentstatus.NewEvent(componentstatus.StatusStopping),
componentstatus.NewEvent(componentstatus.StatusStopped),
},
},
shutdownErr: assert.AnError,
},
{
name: "late shutdown error",
edge: [2]*testNode{rNoErr, eSdErr},
expectedStatuses: map[*componentstatus.InstanceID][]*componentstatus.Event{
instanceIDs[rNoErr]: {
componentstatus.NewEvent(componentstatus.StatusStarting),
componentstatus.NewEvent(componentstatus.StatusOK),
componentstatus.NewEvent(componentstatus.StatusStopping),
componentstatus.NewEvent(componentstatus.StatusStopped),
},
instanceIDs[eSdErr]: {
componentstatus.NewEvent(componentstatus.StatusStarting),
componentstatus.NewEvent(componentstatus.StatusOK),
componentstatus.NewEvent(componentstatus.StatusStopping),
componentstatus.NewPermanentErrorEvent(assert.AnError),
},
},
shutdownErr: assert.AnError,
},
} {
t.Run(tt.name, func(t *testing.T) {
pg := &Graph{componentGraph: simple.NewDirectedGraph()}
pg.telemetry = componenttest.NewNopTelemetrySettings()
actualStatuses := make(map[*componentstatus.InstanceID][]*componentstatus.Event)
rep := status.NewReporter(func(id *componentstatus.InstanceID, ev *componentstatus.Event) {
actualStatuses[id] = append(actualStatuses[id], ev)
}, func(error) {
})
e0, e1 := tt.edge[0], tt.edge[1]
pg.instanceIDs = map[int64]*componentstatus.InstanceID{
e0.ID(): instanceIDs[e0],
e1.ID(): instanceIDs[e1],
}
pg.componentGraph.SetEdge(simple.Edge{F: e0, T: e1})
require.ErrorIs(t, pg.StartAll(context.Background(), &Host{Reporter: rep}), tt.startupErr)
assert.Equal(t, tt.shutdownErr, pg.ShutdownAll(context.Background(), rep))
assertEqualStatuses(t, tt.expectedStatuses, actualStatuses)
})
}
}
================================================
FILE: service/internal/graph/metadata.yaml
================================================
type: graph
github_project: open-telemetry/opentelemetry-collector
status:
disable_codecov_badge: true
class: service
codeowners:
emeritus:
- djaglowski
================================================
FILE: service/internal/graph/obs_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package graph
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/connector"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/pdata/testdata"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/pipeline/xpipeline"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/service/internal/builders"
"go.opentelemetry.io/collector/service/internal/obsconsumer"
"go.opentelemetry.io/collector/service/internal/testcomponents"
"go.opentelemetry.io/collector/service/pipelines"
)
func TestComponentInstrumentation(t *testing.T) {
setObsConsumerGateForTest(t, true)
// All IDs have a name to ensure the "otelcol.component.id" attribute is not just the type
rcvrID := component.MustNewIDWithName("examplereceiver", "foo")
procID := component.MustNewIDWithName("exampleprocessor", "bar")
routerID := component.MustNewIDWithName("examplerouter", "baz")
expRightID := component.MustNewIDWithName("exampleexporter", "right")
expLeftID := component.MustNewIDWithName("exampleexporter", "left")
tracesInID := pipeline.NewIDWithName(pipeline.SignalTraces, "in")
tracesRightID := pipeline.NewIDWithName(pipeline.SignalTraces, "right")
tracesLeftID := pipeline.NewIDWithName(pipeline.SignalTraces, "left")
metricsInID := pipeline.NewIDWithName(pipeline.SignalMetrics, "in")
metricsRightID := pipeline.NewIDWithName(pipeline.SignalMetrics, "right")
metricsLeftID := pipeline.NewIDWithName(pipeline.SignalMetrics, "left")
logsInID := pipeline.NewIDWithName(pipeline.SignalLogs, "in")
logsRightID := pipeline.NewIDWithName(pipeline.SignalLogs, "right")
logsLeftID := pipeline.NewIDWithName(pipeline.SignalLogs, "left")
profilesInID := pipeline.NewIDWithName(xpipeline.SignalProfiles, "in")
profilesRightID := pipeline.NewIDWithName(xpipeline.SignalProfiles, "right")
profilesLeftID := pipeline.NewIDWithName(xpipeline.SignalProfiles, "left")
ctx := context.Background()
testTel := componenttest.NewTelemetry()
set := Settings{
Telemetry: testTel.NewTelemetrySettings(),
BuildInfo: component.NewDefaultBuildInfo(),
ReceiverBuilder: builders.NewReceiver(
map[component.ID]component.Config{
rcvrID: testcomponents.ExampleReceiverFactory.CreateDefaultConfig(),
},
map[component.Type]receiver.Factory{
testcomponents.ExampleReceiverFactory.Type(): testcomponents.ExampleReceiverFactory,
},
),
ProcessorBuilder: builders.NewProcessor(
map[component.ID]component.Config{
procID: testcomponents.ExampleProcessorFactory.CreateDefaultConfig(),
},
map[component.Type]processor.Factory{
testcomponents.ExampleProcessorFactory.Type(): testcomponents.ExampleProcessorFactory,
},
),
ExporterBuilder: builders.NewExporter(
map[component.ID]component.Config{
expRightID: testcomponents.ExampleExporterFactory.CreateDefaultConfig(),
expLeftID: testcomponents.ExampleExporterFactory.CreateDefaultConfig(),
},
map[component.Type]exporter.Factory{
testcomponents.ExampleExporterFactory.Type(): testcomponents.ExampleExporterFactory,
},
),
ConnectorBuilder: builders.NewConnector(
map[component.ID]component.Config{
routerID: testcomponents.ExampleRouterConfig{
Traces: &testcomponents.LeftRightConfig{
Right: tracesRightID,
Left: tracesLeftID,
},
Metrics: &testcomponents.LeftRightConfig{
Right: metricsRightID,
Left: metricsLeftID,
},
Logs: &testcomponents.LeftRightConfig{
Right: logsRightID,
Left: logsLeftID,
},
Profiles: &testcomponents.LeftRightConfig{
Right: profilesRightID,
Left: profilesLeftID,
},
},
},
map[component.Type]connector.Factory{
testcomponents.ExampleRouterFactory.Type(): testcomponents.ExampleRouterFactory,
},
),
PipelineConfigs: pipelines.Config{
tracesInID: {
Receivers: []component.ID{rcvrID},
Processors: []component.ID{procID},
Exporters: []component.ID{routerID},
},
tracesRightID: {
Receivers: []component.ID{routerID},
Exporters: []component.ID{expRightID},
},
tracesLeftID: {
Receivers: []component.ID{routerID},
Exporters: []component.ID{expLeftID},
},
metricsInID: {
Receivers: []component.ID{rcvrID},
Processors: []component.ID{procID},
Exporters: []component.ID{routerID},
},
metricsRightID: {
Receivers: []component.ID{routerID},
Exporters: []component.ID{expRightID},
},
metricsLeftID: {
Receivers: []component.ID{routerID},
Exporters: []component.ID{expLeftID},
},
logsInID: {
Receivers: []component.ID{rcvrID},
Processors: []component.ID{procID},
Exporters: []component.ID{routerID},
},
logsRightID: {
Receivers: []component.ID{routerID},
Exporters: []component.ID{expRightID},
},
logsLeftID: {
Receivers: []component.ID{routerID},
Exporters: []component.ID{expLeftID},
},
profilesInID: {
Receivers: []component.ID{rcvrID},
Processors: []component.ID{procID},
Exporters: []component.ID{routerID},
},
profilesRightID: {
Receivers: []component.ID{routerID},
Exporters: []component.ID{expRightID},
},
profilesLeftID: {
Receivers: []component.ID{routerID},
Exporters: []component.ID{expLeftID},
},
},
}
pg, err := Build(ctx, set)
require.NoError(t, err)
allReceivers := pg.getReceivers()
assert.Len(t, pg.pipelines, len(set.PipelineConfigs))
// First 3 right, next 2 left
tracesReceiver := allReceivers[pipeline.SignalTraces][rcvrID].(*testcomponents.ExampleReceiver)
require.NoError(t, tracesReceiver.ConsumeTraces(ctx, testdata.GenerateTraces(3)))
require.NoError(t, tracesReceiver.ConsumeTraces(ctx, testdata.GenerateTraces(2)))
// First 5 right, next 4 left
metricsReceiver := allReceivers[pipeline.SignalMetrics][rcvrID].(*testcomponents.ExampleReceiver)
require.NoError(t, metricsReceiver.ConsumeMetrics(ctx, testdata.GenerateMetrics(5)))
require.NoError(t, metricsReceiver.ConsumeMetrics(ctx, testdata.GenerateMetrics(4)))
// First 7 right, next 6 left
logsReceiver := allReceivers[pipeline.SignalLogs][rcvrID].(*testcomponents.ExampleReceiver)
require.NoError(t, logsReceiver.ConsumeLogs(ctx, testdata.GenerateLogs(7)))
require.NoError(t, logsReceiver.ConsumeLogs(ctx, testdata.GenerateLogs(6)))
// First 9 right, next 8 left
profilesReceiver := allReceivers[xpipeline.SignalProfiles][rcvrID].(*testcomponents.ExampleReceiver)
require.NoError(t, profilesReceiver.ConsumeProfiles(ctx, testdata.GenerateProfiles(9)))
require.NoError(t, profilesReceiver.ConsumeProfiles(ctx, testdata.GenerateProfiles(8)))
// TODO fix metric name prefix delimiter
expectedScopeMetrics := simpleScopeMetrics{
// Traces
attribute.NewSet(
attribute.String("otelcol.component.kind", "receiver"),
attribute.String("otelcol.component.id", "examplereceiver/foo"),
attribute.String("otelcol.signal", "traces"),
): simpleMetricMap{
"otelcol.receiver.produced.items": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 5,
},
"otelcol.receiver.produced.size": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 866,
},
},
attribute.NewSet(
attribute.String("otelcol.component.kind", "processor"),
attribute.String("otelcol.component.id", "exampleprocessor/bar"),
attribute.String("otelcol.pipeline.id", "traces/in"),
attribute.String("otelcol.signal", "traces"),
): simpleMetricMap{
"otelcol.processor.consumed.items": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 5,
},
"otelcol.processor.produced.items": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 5,
},
"otelcol.processor.consumed.size": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 866,
},
"otelcol.processor.produced.size": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 866,
},
},
attribute.NewSet(
attribute.String("otelcol.component.kind", "connector"),
attribute.String("otelcol.component.id", "examplerouter/baz"),
attribute.String("otelcol.signal", "traces"),
attribute.String("otelcol.signal.output", "traces"),
): simpleMetricMap{
"otelcol.connector.consumed.items": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 5,
},
"otelcol.connector.produced.items": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
attribute.String("otelcol.pipeline.id", "traces/right"),
): 3,
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
attribute.String("otelcol.pipeline.id", "traces/left"),
): 2,
},
"otelcol.connector.consumed.size": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 866,
},
"otelcol.connector.produced.size": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
attribute.String("otelcol.pipeline.id", "traces/right"),
): 528,
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
attribute.String("otelcol.pipeline.id", "traces/left"),
): 338,
},
},
attribute.NewSet(
attribute.String("otelcol.component.kind", "exporter"),
attribute.String("otelcol.component.id", "exampleexporter/right"),
attribute.String("otelcol.signal", "traces"),
): simpleMetricMap{
"otelcol.exporter.consumed.items": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 3,
},
"otelcol.exporter.consumed.size": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 528,
},
},
attribute.NewSet(
attribute.String("otelcol.component.kind", "exporter"),
attribute.String("otelcol.component.id", "exampleexporter/left"),
attribute.String("otelcol.signal", "traces"),
): simpleMetricMap{
"otelcol.exporter.consumed.items": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 2,
},
"otelcol.exporter.consumed.size": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 338,
},
},
// Metrics
attribute.NewSet(
attribute.String("otelcol.component.kind", "receiver"),
attribute.String("otelcol.component.id", "examplereceiver/foo"),
attribute.String("otelcol.signal", "metrics"),
): simpleMetricMap{
"otelcol.receiver.produced.items": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 18, // GenerateMetrics(9) produces 18 data points
},
"otelcol.receiver.produced.size": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 1741, // GenerateMetrics(9) produces 18 data points
},
},
attribute.NewSet(
attribute.String("otelcol.component.kind", "processor"),
attribute.String("otelcol.component.id", "exampleprocessor/bar"),
attribute.String("otelcol.pipeline.id", "metrics/in"),
attribute.String("otelcol.signal", "metrics"),
): simpleMetricMap{
"otelcol.processor.consumed.items": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 18, // GenerateMetrics(9) produces 18 data points
},
"otelcol.processor.produced.items": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 18, // GenerateMetrics(9) produces 18 data points
},
"otelcol.processor.consumed.size": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 1741, // GenerateMetrics(9) produces 18 data points
},
"otelcol.processor.produced.size": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 1741, // GenerateMetrics(9) produces 18 data points
},
},
attribute.NewSet(
attribute.String("otelcol.component.kind", "connector"),
attribute.String("otelcol.component.id", "examplerouter/baz"),
attribute.String("otelcol.signal", "metrics"),
attribute.String("otelcol.signal.output", "metrics"),
): simpleMetricMap{
"otelcol.connector.consumed.items": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 18, // GenerateMetrics(9) produces 18 data points
},
"otelcol.connector.produced.items": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
attribute.String("otelcol.pipeline.id", "metrics/right"),
): 10, // GenerateMetrics(5) produces 10 data points
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
attribute.String("otelcol.pipeline.id", "metrics/left"),
): 8, // GenerateMetrics(4) produces 8 data points
},
"otelcol.connector.consumed.size": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 1741, // GenerateMetrics(9) produces 18 data points
},
"otelcol.connector.produced.size": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
attribute.String("otelcol.pipeline.id", "metrics/right"),
): 1035, // GenerateMetrics(5) produces 10 data points
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
attribute.String("otelcol.pipeline.id", "metrics/left"),
): 706, // GenerateMetrics(4) produces 8 data points
},
},
attribute.NewSet(
attribute.String("otelcol.component.kind", "exporter"),
attribute.String("otelcol.component.id", "exampleexporter/right"),
attribute.String("otelcol.signal", "metrics"),
): simpleMetricMap{
"otelcol.exporter.consumed.items": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 10, // GenerateMetrics(5) produces 10 data points
},
"otelcol.exporter.consumed.size": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 1035, // GenerateMetrics(9) produces 18 data points
},
},
attribute.NewSet(
attribute.String("otelcol.component.kind", "exporter"),
attribute.String("otelcol.component.id", "exampleexporter/left"),
attribute.String("otelcol.signal", "metrics"),
): simpleMetricMap{
"otelcol.exporter.consumed.items": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 8, // GenerateMetrics(4) produces 8 data points
},
"otelcol.exporter.consumed.size": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 706, // GenerateMetrics(9) produces 18 data points
},
},
// Logs
attribute.NewSet(
attribute.String("otelcol.component.kind", "receiver"),
attribute.String("otelcol.component.id", "examplereceiver/foo"),
attribute.String("otelcol.signal", "logs"),
): simpleMetricMap{
"otelcol.receiver.produced.items": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 13,
},
"otelcol.receiver.produced.size": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 1363,
},
},
attribute.NewSet(
attribute.String("otelcol.component.kind", "processor"),
attribute.String("otelcol.component.id", "exampleprocessor/bar"),
attribute.String("otelcol.pipeline.id", "logs/in"),
attribute.String("otelcol.signal", "logs"),
): simpleMetricMap{
"otelcol.processor.consumed.items": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 13,
},
"otelcol.processor.produced.items": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 13,
},
"otelcol.processor.consumed.size": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 1363,
},
"otelcol.processor.produced.size": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 1363,
},
},
attribute.NewSet(
attribute.String("otelcol.component.kind", "connector"),
attribute.String("otelcol.component.id", "examplerouter/baz"),
attribute.String("otelcol.signal", "logs"),
attribute.String("otelcol.signal.output", "logs"),
): simpleMetricMap{
"otelcol.connector.consumed.items": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 13,
},
"otelcol.connector.produced.items": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
attribute.String("otelcol.pipeline.id", "logs/right"),
): 7,
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
attribute.String("otelcol.pipeline.id", "logs/left"),
): 6,
},
"otelcol.connector.consumed.size": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 1363,
},
"otelcol.connector.produced.size": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
attribute.String("otelcol.pipeline.id", "logs/right"),
): 737,
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
attribute.String("otelcol.pipeline.id", "logs/left"),
): 626,
},
},
attribute.NewSet(
attribute.String("otelcol.component.kind", "exporter"),
attribute.String("otelcol.component.id", "exampleexporter/right"),
attribute.String("otelcol.signal", "logs"),
): simpleMetricMap{
"otelcol.exporter.consumed.items": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 7,
},
"otelcol.exporter.consumed.size": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 737,
},
},
attribute.NewSet(
attribute.String("otelcol.component.kind", "exporter"),
attribute.String("otelcol.component.id", "exampleexporter/left"),
attribute.String("otelcol.signal", "logs"),
): simpleMetricMap{
"otelcol.exporter.consumed.items": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 6,
},
"otelcol.exporter.consumed.size": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 626,
},
},
// Profiles
attribute.NewSet(
attribute.String("otelcol.component.kind", "receiver"),
attribute.String("otelcol.component.id", "examplereceiver/foo"),
attribute.String("otelcol.signal", "profiles"),
): simpleMetricMap{
"otelcol.receiver.produced.items": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 17,
},
"otelcol.receiver.produced.size": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 1055,
},
},
attribute.NewSet(
attribute.String("otelcol.component.kind", "processor"),
attribute.String("otelcol.component.id", "exampleprocessor/bar"),
attribute.String("otelcol.pipeline.id", "profiles/in"),
attribute.String("otelcol.signal", "profiles"),
): simpleMetricMap{
"otelcol.processor.consumed.items": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 17,
},
"otelcol.processor.produced.items": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 17,
},
"otelcol.processor.consumed.size": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 1055,
},
"otelcol.processor.produced.size": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 1055,
},
},
attribute.NewSet(
attribute.String("otelcol.component.kind", "connector"),
attribute.String("otelcol.component.id", "examplerouter/baz"),
attribute.String("otelcol.signal", "profiles"),
attribute.String("otelcol.signal.output", "profiles"),
): simpleMetricMap{
"otelcol.connector.consumed.items": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 17,
},
"otelcol.connector.produced.items": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
attribute.String("otelcol.pipeline.id", "profiles/right"),
): 9,
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
attribute.String("otelcol.pipeline.id", "profiles/left"),
): 8,
},
"otelcol.connector.consumed.size": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 1055,
},
"otelcol.connector.produced.size": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
attribute.String("otelcol.pipeline.id", "profiles/right"),
): 552,
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
attribute.String("otelcol.pipeline.id", "profiles/left"),
): 503,
},
},
attribute.NewSet(
attribute.String("otelcol.component.kind", "exporter"),
attribute.String("otelcol.component.id", "exampleexporter/right"),
attribute.String("otelcol.signal", "profiles"),
): simpleMetricMap{
"otelcol.exporter.consumed.items": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 9,
},
"otelcol.exporter.consumed.size": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 552,
},
},
attribute.NewSet(
attribute.String("otelcol.component.kind", "exporter"),
attribute.String("otelcol.component.id", "exampleexporter/left"),
attribute.String("otelcol.signal", "profiles"),
): simpleMetricMap{
"otelcol.exporter.consumed.items": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 8,
},
"otelcol.exporter.consumed.size": simpleMetric{
attribute.NewSet(
attribute.String(obsconsumer.ComponentOutcome, "success"),
): 503,
},
},
}
// Verify telemetry was properly emitted by components
var rm metricdata.ResourceMetrics
require.NoError(t, testTel.Reader.Collect(ctx, &rm))
require.NotNil(t, rm)
require.NotNil(t, rm.ScopeMetrics)
assert.Len(t, rm.ScopeMetrics, len(expectedScopeMetrics))
for _, actualScopeMetrics := range rm.ScopeMetrics {
expectedScopeMetrics, ok := expectedScopeMetrics[actualScopeMetrics.Scope.Attributes]
require.True(t, ok)
for _, actualMetric := range actualScopeMetrics.Metrics {
expectedMetric, ok := expectedScopeMetrics[actualMetric.Name]
require.True(t, ok)
for _, actualPoint := range actualMetric.Data.(metricdata.Sum[int64]).DataPoints {
expectedPoint, ok := expectedMetric[actualPoint.Attributes]
require.True(t, ok)
assert.Equal(t, int64(expectedPoint), actualPoint.Value)
}
}
}
assert.NoError(t, testTel.Shutdown(ctx))
}
// map[dataPointAttrs]value
type simpleMetric map[attribute.Set]int
// map[metricName]simpleMetric
type simpleMetricMap map[string]simpleMetric
// map[scopeAttrs]simpleMetricMap
type simpleScopeMetrics map[attribute.Set]simpleMetricMap
================================================
FILE: service/internal/graph/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package graph
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: service/internal/graph/processor.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package graph // import "go.opentelemetry.io/collector/service/internal/graph"
import (
"context"
"fmt"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/pipeline/xpipeline"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/service/internal/attribute"
"go.opentelemetry.io/collector/service/internal/builders"
"go.opentelemetry.io/collector/service/internal/componentattribute"
"go.opentelemetry.io/collector/service/internal/metadata"
"go.opentelemetry.io/collector/service/internal/obsconsumer"
"go.opentelemetry.io/collector/service/internal/refconsumer"
)
var _ consumerNode = (*processorNode)(nil)
// Every processor instance is unique to one pipeline.
// Therefore, nodeID is derived from "pipeline ID" and "component ID".
type processorNode struct {
attribute.Attributes
componentID component.ID
pipelineID pipeline.ID
component.Component
consumer baseConsumer
}
func newProcessorNode(pipelineID pipeline.ID, procID component.ID) *processorNode {
return &processorNode{
Attributes: attribute.Processor(pipelineID, procID),
componentID: procID,
pipelineID: pipelineID,
}
}
func (n *processorNode) getConsumer() baseConsumer {
return n.consumer
}
func (n *processorNode) buildComponent(ctx context.Context,
tel component.TelemetrySettings,
info component.BuildInfo,
builder *builders.ProcessorBuilder,
next baseConsumer,
) error {
set := processor.Settings{
ID: n.componentID,
TelemetrySettings: componentattribute.TelemetrySettingsWithAttributes(tel, *n.Set()),
BuildInfo: info,
}
tb, err := metadata.NewTelemetryBuilder(set.TelemetrySettings)
if err != nil {
return err
}
producedSettings := obsconsumer.Settings{
ItemCounter: tb.ProcessorProducedItems,
SizeCounter: tb.ProcessorProducedSize,
Logger: set.Logger,
}
consumedSettings := obsconsumer.Settings{
ItemCounter: tb.ProcessorConsumedItems,
SizeCounter: tb.ProcessorConsumedSize,
Logger: set.Logger,
}
switch n.pipelineID.Signal() {
case pipeline.SignalTraces:
n.Component, err = builder.CreateTraces(ctx, set,
obsconsumer.NewTraces(next.(consumer.Traces), producedSettings),
)
if err != nil {
return fmt.Errorf("failed to create %q processor, in pipeline %q: %w", set.ID, n.pipelineID.String(), err)
}
n.consumer = obsconsumer.NewTraces(n.Component.(consumer.Traces), consumedSettings)
n.consumer = refconsumer.NewTraces(n.consumer.(consumer.Traces))
case pipeline.SignalMetrics:
n.Component, err = builder.CreateMetrics(ctx, set,
obsconsumer.NewMetrics(next.(consumer.Metrics), producedSettings))
if err != nil {
return fmt.Errorf("failed to create %q processor, in pipeline %q: %w", set.ID, n.pipelineID.String(), err)
}
n.consumer = obsconsumer.NewMetrics(n.Component.(consumer.Metrics), consumedSettings)
n.consumer = refconsumer.NewMetrics(n.consumer.(consumer.Metrics))
case pipeline.SignalLogs:
n.Component, err = builder.CreateLogs(ctx, set,
obsconsumer.NewLogs(next.(consumer.Logs), producedSettings))
if err != nil {
return fmt.Errorf("failed to create %q processor, in pipeline %q: %w", set.ID, n.pipelineID.String(), err)
}
n.consumer = obsconsumer.NewLogs(n.Component.(consumer.Logs), consumedSettings)
n.consumer = refconsumer.NewLogs(n.consumer.(consumer.Logs))
case xpipeline.SignalProfiles:
n.Component, err = builder.CreateProfiles(ctx, set,
obsconsumer.NewProfiles(next.(xconsumer.Profiles), producedSettings))
if err != nil {
return fmt.Errorf("failed to create %q processor, in pipeline %q: %w", set.ID, n.pipelineID.String(), err)
}
n.consumer = obsconsumer.NewProfiles(n.Component.(xconsumer.Profiles), consumedSettings)
n.consumer = refconsumer.NewProfiles(n.consumer.(xconsumer.Profiles))
default:
return fmt.Errorf("error creating processor %q in pipeline %q, data type %q is not supported", set.ID, n.pipelineID.String(), n.pipelineID.Signal())
}
return nil
}
================================================
FILE: service/internal/graph/receiver.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package graph // import "go.opentelemetry.io/collector/service/internal/graph"
import (
"context"
"fmt"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/internal/fanoutconsumer"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/pipeline/xpipeline"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/service/internal/attribute"
"go.opentelemetry.io/collector/service/internal/builders"
"go.opentelemetry.io/collector/service/internal/componentattribute"
"go.opentelemetry.io/collector/service/internal/metadata"
"go.opentelemetry.io/collector/service/internal/obsconsumer"
)
// A receiver instance can be shared by multiple pipelines of the same type.
// Therefore, nodeID is derived from "pipeline type" and "component ID".
type receiverNode struct {
attribute.Attributes
componentID component.ID
pipelineType pipeline.Signal
component.Component
}
func newReceiverNode(pipelineType pipeline.Signal, recvID component.ID) *receiverNode {
return &receiverNode{
Attributes: attribute.Receiver(pipelineType, recvID),
componentID: recvID,
pipelineType: pipelineType,
}
}
func (n *receiverNode) buildComponent(ctx context.Context,
tel component.TelemetrySettings,
info component.BuildInfo,
builder *builders.ReceiverBuilder,
nexts []baseConsumer,
) error {
set := receiver.Settings{
ID: n.componentID,
TelemetrySettings: componentattribute.TelemetrySettingsWithAttributes(tel, *n.Set()),
BuildInfo: info,
}
tb, err := metadata.NewTelemetryBuilder(set.TelemetrySettings)
if err != nil {
return err
}
producedSettings := obsconsumer.Settings{
ItemCounter: tb.ReceiverProducedItems,
SizeCounter: tb.ReceiverProducedSize,
Logger: set.Logger,
}
switch n.pipelineType {
case pipeline.SignalTraces:
var consumers []consumer.Traces
for _, next := range nexts {
consumers = append(consumers, next.(consumer.Traces))
}
n.Component, err = builder.CreateTraces(ctx, set,
obsconsumer.NewTraces(fanoutconsumer.NewTraces(consumers), producedSettings),
)
case pipeline.SignalMetrics:
var consumers []consumer.Metrics
for _, next := range nexts {
consumers = append(consumers, next.(consumer.Metrics))
}
n.Component, err = builder.CreateMetrics(ctx, set,
obsconsumer.NewMetrics(fanoutconsumer.NewMetrics(consumers), producedSettings))
case pipeline.SignalLogs:
var consumers []consumer.Logs
for _, next := range nexts {
consumers = append(consumers, next.(consumer.Logs))
}
n.Component, err = builder.CreateLogs(ctx, set,
obsconsumer.NewLogs(fanoutconsumer.NewLogs(consumers), producedSettings))
case xpipeline.SignalProfiles:
var consumers []xconsumer.Profiles
for _, next := range nexts {
consumers = append(consumers, next.(xconsumer.Profiles))
}
n.Component, err = builder.CreateProfiles(ctx, set,
obsconsumer.NewProfiles(fanoutconsumer.NewProfiles(consumers), producedSettings))
default:
return fmt.Errorf("error creating receiver %q for data type %q is not supported", set.ID, n.pipelineType)
}
if err != nil {
return fmt.Errorf("failed to create %q receiver for data type %q: %w", set.ID, n.pipelineType, err)
}
return nil
}
================================================
FILE: service/internal/graph/util_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package graph
import (
"context"
"errors"
"hash/fnv"
"sync"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/connector"
"go.opentelemetry.io/collector/connector/xconnector"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/xexporter"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pipeline"
"go.opentelemetry.io/collector/pipeline/xpipeline"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/xprocessor"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/xreceiver"
"go.opentelemetry.io/collector/service/internal/metadata"
"go.opentelemetry.io/collector/service/pipelines"
)
var _ component.Component = (*testNode)(nil)
type testNode struct {
id component.ID
startErr error
shutdownErr error
}
// ID satisfies the graph.Node interface, allowing
// testNode to be used in a simple.DirectedGraph
func (n *testNode) ID() int64 {
h := fnv.New64a()
h.Write([]byte(n.id.String()))
// The graph identifies nodes by an int64 ID, but fnv gives us a uint64.
// It is safe to cast because the meaning of the number is irrelevant.
// We only care that each node has a unique 64 bit ID, which is unaltered by this cast.
return int64(h.Sum64()) // #nosec G115
}
func (n *testNode) Start(ctx context.Context, _ component.Host) error {
if n.startErr != nil {
return n.startErr
}
if cwo, ok := ctx.(*contextWithOrder); ok {
cwo.record(n.id)
}
return nil
}
func (n *testNode) Shutdown(ctx context.Context) error {
if n.shutdownErr != nil {
return n.shutdownErr
}
if cwo, ok := ctx.(*contextWithOrder); ok {
cwo.record(n.id)
}
return nil
}
type contextWithOrder struct {
context.Context
sync.Mutex
next int
order map[component.ID]int
}
func (c *contextWithOrder) record(id component.ID) {
c.Lock()
c.order[id] = c.next
c.next++
c.Unlock()
}
func (g *Graph) getReceivers() map[pipeline.Signal]map[component.ID]component.Component {
receiversMap := make(map[pipeline.Signal]map[component.ID]component.Component)
receiversMap[pipeline.SignalTraces] = make(map[component.ID]component.Component)
receiversMap[pipeline.SignalMetrics] = make(map[component.ID]component.Component)
receiversMap[pipeline.SignalLogs] = make(map[component.ID]component.Component)
receiversMap[xpipeline.SignalProfiles] = make(map[component.ID]component.Component)
for _, pg := range g.pipelines {
for _, rcvrNode := range pg.receivers {
rcvrOrConnNode := g.componentGraph.Node(rcvrNode.ID())
rcvrNode, ok := rcvrOrConnNode.(*receiverNode)
if !ok {
continue
}
receiversMap[rcvrNode.pipelineType][rcvrNode.componentID] = rcvrNode.Component
}
}
return receiversMap
}
// Calculates the expected number of receiver and exporter instances in the specified pipeline.
//
// Expect one instance of each receiver and exporter, unless it is a connector.
//
// For Connectors:
// - Let E equal the number of pipeline types in which the connector is used as an exporter.
// - Let R equal the number of pipeline types in which the connector is used as a receiver.
//
// Within the graph as a whole, we expect E*R instances, i.e. one per combination of data types.
//
// However, within an individual pipeline, we expect:
// - E instances of the connector as a receiver.
// - R instances of the connector as an exporter.
func expectedInstances(m pipelines.Config, pID pipeline.ID) (int, int) {
exConnectorType := component.MustNewType("exampleconnector")
var r, e int
for _, rID := range m[pID].Receivers {
if rID.Type() != exConnectorType {
r++
continue
}
// This is a connector. Count the pipeline types where it is an exporter.
typeMap := map[pipeline.Signal]bool{}
for pID, pCfg := range m {
for _, eID := range pCfg.Exporters {
if eID == rID {
typeMap[pID.Signal()] = true
}
}
}
r += len(typeMap)
}
for _, eID := range m[pID].Exporters {
if eID.Type() != exConnectorType {
e++
continue
}
// This is a connector. Count the pipeline types where it is a receiver.
typeMap := map[pipeline.Signal]bool{}
for pID, pCfg := range m {
for _, rID := range pCfg.Receivers {
if rID == eID {
typeMap[pID.Signal()] = true
}
}
}
e += len(typeMap)
}
return r, e
}
func newBadReceiverFactory() receiver.Factory {
return receiver.NewFactory(component.MustNewType("bf"), func() component.Config {
return &struct{}{}
})
}
func newBadProcessorFactory() processor.Factory {
return processor.NewFactory(component.MustNewType("bf"), func() component.Config {
return &struct{}{}
})
}
func newBadExporterFactory() exporter.Factory {
return exporter.NewFactory(component.MustNewType("bf"), func() component.Config {
return &struct{}{}
})
}
func newBadConnectorFactory() connector.Factory {
return connector.NewFactory(component.MustNewType("bf"), func() component.Config {
return &struct{}{}
})
}
func newErrReceiverFactory() receiver.Factory {
return xreceiver.NewFactory(component.MustNewType("err"),
func() component.Config { return &struct{}{} },
xreceiver.WithTraces(func(context.Context, receiver.Settings, component.Config, consumer.Traces) (receiver.Traces, error) {
return &errComponent{}, nil
}, component.StabilityLevelUndefined),
xreceiver.WithLogs(func(context.Context, receiver.Settings, component.Config, consumer.Logs) (receiver.Logs, error) {
return &errComponent{}, nil
}, component.StabilityLevelUndefined),
xreceiver.WithMetrics(func(context.Context, receiver.Settings, component.Config, consumer.Metrics) (receiver.Metrics, error) {
return &errComponent{}, nil
}, component.StabilityLevelUndefined),
xreceiver.WithProfiles(func(context.Context, receiver.Settings, component.Config, xconsumer.Profiles) (xreceiver.Profiles, error) {
return &errComponent{}, nil
}, component.StabilityLevelUndefined),
)
}
func newErrProcessorFactory() processor.Factory {
return xprocessor.NewFactory(component.MustNewType("err"),
func() component.Config { return &struct{}{} },
xprocessor.WithTraces(func(context.Context, processor.Settings, component.Config, consumer.Traces) (processor.Traces, error) {
return &errComponent{}, nil
}, component.StabilityLevelUndefined),
xprocessor.WithLogs(func(context.Context, processor.Settings, component.Config, consumer.Logs) (processor.Logs, error) {
return &errComponent{}, nil
}, component.StabilityLevelUndefined),
xprocessor.WithMetrics(func(context.Context, processor.Settings, component.Config, consumer.Metrics) (processor.Metrics, error) {
return &errComponent{}, nil
}, component.StabilityLevelUndefined),
xprocessor.WithProfiles(func(context.Context, processor.Settings, component.Config, xconsumer.Profiles) (xprocessor.Profiles, error) {
return &errComponent{}, nil
}, component.StabilityLevelUndefined),
)
}
func newErrExporterFactory() exporter.Factory {
return xexporter.NewFactory(component.MustNewType("err"),
func() component.Config { return &struct{}{} },
xexporter.WithTraces(func(context.Context, exporter.Settings, component.Config) (exporter.Traces, error) {
return &errComponent{}, nil
}, component.StabilityLevelUndefined),
xexporter.WithLogs(func(context.Context, exporter.Settings, component.Config) (exporter.Logs, error) {
return &errComponent{}, nil
}, component.StabilityLevelUndefined),
xexporter.WithMetrics(func(context.Context, exporter.Settings, component.Config) (exporter.Metrics, error) {
return &errComponent{}, nil
}, component.StabilityLevelUndefined),
xexporter.WithProfiles(func(context.Context, exporter.Settings, component.Config) (xexporter.Profiles, error) {
return &errComponent{}, nil
}, component.StabilityLevelUndefined),
)
}
func newErrConnectorFactory() connector.Factory {
return xconnector.NewFactory(component.MustNewType("err"), func() component.Config {
return &struct{}{}
},
xconnector.WithTracesToTraces(func(context.Context, connector.Settings, component.Config, consumer.Traces) (connector.Traces, error) {
return &errComponent{}, nil
}, component.StabilityLevelUnmaintained),
xconnector.WithTracesToMetrics(func(context.Context, connector.Settings, component.Config, consumer.Metrics) (connector.Traces, error) {
return &errComponent{}, nil
}, component.StabilityLevelUnmaintained),
xconnector.WithTracesToLogs(func(context.Context, connector.Settings, component.Config, consumer.Logs) (connector.Traces, error) {
return &errComponent{}, nil
}, component.StabilityLevelUnmaintained),
xconnector.WithTracesToProfiles(func(context.Context, connector.Settings, component.Config, xconsumer.Profiles) (connector.Traces, error) {
return &errComponent{}, nil
}, component.StabilityLevelUnmaintained),
xconnector.WithMetricsToTraces(func(context.Context, connector.Settings, component.Config, consumer.Traces) (connector.Metrics, error) {
return &errComponent{}, nil
}, component.StabilityLevelUnmaintained),
xconnector.WithMetricsToMetrics(func(context.Context, connector.Settings, component.Config, consumer.Metrics) (connector.Metrics, error) {
return &errComponent{}, nil
}, component.StabilityLevelUnmaintained),
xconnector.WithMetricsToLogs(func(context.Context, connector.Settings, component.Config, consumer.Logs) (connector.Metrics, error) {
return &errComponent{}, nil
}, component.StabilityLevelUnmaintained),
xconnector.WithMetricsToProfiles(func(context.Context, connector.Settings, component.Config, xconsumer.Profiles) (connector.Metrics, error) {
return &errComponent{}, nil
}, component.StabilityLevelUnmaintained),
xconnector.WithLogsToTraces(func(context.Context, connector.Settings, component.Config, consumer.Traces) (connector.Logs, error) {
return &errComponent{}, nil
}, component.StabilityLevelUnmaintained),
xconnector.WithLogsToMetrics(func(context.Context, connector.Settings, component.Config, consumer.Metrics) (connector.Logs, error) {
return &errComponent{}, nil
}, component.StabilityLevelUnmaintained),
xconnector.WithLogsToLogs(func(context.Context, connector.Settings, component.Config, consumer.Logs) (connector.Logs, error) {
return &errComponent{}, nil
}, component.StabilityLevelUnmaintained),
xconnector.WithLogsToProfiles(func(context.Context, connector.Settings, component.Config, xconsumer.Profiles) (connector.Logs, error) {
return &errComponent{}, nil
}, component.StabilityLevelUnmaintained),
xconnector.WithProfilesToTraces(func(context.Context, connector.Settings, component.Config, consumer.Traces) (xconnector.Profiles, error) {
return &errComponent{}, nil
}, component.StabilityLevelUnmaintained),
xconnector.WithProfilesToMetrics(func(context.Context, connector.Settings, component.Config, consumer.Metrics) (xconnector.Profiles, error) {
return &errComponent{}, nil
}, component.StabilityLevelUnmaintained),
xconnector.WithProfilesToLogs(func(context.Context, connector.Settings, component.Config, consumer.Logs) (xconnector.Profiles, error) {
return &errComponent{}, nil
}, component.StabilityLevelUnmaintained),
xconnector.WithProfilesToProfiles(func(context.Context, connector.Settings, component.Config, xconsumer.Profiles) (xconnector.Profiles, error) {
return &errComponent{}, nil
}, component.StabilityLevelUnmaintained),
)
}
type errComponent struct {
consumertest.Consumer
}
func (e errComponent) Capabilities() consumer.Capabilities {
return consumer.Capabilities{MutatesData: false}
}
func (e errComponent) Start(context.Context, component.Host) error {
return errors.New("my error")
}
func (e errComponent) Shutdown(context.Context) error {
return errors.New("my error")
}
func setObsConsumerGateForTest(t *testing.T, enabled bool) {
initial := metadata.TelemetryNewPipelineTelemetryFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.TelemetryNewPipelineTelemetryFeatureGate.ID(), enabled))
t.Cleanup(func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.TelemetryNewPipelineTelemetryFeatureGate.ID(), initial))
})
}
================================================
FILE: service/internal/graph/zpages.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package graph // import "go.opentelemetry.io/collector/service/internal/graph"
import (
"net/http"
"sort"
"go.opentelemetry.io/collector/service/internal/zpages"
)
const (
// URL Params
zPipelineName = "pipelinenamez"
zComponentName = "componentnamez"
zComponentKind = "componentkindz"
)
func (g *Graph) HandleZPages(w http.ResponseWriter, r *http.Request) {
qValues := r.URL.Query()
pipelineName := qValues.Get(zPipelineName)
componentName := qValues.Get(zComponentName)
componentKind := qValues.Get(zComponentKind)
w.Header().Set("Content-Type", "text/html; charset=utf-8")
zpages.WriteHTMLPageHeader(w, zpages.HeaderData{Title: "builtPipelines"})
sumData := zpages.SummaryPipelinesTableData{}
sumData.Rows = make([]zpages.SummaryPipelinesTableRowData, 0, len(g.pipelines))
for c, p := range g.pipelines {
recvIDs := make([]string, 0, len(p.receivers))
for _, c := range p.receivers {
switch n := c.(type) {
case *receiverNode:
recvIDs = append(recvIDs, n.componentID.String())
case *connectorNode:
recvIDs = append(recvIDs, n.componentID.String()+" (connector)")
}
}
procIDs := make([]string, 0, len(p.processors))
for _, c := range p.processors {
procIDs = append(procIDs, c.(*processorNode).componentID.String())
}
exprIDs := make([]string, 0, len(p.exporters))
for _, c := range p.exporters {
switch n := c.(type) {
case *exporterNode:
exprIDs = append(exprIDs, n.componentID.String())
case *connectorNode:
exprIDs = append(exprIDs, n.componentID.String()+" (connector)")
}
}
sumData.Rows = append(sumData.Rows, zpages.SummaryPipelinesTableRowData{
FullName: c.String(),
InputType: c.Signal().String(),
MutatesData: p.capabilitiesNode.getConsumer().Capabilities().MutatesData,
Receivers: recvIDs,
Processors: procIDs,
Exporters: exprIDs,
})
}
sort.Slice(sumData.Rows, func(i, j int) bool {
return sumData.Rows[i].FullName < sumData.Rows[j].FullName
})
zpages.WriteHTMLPipelinesSummaryTable(w, sumData)
if pipelineName != "" && componentName != "" && componentKind != "" {
fullName := componentName
if componentKind == "processor" {
fullName = pipelineName + "/" + componentName
}
zpages.WriteHTMLComponentHeader(w, zpages.ComponentHeaderData{
Name: componentKind + ": " + fullName,
})
// TODO: Add config + status info.
}
zpages.WriteHTMLPageFooter(w)
}
================================================
FILE: service/internal/metadata/generated_feature_gates.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/featuregate"
)
var ServiceAllowNoPipelinesFeatureGate = featuregate.GlobalRegistry().MustRegister(
"service.AllowNoPipelines",
featuregate.StageAlpha,
featuregate.WithRegisterDescription("Allow starting the Collector without starting any pipelines."),
featuregate.WithRegisterReferenceURL("https://github.com/open-telemetry/opentelemetry-collector/pull/12613"),
featuregate.WithRegisterFromVersion("v0.122.0"),
)
var ServiceProfilesSupportFeatureGate = featuregate.GlobalRegistry().MustRegister(
"service.profilesSupport",
featuregate.StageAlpha,
featuregate.WithRegisterDescription("Controls whether profiles support can be enabled"),
featuregate.WithRegisterReferenceURL("https://github.com/open-telemetry/opentelemetry-collector/pull/11477"),
featuregate.WithRegisterFromVersion("v0.112.0"),
)
var TelemetryUseLocalHostAsDefaultMetricsAddressFeatureGate = featuregate.GlobalRegistry().MustRegister(
"telemetry.UseLocalHostAsDefaultMetricsAddress",
featuregate.StageBeta,
featuregate.WithRegisterDescription("Controls whether default Prometheus metrics server use localhost as the default host for their endpoints"),
featuregate.WithRegisterReferenceURL("https://github.com/open-telemetry/opentelemetry-collector/pull/11251"),
featuregate.WithRegisterFromVersion("v0.111.0"),
)
var TelemetryNewPipelineTelemetryFeatureGate = featuregate.GlobalRegistry().MustRegister(
"telemetry.newPipelineTelemetry",
featuregate.StageAlpha,
featuregate.WithRegisterDescription("Injects component-identifying scope attributes in internal Collector metrics"),
featuregate.WithRegisterReferenceURL("https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/rfcs/component-universal-telemetry.md"),
featuregate.WithRegisterFromVersion("v0.123.0"),
)
================================================
FILE: service/internal/metadata/generated_telemetry.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"context"
"errors"
"sync"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/metric/embedded"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/collector/component"
)
func Meter(settings component.TelemetrySettings) metric.Meter {
return settings.MeterProvider.Meter("go.opentelemetry.io/collector/service")
}
func Tracer(settings component.TelemetrySettings) trace.Tracer {
return settings.TracerProvider.Tracer("go.opentelemetry.io/collector/service")
}
// TelemetryBuilder provides an interface for components to report telemetry
// as defined in metadata and user config.
type TelemetryBuilder struct {
meter metric.Meter
mu sync.Mutex
registrations []metric.Registration
ConnectorConsumedItems metric.Int64Counter
ConnectorConsumedSize metric.Int64Counter
ConnectorProducedItems metric.Int64Counter
ConnectorProducedSize metric.Int64Counter
ExporterConsumedItems metric.Int64Counter
ExporterConsumedSize metric.Int64Counter
ProcessCPUSeconds metric.Float64ObservableCounter
ProcessMemoryRss metric.Int64ObservableGauge
ProcessRuntimeHeapAllocBytes metric.Int64ObservableGauge
ProcessRuntimeTotalAllocBytes metric.Int64ObservableCounter
ProcessRuntimeTotalSysMemoryBytes metric.Int64ObservableGauge
ProcessUptime metric.Float64ObservableCounter
ProcessorConsumedItems metric.Int64Counter
ProcessorConsumedSize metric.Int64Counter
ProcessorProducedItems metric.Int64Counter
ProcessorProducedSize metric.Int64Counter
ReceiverProducedItems metric.Int64Counter
ReceiverProducedSize metric.Int64Counter
}
// TelemetryBuilderOption applies changes to default builder.
type TelemetryBuilderOption interface {
apply(*TelemetryBuilder)
}
type telemetryBuilderOptionFunc func(mb *TelemetryBuilder)
func (tbof telemetryBuilderOptionFunc) apply(mb *TelemetryBuilder) {
tbof(mb)
}
// RegisterProcessCPUSecondsCallback sets callback for observable ProcessCPUSeconds metric.
func (builder *TelemetryBuilder) RegisterProcessCPUSecondsCallback(cb metric.Float64Callback) error {
reg, err := builder.meter.RegisterCallback(func(ctx context.Context, o metric.Observer) error {
cb(ctx, &observerFloat64{inst: builder.ProcessCPUSeconds, obs: o})
return nil
}, builder.ProcessCPUSeconds)
if err != nil {
return err
}
builder.mu.Lock()
defer builder.mu.Unlock()
builder.registrations = append(builder.registrations, reg)
return nil
}
// RegisterProcessMemoryRssCallback sets callback for observable ProcessMemoryRss metric.
func (builder *TelemetryBuilder) RegisterProcessMemoryRssCallback(cb metric.Int64Callback) error {
reg, err := builder.meter.RegisterCallback(func(ctx context.Context, o metric.Observer) error {
cb(ctx, &observerInt64{inst: builder.ProcessMemoryRss, obs: o})
return nil
}, builder.ProcessMemoryRss)
if err != nil {
return err
}
builder.mu.Lock()
defer builder.mu.Unlock()
builder.registrations = append(builder.registrations, reg)
return nil
}
// RegisterProcessRuntimeHeapAllocBytesCallback sets callback for observable ProcessRuntimeHeapAllocBytes metric.
func (builder *TelemetryBuilder) RegisterProcessRuntimeHeapAllocBytesCallback(cb metric.Int64Callback) error {
reg, err := builder.meter.RegisterCallback(func(ctx context.Context, o metric.Observer) error {
cb(ctx, &observerInt64{inst: builder.ProcessRuntimeHeapAllocBytes, obs: o})
return nil
}, builder.ProcessRuntimeHeapAllocBytes)
if err != nil {
return err
}
builder.mu.Lock()
defer builder.mu.Unlock()
builder.registrations = append(builder.registrations, reg)
return nil
}
// RegisterProcessRuntimeTotalAllocBytesCallback sets callback for observable ProcessRuntimeTotalAllocBytes metric.
func (builder *TelemetryBuilder) RegisterProcessRuntimeTotalAllocBytesCallback(cb metric.Int64Callback) error {
reg, err := builder.meter.RegisterCallback(func(ctx context.Context, o metric.Observer) error {
cb(ctx, &observerInt64{inst: builder.ProcessRuntimeTotalAllocBytes, obs: o})
return nil
}, builder.ProcessRuntimeTotalAllocBytes)
if err != nil {
return err
}
builder.mu.Lock()
defer builder.mu.Unlock()
builder.registrations = append(builder.registrations, reg)
return nil
}
// RegisterProcessRuntimeTotalSysMemoryBytesCallback sets callback for observable ProcessRuntimeTotalSysMemoryBytes metric.
func (builder *TelemetryBuilder) RegisterProcessRuntimeTotalSysMemoryBytesCallback(cb metric.Int64Callback) error {
reg, err := builder.meter.RegisterCallback(func(ctx context.Context, o metric.Observer) error {
cb(ctx, &observerInt64{inst: builder.ProcessRuntimeTotalSysMemoryBytes, obs: o})
return nil
}, builder.ProcessRuntimeTotalSysMemoryBytes)
if err != nil {
return err
}
builder.mu.Lock()
defer builder.mu.Unlock()
builder.registrations = append(builder.registrations, reg)
return nil
}
// RegisterProcessUptimeCallback sets callback for observable ProcessUptime metric.
func (builder *TelemetryBuilder) RegisterProcessUptimeCallback(cb metric.Float64Callback) error {
reg, err := builder.meter.RegisterCallback(func(ctx context.Context, o metric.Observer) error {
cb(ctx, &observerFloat64{inst: builder.ProcessUptime, obs: o})
return nil
}, builder.ProcessUptime)
if err != nil {
return err
}
builder.mu.Lock()
defer builder.mu.Unlock()
builder.registrations = append(builder.registrations, reg)
return nil
}
type observerInt64 struct {
embedded.Int64Observer
inst metric.Int64Observable
obs metric.Observer
}
func (oi *observerInt64) Observe(value int64, opts ...metric.ObserveOption) {
oi.obs.ObserveInt64(oi.inst, value, opts...)
}
type observerFloat64 struct {
embedded.Float64Observer
inst metric.Float64Observable
obs metric.Observer
}
func (oi *observerFloat64) Observe(value float64, opts ...metric.ObserveOption) {
oi.obs.ObserveFloat64(oi.inst, value, opts...)
}
// Shutdown unregister all registered callbacks for async instruments.
func (builder *TelemetryBuilder) Shutdown() {
builder.mu.Lock()
defer builder.mu.Unlock()
for _, reg := range builder.registrations {
reg.Unregister()
}
}
// NewTelemetryBuilder provides a struct with methods to update all internal telemetry
// for a component
func NewTelemetryBuilder(settings component.TelemetrySettings, options ...TelemetryBuilderOption) (*TelemetryBuilder, error) {
builder := TelemetryBuilder{}
for _, op := range options {
op.apply(&builder)
}
builder.meter = Meter(settings)
var err, errs error
builder.ConnectorConsumedItems, err = builder.meter.Int64Counter(
"otelcol.connector.consumed.items",
metric.WithDescription("Number of items passed to the connector. [Development]"),
metric.WithUnit("{item}"),
)
errs = errors.Join(errs, err)
builder.ConnectorConsumedSize, err = builder.meter.Int64Counter(
"otelcol.connector.consumed.size",
metric.WithDescription("Size of items passed to the connector, based on ProtoMarshaler.Sizer. [Development]"),
metric.WithUnit("{item}"),
)
errs = errors.Join(errs, err)
builder.ConnectorProducedItems, err = builder.meter.Int64Counter(
"otelcol.connector.produced.items",
metric.WithDescription("Number of items emitted from the connector. [Development]"),
metric.WithUnit("{item}"),
)
errs = errors.Join(errs, err)
builder.ConnectorProducedSize, err = builder.meter.Int64Counter(
"otelcol.connector.produced.size",
metric.WithDescription("Size of items emitted from the connector, based on ProtoMarshaler.Sizer. [Development]"),
metric.WithUnit("{item}"),
)
errs = errors.Join(errs, err)
builder.ExporterConsumedItems, err = builder.meter.Int64Counter(
"otelcol.exporter.consumed.items",
metric.WithDescription("Number of items passed to the exporter. [Development]"),
metric.WithUnit("{item}"),
)
errs = errors.Join(errs, err)
builder.ExporterConsumedSize, err = builder.meter.Int64Counter(
"otelcol.exporter.consumed.size",
metric.WithDescription("Size of items passed to the exporter, based on ProtoMarshaler.Sizer. [Development]"),
metric.WithUnit("{item}"),
)
errs = errors.Join(errs, err)
builder.ProcessCPUSeconds, err = builder.meter.Float64ObservableCounter(
"otelcol_process_cpu_seconds",
metric.WithDescription("Total CPU user and system time in seconds [Alpha]"),
metric.WithUnit("s"),
)
errs = errors.Join(errs, err)
builder.ProcessMemoryRss, err = builder.meter.Int64ObservableGauge(
"otelcol_process_memory_rss",
metric.WithDescription("Total physical memory (resident set size) [Alpha]"),
metric.WithUnit("By"),
)
errs = errors.Join(errs, err)
builder.ProcessRuntimeHeapAllocBytes, err = builder.meter.Int64ObservableGauge(
"otelcol_process_runtime_heap_alloc_bytes",
metric.WithDescription("Bytes of allocated heap objects (see 'go doc runtime.MemStats.HeapAlloc') [Alpha]"),
metric.WithUnit("By"),
)
errs = errors.Join(errs, err)
builder.ProcessRuntimeTotalAllocBytes, err = builder.meter.Int64ObservableCounter(
"otelcol_process_runtime_total_alloc_bytes",
metric.WithDescription("Cumulative bytes allocated for heap objects (see 'go doc runtime.MemStats.TotalAlloc') [Alpha]"),
metric.WithUnit("By"),
)
errs = errors.Join(errs, err)
builder.ProcessRuntimeTotalSysMemoryBytes, err = builder.meter.Int64ObservableGauge(
"otelcol_process_runtime_total_sys_memory_bytes",
metric.WithDescription("Total bytes of memory obtained from the OS (see 'go doc runtime.MemStats.Sys') [Alpha]"),
metric.WithUnit("By"),
)
errs = errors.Join(errs, err)
builder.ProcessUptime, err = builder.meter.Float64ObservableCounter(
"otelcol_process_uptime",
metric.WithDescription("Uptime of the process [Alpha]"),
metric.WithUnit("s"),
)
errs = errors.Join(errs, err)
builder.ProcessorConsumedItems, err = builder.meter.Int64Counter(
"otelcol.processor.consumed.items",
metric.WithDescription("Number of items passed to the processor. [Development]"),
metric.WithUnit("{item}"),
)
errs = errors.Join(errs, err)
builder.ProcessorConsumedSize, err = builder.meter.Int64Counter(
"otelcol.processor.consumed.size",
metric.WithDescription("Size of items passed to the processor, based on ProtoMarshaler.Sizer. [Development]"),
metric.WithUnit("{item}"),
)
errs = errors.Join(errs, err)
builder.ProcessorProducedItems, err = builder.meter.Int64Counter(
"otelcol.processor.produced.items",
metric.WithDescription("Number of items emitted from the processor. [Development]"),
metric.WithUnit("{item}"),
)
errs = errors.Join(errs, err)
builder.ProcessorProducedSize, err = builder.meter.Int64Counter(
"otelcol.processor.produced.size",
metric.WithDescription("Size of items emitted from the processor, based on ProtoMarshaler.Sizer. [Development]"),
metric.WithUnit("{item}"),
)
errs = errors.Join(errs, err)
builder.ReceiverProducedItems, err = builder.meter.Int64Counter(
"otelcol.receiver.produced.items",
metric.WithDescription("Number of items emitted from the receiver. [Development]"),
metric.WithUnit("{item}"),
)
errs = errors.Join(errs, err)
builder.ReceiverProducedSize, err = builder.meter.Int64Counter(
"otelcol.receiver.produced.size",
metric.WithDescription("Size of items emitted from the receiver, based on ProtoMarshaler.Sizer. [Development]"),
metric.WithUnit("{item}"),
)
errs = errors.Join(errs, err)
return &builder, errs
}
================================================
FILE: service/internal/metadata/generated_telemetry_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/metric"
embeddedmetric "go.opentelemetry.io/otel/metric/embedded"
noopmetric "go.opentelemetry.io/otel/metric/noop"
"go.opentelemetry.io/otel/trace"
embeddedtrace "go.opentelemetry.io/otel/trace/embedded"
nooptrace "go.opentelemetry.io/otel/trace/noop"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
)
type mockMeter struct {
noopmetric.Meter
name string
}
type mockMeterProvider struct {
embeddedmetric.MeterProvider
}
func (m mockMeterProvider) Meter(name string, opts ...metric.MeterOption) metric.Meter {
return mockMeter{name: name}
}
type mockTracer struct {
nooptrace.Tracer
name string
}
type mockTracerProvider struct {
embeddedtrace.TracerProvider
}
func (m mockTracerProvider) Tracer(name string, opts ...trace.TracerOption) trace.Tracer {
return mockTracer{name: name}
}
func TestProviders(t *testing.T) {
set := component.TelemetrySettings{
MeterProvider: mockMeterProvider{},
TracerProvider: mockTracerProvider{},
}
meter := Meter(set)
if m, ok := meter.(mockMeter); ok {
require.Equal(t, "go.opentelemetry.io/collector/service", m.name)
} else {
require.Fail(t, "returned Meter not mockMeter")
}
tracer := Tracer(set)
if m, ok := tracer.(mockTracer); ok {
require.Equal(t, "go.opentelemetry.io/collector/service", m.name)
} else {
require.Fail(t, "returned Meter not mockTracer")
}
}
func TestNewTelemetryBuilder(t *testing.T) {
set := componenttest.NewNopTelemetrySettings()
applied := false
_, err := NewTelemetryBuilder(set, telemetryBuilderOptionFunc(func(b *TelemetryBuilder) {
applied = true
}))
require.NoError(t, err)
require.True(t, applied)
}
================================================
FILE: service/internal/metadatatest/generated_telemetrytest.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadatatest
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"go.opentelemetry.io/collector/component/componenttest"
)
func AssertEqualConnectorConsumedItems(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol.connector.consumed.items",
Description: "Number of items passed to the connector. [Development]",
Unit: "{item}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol.connector.consumed.items")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualConnectorConsumedSize(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol.connector.consumed.size",
Description: "Size of items passed to the connector, based on ProtoMarshaler.Sizer. [Development]",
Unit: "{item}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol.connector.consumed.size")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualConnectorProducedItems(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol.connector.produced.items",
Description: "Number of items emitted from the connector. [Development]",
Unit: "{item}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol.connector.produced.items")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualConnectorProducedSize(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol.connector.produced.size",
Description: "Size of items emitted from the connector, based on ProtoMarshaler.Sizer. [Development]",
Unit: "{item}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol.connector.produced.size")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualExporterConsumedItems(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol.exporter.consumed.items",
Description: "Number of items passed to the exporter. [Development]",
Unit: "{item}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol.exporter.consumed.items")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualExporterConsumedSize(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol.exporter.consumed.size",
Description: "Size of items passed to the exporter, based on ProtoMarshaler.Sizer. [Development]",
Unit: "{item}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol.exporter.consumed.size")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualProcessCPUSeconds(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[float64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_process_cpu_seconds",
Description: "Total CPU user and system time in seconds [Alpha]",
Unit: "s",
Data: metricdata.Sum[float64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_process_cpu_seconds")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualProcessMemoryRss(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_process_memory_rss",
Description: "Total physical memory (resident set size) [Alpha]",
Unit: "By",
Data: metricdata.Gauge[int64]{
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_process_memory_rss")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualProcessRuntimeHeapAllocBytes(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_process_runtime_heap_alloc_bytes",
Description: "Bytes of allocated heap objects (see 'go doc runtime.MemStats.HeapAlloc') [Alpha]",
Unit: "By",
Data: metricdata.Gauge[int64]{
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_process_runtime_heap_alloc_bytes")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualProcessRuntimeTotalAllocBytes(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_process_runtime_total_alloc_bytes",
Description: "Cumulative bytes allocated for heap objects (see 'go doc runtime.MemStats.TotalAlloc') [Alpha]",
Unit: "By",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_process_runtime_total_alloc_bytes")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualProcessRuntimeTotalSysMemoryBytes(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_process_runtime_total_sys_memory_bytes",
Description: "Total bytes of memory obtained from the OS (see 'go doc runtime.MemStats.Sys') [Alpha]",
Unit: "By",
Data: metricdata.Gauge[int64]{
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_process_runtime_total_sys_memory_bytes")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualProcessUptime(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[float64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_process_uptime",
Description: "Uptime of the process [Alpha]",
Unit: "s",
Data: metricdata.Sum[float64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_process_uptime")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualProcessorConsumedItems(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol.processor.consumed.items",
Description: "Number of items passed to the processor. [Development]",
Unit: "{item}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol.processor.consumed.items")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualProcessorConsumedSize(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol.processor.consumed.size",
Description: "Size of items passed to the processor, based on ProtoMarshaler.Sizer. [Development]",
Unit: "{item}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol.processor.consumed.size")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualProcessorProducedItems(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol.processor.produced.items",
Description: "Number of items emitted from the processor. [Development]",
Unit: "{item}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol.processor.produced.items")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualProcessorProducedSize(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol.processor.produced.size",
Description: "Size of items emitted from the processor, based on ProtoMarshaler.Sizer. [Development]",
Unit: "{item}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol.processor.produced.size")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualReceiverProducedItems(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol.receiver.produced.items",
Description: "Number of items emitted from the receiver. [Development]",
Unit: "{item}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol.receiver.produced.items")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualReceiverProducedSize(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol.receiver.produced.size",
Description: "Size of items emitted from the receiver, based on ProtoMarshaler.Sizer. [Development]",
Unit: "{item}",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol.receiver.produced.size")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
================================================
FILE: service/internal/metadatatest/generated_telemetrytest_test.go
================================================
// Code generated by mdatagen. DO NOT EDIT.
package metadatatest
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/service/internal/metadata"
)
func TestSetupTelemetry(t *testing.T) {
testTel := componenttest.NewTelemetry()
tb, err := metadata.NewTelemetryBuilder(testTel.NewTelemetrySettings())
require.NoError(t, err)
defer tb.Shutdown()
require.NoError(t, tb.RegisterProcessCPUSecondsCallback(func(_ context.Context, observer metric.Float64Observer) error {
observer.Observe(1)
return nil
}))
require.NoError(t, tb.RegisterProcessMemoryRssCallback(func(_ context.Context, observer metric.Int64Observer) error {
observer.Observe(1)
return nil
}))
require.NoError(t, tb.RegisterProcessRuntimeHeapAllocBytesCallback(func(_ context.Context, observer metric.Int64Observer) error {
observer.Observe(1)
return nil
}))
require.NoError(t, tb.RegisterProcessRuntimeTotalAllocBytesCallback(func(_ context.Context, observer metric.Int64Observer) error {
observer.Observe(1)
return nil
}))
require.NoError(t, tb.RegisterProcessRuntimeTotalSysMemoryBytesCallback(func(_ context.Context, observer metric.Int64Observer) error {
observer.Observe(1)
return nil
}))
require.NoError(t, tb.RegisterProcessUptimeCallback(func(_ context.Context, observer metric.Float64Observer) error {
observer.Observe(1)
return nil
}))
tb.ConnectorConsumedItems.Add(context.Background(), 1)
tb.ConnectorConsumedSize.Add(context.Background(), 1)
tb.ConnectorProducedItems.Add(context.Background(), 1)
tb.ConnectorProducedSize.Add(context.Background(), 1)
tb.ExporterConsumedItems.Add(context.Background(), 1)
tb.ExporterConsumedSize.Add(context.Background(), 1)
tb.ProcessorConsumedItems.Add(context.Background(), 1)
tb.ProcessorConsumedSize.Add(context.Background(), 1)
tb.ProcessorProducedItems.Add(context.Background(), 1)
tb.ProcessorProducedSize.Add(context.Background(), 1)
tb.ReceiverProducedItems.Add(context.Background(), 1)
tb.ReceiverProducedSize.Add(context.Background(), 1)
AssertEqualConnectorConsumedItems(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualConnectorConsumedSize(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualConnectorProducedItems(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualConnectorProducedSize(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualExporterConsumedItems(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualExporterConsumedSize(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualProcessCPUSeconds(t, testTel,
[]metricdata.DataPoint[float64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualProcessMemoryRss(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualProcessRuntimeHeapAllocBytes(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualProcessRuntimeTotalAllocBytes(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualProcessRuntimeTotalSysMemoryBytes(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualProcessUptime(t, testTel,
[]metricdata.DataPoint[float64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualProcessorConsumedItems(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualProcessorConsumedSize(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualProcessorProducedItems(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualProcessorProducedSize(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualReceiverProducedItems(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
AssertEqualReceiverProducedSize(t, testTel,
[]metricdata.DataPoint[int64]{{Value: 1}},
metricdatatest.IgnoreTimestamp())
require.NoError(t, testTel.Shutdown(context.Background()))
}
================================================
FILE: service/internal/metricviews/views.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package metricviews // import "go.opentelemetry.io/collector/service/internal/metricviews"
import (
config "go.opentelemetry.io/contrib/otelconf/v0.3.0"
"go.opentelemetry.io/collector/config/configtelemetry"
)
// DefaultViews builds the default metric views used by the service.
func DefaultViews(level configtelemetry.Level) []config.View {
views := []config.View{}
if level < configtelemetry.LevelDetailed {
// Drop all otelhttp and otelgrpc metrics if the level is not detailed.
views = append(views,
dropViewOption(&config.ViewSelector{
MeterName: ptr("go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"),
}),
dropViewOption(&config.ViewSelector{
MeterName: ptr("go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"),
}),
// Drop duration metric if the level is not detailed
dropViewOption(&config.ViewSelector{
MeterName: ptr("go.opentelemetry.io/collector/processor/processorhelper"),
InstrumentName: ptr("otelcol_processor_internal_duration"),
}),
)
}
// otel-arrow library metrics
// See https://github.com/open-telemetry/otel-arrow/blob/c39257/pkg/otel/arrow_record/consumer.go#L174-L176
if level < configtelemetry.LevelNormal {
scope := ptr("otel-arrow/pkg/otel/arrow_record")
views = append(views,
dropViewOption(&config.ViewSelector{
MeterName: scope,
InstrumentName: ptr("arrow_batch_records"),
}),
dropViewOption(&config.ViewSelector{
MeterName: scope,
InstrumentName: ptr("arrow_schema_resets"),
}),
dropViewOption(&config.ViewSelector{
MeterName: scope,
InstrumentName: ptr("arrow_memory_inuse"),
}),
)
}
// contrib's internal/otelarrow/netstats metrics
// See
// - https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/a25f05/internal/otelarrow/netstats/netstats.go#L130
// - https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/a25f05/internal/otelarrow/netstats/netstats.go#L165
if level < configtelemetry.LevelDetailed {
scope := ptr("github.com/open-telemetry/opentelemetry-collector-contrib/internal/otelarrow/netstats")
views = append(views,
// Compressed size metrics.
dropViewOption(&config.ViewSelector{
MeterName: scope,
InstrumentName: ptr("otelcol_*_compressed_size"),
}),
dropViewOption(&config.ViewSelector{
MeterName: scope,
InstrumentName: ptr("otelcol_*_compressed_size"),
}),
// makeRecvMetrics for exporters.
dropViewOption(&config.ViewSelector{
MeterName: scope,
InstrumentName: ptr("otelcol_exporter_recv"),
}),
dropViewOption(&config.ViewSelector{
MeterName: scope,
InstrumentName: ptr("otelcol_exporter_recv_wire"),
}),
// makeSentMetrics for receivers.
dropViewOption(&config.ViewSelector{
MeterName: scope,
InstrumentName: ptr("otelcol_receiver_sent"),
}),
dropViewOption(&config.ViewSelector{
MeterName: scope,
InstrumentName: ptr("otelcol_receiver_sent_wire"),
}),
)
}
// Batch exporter metrics
if level < configtelemetry.LevelDetailed {
scope := ptr("go.opentelemetry.io/collector/exporter/exporterhelper")
views = append(views,
dropViewOption(&config.ViewSelector{
MeterName: scope,
InstrumentName: ptr("otelcol_exporter_queue_batch_send_size_bytes"),
}),
dropViewOption(&config.ViewSelector{
MeterName: scope,
InstrumentName: ptr("otelcol_exporter_queue_batch_send_size"),
}),
config.View{
Selector: &config.ViewSelector{
MeterName: scope,
InstrumentName: ptr("otelcol_exporter_send_failed_*"),
},
Stream: &config.ViewStream{
AttributeKeys: &config.IncludeExclude{
Excluded: []string{"error.type", "error.permanent"},
},
},
},
)
}
// Batch processor metrics
scope := ptr("go.opentelemetry.io/collector/processor/batchprocessor")
if level < configtelemetry.LevelNormal {
views = append(views, dropViewOption(&config.ViewSelector{
MeterName: scope,
}))
} else if level < configtelemetry.LevelDetailed {
views = append(views, dropViewOption(&config.ViewSelector{
MeterName: scope,
InstrumentName: ptr("otelcol_processor_batch_batch_send_size_bytes"),
}))
}
// Internal graph metrics
graphScope := ptr("go.opentelemetry.io/collector/service")
if level < configtelemetry.LevelDetailed {
views = append(views,
dropViewOption(&config.ViewSelector{
MeterName: graphScope,
InstrumentName: ptr("otelcol.*.consumed.size"),
}),
dropViewOption(&config.ViewSelector{
MeterName: graphScope,
InstrumentName: ptr("otelcol.*.produced.size"),
}))
}
return views
}
func dropViewOption(selector *config.ViewSelector) config.View {
return config.View{
Selector: selector,
Stream: &config.ViewStream{
Aggregation: &config.ViewStreamAggregation{
Drop: config.ViewStreamAggregationDrop{},
},
},
}
}
func ptr[T any](v T) *T {
return &v
}
================================================
FILE: service/internal/metricviews/views_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package metricviews
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/config/configtelemetry"
)
func TestDefaultViews(t *testing.T) {
for _, tt := range []struct {
name string
level configtelemetry.Level
wantViewsCount int
}{
{
name: "None",
level: configtelemetry.LevelNone,
wantViewsCount: 18,
},
{
name: "Basic",
level: configtelemetry.LevelBasic,
wantViewsCount: 18,
},
{
name: "Normal",
level: configtelemetry.LevelNormal,
wantViewsCount: 15,
},
{
name: "Detailed",
level: configtelemetry.LevelDetailed,
wantViewsCount: 0,
},
} {
t.Run(tt.name, func(t *testing.T) {
views := DefaultViews(tt.level)
assert.Len(t, views, tt.wantViewsCount)
})
}
}
func TestDefaultViewsFiltersSendFailedAttributes(t *testing.T) {
tests := []struct {
name string
level configtelemetry.Level
expectSendFailedFilteredView bool
}{
{
name: "basic level filters send_failed attributes",
level: configtelemetry.LevelBasic,
expectSendFailedFilteredView: true,
},
{
name: "normal level filters send_failed attributes",
level: configtelemetry.LevelNormal,
expectSendFailedFilteredView: true,
},
{
name: "detailed level does not filter send_failed attributes",
level: configtelemetry.LevelDetailed,
expectSendFailedFilteredView: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
views := DefaultViews(tt.level)
foundSendFailedView := false
for _, view := range views {
if view.Selector == nil ||
view.Selector.InstrumentName == nil ||
*view.Selector.InstrumentName != "otelcol_exporter_send_failed_*" {
continue
}
foundSendFailedView = true
require.NotNil(t, view.Stream, "send_failed view should have a stream")
require.NotNil(t, view.Stream.AttributeKeys, "send_failed view should have attribute keys")
require.Equal(t, []string{"error.type", "error.permanent"}, view.Stream.AttributeKeys.Excluded,
"send_failed view should exclude 'error.type' and 'error.permanent' attributes")
break
}
if tt.expectSendFailedFilteredView {
assert.True(t, foundSendFailedView,
"Expected to find send_failed attribute filtering view at level %s", tt.level)
} else {
assert.False(t, foundSendFailedView,
"Did not expect to find send_failed attribute filtering view at level %s", tt.level)
}
})
}
}
func TestDefaultViews_BatchExporterMetrics(t *testing.T) {
tests := []struct {
name string
level configtelemetry.Level
shouldDropBucket bool
shouldDropBytes bool
}{
{
name: "basic level drops bytes",
level: configtelemetry.LevelBasic,
shouldDropBucket: true,
shouldDropBytes: true,
},
{
name: "normal level drops bytes",
level: configtelemetry.LevelNormal,
shouldDropBucket: true,
shouldDropBytes: true,
},
{
name: "detailed level does not drop bytes",
level: configtelemetry.LevelDetailed,
shouldDropBucket: false,
shouldDropBytes: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
views := DefaultViews(tt.level)
exporterHelperScope := "go.opentelemetry.io/collector/exporter/exporterhelper"
bucketMetricName := "otelcol_exporter_queue_batch_send_size"
bytesMetricName := "otelcol_exporter_queue_batch_send_size_bytes"
var foundBucketDrop, foundBytesDrop bool
for _, view := range views {
if view.Selector != nil {
if view.Selector.MeterName != nil && *view.Selector.MeterName == exporterHelperScope {
if view.Selector.InstrumentName != nil {
if *view.Selector.InstrumentName == bucketMetricName {
foundBucketDrop = true
// Verify it's a drop view
require.NotNil(t, view.Stream)
require.NotNil(t, view.Stream.Aggregation)
require.NotNil(t, view.Stream.Aggregation.Drop)
}
if *view.Selector.InstrumentName == bytesMetricName {
foundBytesDrop = true
// Verify it's a drop view
require.NotNil(t, view.Stream)
require.NotNil(t, view.Stream.Aggregation)
require.NotNil(t, view.Stream.Aggregation.Drop)
}
}
}
}
}
assert.Equal(t, tt.shouldDropBucket, foundBucketDrop,
"bucket metric drop view should be %v for level %v", tt.shouldDropBucket, tt.level)
assert.Equal(t, tt.shouldDropBytes, foundBytesDrop,
"bytes metric drop view should be %v for level %v", tt.shouldDropBytes, tt.level)
})
}
}
================================================
FILE: service/internal/moduleinfo/moduleinfo.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package moduleinfo // import "go.opentelemetry.io/collector/service/internal/moduleinfo"
import "go.opentelemetry.io/collector/component"
type ModuleInfo struct {
// BuilderRef is the raw string passed in the builder configuration used to build this service.
BuilderRef string
}
// ModuleInfos describes the go module for each component.
type ModuleInfos struct {
Receiver map[component.Type]ModuleInfo
Processor map[component.Type]ModuleInfo
Exporter map[component.Type]ModuleInfo
Extension map[component.Type]ModuleInfo
Connector map[component.Type]ModuleInfo
}
================================================
FILE: service/internal/obsconsumer/consumer_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package obsconsumer_test
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.uber.org/zap"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/service/internal/obsconsumer"
)
type failingConsumer struct {
err error
}
var (
_ consumer.Metrics = (*failingConsumer)(nil)
_ consumer.Logs = (*failingConsumer)(nil)
_ consumer.Traces = (*failingConsumer)(nil)
_ xconsumer.Profiles = (*failingConsumer)(nil)
)
func (*failingConsumer) Capabilities() consumer.Capabilities {
return consumer.Capabilities{}
}
func (fc *failingConsumer) ConsumeMetrics(_ context.Context, _ pmetric.Metrics) error {
return fc.err
}
func (fc *failingConsumer) ConsumeLogs(_ context.Context, _ plog.Logs) error {
return fc.err
}
func (fc *failingConsumer) ConsumeTraces(_ context.Context, _ ptrace.Traces) error {
return fc.err
}
func (fc *failingConsumer) ConsumeProfiles(_ context.Context, _ pprofile.Profiles) error {
return fc.err
}
func TestConsumeRefused(t *testing.T) {
setGateForTest(t, true)
ctx := context.Background()
originalErr := errors.New("test error")
expectedErr := consumererror.NewDownstream(originalErr)
mockConsumer := &failingConsumer{err: originalErr}
// Use delta temporality so sums don't accumulate across tests
reader := sdkmetric.NewManualReader(sdkmetric.WithTemporalitySelector(func(_ sdkmetric.InstrumentKind) metricdata.Temporality {
return metricdata.DeltaTemporality
}))
mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader))
meter := mp.Meter("test")
receivedItemsCounter, err := meter.Int64Counter("received.items")
require.NoError(t, err)
receivedSizeCounter, err := meter.Int64Counter("received.size")
require.NoError(t, err)
producedItemsCounter, err := meter.Int64Counter("produced.items")
require.NoError(t, err)
producedSizeConter, err := meter.Int64Counter("produced.size")
require.NoError(t, err)
logger := zap.NewNop()
receivedSettings := obsconsumer.Settings{ItemCounter: receivedItemsCounter, SizeCounter: receivedSizeCounter, Logger: logger}
producedSettings := obsconsumer.Settings{ItemCounter: producedItemsCounter, SizeCounter: producedSizeConter, Logger: logger}
type testCase struct {
name string
testConsumer func() error
}
testCases := []testCase{
{
name: "metrics",
testConsumer: func() error {
consumer1 := obsconsumer.NewMetrics(mockConsumer, receivedSettings)
consumer2 := obsconsumer.NewMetrics(consumer1, producedSettings)
md := pmetric.NewMetrics()
md.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty().SetEmptyGauge().DataPoints().AppendEmpty()
return consumer2.ConsumeMetrics(ctx, md)
},
},
{
name: "logs",
testConsumer: func() error {
consumer1 := obsconsumer.NewLogs(mockConsumer, receivedSettings)
consumer2 := obsconsumer.NewLogs(consumer1, producedSettings)
ld := plog.NewLogs()
ld.ResourceLogs().AppendEmpty().ScopeLogs().AppendEmpty().LogRecords().AppendEmpty()
return consumer2.ConsumeLogs(ctx, ld)
},
},
{
name: "traces",
testConsumer: func() error {
consumer1 := obsconsumer.NewTraces(mockConsumer, receivedSettings)
consumer2 := obsconsumer.NewTraces(consumer1, producedSettings)
td := ptrace.NewTraces()
td.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans().AppendEmpty()
return consumer2.ConsumeTraces(ctx, td)
},
},
{
name: "profiles",
testConsumer: func() error {
consumer1 := obsconsumer.NewProfiles(mockConsumer, receivedSettings)
consumer2 := obsconsumer.NewProfiles(consumer1, producedSettings)
pd := pprofile.NewProfiles()
pd.ResourceProfiles().AppendEmpty().ScopeProfiles().AppendEmpty().Profiles().AppendEmpty().Samples().AppendEmpty()
return consumer2.ConsumeProfiles(ctx, pd)
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := tc.testConsumer()
assert.Equal(t, expectedErr, err)
var rm metricdata.ResourceMetrics
err = reader.Collect(ctx, &rm)
require.NoError(t, err)
require.Len(t, rm.ScopeMetrics, 1)
require.Len(t, rm.ScopeMetrics[0].Metrics, 4)
var receivedItemMetric, receivedSizeMetric metricdata.Metrics
var producedItemMetric, producedSizeMetric metricdata.Metrics
for _, m := range rm.ScopeMetrics[0].Metrics {
switch m.Name {
case "received.items":
receivedItemMetric = m
case "received.size":
receivedSizeMetric = m
case "produced.items":
producedItemMetric = m
case "produced.size":
producedSizeMetric = m
}
}
require.NotNil(t, receivedItemMetric)
require.NotNil(t, receivedSizeMetric)
require.NotNil(t, producedItemMetric)
require.NotNil(t, producedSizeMetric)
data := receivedItemMetric.Data.(metricdata.Sum[int64])
require.Len(t, data.DataPoints, 1)
require.Equal(t, int64(1), data.DataPoints[0].Value)
attrs := data.DataPoints[0].Attributes
require.Equal(t, 1, attrs.Len())
val, ok := attrs.Value(attribute.Key(obsconsumer.ComponentOutcome))
require.True(t, ok)
require.Equal(t, "failure", val.Emit())
data = receivedSizeMetric.Data.(metricdata.Sum[int64])
require.Len(t, data.DataPoints, 1)
require.Positive(t, data.DataPoints[0].Value)
attrs = data.DataPoints[0].Attributes
require.Equal(t, 1, attrs.Len())
val, ok = attrs.Value(attribute.Key(obsconsumer.ComponentOutcome))
require.True(t, ok)
require.Equal(t, "failure", val.Emit())
data = producedItemMetric.Data.(metricdata.Sum[int64])
require.Len(t, data.DataPoints, 1)
require.Equal(t, int64(1), data.DataPoints[0].Value)
attrs = data.DataPoints[0].Attributes
require.Equal(t, 1, attrs.Len())
val, ok = attrs.Value(attribute.Key(obsconsumer.ComponentOutcome))
require.True(t, ok)
require.Equal(t, "refused", val.Emit())
data = producedSizeMetric.Data.(metricdata.Sum[int64])
require.Len(t, data.DataPoints, 1)
require.Positive(t, data.DataPoints[0].Value)
attrs = data.DataPoints[0].Attributes
require.Equal(t, 1, attrs.Len())
val, ok = attrs.Value(attribute.Key(obsconsumer.ComponentOutcome))
require.True(t, ok)
require.Equal(t, "refused", val.Emit())
})
}
}
================================================
FILE: service/internal/obsconsumer/enabled.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package obsconsumer // import "go.opentelemetry.io/collector/service/internal/obsconsumer"
import (
"context"
"go.opentelemetry.io/otel/metric"
)
type enabledInstrument interface {
Enabled(context.Context) bool
}
func isEnabled(ctx context.Context, inst metric.Int64Counter) bool {
ei, ok := inst.(enabledInstrument)
return !ok || ei.Enabled(ctx)
}
================================================
FILE: service/internal/obsconsumer/enabled_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package obsconsumer_test
import (
"context"
"go.opentelemetry.io/otel/metric"
)
type disabledCounter struct {
metric.Int64Counter
}
func newDisabledCounter(embedded metric.Int64Counter) *disabledCounter {
return &disabledCounter{Int64Counter: embedded}
}
func (m *disabledCounter) Enabled(context.Context) bool {
return false
}
func (m *disabledCounter) Add(ctx context.Context, value int64, opts ...metric.AddOption) {
m.Int64Counter.Add(ctx, value, opts...)
}
================================================
FILE: service/internal/obsconsumer/gate_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package obsconsumer_test
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/service/internal/metadata"
)
func setGateForTest(t *testing.T, enabled bool) {
initial := metadata.TelemetryNewPipelineTelemetryFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.TelemetryNewPipelineTelemetryFeatureGate.ID(), enabled))
t.Cleanup(func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.TelemetryNewPipelineTelemetryFeatureGate.ID(), initial))
})
}
================================================
FILE: service/internal/obsconsumer/logs.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package obsconsumer // import "go.opentelemetry.io/collector/service/internal/obsconsumer"
import (
"context"
"go.uber.org/zap"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/internal/telemetry"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/service/internal/metadata"
)
var (
_ consumer.Logs = obsLogs{}
logsMarshaler = &plog.ProtoMarshaler{}
)
func NewLogs(cons consumer.Logs, set Settings, opts ...Option) consumer.Logs {
if !metadata.TelemetryNewPipelineTelemetryFeatureGate.IsEnabled() {
return cons
}
o := options{}
for _, opt := range opts {
opt(&o)
}
consumerSet := Settings{
ItemCounter: set.ItemCounter,
SizeCounter: set.SizeCounter,
Logger: set.Logger.With(telemetry.ToZapFields(o.staticDataPointAttributes)...),
}
return obsLogs{
consumer: cons,
set: consumerSet,
compiledOptions: o.compile(),
}
}
type obsLogs struct {
consumer consumer.Logs
set Settings
compiledOptions
}
// ConsumeLogs measures telemetry before calling ConsumeLogs because the data may be mutated downstream
func (c obsLogs) ConsumeLogs(ctx context.Context, ld plog.Logs) error {
// Use a pointer to so that deferred function can depend on the result of ConsumeLogs
attrs := &c.withSuccessAttrs
itemCount := ld.LogRecordCount()
defer func() {
c.set.ItemCounter.Add(ctx, int64(itemCount), *attrs)
}()
if isEnabled(ctx, c.set.SizeCounter) {
byteCount := int64(logsMarshaler.LogsSize(ld))
defer func() {
c.set.SizeCounter.Add(ctx, byteCount, *attrs)
}()
}
err := c.consumer.ConsumeLogs(ctx, ld)
if err != nil {
if consumererror.IsDownstream(err) {
attrs = &c.withRefusedAttrs
} else {
attrs = &c.withFailureAttrs
err = consumererror.NewDownstream(err)
}
if c.set.Logger.Core().Enabled(zap.DebugLevel) {
c.set.Logger.Debug("Logs pipeline component had an error", zap.Error(err), zap.Int("item count", itemCount))
}
}
return err
}
func (c obsLogs) Capabilities() consumer.Capabilities {
return c.consumer.Capabilities()
}
================================================
FILE: service/internal/obsconsumer/logs_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package obsconsumer_test
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/service/internal/obsconsumer"
)
type mockLogsConsumer struct {
err error
capabilities consumer.Capabilities
}
func (m *mockLogsConsumer) ConsumeLogs(_ context.Context, _ plog.Logs) error {
return m.err
}
func (m *mockLogsConsumer) Capabilities() consumer.Capabilities {
return m.capabilities
}
func TestLogsNopWhenGateDisabled(t *testing.T) {
setGateForTest(t, false)
mp := sdkmetric.NewMeterProvider()
meter := mp.Meter("test")
itemCounter, err := meter.Int64Counter("item_counter")
require.NoError(t, err)
sizeCounter, err := meter.Int64Counter("size_counter")
require.NoError(t, err)
cons := consumertest.NewNop()
require.Equal(t, cons, obsconsumer.NewLogs(cons, obsconsumer.Settings{ItemCounter: itemCounter, SizeCounter: sizeCounter, Logger: zap.NewNop()}))
}
func TestLogsItemsOnly(t *testing.T) {
setGateForTest(t, true)
ctx := context.Background()
mockConsumer := &mockLogsConsumer{}
reader := sdkmetric.NewManualReader()
mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader))
meter := mp.Meter("test")
itemCounter, err := meter.Int64Counter("item_counter")
require.NoError(t, err)
sizeCounter, err := meter.Int64Counter("size_counter")
require.NoError(t, err)
sizeCounterDisabled := newDisabledCounter(sizeCounter)
core, logs := observer.New(zap.DebugLevel)
logger := zap.New(core)
consumer := obsconsumer.NewLogs(mockConsumer, obsconsumer.Settings{ItemCounter: itemCounter, SizeCounter: sizeCounterDisabled, Logger: logger})
ld := plog.NewLogs()
r := ld.ResourceLogs().AppendEmpty()
sl := r.ScopeLogs().AppendEmpty()
sl.LogRecords().AppendEmpty()
err = consumer.ConsumeLogs(ctx, ld)
require.NoError(t, err)
var rm metricdata.ResourceMetrics
err = reader.Collect(ctx, &rm)
require.NoError(t, err)
require.Len(t, rm.ScopeMetrics, 1)
require.Len(t, rm.ScopeMetrics[0].Metrics, 1)
metric := rm.ScopeMetrics[0].Metrics[0]
require.Equal(t, "item_counter", metric.Name)
data := metric.Data.(metricdata.Sum[int64])
require.Len(t, data.DataPoints, 1)
require.Equal(t, int64(1), data.DataPoints[0].Value)
attrs := data.DataPoints[0].Attributes
require.Equal(t, 1, attrs.Len())
val, ok := attrs.Value(attribute.Key(obsconsumer.ComponentOutcome))
require.True(t, ok)
require.Equal(t, "success", val.Emit())
// Check that the logger was not called
assert.Empty(t, logs.All())
}
func TestLogsConsumeSuccess(t *testing.T) {
setGateForTest(t, true)
ctx := context.Background()
mockConsumer := &mockLogsConsumer{}
reader := sdkmetric.NewManualReader()
mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader))
meter := mp.Meter("test")
itemCounter, err := meter.Int64Counter("item_counter")
require.NoError(t, err)
sizeCounter, err := meter.Int64Counter("size_counter")
require.NoError(t, err)
core, logs := observer.New(zap.DebugLevel)
logger := zap.New(core)
consumer := obsconsumer.NewLogs(mockConsumer, obsconsumer.Settings{ItemCounter: itemCounter, SizeCounter: sizeCounter, Logger: logger})
ld := plog.NewLogs()
r := ld.ResourceLogs().AppendEmpty()
sl := r.ScopeLogs().AppendEmpty()
sl.LogRecords().AppendEmpty()
err = consumer.ConsumeLogs(ctx, ld)
require.NoError(t, err)
var rm metricdata.ResourceMetrics
err = reader.Collect(ctx, &rm)
require.NoError(t, err)
require.Len(t, rm.ScopeMetrics, 1)
require.Len(t, rm.ScopeMetrics[0].Metrics, 2)
var itemMetric, sizeMetric metricdata.Metrics
for _, m := range rm.ScopeMetrics[0].Metrics {
switch m.Name {
case "item_counter":
itemMetric = m
case "size_counter":
sizeMetric = m
}
}
require.NotNil(t, itemMetric)
require.NotNil(t, sizeMetric)
itemData := itemMetric.Data.(metricdata.Sum[int64])
require.Len(t, itemData.DataPoints, 1)
require.Equal(t, int64(1), itemData.DataPoints[0].Value)
itemAttrs := itemData.DataPoints[0].Attributes
require.Equal(t, 1, itemAttrs.Len())
val, ok := itemAttrs.Value(attribute.Key(obsconsumer.ComponentOutcome))
require.True(t, ok)
require.Equal(t, "success", val.Emit())
sizeData := sizeMetric.Data.(metricdata.Sum[int64])
require.Len(t, sizeData.DataPoints, 1)
require.Positive(t, sizeData.DataPoints[0].Value)
sizeAttrs := sizeData.DataPoints[0].Attributes
require.Equal(t, 1, sizeAttrs.Len())
val, ok = sizeAttrs.Value(attribute.Key(obsconsumer.ComponentOutcome))
require.True(t, ok)
require.Equal(t, "success", val.Emit())
// Check that the logger was not called
assert.Empty(t, logs.All())
}
func TestLogsConsumeFailure(t *testing.T) {
setGateForTest(t, true)
ctx := context.Background()
expectedErr := errors.New("test error")
downstreamErr := consumererror.NewDownstream(expectedErr)
mockConsumer := &mockLogsConsumer{err: expectedErr}
reader := sdkmetric.NewManualReader()
mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader))
meter := mp.Meter("test")
itemCounter, err := meter.Int64Counter("item_counter")
require.NoError(t, err)
sizeCounter, err := meter.Int64Counter("size_counter")
require.NoError(t, err)
core, logs := observer.New(zap.DebugLevel)
logger := zap.New(core)
consumer := obsconsumer.NewLogs(mockConsumer, obsconsumer.Settings{ItemCounter: itemCounter, SizeCounter: sizeCounter, Logger: logger})
ld := plog.NewLogs()
r := ld.ResourceLogs().AppendEmpty()
sl := r.ScopeLogs().AppendEmpty()
sl.LogRecords().AppendEmpty()
err = consumer.ConsumeLogs(ctx, ld)
assert.Equal(t, downstreamErr, err)
var rm metricdata.ResourceMetrics
err = reader.Collect(ctx, &rm)
require.NoError(t, err)
require.Len(t, rm.ScopeMetrics, 1)
require.Len(t, rm.ScopeMetrics[0].Metrics, 2)
var itemMetric, sizeMetric metricdata.Metrics
for _, m := range rm.ScopeMetrics[0].Metrics {
switch m.Name {
case "item_counter":
itemMetric = m
case "size_counter":
sizeMetric = m
}
}
require.NotNil(t, itemMetric)
require.NotNil(t, sizeMetric)
itemData := itemMetric.Data.(metricdata.Sum[int64])
require.Len(t, itemData.DataPoints, 1)
require.Equal(t, int64(1), itemData.DataPoints[0].Value)
itemAttrs := itemData.DataPoints[0].Attributes
require.Equal(t, 1, itemAttrs.Len())
val, ok := itemAttrs.Value(attribute.Key(obsconsumer.ComponentOutcome))
require.True(t, ok)
require.Equal(t, "failure", val.Emit())
require.NotNil(t, sizeMetric)
sizeData := sizeMetric.Data.(metricdata.Sum[int64])
require.Len(t, sizeData.DataPoints, 1)
require.Positive(t, sizeData.DataPoints[0].Value)
sizeAttrs := sizeData.DataPoints[0].Attributes
require.Equal(t, 1, sizeAttrs.Len())
val, ok = sizeAttrs.Value(attribute.Key(obsconsumer.ComponentOutcome))
require.True(t, ok)
require.Equal(t, "failure", val.Emit())
// Check that the logger was called with an error
require.Len(t, logs.All(), 1)
assert.Contains(t, logs.All()[0].Message, "Logs pipeline component had an error")
}
func TestLogsWithStaticAttributes(t *testing.T) {
setGateForTest(t, true)
ctx := context.Background()
mockConsumer := &mockLogsConsumer{}
reader := sdkmetric.NewManualReader()
mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader))
meter := mp.Meter("test")
itemCounter, err := meter.Int64Counter("item_counter")
require.NoError(t, err)
sizeCounter, err := meter.Int64Counter("size_counter")
require.NoError(t, err)
staticAttr := attribute.String("test", "value")
core, logs := observer.New(zap.DebugLevel)
logger := zap.New(core)
consumer := obsconsumer.NewLogs(mockConsumer, obsconsumer.Settings{ItemCounter: itemCounter, SizeCounter: sizeCounter, Logger: logger},
obsconsumer.WithStaticDataPointAttribute(staticAttr))
ld := plog.NewLogs()
r := ld.ResourceLogs().AppendEmpty()
sl := r.ScopeLogs().AppendEmpty()
sl.LogRecords().AppendEmpty()
err = consumer.ConsumeLogs(ctx, ld)
require.NoError(t, err)
var rm metricdata.ResourceMetrics
err = reader.Collect(ctx, &rm)
require.NoError(t, err)
require.Len(t, rm.ScopeMetrics, 1)
require.Len(t, rm.ScopeMetrics[0].Metrics, 2)
var itemMetric, sizeMetric metricdata.Metrics
for _, m := range rm.ScopeMetrics[0].Metrics {
switch m.Name {
case "item_counter":
itemMetric = m
case "size_counter":
sizeMetric = m
}
}
require.NotNil(t, itemMetric)
itemData := itemMetric.Data.(metricdata.Sum[int64])
require.Len(t, itemData.DataPoints, 1)
require.Equal(t, int64(1), itemData.DataPoints[0].Value)
itemAttrs := itemData.DataPoints[0].Attributes
require.Equal(t, 2, itemAttrs.Len())
val, ok := itemAttrs.Value(attribute.Key("test"))
require.True(t, ok)
require.Equal(t, "value", val.Emit())
val, ok = itemAttrs.Value(attribute.Key(obsconsumer.ComponentOutcome))
require.True(t, ok)
require.Equal(t, "success", val.Emit())
sizeData := sizeMetric.Data.(metricdata.Sum[int64])
require.Len(t, sizeData.DataPoints, 1)
require.Positive(t, sizeData.DataPoints[0].Value)
sizeAttrs := sizeData.DataPoints[0].Attributes
require.Equal(t, 2, sizeAttrs.Len())
val, ok = sizeAttrs.Value(attribute.Key("test"))
require.True(t, ok)
require.Equal(t, "value", val.Emit())
val, ok = sizeAttrs.Value(attribute.Key(obsconsumer.ComponentOutcome))
require.True(t, ok)
require.Equal(t, "success", val.Emit())
// Check that the logger was not called
assert.Empty(t, logs.All())
}
func TestLogsMultipleItemsMixedOutcomes(t *testing.T) {
setGateForTest(t, true)
ctx := context.Background()
expectedErr := errors.New("test error")
downstreamErr := consumererror.NewDownstream(expectedErr)
mockConsumer := &mockLogsConsumer{}
reader := sdkmetric.NewManualReader()
mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader))
meter := mp.Meter("test")
itemCounter, err := meter.Int64Counter("item_counter")
require.NoError(t, err)
sizeCounter, err := meter.Int64Counter("size_counter")
require.NoError(t, err)
core, logs := observer.New(zap.DebugLevel)
logger := zap.New(core)
consumer := obsconsumer.NewLogs(mockConsumer, obsconsumer.Settings{ItemCounter: itemCounter, SizeCounter: sizeCounter, Logger: logger})
// First batch: 2 successful items
ld1 := plog.NewLogs()
for range 2 {
r := ld1.ResourceLogs().AppendEmpty()
sl := r.ScopeLogs().AppendEmpty()
sl.LogRecords().AppendEmpty()
}
err = consumer.ConsumeLogs(ctx, ld1)
require.NoError(t, err)
// Second batch: 1 failed item
mockConsumer.err = expectedErr
ld2 := plog.NewLogs()
r := ld2.ResourceLogs().AppendEmpty()
sl := r.ScopeLogs().AppendEmpty()
sl.LogRecords().AppendEmpty()
err = consumer.ConsumeLogs(ctx, ld2)
assert.Equal(t, downstreamErr, err)
// Third batch: 2 successful items
mockConsumer.err = nil
ld3 := plog.NewLogs()
for range 2 {
r = ld3.ResourceLogs().AppendEmpty()
sl = r.ScopeLogs().AppendEmpty()
sl.LogRecords().AppendEmpty()
}
err = consumer.ConsumeLogs(ctx, ld3)
require.NoError(t, err)
// Fourth batch: 1 failed item
mockConsumer.err = expectedErr
ld4 := plog.NewLogs()
r = ld4.ResourceLogs().AppendEmpty()
sl = r.ScopeLogs().AppendEmpty()
sl.LogRecords().AppendEmpty()
err = consumer.ConsumeLogs(ctx, ld4)
assert.Equal(t, downstreamErr, err)
var rm metricdata.ResourceMetrics
err = reader.Collect(ctx, &rm)
require.NoError(t, err)
require.Len(t, rm.ScopeMetrics, 1)
require.Len(t, rm.ScopeMetrics[0].Metrics, 2)
var itemMetric, sizeMetric metricdata.Metrics
for _, m := range rm.ScopeMetrics[0].Metrics {
switch m.Name {
case "item_counter":
itemMetric = m
case "size_counter":
sizeMetric = m
}
}
require.NotNil(t, itemMetric)
require.NotNil(t, sizeMetric)
itemData := itemMetric.Data.(metricdata.Sum[int64])
require.Len(t, itemData.DataPoints, 2)
sizeData := sizeMetric.Data.(metricdata.Sum[int64])
require.Len(t, sizeData.DataPoints, 2)
var successDP, failureDP metricdata.DataPoint[int64]
for _, dp := range itemData.DataPoints {
val, ok := dp.Attributes.Value(attribute.Key(obsconsumer.ComponentOutcome))
if ok && val.Emit() == "success" {
successDP = dp
} else {
failureDP = dp
}
}
require.Equal(t, int64(4), successDP.Value)
require.Equal(t, int64(2), failureDP.Value)
var successSizeDP, failureSizeDP metricdata.DataPoint[int64]
for _, dp := range sizeData.DataPoints {
val, ok := dp.Attributes.Value(attribute.Key(obsconsumer.ComponentOutcome))
if ok && val.Emit() == "success" {
successSizeDP = dp
} else {
failureSizeDP = dp
}
}
require.Equal(t, int64(64), successSizeDP.Value)
require.Equal(t, int64(32), failureSizeDP.Value)
// Check that the logger was called for errors
require.Len(t, logs.All(), 2)
for _, log := range logs.All() {
assert.Contains(t, log.Message, "Logs pipeline component had an error")
}
}
func TestLogsCapabilities(t *testing.T) {
setGateForTest(t, true)
mockConsumer := &mockLogsConsumer{
capabilities: consumer.Capabilities{MutatesData: true},
}
reader := sdkmetric.NewManualReader()
mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader))
meter := mp.Meter("test")
itemCounter, err := meter.Int64Counter("item_counter")
require.NoError(t, err)
sizeCounter, err := meter.Int64Counter("size_counter")
require.NoError(t, err)
sizeCounterDisabled := newDisabledCounter(sizeCounter)
// Test with item counter only
consumer := obsconsumer.NewLogs(mockConsumer, obsconsumer.Settings{ItemCounter: itemCounter, SizeCounter: sizeCounterDisabled, Logger: zap.NewNop()})
require.Equal(t, consumer.Capabilities(), mockConsumer.capabilities)
// Test with both counters
consumer = obsconsumer.NewLogs(mockConsumer, obsconsumer.Settings{ItemCounter: itemCounter, SizeCounter: sizeCounter, Logger: zap.NewNop()})
require.Equal(t, consumer.Capabilities(), mockConsumer.capabilities)
}
================================================
FILE: service/internal/obsconsumer/metrics.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package obsconsumer // import "go.opentelemetry.io/collector/service/internal/obsconsumer"
import (
"context"
"go.uber.org/zap"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/internal/telemetry"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/service/internal/metadata"
)
var (
_ consumer.Metrics = obsMetrics{}
metricsMarshaler = &pmetric.ProtoMarshaler{}
)
func NewMetrics(cons consumer.Metrics, set Settings, opts ...Option) consumer.Metrics {
if !metadata.TelemetryNewPipelineTelemetryFeatureGate.IsEnabled() {
return cons
}
o := options{}
for _, opt := range opts {
opt(&o)
}
consumerSet := Settings{
ItemCounter: set.ItemCounter,
SizeCounter: set.SizeCounter,
Logger: set.Logger.With(telemetry.ToZapFields(o.staticDataPointAttributes)...),
}
return obsMetrics{
consumer: cons,
set: consumerSet,
compiledOptions: o.compile(),
}
}
type obsMetrics struct {
consumer consumer.Metrics
set Settings
compiledOptions
}
// ConsumeMetrics measures telemetry before calling ConsumeMetrics because the data may be mutated downstream
func (c obsMetrics) ConsumeMetrics(ctx context.Context, md pmetric.Metrics) error {
// Use a pointer to so that deferred function can depend on the result of ConsumeMetrics
attrs := &c.withSuccessAttrs
itemCount := md.DataPointCount()
defer func() {
c.set.ItemCounter.Add(ctx, int64(itemCount), *attrs)
}()
if isEnabled(ctx, c.set.SizeCounter) {
byteCount := int64(metricsMarshaler.MetricsSize(md))
defer func() {
c.set.SizeCounter.Add(ctx, byteCount, *attrs)
}()
}
err := c.consumer.ConsumeMetrics(ctx, md)
if err != nil {
if consumererror.IsDownstream(err) {
attrs = &c.withRefusedAttrs
} else {
attrs = &c.withFailureAttrs
err = consumererror.NewDownstream(err)
}
if c.set.Logger.Core().Enabled(zap.DebugLevel) {
c.set.Logger.Debug("Metrics pipeline component had an error", zap.Error(err), zap.Int("item count", itemCount))
}
}
return err
}
func (c obsMetrics) Capabilities() consumer.Capabilities {
return c.consumer.Capabilities()
}
================================================
FILE: service/internal/obsconsumer/metrics_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package obsconsumer_test
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/service/internal/obsconsumer"
)
type mockMetricsConsumer struct {
err error
capabilities consumer.Capabilities
}
func (m *mockMetricsConsumer) ConsumeMetrics(_ context.Context, _ pmetric.Metrics) error {
return m.err
}
func (m *mockMetricsConsumer) Capabilities() consumer.Capabilities {
return m.capabilities
}
func TestMetricsNopWhenGateDisabled(t *testing.T) {
setGateForTest(t, false)
mp := sdkmetric.NewMeterProvider()
meter := mp.Meter("test")
itemCounter, err := meter.Int64Counter("item_counter")
require.NoError(t, err)
sizeCounter, err := meter.Int64Counter("size_counter")
require.NoError(t, err)
cons := consumertest.NewNop()
require.Equal(t, cons, obsconsumer.NewMetrics(cons, obsconsumer.Settings{ItemCounter: itemCounter, SizeCounter: sizeCounter, Logger: zap.NewNop()}))
}
func TestMetricsItemsOnly(t *testing.T) {
setGateForTest(t, true)
ctx := context.Background()
mockConsumer := &mockMetricsConsumer{}
reader := sdkmetric.NewManualReader()
mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader))
meter := mp.Meter("test")
itemCounter, err := meter.Int64Counter("item_counter")
require.NoError(t, err)
sizeCounter, err := meter.Int64Counter("size_counter")
require.NoError(t, err)
sizeCounterDisabled := newDisabledCounter(sizeCounter)
core, logs := observer.New(zap.DebugLevel)
logger := zap.New(core)
consumer := obsconsumer.NewMetrics(mockConsumer, obsconsumer.Settings{ItemCounter: itemCounter, SizeCounter: sizeCounterDisabled, Logger: logger})
md := pmetric.NewMetrics()
rm := md.ResourceMetrics().AppendEmpty()
sm := rm.ScopeMetrics().AppendEmpty()
m := sm.Metrics().AppendEmpty()
m.SetEmptyGauge().DataPoints().AppendEmpty()
err = consumer.ConsumeMetrics(ctx, md)
require.NoError(t, err)
var metrics metricdata.ResourceMetrics
err = reader.Collect(ctx, &metrics)
require.NoError(t, err)
require.Len(t, metrics.ScopeMetrics, 1)
require.Len(t, metrics.ScopeMetrics[0].Metrics, 1)
metric := metrics.ScopeMetrics[0].Metrics[0]
require.Equal(t, "item_counter", metric.Name)
data := metric.Data.(metricdata.Sum[int64])
require.Len(t, data.DataPoints, 1)
require.Equal(t, int64(1), data.DataPoints[0].Value)
attrs := data.DataPoints[0].Attributes
require.Equal(t, 1, attrs.Len())
val, ok := attrs.Value(attribute.Key(obsconsumer.ComponentOutcome))
require.True(t, ok)
require.Equal(t, "success", val.Emit())
// Check that the logger was not called
assert.Empty(t, logs.All())
}
func TestMetricsConsumeSuccess(t *testing.T) {
setGateForTest(t, true)
ctx := context.Background()
mockConsumer := &mockMetricsConsumer{}
reader := sdkmetric.NewManualReader()
mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader))
meter := mp.Meter("test")
itemCounter, err := meter.Int64Counter("item_counter")
require.NoError(t, err)
sizeCounter, err := meter.Int64Counter("size_counter")
require.NoError(t, err)
core, logs := observer.New(zap.DebugLevel)
logger := zap.New(core)
consumer := obsconsumer.NewMetrics(mockConsumer, obsconsumer.Settings{ItemCounter: itemCounter, SizeCounter: sizeCounter, Logger: logger})
md := pmetric.NewMetrics()
r := md.ResourceMetrics().AppendEmpty()
sm := r.ScopeMetrics().AppendEmpty()
m := sm.Metrics().AppendEmpty()
m.SetEmptyGauge().DataPoints().AppendEmpty()
err = consumer.ConsumeMetrics(ctx, md)
require.NoError(t, err)
var metrics metricdata.ResourceMetrics
err = reader.Collect(ctx, &metrics)
require.NoError(t, err)
require.Len(t, metrics.ScopeMetrics, 1)
require.Len(t, metrics.ScopeMetrics[0].Metrics, 2)
var itemMetric, sizeMetric metricdata.Metrics
for _, m := range metrics.ScopeMetrics[0].Metrics {
switch m.Name {
case "item_counter":
itemMetric = m
case "size_counter":
sizeMetric = m
}
}
require.NotNil(t, itemMetric)
require.NotNil(t, sizeMetric)
itemData := itemMetric.Data.(metricdata.Sum[int64])
require.Len(t, itemData.DataPoints, 1)
require.Equal(t, int64(1), itemData.DataPoints[0].Value)
itemAttrs := itemData.DataPoints[0].Attributes
require.Equal(t, 1, itemAttrs.Len())
val, ok := itemAttrs.Value(attribute.Key(obsconsumer.ComponentOutcome))
require.True(t, ok)
require.Equal(t, "success", val.Emit())
sizeData := sizeMetric.Data.(metricdata.Sum[int64])
require.Len(t, sizeData.DataPoints, 1)
require.Positive(t, sizeData.DataPoints[0].Value)
attrs := sizeData.DataPoints[0].Attributes
require.Equal(t, 1, attrs.Len())
val, ok = attrs.Value(attribute.Key(obsconsumer.ComponentOutcome))
require.True(t, ok)
require.Equal(t, "success", val.Emit())
// Check that the logger was not called
assert.Empty(t, logs.All())
}
func TestMetricsConsumeFailure(t *testing.T) {
setGateForTest(t, true)
ctx := context.Background()
expectedErr := errors.New("test error")
downstreamErr := consumererror.NewDownstream(expectedErr)
mockConsumer := &mockMetricsConsumer{err: expectedErr}
reader := sdkmetric.NewManualReader()
mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader))
meter := mp.Meter("test")
itemCounter, err := meter.Int64Counter("item_counter")
require.NoError(t, err)
sizeCounter, err := meter.Int64Counter("size_counter")
require.NoError(t, err)
core, logs := observer.New(zap.DebugLevel)
logger := zap.New(core)
consumer := obsconsumer.NewMetrics(mockConsumer, obsconsumer.Settings{ItemCounter: itemCounter, SizeCounter: sizeCounter, Logger: logger})
md := pmetric.NewMetrics()
r := md.ResourceMetrics().AppendEmpty()
sm := r.ScopeMetrics().AppendEmpty()
m := sm.Metrics().AppendEmpty()
m.SetEmptyGauge().DataPoints().AppendEmpty()
err = consumer.ConsumeMetrics(ctx, md)
assert.Equal(t, downstreamErr, err)
var metrics metricdata.ResourceMetrics
err = reader.Collect(ctx, &metrics)
require.NoError(t, err)
require.Len(t, metrics.ScopeMetrics, 1)
require.Len(t, metrics.ScopeMetrics[0].Metrics, 2)
// Find the item counter and size counter metrics
var itemMetric, sizeMetric metricdata.Metrics
for _, m := range metrics.ScopeMetrics[0].Metrics {
switch m.Name {
case "item_counter":
itemMetric = m
case "size_counter":
sizeMetric = m
}
}
require.NotNil(t, itemMetric)
require.NotNil(t, sizeMetric)
itemData := itemMetric.Data.(metricdata.Sum[int64])
require.Len(t, itemData.DataPoints, 1)
require.Equal(t, int64(1), itemData.DataPoints[0].Value)
itemAttrs := itemData.DataPoints[0].Attributes
require.Equal(t, 1, itemAttrs.Len())
val, ok := itemAttrs.Value(attribute.Key(obsconsumer.ComponentOutcome))
require.True(t, ok)
require.Equal(t, "failure", val.Emit())
sizeData := sizeMetric.Data.(metricdata.Sum[int64])
require.Len(t, sizeData.DataPoints, 1)
require.Positive(t, sizeData.DataPoints[0].Value)
sizeAttrs := sizeData.DataPoints[0].Attributes
require.Equal(t, 1, sizeAttrs.Len())
val, ok = sizeAttrs.Value(attribute.Key(obsconsumer.ComponentOutcome))
require.True(t, ok)
require.Equal(t, "failure", val.Emit())
// Check that the logger was called with an error
require.Len(t, logs.All(), 1)
assert.Contains(t, logs.All()[0].Message, "Metrics pipeline component had an error")
}
func TestMetricsWithStaticAttributes(t *testing.T) {
setGateForTest(t, true)
ctx := context.Background()
mockConsumer := &mockMetricsConsumer{}
reader := sdkmetric.NewManualReader()
mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader))
meter := mp.Meter("test")
itemCounter, err := meter.Int64Counter("item_counter")
require.NoError(t, err)
sizeCounter, err := meter.Int64Counter("size_counter")
require.NoError(t, err)
staticAttr := attribute.String("test", "value")
core, logs := observer.New(zap.DebugLevel)
logger := zap.New(core)
consumer := obsconsumer.NewMetrics(mockConsumer, obsconsumer.Settings{ItemCounter: itemCounter, SizeCounter: sizeCounter, Logger: logger},
obsconsumer.WithStaticDataPointAttribute(staticAttr))
md := pmetric.NewMetrics()
rm := md.ResourceMetrics().AppendEmpty()
rm.ScopeMetrics().AppendEmpty()
sm := rm.ScopeMetrics().AppendEmpty()
m := sm.Metrics().AppendEmpty()
m.SetEmptyGauge().DataPoints().AppendEmpty()
err = consumer.ConsumeMetrics(ctx, md)
require.NoError(t, err)
var metrics metricdata.ResourceMetrics
err = reader.Collect(ctx, &metrics)
require.NoError(t, err)
require.Len(t, metrics.ScopeMetrics, 1)
require.Len(t, metrics.ScopeMetrics[0].Metrics, 2)
var itemMetric, sizeMetric metricdata.Metrics
for _, m := range metrics.ScopeMetrics[0].Metrics {
switch m.Name {
case "item_counter":
itemMetric = m
case "size_counter":
sizeMetric = m
}
}
require.NotNil(t, itemMetric)
require.NotNil(t, sizeMetric)
itemData := itemMetric.Data.(metricdata.Sum[int64])
require.Len(t, itemData.DataPoints, 1)
require.Equal(t, int64(1), itemData.DataPoints[0].Value)
itemAttrs := itemData.DataPoints[0].Attributes
require.Equal(t, 2, itemAttrs.Len())
val, ok := itemAttrs.Value(attribute.Key("test"))
require.True(t, ok)
require.Equal(t, "value", val.Emit())
val, ok = itemAttrs.Value(attribute.Key(obsconsumer.ComponentOutcome))
require.True(t, ok)
require.Equal(t, "success", val.Emit())
sizeData := sizeMetric.Data.(metricdata.Sum[int64])
require.Len(t, sizeData.DataPoints, 1)
require.Positive(t, sizeData.DataPoints[0].Value)
sizeAttrs := sizeData.DataPoints[0].Attributes
require.Equal(t, 2, sizeAttrs.Len())
val, ok = sizeAttrs.Value(attribute.Key("test"))
require.True(t, ok)
require.Equal(t, "value", val.Emit())
val, ok = sizeAttrs.Value(attribute.Key(obsconsumer.ComponentOutcome))
require.True(t, ok)
require.Equal(t, "success", val.Emit())
// Check that the logger was not called
assert.Empty(t, logs.All())
}
func TestMetricsMultipleItemsMixedOutcomes(t *testing.T) {
setGateForTest(t, true)
ctx := context.Background()
expectedErr := errors.New("test error")
downstreamErr := consumererror.NewDownstream(expectedErr)
mockConsumer := &mockMetricsConsumer{}
reader := sdkmetric.NewManualReader()
mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader))
meter := mp.Meter("test")
itemCounter, err := meter.Int64Counter("item_counter")
require.NoError(t, err)
sizeCounter, err := meter.Int64Counter("size_counter")
require.NoError(t, err)
core, logs := observer.New(zap.DebugLevel)
logger := zap.New(core)
consumer := obsconsumer.NewMetrics(mockConsumer, obsconsumer.Settings{ItemCounter: itemCounter, SizeCounter: sizeCounter, Logger: logger})
// First batch: 2 successful items
md1 := pmetric.NewMetrics()
for range 2 {
r := md1.ResourceMetrics().AppendEmpty()
sm := r.ScopeMetrics().AppendEmpty()
m := sm.Metrics().AppendEmpty()
m.SetEmptyGauge().DataPoints().AppendEmpty()
}
err = consumer.ConsumeMetrics(ctx, md1)
require.NoError(t, err)
// Second batch: 1 failed item
mockConsumer.err = expectedErr
md2 := pmetric.NewMetrics()
r := md2.ResourceMetrics().AppendEmpty()
sm := r.ScopeMetrics().AppendEmpty()
m := sm.Metrics().AppendEmpty()
m.SetEmptyGauge().DataPoints().AppendEmpty()
err = consumer.ConsumeMetrics(ctx, md2)
assert.Equal(t, downstreamErr, err)
// Third batch: 2 successful items
mockConsumer.err = nil
md3 := pmetric.NewMetrics()
for range 2 {
r = md3.ResourceMetrics().AppendEmpty()
sm = r.ScopeMetrics().AppendEmpty()
m = sm.Metrics().AppendEmpty()
m.SetEmptyGauge().DataPoints().AppendEmpty()
}
err = consumer.ConsumeMetrics(ctx, md3)
require.NoError(t, err)
// Fourth batch: 1 failed item
mockConsumer.err = expectedErr
md4 := pmetric.NewMetrics()
r = md4.ResourceMetrics().AppendEmpty()
sm = r.ScopeMetrics().AppendEmpty()
m = sm.Metrics().AppendEmpty()
m.SetEmptyGauge().DataPoints().AppendEmpty()
err = consumer.ConsumeMetrics(ctx, md4)
assert.Equal(t, downstreamErr, err)
var metrics metricdata.ResourceMetrics
err = reader.Collect(ctx, &metrics)
require.NoError(t, err)
require.Len(t, metrics.ScopeMetrics, 1)
require.Len(t, metrics.ScopeMetrics[0].Metrics, 2)
var itemMetric, sizeMetric metricdata.Metrics
for _, m := range metrics.ScopeMetrics[0].Metrics {
switch m.Name {
case "item_counter":
itemMetric = m
case "size_counter":
sizeMetric = m
}
}
require.NotNil(t, itemMetric)
require.NotNil(t, sizeMetric)
itemData := itemMetric.Data.(metricdata.Sum[int64])
require.Len(t, itemData.DataPoints, 2)
sizeData := sizeMetric.Data.(metricdata.Sum[int64])
require.Len(t, sizeData.DataPoints, 2)
// Find success and failure data points
var successDP, failureDP metricdata.DataPoint[int64]
for _, dp := range itemData.DataPoints {
val, ok := dp.Attributes.Value(attribute.Key(obsconsumer.ComponentOutcome))
if ok && val.Emit() == "success" {
successDP = dp
} else {
failureDP = dp
}
}
require.Equal(t, int64(4), successDP.Value)
require.Equal(t, int64(2), failureDP.Value)
for _, dp := range sizeData.DataPoints {
val, ok := dp.Attributes.Value(attribute.Key(obsconsumer.ComponentOutcome))
if ok && val.Emit() == "success" {
successDP = dp
} else {
failureDP = dp
}
}
require.Equal(t, int64(56), successDP.Value)
require.Equal(t, int64(28), failureDP.Value)
// Check that the logger was called for errors
require.Len(t, logs.All(), 2)
for _, log := range logs.All() {
assert.Contains(t, log.Message, "Metrics pipeline component had an error")
}
}
func TestMetricsCapabilities(t *testing.T) {
setGateForTest(t, true)
mockConsumer := &mockMetricsConsumer{
capabilities: consumer.Capabilities{MutatesData: true},
}
reader := sdkmetric.NewManualReader()
mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader))
meter := mp.Meter("test")
itemCounter, err := meter.Int64Counter("item_counter")
require.NoError(t, err)
sizeCounter, err := meter.Int64Counter("size_counter")
require.NoError(t, err)
sizeCounterDisabled := newDisabledCounter(sizeCounter)
// Test with item counter only
consumer := obsconsumer.NewMetrics(mockConsumer, obsconsumer.Settings{ItemCounter: itemCounter, SizeCounter: sizeCounterDisabled, Logger: zap.NewNop()})
require.Equal(t, consumer.Capabilities(), mockConsumer.capabilities)
// Test with both counters
consumer = obsconsumer.NewMetrics(mockConsumer, obsconsumer.Settings{ItemCounter: itemCounter, SizeCounter: sizeCounter, Logger: zap.NewNop()})
require.Equal(t, consumer.Capabilities(), mockConsumer.capabilities)
}
================================================
FILE: service/internal/obsconsumer/option.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package obsconsumer // import "go.opentelemetry.io/collector/service/internal/obsconsumer"
import (
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
)
const (
ComponentOutcome = "otelcol.component.outcome"
)
// Option modifies the consumer behavior.
type Option func(*options)
type options struct {
staticDataPointAttributes []attribute.KeyValue
}
// WithStaticDataPointAttribute returns an Option that adds a static attribute to data points.
func WithStaticDataPointAttribute(attr attribute.KeyValue) Option {
return func(opts *options) {
opts.staticDataPointAttributes = append(opts.staticDataPointAttributes, attr)
}
}
type compiledOptions struct {
withSuccessAttrs metric.AddOption
withFailureAttrs metric.AddOption
withRefusedAttrs metric.AddOption
}
func (o *options) compile() compiledOptions {
successAttrs := make([]attribute.KeyValue, 0, 1+len(o.staticDataPointAttributes))
successAttrs = append(successAttrs, attribute.String(ComponentOutcome, "success"))
successAttrs = append(successAttrs, o.staticDataPointAttributes...)
failureAttrs := make([]attribute.KeyValue, 0, 1+len(o.staticDataPointAttributes))
failureAttrs = append(failureAttrs, attribute.String(ComponentOutcome, "failure"))
failureAttrs = append(failureAttrs, o.staticDataPointAttributes...)
refusedAttrs := make([]attribute.KeyValue, 0, 1+len(o.staticDataPointAttributes))
refusedAttrs = append(refusedAttrs, attribute.String(ComponentOutcome, "refused"))
refusedAttrs = append(refusedAttrs, o.staticDataPointAttributes...)
return compiledOptions{
withSuccessAttrs: metric.WithAttributes(successAttrs...),
withFailureAttrs: metric.WithAttributes(failureAttrs...),
withRefusedAttrs: metric.WithAttributes(refusedAttrs...),
}
}
================================================
FILE: service/internal/obsconsumer/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package obsconsumer
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: service/internal/obsconsumer/profiles.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package obsconsumer // import "go.opentelemetry.io/collector/service/internal/obsconsumer"
import (
"context"
"go.uber.org/zap"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/internal/telemetry"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/service/internal/metadata"
)
var (
_ xconsumer.Profiles = obsProfiles{}
profilesMarshaler = pprofile.ProtoMarshaler{}
)
func NewProfiles(cons xconsumer.Profiles, set Settings, opts ...Option) xconsumer.Profiles {
if !metadata.TelemetryNewPipelineTelemetryFeatureGate.IsEnabled() {
return cons
}
o := options{}
for _, opt := range opts {
opt(&o)
}
consumerSet := Settings{
ItemCounter: set.ItemCounter,
SizeCounter: set.SizeCounter,
Logger: set.Logger.With(telemetry.ToZapFields(o.staticDataPointAttributes)...),
}
return obsProfiles{
consumer: cons,
set: consumerSet,
compiledOptions: o.compile(),
}
}
type obsProfiles struct {
consumer xconsumer.Profiles
set Settings
compiledOptions
}
// ConsumeProfiles measures telemetry before calling ConsumeProfiles because the data may be mutated downstream
func (c obsProfiles) ConsumeProfiles(ctx context.Context, pd pprofile.Profiles) error {
// Measure before calling ConsumeProfiles because the data may be mutated downstream
attrs := &c.withSuccessAttrs
itemCount := pd.SampleCount()
defer func() {
c.set.ItemCounter.Add(ctx, int64(itemCount), *attrs)
}()
if isEnabled(ctx, c.set.SizeCounter) {
byteCount := int64(profilesMarshaler.ProfilesSize(pd))
defer func() {
c.set.SizeCounter.Add(ctx, byteCount, *attrs)
}()
}
err := c.consumer.ConsumeProfiles(ctx, pd)
if err != nil {
if consumererror.IsDownstream(err) {
attrs = &c.withRefusedAttrs
} else {
attrs = &c.withFailureAttrs
err = consumererror.NewDownstream(err)
}
if c.set.Logger.Core().Enabled(zap.DebugLevel) {
c.set.Logger.Debug("Profiles pipeline component had an error", zap.Error(err), zap.Int("item count", itemCount))
}
}
return err
}
func (c obsProfiles) Capabilities() consumer.Capabilities {
return c.consumer.Capabilities()
}
================================================
FILE: service/internal/obsconsumer/profiles_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package obsconsumer_test
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/service/internal/obsconsumer"
)
type mockProfilesConsumer struct {
err error
capabilities consumer.Capabilities
}
func (m *mockProfilesConsumer) ConsumeProfiles(_ context.Context, _ pprofile.Profiles) error {
return m.err
}
func (m *mockProfilesConsumer) Capabilities() consumer.Capabilities {
return m.capabilities
}
func TestProfilesNopWhenGateDisabled(t *testing.T) {
setGateForTest(t, false)
mp := sdkmetric.NewMeterProvider()
meter := mp.Meter("test")
itemCounter, err := meter.Int64Counter("item_counter")
require.NoError(t, err)
sizeCounter, err := meter.Int64Counter("size_counter")
require.NoError(t, err)
cons := consumertest.NewNop()
require.Equal(t, cons, obsconsumer.NewProfiles(cons, obsconsumer.Settings{ItemCounter: itemCounter, SizeCounter: sizeCounter, Logger: zap.NewNop()}))
}
func TestProfilesItemsOnly(t *testing.T) {
setGateForTest(t, true)
ctx := context.Background()
mockConsumer := &mockProfilesConsumer{}
reader := sdkmetric.NewManualReader()
mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader))
meter := mp.Meter("test")
itemCounter, err := meter.Int64Counter("item_counter")
require.NoError(t, err)
sizeCounter, err := meter.Int64Counter("size_counter")
require.NoError(t, err)
sizeCounterDisabled := newDisabledCounter(sizeCounter)
core, logs := observer.New(zap.DebugLevel)
logger := zap.New(core)
consumer := obsconsumer.NewProfiles(mockConsumer, obsconsumer.Settings{ItemCounter: itemCounter, SizeCounter: sizeCounterDisabled, Logger: logger})
pd := pprofile.NewProfiles()
r := pd.ResourceProfiles().AppendEmpty()
sp := r.ScopeProfiles().AppendEmpty()
sp.Profiles().AppendEmpty().Samples().AppendEmpty()
err = consumer.ConsumeProfiles(ctx, pd)
require.NoError(t, err)
var rm metricdata.ResourceMetrics
err = reader.Collect(ctx, &rm)
require.NoError(t, err)
require.Len(t, rm.ScopeMetrics, 1)
require.Len(t, rm.ScopeMetrics[0].Metrics, 1)
metric := rm.ScopeMetrics[0].Metrics[0]
require.Equal(t, "item_counter", metric.Name)
data := metric.Data.(metricdata.Sum[int64])
require.Len(t, data.DataPoints, 1)
require.Equal(t, int64(1), data.DataPoints[0].Value)
attrs := data.DataPoints[0].Attributes
require.Equal(t, 1, attrs.Len())
val, ok := attrs.Value(attribute.Key(obsconsumer.ComponentOutcome))
require.True(t, ok)
require.Equal(t, "success", val.Emit())
// Check that the logger was not called
assert.Empty(t, logs.All())
}
func TestProfilesConsumeSuccess(t *testing.T) {
setGateForTest(t, true)
ctx := context.Background()
mockConsumer := &mockProfilesConsumer{}
reader := sdkmetric.NewManualReader()
mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader))
meter := mp.Meter("test")
itemCounter, err := meter.Int64Counter("item_counter")
require.NoError(t, err)
sizeCounter, err := meter.Int64Counter("size_counter")
require.NoError(t, err)
core, logs := observer.New(zap.DebugLevel)
logger := zap.New(core)
consumer := obsconsumer.NewProfiles(mockConsumer, obsconsumer.Settings{ItemCounter: itemCounter, SizeCounter: sizeCounter, Logger: logger})
pd := pprofile.NewProfiles()
r := pd.ResourceProfiles().AppendEmpty()
sp := r.ScopeProfiles().AppendEmpty()
sp.Profiles().AppendEmpty().Samples().AppendEmpty()
err = consumer.ConsumeProfiles(ctx, pd)
require.NoError(t, err)
var rm metricdata.ResourceMetrics
err = reader.Collect(ctx, &rm)
require.NoError(t, err)
require.Len(t, rm.ScopeMetrics, 1)
require.Len(t, rm.ScopeMetrics[0].Metrics, 2)
var itemMetric, sizeMetric metricdata.Metrics
for _, m := range rm.ScopeMetrics[0].Metrics {
switch m.Name {
case "item_counter":
itemMetric = m
case "size_counter":
sizeMetric = m
}
}
require.NotNil(t, itemMetric)
require.NotNil(t, sizeMetric)
itemData := itemMetric.Data.(metricdata.Sum[int64])
require.Len(t, itemData.DataPoints, 1)
require.Equal(t, int64(1), itemData.DataPoints[0].Value)
itemAttrs := itemData.DataPoints[0].Attributes
require.Equal(t, 1, itemAttrs.Len())
val, ok := itemAttrs.Value(attribute.Key(obsconsumer.ComponentOutcome))
require.True(t, ok)
require.Equal(t, "success", val.Emit())
sizeData := sizeMetric.Data.(metricdata.Sum[int64])
require.Len(t, sizeData.DataPoints, 1)
require.Positive(t, sizeData.DataPoints[0].Value)
sizeAttrs := sizeData.DataPoints[0].Attributes
require.Equal(t, 1, sizeAttrs.Len())
val, ok = sizeAttrs.Value(attribute.Key(obsconsumer.ComponentOutcome))
require.True(t, ok)
require.Equal(t, "success", val.Emit())
// Check that the logger was not called
assert.Empty(t, logs.All())
}
func TestProfilesConsumeFailure(t *testing.T) {
setGateForTest(t, true)
ctx := context.Background()
expectedErr := errors.New("test error")
downstreamErr := consumererror.NewDownstream(expectedErr)
mockConsumer := &mockProfilesConsumer{err: expectedErr}
reader := sdkmetric.NewManualReader()
mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader))
meter := mp.Meter("test")
itemCounter, err := meter.Int64Counter("item_counter")
require.NoError(t, err)
sizeCounter, err := meter.Int64Counter("size_counter")
require.NoError(t, err)
core, logs := observer.New(zap.DebugLevel)
logger := zap.New(core)
consumer := obsconsumer.NewProfiles(mockConsumer, obsconsumer.Settings{ItemCounter: itemCounter, SizeCounter: sizeCounter, Logger: logger})
pd := pprofile.NewProfiles()
r := pd.ResourceProfiles().AppendEmpty()
sp := r.ScopeProfiles().AppendEmpty()
sp.Profiles().AppendEmpty().Samples().AppendEmpty()
err = consumer.ConsumeProfiles(ctx, pd)
assert.Equal(t, downstreamErr, err)
var rm metricdata.ResourceMetrics
err = reader.Collect(ctx, &rm)
require.NoError(t, err)
require.Len(t, rm.ScopeMetrics, 1)
require.Len(t, rm.ScopeMetrics[0].Metrics, 2)
var itemMetric, sizeMetric metricdata.Metrics
for _, m := range rm.ScopeMetrics[0].Metrics {
switch m.Name {
case "item_counter":
itemMetric = m
case "size_counter":
sizeMetric = m
}
}
require.NotNil(t, itemMetric)
require.NotNil(t, sizeMetric)
itemData := itemMetric.Data.(metricdata.Sum[int64])
require.Len(t, itemData.DataPoints, 1)
require.Equal(t, int64(1), itemData.DataPoints[0].Value)
itemAttrs := itemData.DataPoints[0].Attributes
require.Equal(t, 1, itemAttrs.Len())
val, ok := itemAttrs.Value(attribute.Key(obsconsumer.ComponentOutcome))
require.True(t, ok)
require.Equal(t, "failure", val.Emit())
sizeData := sizeMetric.Data.(metricdata.Sum[int64])
require.Len(t, sizeData.DataPoints, 1)
require.Positive(t, sizeData.DataPoints[0].Value)
sizeAttrs := sizeData.DataPoints[0].Attributes
require.Equal(t, 1, sizeAttrs.Len())
val, ok = sizeAttrs.Value(attribute.Key(obsconsumer.ComponentOutcome))
require.True(t, ok)
require.Equal(t, "failure", val.Emit())
// Check that the logger was called with an error
require.Len(t, logs.All(), 1)
assert.Contains(t, logs.All()[0].Message, "Profiles pipeline component had an error")
}
func TestProfilesWithStaticAttributes(t *testing.T) {
setGateForTest(t, true)
ctx := context.Background()
mockConsumer := &mockProfilesConsumer{}
reader := sdkmetric.NewManualReader()
mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader))
meter := mp.Meter("test")
itemCounter, err := meter.Int64Counter("item_counter")
require.NoError(t, err)
sizeCounter, err := meter.Int64Counter("size_counter")
require.NoError(t, err)
staticAttr := attribute.String("test", "value")
core, logs := observer.New(zap.DebugLevel)
logger := zap.New(core)
consumer := obsconsumer.NewProfiles(mockConsumer, obsconsumer.Settings{ItemCounter: itemCounter, SizeCounter: sizeCounter, Logger: logger},
obsconsumer.WithStaticDataPointAttribute(staticAttr))
pd := pprofile.NewProfiles()
r := pd.ResourceProfiles().AppendEmpty()
sp := r.ScopeProfiles().AppendEmpty()
sp.Profiles().AppendEmpty().Samples().AppendEmpty()
err = consumer.ConsumeProfiles(ctx, pd)
require.NoError(t, err)
var rm metricdata.ResourceMetrics
err = reader.Collect(ctx, &rm)
require.NoError(t, err)
require.Len(t, rm.ScopeMetrics, 1)
require.Len(t, rm.ScopeMetrics[0].Metrics, 2)
var itemMetric, sizeMetric metricdata.Metrics
for _, m := range rm.ScopeMetrics[0].Metrics {
switch m.Name {
case "item_counter":
itemMetric = m
case "size_counter":
sizeMetric = m
}
}
require.NotNil(t, itemMetric)
require.NotNil(t, sizeMetric)
itemData := itemMetric.Data.(metricdata.Sum[int64])
require.Len(t, itemData.DataPoints, 1)
require.Equal(t, int64(1), itemData.DataPoints[0].Value)
itemAttrs := itemData.DataPoints[0].Attributes
require.Equal(t, 2, itemAttrs.Len())
val, ok := itemAttrs.Value(attribute.Key("test"))
require.True(t, ok)
require.Equal(t, "value", val.Emit())
val, ok = itemAttrs.Value(attribute.Key(obsconsumer.ComponentOutcome))
require.True(t, ok)
require.Equal(t, "success", val.Emit())
sizeData := sizeMetric.Data.(metricdata.Sum[int64])
require.Len(t, sizeData.DataPoints, 1)
require.Positive(t, sizeData.DataPoints[0].Value)
sizeAttrs := sizeData.DataPoints[0].Attributes
require.Equal(t, 2, sizeAttrs.Len())
val, ok = sizeAttrs.Value(attribute.Key("test"))
require.True(t, ok)
require.Equal(t, "value", val.Emit())
val, ok = sizeAttrs.Value(attribute.Key(obsconsumer.ComponentOutcome))
require.True(t, ok)
require.Equal(t, "success", val.Emit())
// Check that the logger was not called
assert.Empty(t, logs.All())
}
func TestProfilesMultipleItemsMixedOutcomes(t *testing.T) {
setGateForTest(t, true)
ctx := context.Background()
expectedErr := errors.New("test error")
downstreamErr := consumererror.NewDownstream(expectedErr)
mockConsumer := &mockProfilesConsumer{}
reader := sdkmetric.NewManualReader()
mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader))
meter := mp.Meter("test")
itemCounter, err := meter.Int64Counter("item_counter")
require.NoError(t, err)
sizeCounter, err := meter.Int64Counter("size_counter")
require.NoError(t, err)
core, logs := observer.New(zap.DebugLevel)
logger := zap.New(core)
consumer := obsconsumer.NewProfiles(mockConsumer, obsconsumer.Settings{ItemCounter: itemCounter, SizeCounter: sizeCounter, Logger: logger})
// First batch: 2 successful items
pd1 := pprofile.NewProfiles()
for range 2 {
r := pd1.ResourceProfiles().AppendEmpty()
sp := r.ScopeProfiles().AppendEmpty()
sp.Profiles().AppendEmpty().Samples().AppendEmpty()
}
err = consumer.ConsumeProfiles(ctx, pd1)
require.NoError(t, err)
// Second batch: 1 failed item
mockConsumer.err = expectedErr
pd2 := pprofile.NewProfiles()
r := pd2.ResourceProfiles().AppendEmpty()
sp := r.ScopeProfiles().AppendEmpty()
sp.Profiles().AppendEmpty().Samples().AppendEmpty()
err = consumer.ConsumeProfiles(ctx, pd2)
assert.Equal(t, downstreamErr, err)
// Third batch: 2 successful items
mockConsumer.err = nil
pd3 := pprofile.NewProfiles()
for range 2 {
r = pd3.ResourceProfiles().AppendEmpty()
sp = r.ScopeProfiles().AppendEmpty()
sp.Profiles().AppendEmpty().Samples().AppendEmpty()
}
err = consumer.ConsumeProfiles(ctx, pd3)
require.NoError(t, err)
// Fourth batch: 1 failed item
mockConsumer.err = expectedErr
pd4 := pprofile.NewProfiles()
r = pd4.ResourceProfiles().AppendEmpty()
sp = r.ScopeProfiles().AppendEmpty()
sp.Profiles().AppendEmpty().Samples().AppendEmpty()
err = consumer.ConsumeProfiles(ctx, pd4)
assert.Equal(t, downstreamErr, err)
var rm metricdata.ResourceMetrics
err = reader.Collect(ctx, &rm)
require.NoError(t, err)
require.Len(t, rm.ScopeMetrics, 1)
require.Len(t, rm.ScopeMetrics[0].Metrics, 2)
var itemMetric, sizeMetric metricdata.Metrics
for _, m := range rm.ScopeMetrics[0].Metrics {
switch m.Name {
case "item_counter":
itemMetric = m
case "size_counter":
sizeMetric = m
}
}
require.NotNil(t, itemMetric)
require.NotNil(t, sizeMetric)
itemData := itemMetric.Data.(metricdata.Sum[int64])
require.Len(t, itemData.DataPoints, 2)
sizeData := sizeMetric.Data.(metricdata.Sum[int64])
require.Len(t, sizeData.DataPoints, 2)
var successDP, failureDP metricdata.DataPoint[int64]
for _, dp := range itemData.DataPoints {
val, ok := dp.Attributes.Value(attribute.Key(obsconsumer.ComponentOutcome))
if ok && val.Emit() == "success" {
successDP = dp
} else {
failureDP = dp
}
}
require.Equal(t, int64(4), successDP.Value)
require.Equal(t, int64(2), failureDP.Value)
var successSizeDP, failureSizeDP metricdata.DataPoint[int64]
for _, dp := range sizeData.DataPoints {
val, ok := dp.Attributes.Value(attribute.Key(obsconsumer.ComponentOutcome))
if ok && val.Emit() == "success" {
successSizeDP = dp
} else {
failureSizeDP = dp
}
}
require.Equal(t, int64(76), successSizeDP.Value)
require.Equal(t, int64(40), failureSizeDP.Value)
// Check that the logger was called for errors
require.Len(t, logs.All(), 2)
for _, log := range logs.All() {
assert.Contains(t, log.Message, "Profiles pipeline component had an error")
}
}
func TestProfilesCapabilities(t *testing.T) {
setGateForTest(t, true)
mockConsumer := &mockProfilesConsumer{
capabilities: consumer.Capabilities{MutatesData: true},
}
reader := sdkmetric.NewManualReader()
mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader))
meter := mp.Meter("test")
itemCounter, err := meter.Int64Counter("item_counter")
require.NoError(t, err)
sizeCounter, err := meter.Int64Counter("size_counter")
require.NoError(t, err)
sizeCounterDisabled := newDisabledCounter(sizeCounter)
// Test with item counter only
consumer := obsconsumer.NewProfiles(mockConsumer, obsconsumer.Settings{ItemCounter: itemCounter, SizeCounter: sizeCounterDisabled, Logger: zap.NewNop()})
require.Equal(t, consumer.Capabilities(), mockConsumer.capabilities)
// Test with both counters
consumer = obsconsumer.NewProfiles(mockConsumer, obsconsumer.Settings{ItemCounter: itemCounter, SizeCounter: sizeCounter, Logger: zap.NewNop()})
require.Equal(t, consumer.Capabilities(), mockConsumer.capabilities)
}
================================================
FILE: service/internal/obsconsumer/telemetry.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package obsconsumer // import "go.opentelemetry.io/collector/service/internal/obsconsumer"
import (
"go.opentelemetry.io/otel/metric"
"go.uber.org/zap"
)
// Settings defines the settings for telemetry in the obsconsumer package.
type Settings struct {
// ItemCounter is the metric to count the number of items processed.
ItemCounter metric.Int64Counter
// SizeCounter is the metric to count the size of items processed.
SizeCounter metric.Int64Counter
// Logger is the logger for the obsconsumer package.
Logger *zap.Logger
}
================================================
FILE: service/internal/obsconsumer/traces.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package obsconsumer // import "go.opentelemetry.io/collector/service/internal/obsconsumer"
import (
"context"
"go.uber.org/zap"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/internal/telemetry"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/service/internal/metadata"
)
var (
_ consumer.Traces = obsTraces{}
tracesMarshaler = &ptrace.ProtoMarshaler{}
)
func NewTraces(cons consumer.Traces, set Settings, opts ...Option) consumer.Traces {
if !metadata.TelemetryNewPipelineTelemetryFeatureGate.IsEnabled() {
return cons
}
o := options{}
for _, opt := range opts {
opt(&o)
}
consumerSet := Settings{
ItemCounter: set.ItemCounter,
SizeCounter: set.SizeCounter,
Logger: set.Logger.With(telemetry.ToZapFields(o.staticDataPointAttributes)...),
}
return obsTraces{
consumer: cons,
set: consumerSet,
compiledOptions: o.compile(),
}
}
type obsTraces struct {
consumer consumer.Traces
set Settings
compiledOptions
}
// ConsumeTraces measures telemetry before calling ConsumeTraces because the data may be mutated downstream
func (c obsTraces) ConsumeTraces(ctx context.Context, td ptrace.Traces) error {
// Use a pointer to so that deferred function can depend on the result of ConsumeTraces
attrs := &c.withSuccessAttrs
itemCount := td.SpanCount()
defer func() {
c.set.ItemCounter.Add(ctx, int64(itemCount), *attrs)
}()
if isEnabled(ctx, c.set.SizeCounter) {
byteCount := int64(tracesMarshaler.TracesSize(td))
defer func() {
c.set.SizeCounter.Add(ctx, byteCount, *attrs)
}()
}
err := c.consumer.ConsumeTraces(ctx, td)
if err != nil {
if consumererror.IsDownstream(err) {
attrs = &c.withRefusedAttrs
} else {
attrs = &c.withFailureAttrs
err = consumererror.NewDownstream(err)
}
if c.set.Logger.Core().Enabled(zap.DebugLevel) {
c.set.Logger.Debug("Traces pipeline component had an error", zap.Error(err), zap.Int("item count", itemCount))
}
}
return err
}
func (c obsTraces) Capabilities() consumer.Capabilities {
return c.consumer.Capabilities()
}
================================================
FILE: service/internal/obsconsumer/traces_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package obsconsumer_test
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/service/internal/obsconsumer"
)
type mockTracesConsumer struct {
err error
capabilities consumer.Capabilities
}
func (m *mockTracesConsumer) ConsumeTraces(_ context.Context, _ ptrace.Traces) error {
return m.err
}
func (m *mockTracesConsumer) Capabilities() consumer.Capabilities {
return m.capabilities
}
func TestTracesNopWhenGateDisabled(t *testing.T) {
setGateForTest(t, false)
mp := sdkmetric.NewMeterProvider()
meter := mp.Meter("test")
itemCounter, err := meter.Int64Counter("item_counter")
require.NoError(t, err)
sizeCounter, err := meter.Int64Counter("size_counter")
require.NoError(t, err)
cons := consumertest.NewNop()
require.Equal(t, cons, obsconsumer.NewTraces(cons, obsconsumer.Settings{ItemCounter: itemCounter, SizeCounter: sizeCounter, Logger: zap.NewNop()}))
}
func TestTracesItemsOnly(t *testing.T) {
setGateForTest(t, true)
ctx := context.Background()
mockConsumer := &mockTracesConsumer{}
reader := sdkmetric.NewManualReader()
mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader))
meter := mp.Meter("test")
itemCounter, err := meter.Int64Counter("item_counter")
require.NoError(t, err)
sizeCounter, err := meter.Int64Counter("size_counter")
require.NoError(t, err)
sizeCounterDisabled := newDisabledCounter(sizeCounter)
core, logs := observer.New(zap.DebugLevel)
logger := zap.New(core)
consumer := obsconsumer.NewTraces(mockConsumer, obsconsumer.Settings{ItemCounter: itemCounter, SizeCounter: sizeCounterDisabled, Logger: logger})
td := ptrace.NewTraces()
r := td.ResourceSpans().AppendEmpty()
ss := r.ScopeSpans().AppendEmpty()
ss.Spans().AppendEmpty()
err = consumer.ConsumeTraces(ctx, td)
require.NoError(t, err)
var metrics metricdata.ResourceMetrics
err = reader.Collect(ctx, &metrics)
require.NoError(t, err)
require.Len(t, metrics.ScopeMetrics, 1)
require.Len(t, metrics.ScopeMetrics[0].Metrics, 1)
metric := metrics.ScopeMetrics[0].Metrics[0]
require.Equal(t, "item_counter", metric.Name)
data := metric.Data.(metricdata.Sum[int64])
require.Len(t, data.DataPoints, 1)
require.Equal(t, int64(1), data.DataPoints[0].Value)
attrs := data.DataPoints[0].Attributes
require.Equal(t, 1, attrs.Len())
val, ok := attrs.Value(attribute.Key(obsconsumer.ComponentOutcome))
require.True(t, ok)
require.Equal(t, "success", val.Emit())
// Check that the logger was not called
assert.Empty(t, logs.All())
}
func TestTracesConsumeSuccess(t *testing.T) {
setGateForTest(t, true)
ctx := context.Background()
mockConsumer := &mockTracesConsumer{}
reader := sdkmetric.NewManualReader()
mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader))
meter := mp.Meter("test")
itemCounter, err := meter.Int64Counter("item_counter")
require.NoError(t, err)
sizeCounter, err := meter.Int64Counter("size_counter")
require.NoError(t, err)
core, logs := observer.New(zap.DebugLevel)
logger := zap.New(core)
consumer := obsconsumer.NewTraces(mockConsumer, obsconsumer.Settings{ItemCounter: itemCounter, SizeCounter: sizeCounter, Logger: logger})
td := ptrace.NewTraces()
r := td.ResourceSpans().AppendEmpty()
ss := r.ScopeSpans().AppendEmpty()
ss.Spans().AppendEmpty()
err = consumer.ConsumeTraces(ctx, td)
require.NoError(t, err)
var rm metricdata.ResourceMetrics
err = reader.Collect(ctx, &rm)
require.NoError(t, err)
require.Len(t, rm.ScopeMetrics, 1)
require.Len(t, rm.ScopeMetrics[0].Metrics, 2)
var itemMetric, sizeMetric metricdata.Metrics
for _, m := range rm.ScopeMetrics[0].Metrics {
switch m.Name {
case "item_counter":
itemMetric = m
case "size_counter":
sizeMetric = m
}
}
require.NotNil(t, itemMetric)
require.NotNil(t, sizeMetric)
itemData := itemMetric.Data.(metricdata.Sum[int64])
require.Len(t, itemData.DataPoints, 1)
require.Equal(t, int64(1), itemData.DataPoints[0].Value)
itemAttrs := itemData.DataPoints[0].Attributes
require.Equal(t, 1, itemAttrs.Len())
val, ok := itemAttrs.Value(attribute.Key(obsconsumer.ComponentOutcome))
require.True(t, ok)
require.Equal(t, "success", val.Emit())
sizeData := sizeMetric.Data.(metricdata.Sum[int64])
require.Len(t, sizeData.DataPoints, 1)
require.Positive(t, sizeData.DataPoints[0].Value)
sizeAttrs := sizeData.DataPoints[0].Attributes
require.Equal(t, 1, sizeAttrs.Len())
val, ok = sizeAttrs.Value(attribute.Key(obsconsumer.ComponentOutcome))
require.True(t, ok)
require.Equal(t, "success", val.Emit())
// Check that the logger was not called
assert.Empty(t, logs.All())
}
func TestTracesConsumeFailure(t *testing.T) {
setGateForTest(t, true)
ctx := context.Background()
expectedErr := errors.New("test error")
downstreamErr := consumererror.NewDownstream(expectedErr)
mockConsumer := &mockTracesConsumer{err: expectedErr}
reader := sdkmetric.NewManualReader()
mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader))
meter := mp.Meter("test")
itemCounter, err := meter.Int64Counter("item_counter")
require.NoError(t, err)
sizeCounter, err := meter.Int64Counter("size_counter")
require.NoError(t, err)
core, logs := observer.New(zap.DebugLevel)
logger := zap.New(core)
consumer := obsconsumer.NewTraces(mockConsumer, obsconsumer.Settings{ItemCounter: itemCounter, SizeCounter: sizeCounter, Logger: logger})
td := ptrace.NewTraces()
r := td.ResourceSpans().AppendEmpty()
ss := r.ScopeSpans().AppendEmpty()
ss.Spans().AppendEmpty()
err = consumer.ConsumeTraces(ctx, td)
assert.Equal(t, downstreamErr, err)
var rm metricdata.ResourceMetrics
err = reader.Collect(ctx, &rm)
require.NoError(t, err)
require.Len(t, rm.ScopeMetrics, 1)
require.Len(t, rm.ScopeMetrics[0].Metrics, 2)
var itemMetric, sizeMetric metricdata.Metrics
for _, m := range rm.ScopeMetrics[0].Metrics {
switch m.Name {
case "item_counter":
itemMetric = m
case "size_counter":
sizeMetric = m
}
}
require.NotNil(t, itemMetric)
require.NotNil(t, sizeMetric)
itemData := itemMetric.Data.(metricdata.Sum[int64])
require.Len(t, itemData.DataPoints, 1)
require.Equal(t, int64(1), itemData.DataPoints[0].Value)
itemAttrs := itemData.DataPoints[0].Attributes
require.Equal(t, 1, itemAttrs.Len())
val, ok := itemAttrs.Value(attribute.Key(obsconsumer.ComponentOutcome))
require.True(t, ok)
require.Equal(t, "failure", val.Emit())
sizeData := sizeMetric.Data.(metricdata.Sum[int64])
require.Len(t, sizeData.DataPoints, 1)
require.Positive(t, sizeData.DataPoints[0].Value)
sizeAttrs := sizeData.DataPoints[0].Attributes
require.Equal(t, 1, sizeAttrs.Len())
val, ok = sizeAttrs.Value(attribute.Key(obsconsumer.ComponentOutcome))
require.True(t, ok)
require.Equal(t, "failure", val.Emit())
// Check that the logger was called with an error
require.Len(t, logs.All(), 1)
assert.Contains(t, logs.All()[0].Message, "Traces pipeline component had an error")
}
func TestTracesWithStaticAttributes(t *testing.T) {
setGateForTest(t, true)
ctx := context.Background()
mockConsumer := &mockTracesConsumer{}
reader := sdkmetric.NewManualReader()
mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader))
meter := mp.Meter("test")
itemCounter, err := meter.Int64Counter("item_counter")
require.NoError(t, err)
sizeCounter, err := meter.Int64Counter("size_counter")
require.NoError(t, err)
staticAttr := attribute.String("test", "value")
core, logs := observer.New(zap.DebugLevel)
logger := zap.New(core)
consumer := obsconsumer.NewTraces(mockConsumer, obsconsumer.Settings{ItemCounter: itemCounter, SizeCounter: sizeCounter, Logger: logger},
obsconsumer.WithStaticDataPointAttribute(staticAttr))
td := ptrace.NewTraces()
r := td.ResourceSpans().AppendEmpty()
ss := r.ScopeSpans().AppendEmpty()
ss.Spans().AppendEmpty()
err = consumer.ConsumeTraces(ctx, td)
require.NoError(t, err)
var rm metricdata.ResourceMetrics
err = reader.Collect(ctx, &rm)
require.NoError(t, err)
require.Len(t, rm.ScopeMetrics, 1)
require.Len(t, rm.ScopeMetrics[0].Metrics, 2)
var itemMetric, sizeMetric metricdata.Metrics
for _, m := range rm.ScopeMetrics[0].Metrics {
switch m.Name {
case "item_counter":
itemMetric = m
case "size_counter":
sizeMetric = m
}
}
require.NotNil(t, itemMetric)
require.NotNil(t, sizeMetric)
itemData := itemMetric.Data.(metricdata.Sum[int64])
require.Len(t, itemData.DataPoints, 1)
require.Equal(t, int64(1), itemData.DataPoints[0].Value)
itemAttrs := itemData.DataPoints[0].Attributes
require.Equal(t, 2, itemAttrs.Len())
val, ok := itemAttrs.Value(attribute.Key("test"))
require.True(t, ok)
require.Equal(t, "value", val.Emit())
val, ok = itemAttrs.Value(attribute.Key(obsconsumer.ComponentOutcome))
require.True(t, ok)
require.Equal(t, "success", val.Emit())
sizeData := sizeMetric.Data.(metricdata.Sum[int64])
require.Len(t, sizeData.DataPoints, 1)
require.Positive(t, sizeData.DataPoints[0].Value)
sizeAttrs := sizeData.DataPoints[0].Attributes
require.Equal(t, 2, sizeAttrs.Len())
val, ok = sizeAttrs.Value(attribute.Key("test"))
require.True(t, ok)
require.Equal(t, "value", val.Emit())
val, ok = sizeAttrs.Value(attribute.Key(obsconsumer.ComponentOutcome))
require.True(t, ok)
require.Equal(t, "success", val.Emit())
// Check that the logger was not called
assert.Empty(t, logs.All())
}
func TestTracesMultipleItemsMixedOutcomes(t *testing.T) {
setGateForTest(t, true)
ctx := context.Background()
expectedErr := errors.New("test error")
downstreamErr := consumererror.NewDownstream(expectedErr)
mockConsumer := &mockTracesConsumer{}
reader := sdkmetric.NewManualReader()
mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader))
meter := mp.Meter("test")
itemCounter, err := meter.Int64Counter("item_counter")
require.NoError(t, err)
sizeCounter, err := meter.Int64Counter("size_counter")
require.NoError(t, err)
core, logs := observer.New(zap.DebugLevel)
logger := zap.New(core)
consumer := obsconsumer.NewTraces(mockConsumer, obsconsumer.Settings{ItemCounter: itemCounter, SizeCounter: sizeCounter, Logger: logger})
// First batch: 2 successful items
td1 := ptrace.NewTraces()
for range 2 {
r := td1.ResourceSpans().AppendEmpty()
ss := r.ScopeSpans().AppendEmpty()
ss.Spans().AppendEmpty()
}
err = consumer.ConsumeTraces(ctx, td1)
require.NoError(t, err)
// Second batch: 1 failed item
mockConsumer.err = expectedErr
td2 := ptrace.NewTraces()
r := td2.ResourceSpans().AppendEmpty()
ss := r.ScopeSpans().AppendEmpty()
ss.Spans().AppendEmpty()
err = consumer.ConsumeTraces(ctx, td2)
assert.Equal(t, downstreamErr, err)
// Third batch: 2 successful items
mockConsumer.err = nil
td3 := ptrace.NewTraces()
for range 2 {
r = td3.ResourceSpans().AppendEmpty()
ss = r.ScopeSpans().AppendEmpty()
ss.Spans().AppendEmpty()
}
err = consumer.ConsumeTraces(ctx, td3)
require.NoError(t, err)
// Fourth batch: 1 failed item
mockConsumer.err = expectedErr
td4 := ptrace.NewTraces()
r = td4.ResourceSpans().AppendEmpty()
ss = r.ScopeSpans().AppendEmpty()
ss.Spans().AppendEmpty()
err = consumer.ConsumeTraces(ctx, td4)
assert.Equal(t, downstreamErr, err)
var rm metricdata.ResourceMetrics
err = reader.Collect(ctx, &rm)
require.NoError(t, err)
require.Len(t, rm.ScopeMetrics, 1)
require.Len(t, rm.ScopeMetrics[0].Metrics, 2)
var itemMetric, sizeMetric metricdata.Metrics
for _, m := range rm.ScopeMetrics[0].Metrics {
switch m.Name {
case "item_counter":
itemMetric = m
case "size_counter":
sizeMetric = m
}
}
require.NotNil(t, itemMetric)
require.NotNil(t, sizeMetric)
itemData := itemMetric.Data.(metricdata.Sum[int64])
require.Len(t, itemData.DataPoints, 2)
sizeData := sizeMetric.Data.(metricdata.Sum[int64])
require.Len(t, sizeData.DataPoints, 2)
// Find success and failure data points
var successDP, failureDP metricdata.DataPoint[int64]
for _, dp := range itemData.DataPoints {
val, ok := dp.Attributes.Value(attribute.Key(obsconsumer.ComponentOutcome))
if ok && val.Emit() == "success" {
successDP = dp
} else {
failureDP = dp
}
}
require.Equal(t, int64(4), successDP.Value)
require.Equal(t, int64(2), failureDP.Value)
var successSizeDP, failureSizeDP metricdata.DataPoint[int64]
for _, dp := range sizeData.DataPoints {
val, ok := dp.Attributes.Value(attribute.Key(obsconsumer.ComponentOutcome))
if ok && val.Emit() == "success" {
successSizeDP = dp
} else {
failureSizeDP = dp
}
}
require.Equal(t, int64(72), successSizeDP.Value)
require.Equal(t, int64(36), failureSizeDP.Value)
// Check that the logger was called for errors
require.Len(t, logs.All(), 2)
for _, log := range logs.All() {
assert.Contains(t, log.Message, "Traces pipeline component had an error")
}
}
func TestTracesCapabilities(t *testing.T) {
setGateForTest(t, true)
mockConsumer := &mockTracesConsumer{
capabilities: consumer.Capabilities{MutatesData: true},
}
reader := sdkmetric.NewManualReader()
mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader))
meter := mp.Meter("test")
itemCounter, err := meter.Int64Counter("item_counter")
require.NoError(t, err)
sizeCounter, err := meter.Int64Counter("size_counter")
require.NoError(t, err)
sizeCounterDisabled := newDisabledCounter(sizeCounter)
// Test with item counter only
consumer := obsconsumer.NewTraces(mockConsumer, obsconsumer.Settings{ItemCounter: itemCounter, SizeCounter: sizeCounterDisabled, Logger: zap.NewNop()})
require.Equal(t, consumer.Capabilities(), mockConsumer.capabilities)
// Test with both counters
consumer = obsconsumer.NewTraces(mockConsumer, obsconsumer.Settings{ItemCounter: itemCounter, SizeCounter: sizeCounter, Logger: zap.NewNop()})
require.Equal(t, consumer.Capabilities(), mockConsumer.capabilities)
}
================================================
FILE: service/internal/proctelemetry/process_telemetry.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package proctelemetry // import "go.opentelemetry.io/collector/service/internal/proctelemetry"
import (
"context"
"errors"
"os"
"runtime"
"sync"
"time"
"github.com/shirou/gopsutil/v4/common"
"github.com/shirou/gopsutil/v4/process"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/service/internal/metadata"
)
// processMetrics is a struct that contains views related to process metrics (cpu, mem, etc)
type processMetrics struct {
startTimeUnixNano int64
proc *process.Process
context context.Context
// mu protects everything bellow.
mu sync.Mutex
lastMsRead time.Time
ms *runtime.MemStats
}
type RegisterOption interface {
apply(*registerOption)
}
type registerOption struct {
hostProc string
}
type registerOptionFunc func(*registerOption)
func (fn registerOptionFunc) apply(set *registerOption) {
fn(set)
}
// WithHostProc overrides the /proc folder on Linux used by process telemetry.
func WithHostProc(hostProc string) RegisterOption {
return registerOptionFunc(func(uo *registerOption) {
uo.hostProc = hostProc
})
}
// RegisterProcessMetrics creates a new set of processMetrics (mem, cpu) that can be used to measure
// basic information about this process.
func RegisterProcessMetrics(cfg component.TelemetrySettings, opts ...RegisterOption) error {
set := registerOption{}
for _, opt := range opts {
opt.apply(&set)
}
var err error
pm := &processMetrics{
startTimeUnixNano: time.Now().UnixNano(),
ms: &runtime.MemStats{},
}
ctx := context.Background()
if set.hostProc != "" {
ctx = context.WithValue(ctx, common.EnvKey, common.EnvMap{common.HostProcEnvKey: set.hostProc})
}
pm.context = ctx
pm.proc, err = process.NewProcessWithContext(pm.context, int32(os.Getpid()))
if err != nil {
return err
}
tb, err := metadata.NewTelemetryBuilder(cfg)
if err != nil {
return err
}
return errors.Join(
tb.RegisterProcessUptimeCallback(pm.updateProcessUptime),
tb.RegisterProcessRuntimeHeapAllocBytesCallback(pm.updateAllocMem),
tb.RegisterProcessRuntimeTotalAllocBytesCallback(pm.updateTotalAllocMem),
tb.RegisterProcessRuntimeTotalSysMemoryBytesCallback(pm.updateSysMem),
tb.RegisterProcessCPUSecondsCallback(pm.updateCPUSeconds),
tb.RegisterProcessMemoryRssCallback(pm.updateRSSMemory),
)
}
func (pm *processMetrics) updateProcessUptime(_ context.Context, obs metric.Float64Observer) error {
now := time.Now().UnixNano()
obs.Observe(float64(now-pm.startTimeUnixNano) / 1e9)
return nil
}
func (pm *processMetrics) updateAllocMem(_ context.Context, obs metric.Int64Observer) error {
pm.mu.Lock()
defer pm.mu.Unlock()
pm.readMemStatsIfNeeded()
obs.Observe(int64(pm.ms.Alloc))
return nil
}
func (pm *processMetrics) updateTotalAllocMem(_ context.Context, obs metric.Int64Observer) error {
pm.mu.Lock()
defer pm.mu.Unlock()
pm.readMemStatsIfNeeded()
obs.Observe(int64(pm.ms.TotalAlloc))
return nil
}
func (pm *processMetrics) updateSysMem(_ context.Context, obs metric.Int64Observer) error {
pm.mu.Lock()
defer pm.mu.Unlock()
pm.readMemStatsIfNeeded()
obs.Observe(int64(pm.ms.Sys))
return nil
}
func (pm *processMetrics) updateCPUSeconds(_ context.Context, obs metric.Float64Observer) error {
times, err := pm.proc.TimesWithContext(pm.context) //nolint:contextcheck
if err != nil {
return err
}
obs.Observe(times.User + times.System + times.Idle + times.Nice +
times.Iowait + times.Irq + times.Softirq + times.Steal)
return nil
}
func (pm *processMetrics) updateRSSMemory(_ context.Context, obs metric.Int64Observer) error {
mem, err := pm.proc.MemoryInfoWithContext(pm.context) //nolint:contextcheck
if err != nil {
return err
}
obs.Observe(int64(mem.RSS))
return nil
}
func (pm *processMetrics) readMemStatsIfNeeded() {
now := time.Now()
// If last time we read was less than one second ago just reuse the values
if now.Sub(pm.lastMsRead) < time.Second {
return
}
pm.lastMsRead = now
runtime.ReadMemStats(pm.ms)
}
================================================
FILE: service/internal/proctelemetry/process_telemetry_linux_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:build linux
package proctelemetry
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/service/internal/metadatatest"
)
func TestProcessTelemetryWithHostProc(t *testing.T) {
// Make the sure the environment variable value is not used.
t.Setenv("HOST_PROC", "foo/bar")
tel := componenttest.NewTelemetry()
require.NoError(t, RegisterProcessMetrics(tel.NewTelemetrySettings(), WithHostProc("/proc")))
metadatatest.AssertEqualProcessUptime(t, tel,
[]metricdata.DataPoint[float64]{{}}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue())
metadatatest.AssertEqualProcessRuntimeHeapAllocBytes(t, tel,
[]metricdata.DataPoint[int64]{{}}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue())
metadatatest.AssertEqualProcessRuntimeTotalAllocBytes(t, tel,
[]metricdata.DataPoint[int64]{{}}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue())
metadatatest.AssertEqualProcessRuntimeTotalSysMemoryBytes(t, tel,
[]metricdata.DataPoint[int64]{{}}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue())
metadatatest.AssertEqualProcessCPUSeconds(t, tel,
[]metricdata.DataPoint[float64]{{}}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue())
metadatatest.AssertEqualProcessMemoryRss(t, tel,
[]metricdata.DataPoint[int64]{{}}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue())
}
================================================
FILE: service/internal/proctelemetry/process_telemetry_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package proctelemetry
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/service/internal/metadatatest"
)
func TestProcessTelemetry(t *testing.T) {
tel := componenttest.NewTelemetry()
require.NoError(t, RegisterProcessMetrics(tel.NewTelemetrySettings()))
metadatatest.AssertEqualProcessUptime(t, tel,
[]metricdata.DataPoint[float64]{{}}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue())
metadatatest.AssertEqualProcessRuntimeHeapAllocBytes(t, tel,
[]metricdata.DataPoint[int64]{{}}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue())
metadatatest.AssertEqualProcessRuntimeTotalAllocBytes(t, tel,
[]metricdata.DataPoint[int64]{{}}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue())
metadatatest.AssertEqualProcessRuntimeTotalSysMemoryBytes(t, tel,
[]metricdata.DataPoint[int64]{{}}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue())
metadatatest.AssertEqualProcessCPUSeconds(t, tel,
[]metricdata.DataPoint[float64]{{}}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue())
metadatatest.AssertEqualProcessMemoryRss(t, tel,
[]metricdata.DataPoint[int64]{{}}, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue())
}
================================================
FILE: service/internal/promtest/server_util.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package promtest // import "go.opentelemetry.io/collector/service/internal/promtest"
import (
"net"
"strconv"
"testing"
config "go.opentelemetry.io/contrib/otelconf/v0.3.0"
"go.opentelemetry.io/collector/internal/testutil"
)
func GetAvailableLocalIPv6AddressPrometheus(tb testing.TB) *config.Prometheus {
return addrToPrometheus(testutil.GetAvailableLocalIPv6Address(tb))
}
func GetAvailableLocalAddressPrometheus(tb testing.TB) *config.Prometheus {
return addrToPrometheus(testutil.GetAvailableLocalAddress(tb))
}
func addrToPrometheus(address string) *config.Prometheus {
host, port, err := net.SplitHostPort(address)
if host == "::1" {
host = "[::1]"
}
if err != nil {
return nil
}
portInt, err := strconv.Atoi(port)
if err != nil {
return nil
}
return &config.Prometheus{
Host: &host,
Port: &portInt,
WithoutScopeInfo: ptr(true),
WithoutUnits: ptr(true),
WithoutTypeSuffix: ptr(true),
}
}
func ptr[T any](v T) *T {
return &v
}
================================================
FILE: service/internal/refconsumer/logs.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package refconsumer // import "go.opentelemetry.io/collector/service/internal/refconsumer"
import (
"context"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/xpdata/pref"
)
func NewLogs(cons consumer.Logs) consumer.Logs {
return refLogs{
consumer: cons,
}
}
type refLogs struct {
consumer consumer.Logs
}
// ConsumeLogs measures telemetry before calling ConsumeLogs because the data may be mutated downstream
func (c refLogs) ConsumeLogs(ctx context.Context, ld plog.Logs) error {
if pref.MarkPipelineOwnedLogs(ld) {
defer pref.UnrefLogs(ld)
}
return c.consumer.ConsumeLogs(ctx, ld)
}
func (c refLogs) Capabilities() consumer.Capabilities {
return c.consumer.Capabilities()
}
================================================
FILE: service/internal/refconsumer/logs_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package refconsumer
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/testdata"
"go.opentelemetry.io/collector/pdata/xpdata/pref"
"go.opentelemetry.io/collector/service/internal/metadata"
)
func TestLogsNopWhenGateDisabled(t *testing.T) {
initial := pref.UseProtoPooling.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(pref.UseProtoPooling.ID(), false))
t.Cleanup(func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.TelemetryNewPipelineTelemetryFeatureGate.ID(), initial))
})
refCons := NewLogs(consumertest.NewNop())
ld := testdata.GenerateLogs(10)
assert.Equal(t, 10, ld.LogRecordCount())
require.NoError(t, refCons.ConsumeLogs(t.Context(), ld))
assert.Equal(t, 10, ld.LogRecordCount())
}
func TestLogs(t *testing.T) {
initial := pref.UseProtoPooling.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(pref.UseProtoPooling.ID(), true))
t.Cleanup(func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.TelemetryNewPipelineTelemetryFeatureGate.ID(), initial))
})
refCons := NewLogs(consumertest.NewNop())
ld := testdata.GenerateLogs(10)
assert.Equal(t, 10, ld.LogRecordCount())
require.NoError(t, refCons.ConsumeLogs(t.Context(), ld))
// Data should be reset at this point.
assert.Equal(t, 0, ld.LogRecordCount())
}
================================================
FILE: service/internal/refconsumer/metrics.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package refconsumer // import "go.opentelemetry.io/collector/service/internal/refconsumer"
import (
"context"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/xpdata/pref"
)
func NewMetrics(cons consumer.Metrics) consumer.Metrics {
return refMetrics{
consumer: cons,
}
}
type refMetrics struct {
consumer consumer.Metrics
}
// ConsumeMetrics measures telemetry before calling ConsumeMetrics because the data may be mutated downstream
func (c refMetrics) ConsumeMetrics(ctx context.Context, ld pmetric.Metrics) error {
if pref.MarkPipelineOwnedMetrics(ld) {
defer pref.UnrefMetrics(ld)
}
return c.consumer.ConsumeMetrics(ctx, ld)
}
func (c refMetrics) Capabilities() consumer.Capabilities {
return c.consumer.Capabilities()
}
================================================
FILE: service/internal/refconsumer/metrics_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package refconsumer
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/testdata"
"go.opentelemetry.io/collector/pdata/xpdata/pref"
"go.opentelemetry.io/collector/service/internal/metadata"
)
func TestMetricsNopWhenGateDisabled(t *testing.T) {
initial := pref.UseProtoPooling.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(pref.UseProtoPooling.ID(), false))
t.Cleanup(func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.TelemetryNewPipelineTelemetryFeatureGate.ID(), initial))
})
refCons := NewMetrics(consumertest.NewNop())
md := testdata.GenerateMetrics(10)
assert.Equal(t, 10, md.MetricCount())
require.NoError(t, refCons.ConsumeMetrics(t.Context(), md))
assert.Equal(t, 10, md.MetricCount())
}
func TestMetrics(t *testing.T) {
initial := pref.UseProtoPooling.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(pref.UseProtoPooling.ID(), true))
t.Cleanup(func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.TelemetryNewPipelineTelemetryFeatureGate.ID(), initial))
})
refCons := NewMetrics(consumertest.NewNop())
md := testdata.GenerateMetrics(10)
assert.Equal(t, 10, md.MetricCount())
require.NoError(t, refCons.ConsumeMetrics(t.Context(), md))
// Data shoumd be reset at this point.
assert.Equal(t, 0, md.MetricCount())
}
================================================
FILE: service/internal/refconsumer/profiles.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package refconsumer // import "go.opentelemetry.io/collector/service/internal/refconsumer"
import (
"context"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/xpdata/pref"
)
func NewProfiles(cons xconsumer.Profiles) xconsumer.Profiles {
return refProfiles{
consumer: cons,
}
}
type refProfiles struct {
consumer xconsumer.Profiles
}
// ConsumeProfiles measures telemetry before calling ConsumeProfiles because the data may be mutated downstream
func (c refProfiles) ConsumeProfiles(ctx context.Context, ld pprofile.Profiles) error {
if pref.MarkPipelineOwnedProfiles(ld) {
defer pref.UnrefProfiles(ld)
}
return c.consumer.ConsumeProfiles(ctx, ld)
}
func (c refProfiles) Capabilities() consumer.Capabilities {
return c.consumer.Capabilities()
}
================================================
FILE: service/internal/refconsumer/profiles_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package refconsumer
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/testdata"
"go.opentelemetry.io/collector/pdata/xpdata/pref"
"go.opentelemetry.io/collector/service/internal/metadata"
)
func TestProfilesNopWhenGateDisabled(t *testing.T) {
initial := pref.UseProtoPooling.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(pref.UseProtoPooling.ID(), false))
t.Cleanup(func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.TelemetryNewPipelineTelemetryFeatureGate.ID(), initial))
})
refCons := NewProfiles(consumertest.NewNop())
pd := testdata.GenerateProfiles(10)
assert.Equal(t, 10, pd.SampleCount())
require.NoError(t, refCons.ConsumeProfiles(t.Context(), pd))
assert.Equal(t, 10, pd.SampleCount())
}
func TestProfiles(t *testing.T) {
initial := pref.UseProtoPooling.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(pref.UseProtoPooling.ID(), true))
t.Cleanup(func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.TelemetryNewPipelineTelemetryFeatureGate.ID(), initial))
})
refCons := NewProfiles(consumertest.NewNop())
pd := testdata.GenerateProfiles(10)
assert.Equal(t, 10, pd.SampleCount())
require.NoError(t, refCons.ConsumeProfiles(t.Context(), pd))
// Data shoupd be reset at this point.
assert.Equal(t, 0, pd.SampleCount())
}
================================================
FILE: service/internal/refconsumer/traces.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package refconsumer // import "go.opentelemetry.io/collector/service/internal/refconsumer"
import (
"context"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/pdata/xpdata/pref"
)
func NewTraces(cons consumer.Traces) consumer.Traces {
return refTraces{
consumer: cons,
}
}
type refTraces struct {
consumer consumer.Traces
}
// ConsumeTraces measures telemetry before calling ConsumeTraces because the data may be mutated downstream
func (c refTraces) ConsumeTraces(ctx context.Context, ld ptrace.Traces) error {
if pref.MarkPipelineOwnedTraces(ld) {
defer pref.UnrefTraces(ld)
}
return c.consumer.ConsumeTraces(ctx, ld)
}
func (c refTraces) Capabilities() consumer.Capabilities {
return c.consumer.Capabilities()
}
================================================
FILE: service/internal/refconsumer/traces_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package refconsumer
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/testdata"
"go.opentelemetry.io/collector/pdata/xpdata/pref"
"go.opentelemetry.io/collector/service/internal/metadata"
)
func TestTracesNopWhenGateDisabled(t *testing.T) {
initial := pref.UseProtoPooling.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(pref.UseProtoPooling.ID(), false))
t.Cleanup(func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.TelemetryNewPipelineTelemetryFeatureGate.ID(), initial))
})
refCons := NewTraces(consumertest.NewNop())
td := testdata.GenerateTraces(10)
assert.Equal(t, 10, td.SpanCount())
require.NoError(t, refCons.ConsumeTraces(t.Context(), td))
assert.Equal(t, 10, td.SpanCount())
}
func TestTraces(t *testing.T) {
initial := pref.UseProtoPooling.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(pref.UseProtoPooling.ID(), true))
t.Cleanup(func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.TelemetryNewPipelineTelemetryFeatureGate.ID(), initial))
})
refCons := NewTraces(consumertest.NewNop())
td := testdata.GenerateTraces(10)
assert.Equal(t, 10, td.SpanCount())
require.NoError(t, refCons.ConsumeTraces(t.Context(), td))
// Data shoutd be reset at this point.
assert.Equal(t, 0, td.SpanCount())
}
================================================
FILE: service/internal/resource/config.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package resource // import "go.opentelemetry.io/collector/service/internal/resource"
import (
"github.com/google/uuid"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.38.0"
"go.opentelemetry.io/collector/component"
)
// New resource from telemetry configuration.
func New(buildInfo component.BuildInfo, resourceCfg map[string]*string) *resource.Resource {
var telAttrs []attribute.KeyValue
for k, v := range resourceCfg {
// nil value indicates that the attribute should not be included in the telemetry.
if v != nil {
telAttrs = append(telAttrs, attribute.String(k, *v))
}
}
if _, ok := resourceCfg[string(semconv.ServiceNameKey)]; !ok {
// AttributeServiceName is not specified in the config. Use the default service name.
telAttrs = append(telAttrs, semconv.ServiceNameKey.String(buildInfo.Command))
}
if _, ok := resourceCfg[string(semconv.ServiceInstanceIDKey)]; !ok {
// AttributeServiceInstanceID is not specified in the config. Auto-generate one.
instanceUUID, _ := uuid.NewRandom()
instanceID := instanceUUID.String()
telAttrs = append(telAttrs, semconv.ServiceInstanceIDKey.String(instanceID))
}
if _, ok := resourceCfg[string(semconv.ServiceVersionKey)]; !ok {
// AttributeServiceVersion is not specified in the config. Use the actual
// build version.
telAttrs = append(telAttrs, semconv.ServiceVersionKey.String(buildInfo.Version))
}
return resource.NewWithAttributes(semconv.SchemaURL, telAttrs...)
}
================================================
FILE: service/internal/resource/config_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package resource
import (
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
sdkresource "go.opentelemetry.io/otel/sdk/resource"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pcommon"
)
const (
randomUUIDSpecialValue = "random-uuid"
)
var buildInfo = component.BuildInfo{
Command: "otelcol",
Version: "1.0.0",
}
func ptr[T any](v T) *T {
return &v
}
func TestNew(t *testing.T) {
tests := []struct {
name string
resourceCfg map[string]*string
want map[string]string
}{
{
name: "empty",
resourceCfg: map[string]*string{},
want: map[string]string{
"service.name": "otelcol",
"service.version": "1.0.0",
"service.instance.id": randomUUIDSpecialValue,
},
},
{
name: "overwrite",
resourceCfg: map[string]*string{
"service.name": ptr("my-service"),
"service.version": ptr("1.2.3"),
"service.instance.id": ptr("123"),
},
want: map[string]string{
"service.name": "my-service",
"service.version": "1.2.3",
"service.instance.id": "123",
},
},
{
name: "remove",
resourceCfg: map[string]*string{
"service.name": nil,
"service.version": nil,
"service.instance.id": nil,
},
want: map[string]string{},
},
{
name: "add",
resourceCfg: map[string]*string{
"host.name": ptr("my-host"),
},
want: map[string]string{
"service.name": "otelcol",
"service.version": "1.0.0",
"service.instance.id": randomUUIDSpecialValue,
"host.name": "my-host",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
res := New(buildInfo, tt.resourceCfg)
got := make(map[string]string)
for _, attr := range res.Attributes() {
got[string(attr.Key)] = attr.Value.Emit()
}
if tt.want["service.instance.id"] == randomUUIDSpecialValue {
assert.Contains(t, got, "service.instance.id")
// Check that the value is a valid UUID.
_, err := uuid.Parse(got["service.instance.id"])
require.NoError(t, err)
// Remove so that we can compare the rest of the map.
delete(got, "service.instance.id")
delete(tt.want, "service.instance.id")
}
assert.Equal(t, tt.want, got)
})
}
}
func pdataFromSdk(res *sdkresource.Resource) pcommon.Resource {
// pcommon.NewResource is the best way to generate a new resource currently and is safe to use outside of tests.
// Because the resource is signal agnostic, and we need a net new resource, not an existing one, this is the only
// method of creating it without exposing internal packages.
pcommonRes := pcommon.NewResource()
for _, keyValue := range res.Attributes() {
pcommonRes.Attributes().PutStr(string(keyValue.Key), keyValue.Value.AsString())
}
return pcommonRes
}
func TestBuildResource(t *testing.T) {
buildInfo := component.NewDefaultBuildInfo()
// Check default config
var resMap map[string]*string
otelRes := New(buildInfo, resMap)
res := pdataFromSdk(otelRes)
assert.Equal(t, 3, res.Attributes().Len())
value, ok := res.Attributes().Get("service.name")
assert.True(t, ok)
assert.Equal(t, buildInfo.Command, value.AsString())
value, ok = res.Attributes().Get("service.version")
assert.True(t, ok)
assert.Equal(t, buildInfo.Version, value.AsString())
_, ok = res.Attributes().Get("service.instance.id")
assert.True(t, ok)
// Check override by nil
resMap = map[string]*string{
"service.name": nil,
"service.version": nil,
"service.instance.id": nil,
}
otelRes = New(buildInfo, resMap)
res = pdataFromSdk(otelRes)
// Attributes should not exist since we nil-ified all.
assert.Equal(t, 0, res.Attributes().Len())
// Check override values
strPtr := func(v string) *string { return &v }
resMap = map[string]*string{
"service.name": strPtr("a"),
"service.version": strPtr("b"),
"service.instance.id": strPtr("c"),
}
otelRes = New(buildInfo, resMap)
res = pdataFromSdk(otelRes)
assert.Equal(t, 3, res.Attributes().Len())
value, ok = res.Attributes().Get("service.name")
assert.True(t, ok)
assert.Equal(t, "a", value.AsString())
value, ok = res.Attributes().Get("service.version")
assert.True(t, ok)
assert.Equal(t, "b", value.AsString())
value, ok = res.Attributes().Get("service.instance.id")
assert.True(t, ok)
assert.Equal(t, "c", value.AsString())
}
================================================
FILE: service/internal/status/nop.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package status // import "go.opentelemetry.io/collector/service/internal/status"
import (
"go.opentelemetry.io/collector/component/componentstatus"
)
func NewNopStatusReporter() Reporter {
return &nopStatusReporter{}
}
type nopStatusReporter struct{}
func (r *nopStatusReporter) Ready() {}
func (r *nopStatusReporter) ReportStatus(*componentstatus.InstanceID, *componentstatus.Event) {}
func (r *nopStatusReporter) ReportOKIfStarting(*componentstatus.InstanceID) {}
================================================
FILE: service/internal/status/nop_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package status // import "go.opentelemetry.io/collector/service/internal/status"
import "testing"
func TestNopStatusReporter(*testing.T) {
nop := NewNopStatusReporter()
nop.ReportOKIfStarting(nil)
nop.ReportStatus(nil, nil)
}
================================================
FILE: service/internal/status/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package status
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: service/internal/status/status.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package status // import "go.opentelemetry.io/collector/service/internal/status"
import (
"errors"
"fmt"
"sync"
"go.opentelemetry.io/collector/component/componentstatus"
)
// onTransitionFunc receives a componentstatus.Event on a successful state transition
type onTransitionFunc func(*componentstatus.Event)
// errInvalidStateTransition is returned for invalid state transitions
var errInvalidStateTransition = errors.New("invalid state transition")
// fsm is a finite state machine that models transitions for component status
type fsm struct {
current *componentstatus.Event
transitions map[componentstatus.Status]map[componentstatus.Status]struct{}
onTransition onTransitionFunc
}
// transition will attempt to execute a state transition. If it's successful, it calls the
// onTransitionFunc with a Event representing the new state. Returns an error if the arguments
// result in an invalid status, or if the state transition is not valid.
func (m *fsm) transition(ev *componentstatus.Event) error {
if _, ok := m.transitions[m.current.Status()][ev.Status()]; !ok {
return fmt.Errorf(
"cannot transition from %s to %s: %w",
m.current.Status(),
ev.Status(),
errInvalidStateTransition,
)
}
m.current = ev
m.onTransition(ev)
return nil
}
// newFSM creates a state machine with all valid transitions for componentstatus.Status.
// The initial state is set to componentstatus.StatusNone.
// Transitions between the same status value are always allowed, as the new event may come with updated metadata.
func newFSM(onTransition onTransitionFunc) *fsm {
return &fsm{
current: componentstatus.NewEvent(componentstatus.StatusNone),
onTransition: onTransition,
transitions: map[componentstatus.Status]map[componentstatus.Status]struct{}{
componentstatus.StatusNone: {
componentstatus.StatusStarting: {},
},
componentstatus.StatusStarting: {
componentstatus.StatusOK: {},
componentstatus.StatusRecoverableError: {},
componentstatus.StatusPermanentError: {},
componentstatus.StatusFatalError: {},
componentstatus.StatusStopping: {},
},
componentstatus.StatusOK: {
componentstatus.StatusOK: {},
componentstatus.StatusRecoverableError: {},
componentstatus.StatusPermanentError: {},
componentstatus.StatusFatalError: {},
componentstatus.StatusStopping: {},
},
componentstatus.StatusRecoverableError: {
componentstatus.StatusRecoverableError: {},
componentstatus.StatusOK: {},
componentstatus.StatusPermanentError: {},
componentstatus.StatusFatalError: {},
componentstatus.StatusStopping: {},
},
componentstatus.StatusPermanentError: {
componentstatus.StatusStopping: {},
},
componentstatus.StatusFatalError: {},
componentstatus.StatusStopping: {
componentstatus.StatusRecoverableError: {},
componentstatus.StatusPermanentError: {},
componentstatus.StatusFatalError: {},
componentstatus.StatusStopped: {},
},
componentstatus.StatusStopped: {},
},
}
}
// NotifyStatusFunc is the receiver of status events after successful state transitions
type NotifyStatusFunc func(*componentstatus.InstanceID, *componentstatus.Event)
// InvalidTransitionFunc is the receiver of invalid transition errors
type InvalidTransitionFunc func(error)
// ServiceStatusFunc is the expected type of ReportStatus
type ServiceStatusFunc func(*componentstatus.InstanceID, *componentstatus.Event)
// ErrStatusNotReady is returned when trying to report status before service start
var ErrStatusNotReady = errors.New("report component status is not ready until service start")
// Reporter handles component status reporting
type Reporter interface {
ReportStatus(id *componentstatus.InstanceID, ev *componentstatus.Event)
ReportOKIfStarting(id *componentstatus.InstanceID)
}
type reporter struct {
mu sync.Mutex
fsmMap map[*componentstatus.InstanceID]*fsm
onStatusChange NotifyStatusFunc
onInvalidTransition InvalidTransitionFunc
}
// NewReporter returns a reporter that will invoke the NotifyStatusFunc when a component's status
// has changed.
func NewReporter(onStatusChange NotifyStatusFunc, onInvalidTransition InvalidTransitionFunc) Reporter {
return &reporter{
fsmMap: make(map[*componentstatus.InstanceID]*fsm),
onStatusChange: onStatusChange,
onInvalidTransition: onInvalidTransition,
}
}
// ReportStatus reports status for the given InstanceID
func (r *reporter) ReportStatus(
id *componentstatus.InstanceID,
ev *componentstatus.Event,
) {
r.mu.Lock()
defer r.mu.Unlock()
if err := r.componentFSM(id).transition(ev); err != nil {
r.onInvalidTransition(err)
}
}
func (r *reporter) ReportOKIfStarting(id *componentstatus.InstanceID) {
r.mu.Lock()
defer r.mu.Unlock()
fsm := r.componentFSM(id)
if fsm.current.Status() == componentstatus.StatusStarting {
if err := fsm.transition(componentstatus.NewEvent(componentstatus.StatusOK)); err != nil {
r.onInvalidTransition(err)
}
}
}
// Note: a lock must be acquired before calling this method.
func (r *reporter) componentFSM(id *componentstatus.InstanceID) *fsm {
fsm, ok := r.fsmMap[id]
if !ok {
fsm = newFSM(func(ev *componentstatus.Event) { r.onStatusChange(id, ev) })
r.fsmMap[id] = fsm
}
return fsm
}
// NewReportStatusFunc returns a function to be used as ReportStatus for componentstatus.TelemetrySettings
func NewReportStatusFunc(
id *componentstatus.InstanceID,
srvStatus ServiceStatusFunc,
) func(*componentstatus.Event) {
return func(ev *componentstatus.Event) {
srvStatus(id, ev)
}
}
================================================
FILE: service/internal/status/status_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package status
import (
"fmt"
"sync"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component/componentstatus"
)
func TestStatusFSM(t *testing.T) {
for _, tt := range []struct {
name string
reportedStatuses []componentstatus.Status
expectedStatuses []componentstatus.Status
expectedErrorCount int
}{
{
name: "successful startup and shutdown",
reportedStatuses: []componentstatus.Status{
componentstatus.StatusStarting,
componentstatus.StatusOK,
componentstatus.StatusStopping,
componentstatus.StatusStopped,
},
expectedStatuses: []componentstatus.Status{
componentstatus.StatusStarting,
componentstatus.StatusOK,
componentstatus.StatusStopping,
componentstatus.StatusStopped,
},
},
{
name: "component recovered",
reportedStatuses: []componentstatus.Status{
componentstatus.StatusStarting,
componentstatus.StatusRecoverableError,
componentstatus.StatusOK,
componentstatus.StatusStopping,
componentstatus.StatusStopped,
},
expectedStatuses: []componentstatus.Status{
componentstatus.StatusStarting,
componentstatus.StatusRecoverableError,
componentstatus.StatusOK,
componentstatus.StatusStopping,
componentstatus.StatusStopped,
},
},
{
name: "repeated OK and RecoverableError events are valid",
reportedStatuses: []componentstatus.Status{
componentstatus.StatusStarting,
componentstatus.StatusOK,
componentstatus.StatusOK,
componentstatus.StatusRecoverableError,
componentstatus.StatusRecoverableError,
componentstatus.StatusOK,
componentstatus.StatusOK,
componentstatus.StatusStopping,
componentstatus.StatusStopped,
},
expectedStatuses: []componentstatus.Status{
componentstatus.StatusStarting,
componentstatus.StatusOK,
componentstatus.StatusOK,
componentstatus.StatusRecoverableError,
componentstatus.StatusRecoverableError,
componentstatus.StatusOK,
componentstatus.StatusOK,
componentstatus.StatusStopping,
componentstatus.StatusStopped,
},
},
{
name: "PermanentError is stoppable",
reportedStatuses: []componentstatus.Status{
componentstatus.StatusStarting,
componentstatus.StatusOK,
componentstatus.StatusPermanentError,
componentstatus.StatusOK,
componentstatus.StatusStopping,
},
expectedStatuses: []componentstatus.Status{
componentstatus.StatusStarting,
componentstatus.StatusOK,
componentstatus.StatusPermanentError,
componentstatus.StatusStopping,
},
expectedErrorCount: 1,
},
{
name: "FatalError is terminal",
reportedStatuses: []componentstatus.Status{
componentstatus.StatusStarting,
componentstatus.StatusOK,
componentstatus.StatusFatalError,
componentstatus.StatusOK,
},
expectedStatuses: []componentstatus.Status{
componentstatus.StatusStarting,
componentstatus.StatusOK,
componentstatus.StatusFatalError,
},
expectedErrorCount: 1,
},
{
name: "Stopped is terminal",
reportedStatuses: []componentstatus.Status{
componentstatus.StatusStarting,
componentstatus.StatusOK,
componentstatus.StatusStopping,
componentstatus.StatusStopped,
componentstatus.StatusOK,
},
expectedStatuses: []componentstatus.Status{
componentstatus.StatusStarting,
componentstatus.StatusOK,
componentstatus.StatusStopping,
componentstatus.StatusStopped,
},
expectedErrorCount: 1,
},
} {
t.Run(tt.name, func(t *testing.T) {
var receivedStatuses []componentstatus.Status
fsm := newFSM(
func(ev *componentstatus.Event) {
receivedStatuses = append(receivedStatuses, ev.Status())
},
)
errorCount := 0
for _, status := range tt.reportedStatuses {
if err := fsm.transition(componentstatus.NewEvent(status)); err != nil {
errorCount++
require.ErrorIs(t, err, errInvalidStateTransition)
}
}
require.Equal(t, tt.expectedErrorCount, errorCount)
require.Equal(t, tt.expectedStatuses, receivedStatuses)
})
}
}
func TestValidSeqsToStopped(t *testing.T) {
events := []*componentstatus.Event{
componentstatus.NewEvent(componentstatus.StatusStarting),
componentstatus.NewEvent(componentstatus.StatusOK),
componentstatus.NewEvent(componentstatus.StatusRecoverableError),
componentstatus.NewEvent(componentstatus.StatusPermanentError),
componentstatus.NewEvent(componentstatus.StatusFatalError),
}
for _, ev := range events {
name := fmt.Sprintf("transition from: %s to: %s", ev.Status(), componentstatus.StatusStopped)
t.Run(name, func(t *testing.T) {
fsm := newFSM(func(*componentstatus.Event) {})
if ev.Status() != componentstatus.StatusStarting {
require.NoError(t, fsm.transition(componentstatus.NewEvent(componentstatus.StatusStarting)))
}
require.NoError(t, fsm.transition(ev))
// skipping to stopped is not allowed
err := fsm.transition(componentstatus.NewEvent(componentstatus.StatusStopped))
require.ErrorIs(t, err, errInvalidStateTransition)
// stopping -> stopped is allowed for non-fatal errors
err = fsm.transition(componentstatus.NewEvent(componentstatus.StatusStopping))
if ev.Status() == componentstatus.StatusFatalError {
require.ErrorIs(t, err, errInvalidStateTransition)
} else {
require.NoError(t, err)
require.NoError(t, fsm.transition(componentstatus.NewEvent(componentstatus.StatusStopped)))
}
})
}
}
func TestStatusFuncs(t *testing.T) {
id1 := &componentstatus.InstanceID{}
id2 := &componentstatus.InstanceID{}
actualStatuses := make(map[*componentstatus.InstanceID][]componentstatus.Status)
statusFunc := func(id *componentstatus.InstanceID, ev *componentstatus.Event) {
actualStatuses[id] = append(actualStatuses[id], ev.Status())
}
statuses1 := []componentstatus.Status{
componentstatus.StatusStarting,
componentstatus.StatusOK,
componentstatus.StatusStopping,
componentstatus.StatusStopped,
}
statuses2 := []componentstatus.Status{
componentstatus.StatusStarting,
componentstatus.StatusOK,
componentstatus.StatusRecoverableError,
componentstatus.StatusOK,
componentstatus.StatusStopping,
componentstatus.StatusStopped,
}
expectedStatuses := map[*componentstatus.InstanceID][]componentstatus.Status{
id1: statuses1,
id2: statuses2,
}
rep := NewReporter(statusFunc,
func(err error) {
require.NoError(t, err)
})
comp1Func := NewReportStatusFunc(id1, rep.ReportStatus)
comp2Func := NewReportStatusFunc(id2, rep.ReportStatus)
for _, st := range statuses1 {
comp1Func(componentstatus.NewEvent(st))
}
for _, st := range statuses2 {
comp2Func(componentstatus.NewEvent(st))
}
require.Equal(t, expectedStatuses, actualStatuses)
}
func TestStatusFuncsConcurrent(t *testing.T) {
ids := []*componentstatus.InstanceID{{}, {}, {}, {}}
count := 0
statusFunc := func(*componentstatus.InstanceID, *componentstatus.Event) {
count++
}
rep := NewReporter(statusFunc,
func(err error) {
require.NoError(t, err)
})
wg := sync.WaitGroup{}
wg.Add(len(ids))
for _, id := range ids {
go func() {
compFn := NewReportStatusFunc(id, rep.ReportStatus)
compFn(componentstatus.NewEvent(componentstatus.StatusStarting))
for range 1000 {
compFn(componentstatus.NewEvent(componentstatus.StatusRecoverableError))
compFn(componentstatus.NewEvent(componentstatus.StatusOK))
}
wg.Done()
}()
}
wg.Wait()
require.Equal(t, 8004, count)
}
func TestReportComponentOKIfStarting(t *testing.T) {
for _, tt := range []struct {
name string
initialStatuses []componentstatus.Status
expectedStatuses []componentstatus.Status
}{
{
name: "matching condition: StatusStarting",
initialStatuses: []componentstatus.Status{
componentstatus.StatusStarting,
},
expectedStatuses: []componentstatus.Status{
componentstatus.StatusStarting,
componentstatus.StatusOK,
},
},
{
name: "non-matching condition StatusOK",
initialStatuses: []componentstatus.Status{
componentstatus.StatusStarting,
componentstatus.StatusOK,
},
expectedStatuses: []componentstatus.Status{
componentstatus.StatusStarting,
componentstatus.StatusOK,
},
},
{
name: "non-matching condition RecoverableError",
initialStatuses: []componentstatus.Status{
componentstatus.StatusStarting,
componentstatus.StatusRecoverableError,
},
expectedStatuses: []componentstatus.Status{
componentstatus.StatusStarting,
componentstatus.StatusRecoverableError,
},
},
{
name: "non-matching condition PermanentError",
initialStatuses: []componentstatus.Status{
componentstatus.StatusStarting,
componentstatus.StatusPermanentError,
},
expectedStatuses: []componentstatus.Status{
componentstatus.StatusStarting,
componentstatus.StatusPermanentError,
},
},
} {
t.Run(tt.name, func(t *testing.T) {
var receivedStatuses []componentstatus.Status
rep := NewReporter(
func(_ *componentstatus.InstanceID, ev *componentstatus.Event) {
receivedStatuses = append(receivedStatuses, ev.Status())
},
func(err error) {
require.NoError(t, err)
},
)
id := &componentstatus.InstanceID{}
for _, status := range tt.initialStatuses {
rep.ReportStatus(id, componentstatus.NewEvent(status))
}
rep.ReportOKIfStarting(id)
require.Equal(t, tt.expectedStatuses, receivedStatuses)
})
}
}
================================================
FILE: service/internal/testcomponents/example_connector.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package testcomponents // import "go.opentelemetry.io/collector/service/internal/testcomponents"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/connector"
"go.opentelemetry.io/collector/connector/xconnector"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/pdata/testdata"
)
var connType = component.MustNewType("exampleconnector")
// ExampleConnectorFactory is factory for ExampleConnector.
var ExampleConnectorFactory = xconnector.NewFactory(
connType,
createExampleConnectorDefaultConfig,
xconnector.WithTracesToTraces(createExampleTracesToTraces, component.StabilityLevelDevelopment),
xconnector.WithTracesToMetrics(createExampleTracesToMetrics, component.StabilityLevelDevelopment),
xconnector.WithTracesToLogs(createExampleTracesToLogs, component.StabilityLevelDevelopment),
xconnector.WithTracesToProfiles(createExampleTracesToProfiles, component.StabilityLevelDevelopment),
xconnector.WithMetricsToTraces(createExampleMetricsToTraces, component.StabilityLevelDevelopment),
xconnector.WithMetricsToMetrics(createExampleMetricsToMetrics, component.StabilityLevelDevelopment),
xconnector.WithMetricsToLogs(createExampleMetricsToLogs, component.StabilityLevelDevelopment),
xconnector.WithMetricsToProfiles(createExampleMetricsToProfiles, component.StabilityLevelDevelopment),
xconnector.WithLogsToTraces(createExampleLogsToTraces, component.StabilityLevelDevelopment),
xconnector.WithLogsToMetrics(createExampleLogsToMetrics, component.StabilityLevelDevelopment),
xconnector.WithLogsToLogs(createExampleLogsToLogs, component.StabilityLevelDevelopment),
xconnector.WithLogsToProfiles(createExampleLogsToProfiles, component.StabilityLevelDevelopment),
xconnector.WithProfilesToTraces(createExampleProfilesToTraces, component.StabilityLevelDevelopment),
xconnector.WithProfilesToMetrics(createExampleProfilesToMetrics, component.StabilityLevelDevelopment),
xconnector.WithProfilesToLogs(createExampleProfilesToLogs, component.StabilityLevelDevelopment),
xconnector.WithProfilesToProfiles(createExampleProfilesToProfiles, component.StabilityLevelDevelopment),
)
var MockForwardConnectorFactory = xconnector.NewFactory(
component.MustNewType("mockforward"),
createExampleConnectorDefaultConfig,
xconnector.WithTracesToTraces(createExampleTracesToTraces, component.StabilityLevelDevelopment),
xconnector.WithMetricsToMetrics(createExampleMetricsToMetrics, component.StabilityLevelDevelopment),
xconnector.WithLogsToLogs(createExampleLogsToLogs, component.StabilityLevelDevelopment),
xconnector.WithProfilesToProfiles(createExampleProfilesToProfiles, component.StabilityLevelDevelopment),
)
func createExampleConnectorDefaultConfig() component.Config {
return &struct{}{}
}
func createExampleTracesToTraces(_ context.Context, set connector.Settings, _ component.Config, traces consumer.Traces) (connector.Traces, error) {
return &ExampleConnector{
ConsumeTracesFunc: traces.ConsumeTraces,
mutatesData: set.ID.Name() == "mutate",
}, nil
}
func createExampleTracesToMetrics(_ context.Context, set connector.Settings, _ component.Config, metrics consumer.Metrics) (connector.Traces, error) {
return &ExampleConnector{
ConsumeTracesFunc: func(ctx context.Context, td ptrace.Traces) error {
return metrics.ConsumeMetrics(ctx, testdata.GenerateMetrics(td.SpanCount()))
},
mutatesData: set.ID.Name() == "mutate",
}, nil
}
func createExampleTracesToLogs(_ context.Context, set connector.Settings, _ component.Config, logs consumer.Logs) (connector.Traces, error) {
return &ExampleConnector{
ConsumeTracesFunc: func(ctx context.Context, td ptrace.Traces) error {
return logs.ConsumeLogs(ctx, testdata.GenerateLogs(td.SpanCount()))
},
mutatesData: set.ID.Name() == "mutate",
}, nil
}
func createExampleTracesToProfiles(_ context.Context, set connector.Settings, _ component.Config, profiles xconsumer.Profiles) (connector.Traces, error) {
return &ExampleConnector{
ConsumeTracesFunc: func(ctx context.Context, td ptrace.Traces) error {
return profiles.ConsumeProfiles(ctx, testdata.GenerateProfiles(td.SpanCount()))
},
mutatesData: set.ID.Name() == "mutate",
}, nil
}
func createExampleMetricsToTraces(_ context.Context, set connector.Settings, _ component.Config, traces consumer.Traces) (connector.Metrics, error) {
return &ExampleConnector{
ConsumeMetricsFunc: func(ctx context.Context, md pmetric.Metrics) error {
return traces.ConsumeTraces(ctx, testdata.GenerateTraces(md.MetricCount()))
},
mutatesData: set.ID.Name() == "mutate",
}, nil
}
func createExampleMetricsToMetrics(_ context.Context, set connector.Settings, _ component.Config, metrics consumer.Metrics) (connector.Metrics, error) {
return &ExampleConnector{
ConsumeMetricsFunc: metrics.ConsumeMetrics,
mutatesData: set.ID.Name() == "mutate",
}, nil
}
func createExampleMetricsToLogs(_ context.Context, set connector.Settings, _ component.Config, logs consumer.Logs) (connector.Metrics, error) {
return &ExampleConnector{
ConsumeMetricsFunc: func(ctx context.Context, md pmetric.Metrics) error {
return logs.ConsumeLogs(ctx, testdata.GenerateLogs(md.MetricCount()))
},
mutatesData: set.ID.Name() == "mutate",
}, nil
}
func createExampleMetricsToProfiles(_ context.Context, set connector.Settings, _ component.Config, profiles xconsumer.Profiles) (connector.Metrics, error) {
return &ExampleConnector{
ConsumeMetricsFunc: func(ctx context.Context, md pmetric.Metrics) error {
return profiles.ConsumeProfiles(ctx, testdata.GenerateProfiles(md.MetricCount()))
},
mutatesData: set.ID.Name() == "mutate",
}, nil
}
func createExampleLogsToTraces(_ context.Context, set connector.Settings, _ component.Config, traces consumer.Traces) (connector.Logs, error) {
return &ExampleConnector{
ConsumeLogsFunc: func(ctx context.Context, ld plog.Logs) error {
return traces.ConsumeTraces(ctx, testdata.GenerateTraces(ld.LogRecordCount()))
},
mutatesData: set.ID.Name() == "mutate",
}, nil
}
func createExampleLogsToMetrics(_ context.Context, set connector.Settings, _ component.Config, metrics consumer.Metrics) (connector.Logs, error) {
return &ExampleConnector{
ConsumeLogsFunc: func(ctx context.Context, ld plog.Logs) error {
return metrics.ConsumeMetrics(ctx, testdata.GenerateMetrics(ld.LogRecordCount()))
},
mutatesData: set.ID.Name() == "mutate",
}, nil
}
func createExampleLogsToLogs(_ context.Context, set connector.Settings, _ component.Config, logs consumer.Logs) (connector.Logs, error) {
return &ExampleConnector{
ConsumeLogsFunc: logs.ConsumeLogs,
mutatesData: set.ID.Name() == "mutate",
}, nil
}
func createExampleLogsToProfiles(_ context.Context, set connector.Settings, _ component.Config, profiles xconsumer.Profiles) (connector.Logs, error) {
return &ExampleConnector{
ConsumeLogsFunc: func(ctx context.Context, ld plog.Logs) error {
return profiles.ConsumeProfiles(ctx, testdata.GenerateProfiles(ld.LogRecordCount()))
},
mutatesData: set.ID.Name() == "mutate",
}, nil
}
func createExampleProfilesToTraces(_ context.Context, set connector.Settings, _ component.Config, traces consumer.Traces) (xconnector.Profiles, error) {
return &ExampleConnector{
ConsumeProfilesFunc: func(ctx context.Context, _ pprofile.Profiles) error {
return traces.ConsumeTraces(ctx, testdata.GenerateTraces(1))
},
mutatesData: set.ID.Name() == "mutate",
}, nil
}
func createExampleProfilesToMetrics(_ context.Context, set connector.Settings, _ component.Config, metrics consumer.Metrics) (xconnector.Profiles, error) {
return &ExampleConnector{
ConsumeProfilesFunc: func(ctx context.Context, _ pprofile.Profiles) error {
return metrics.ConsumeMetrics(ctx, testdata.GenerateMetrics(1))
},
mutatesData: set.ID.Name() == "mutate",
}, nil
}
func createExampleProfilesToLogs(_ context.Context, set connector.Settings, _ component.Config, logs consumer.Logs) (xconnector.Profiles, error) {
return &ExampleConnector{
ConsumeProfilesFunc: func(ctx context.Context, _ pprofile.Profiles) error {
return logs.ConsumeLogs(ctx, testdata.GenerateLogs(1))
},
mutatesData: set.ID.Name() == "mutate",
}, nil
}
func createExampleProfilesToProfiles(_ context.Context, set connector.Settings, _ component.Config, profiles xconsumer.Profiles) (xconnector.Profiles, error) {
return &ExampleConnector{
ConsumeProfilesFunc: profiles.ConsumeProfiles,
mutatesData: set.ID.Name() == "mutate",
}, nil
}
type ExampleConnector struct {
componentState
consumer.ConsumeTracesFunc
consumer.ConsumeMetricsFunc
consumer.ConsumeLogsFunc
xconsumer.ConsumeProfilesFunc
mutatesData bool
}
func (c *ExampleConnector) Capabilities() consumer.Capabilities {
return consumer.Capabilities{MutatesData: c.mutatesData}
}
================================================
FILE: service/internal/testcomponents/example_connector_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package testcomponents
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component/componenttest"
)
func TestExampleConnector(t *testing.T) {
conn := &ExampleConnector{}
host := componenttest.NewNopHost()
assert.False(t, conn.Started())
require.NoError(t, conn.Start(context.Background(), host))
assert.True(t, conn.Started())
assert.False(t, conn.Stopped())
require.NoError(t, conn.Shutdown(context.Background()))
assert.True(t, conn.Stopped())
}
================================================
FILE: service/internal/testcomponents/example_exporter.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package testcomponents // import "go.opentelemetry.io/collector/service/internal/testcomponents"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/xexporter"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/pdata/xpdata/pref"
)
var exporterType = component.MustNewType("exampleexporter")
// ExampleExporterFactory is factory for ExampleExporter.
var ExampleExporterFactory = xexporter.NewFactory(
exporterType,
createExporterDefaultConfig,
xexporter.WithTraces(createTracesExporter, component.StabilityLevelDevelopment),
xexporter.WithMetrics(createMetricsExporter, component.StabilityLevelDevelopment),
xexporter.WithLogs(createLogsExporter, component.StabilityLevelDevelopment),
xexporter.WithProfiles(createProfilesExporter, component.StabilityLevelDevelopment),
)
func createExporterDefaultConfig() component.Config {
return &struct{}{}
}
func createTracesExporter(context.Context, exporter.Settings, component.Config) (exporter.Traces, error) {
return &ExampleExporter{}, nil
}
func createMetricsExporter(context.Context, exporter.Settings, component.Config) (exporter.Metrics, error) {
return &ExampleExporter{}, nil
}
func createLogsExporter(context.Context, exporter.Settings, component.Config) (exporter.Logs, error) {
return &ExampleExporter{}, nil
}
func createProfilesExporter(context.Context, exporter.Settings, component.Config) (xexporter.Profiles, error) {
return &ExampleExporter{}, nil
}
// ExampleExporter stores consumed traces, metrics, logs and profiles for testing purposes.
type ExampleExporter struct {
componentState
Traces []ptrace.Traces
Metrics []pmetric.Metrics
Logs []plog.Logs
Profiles []pprofile.Profiles
}
// ConsumeTraces receives ptrace.Traces for processing by the consumer.Traces.
func (exp *ExampleExporter) ConsumeTraces(_ context.Context, td ptrace.Traces) error {
pref.RefTraces(td)
exp.Traces = append(exp.Traces, td)
return nil
}
// ConsumeMetrics receives pmetric.Metrics for processing by the Metrics.
func (exp *ExampleExporter) ConsumeMetrics(_ context.Context, md pmetric.Metrics) error {
pref.RefMetrics(md)
exp.Metrics = append(exp.Metrics, md)
return nil
}
// ConsumeLogs receives plog.Logs for processing by the Logs.
func (exp *ExampleExporter) ConsumeLogs(_ context.Context, ld plog.Logs) error {
pref.RefLogs(ld)
exp.Logs = append(exp.Logs, ld)
return nil
}
// ConsumeProfiles receives pprofile.Profiles for processing by the xconsumer.Profiles.
func (exp *ExampleExporter) ConsumeProfiles(_ context.Context, pd pprofile.Profiles) error {
pref.RefProfiles(pd)
exp.Profiles = append(exp.Profiles, pd)
return nil
}
func (exp *ExampleExporter) Capabilities() consumer.Capabilities {
return consumer.Capabilities{MutatesData: false}
}
================================================
FILE: service/internal/testcomponents/example_exporter_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package testcomponents
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/ptrace"
)
func TestExampleExporter(t *testing.T) {
exp := &ExampleExporter{}
host := componenttest.NewNopHost()
assert.False(t, exp.Started())
require.NoError(t, exp.Start(context.Background(), host))
assert.True(t, exp.Started())
assert.Empty(t, exp.Traces)
require.NoError(t, exp.ConsumeTraces(context.Background(), ptrace.NewTraces()))
assert.Len(t, exp.Traces, 1)
assert.Empty(t, exp.Metrics)
require.NoError(t, exp.ConsumeMetrics(context.Background(), pmetric.NewMetrics()))
assert.Len(t, exp.Metrics, 1)
assert.Empty(t, exp.Logs)
require.NoError(t, exp.ConsumeLogs(context.Background(), plog.NewLogs()))
assert.Len(t, exp.Logs, 1)
assert.Empty(t, exp.Profiles)
require.NoError(t, exp.ConsumeProfiles(context.Background(), pprofile.NewProfiles()))
assert.Len(t, exp.Profiles, 1)
assert.False(t, exp.Stopped())
require.NoError(t, exp.Shutdown(context.Background()))
assert.True(t, exp.Stopped())
}
================================================
FILE: service/internal/testcomponents/example_processor.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package testcomponents // import "go.opentelemetry.io/collector/service/internal/testcomponents"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/xprocessor"
)
var procType = component.MustNewType("exampleprocessor")
// ExampleProcessorFactory is factory for ExampleProcessor.
var ExampleProcessorFactory = xprocessor.NewFactory(
procType,
createDefaultConfig,
xprocessor.WithTraces(createTracesProcessor, component.StabilityLevelDevelopment),
xprocessor.WithMetrics(createMetricsProcessor, component.StabilityLevelDevelopment),
xprocessor.WithLogs(createLogsProcessor, component.StabilityLevelDevelopment),
xprocessor.WithProfiles(createProfilesProcessor, component.StabilityLevelDevelopment),
)
// CreateDefaultConfig creates the default configuration for the Processor.
func createDefaultConfig() component.Config {
return &struct{}{}
}
func createTracesProcessor(_ context.Context, set processor.Settings, _ component.Config, nextConsumer consumer.Traces) (processor.Traces, error) {
return &ExampleProcessor{
ConsumeTracesFunc: nextConsumer.ConsumeTraces,
mutatesData: set.ID.Name() == "mutate",
}, nil
}
func createMetricsProcessor(_ context.Context, set processor.Settings, _ component.Config, nextConsumer consumer.Metrics) (processor.Metrics, error) {
return &ExampleProcessor{
ConsumeMetricsFunc: nextConsumer.ConsumeMetrics,
mutatesData: set.ID.Name() == "mutate",
}, nil
}
func createLogsProcessor(_ context.Context, set processor.Settings, _ component.Config, nextConsumer consumer.Logs) (processor.Logs, error) {
return &ExampleProcessor{
ConsumeLogsFunc: nextConsumer.ConsumeLogs,
mutatesData: set.ID.Name() == "mutate",
}, nil
}
func createProfilesProcessor(_ context.Context, set processor.Settings, _ component.Config, nextConsumer xconsumer.Profiles) (xprocessor.Profiles, error) {
return &ExampleProcessor{
ConsumeProfilesFunc: nextConsumer.ConsumeProfiles,
mutatesData: set.ID.Name() == "mutate",
}, nil
}
type ExampleProcessor struct {
componentState
consumer.ConsumeTracesFunc
consumer.ConsumeMetricsFunc
consumer.ConsumeLogsFunc
xconsumer.ConsumeProfilesFunc
mutatesData bool
}
func (ep *ExampleProcessor) Capabilities() consumer.Capabilities {
return consumer.Capabilities{MutatesData: ep.mutatesData}
}
================================================
FILE: service/internal/testcomponents/example_processor_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package testcomponents
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component/componenttest"
)
func TestExampleProcessor(t *testing.T) {
prc := &ExampleProcessor{}
host := componenttest.NewNopHost()
assert.False(t, prc.Started())
require.NoError(t, prc.Start(context.Background(), host))
assert.True(t, prc.Started())
assert.False(t, prc.Stopped())
require.NoError(t, prc.Shutdown(context.Background()))
assert.True(t, prc.Stopped())
}
================================================
FILE: service/internal/testcomponents/example_receiver.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package testcomponents // import "go.opentelemetry.io/collector/service/internal/testcomponents"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/xreceiver"
)
var receiverType = component.MustNewType("examplereceiver")
// ExampleReceiverFactory is factory for ExampleReceiver.
var ExampleReceiverFactory = xreceiver.NewFactory(
receiverType,
createReceiverDefaultConfig,
xreceiver.WithTraces(createTracesReceiver, component.StabilityLevelDevelopment),
xreceiver.WithMetrics(createMetricsReceiver, component.StabilityLevelDevelopment),
xreceiver.WithLogs(createLogsReceiver, component.StabilityLevelDevelopment),
xreceiver.WithProfiles(createProfilesReceiver, component.StabilityLevelDevelopment),
)
func createReceiverDefaultConfig() component.Config {
return &struct{}{}
}
// createTraces creates a receiver.Traces based on this config.
func createTracesReceiver(
_ context.Context,
_ receiver.Settings,
cfg component.Config,
nextConsumer consumer.Traces,
) (receiver.Traces, error) {
tr := createReceiver(cfg)
tr.ConsumeTracesFunc = nextConsumer.ConsumeTraces
return tr, nil
}
// createMetrics creates a receiver.Metrics based on this config.
func createMetricsReceiver(
_ context.Context,
_ receiver.Settings,
cfg component.Config,
nextConsumer consumer.Metrics,
) (receiver.Metrics, error) {
mr := createReceiver(cfg)
mr.ConsumeMetricsFunc = nextConsumer.ConsumeMetrics
return mr, nil
}
// createLogs creates a receiver.Logs based on this config.
func createLogsReceiver(
_ context.Context,
_ receiver.Settings,
cfg component.Config,
nextConsumer consumer.Logs,
) (receiver.Logs, error) {
lr := createReceiver(cfg)
lr.ConsumeLogsFunc = nextConsumer.ConsumeLogs
return lr, nil
}
// createProfiles creates a receiver.Profiles based on this config.
func createProfilesReceiver(
_ context.Context,
_ receiver.Settings,
cfg component.Config,
nextConsumer xconsumer.Profiles,
) (xreceiver.Profiles, error) {
tr := createReceiver(cfg)
tr.ConsumeProfilesFunc = nextConsumer.ConsumeProfiles
return tr, nil
}
func createReceiver(cfg component.Config) *ExampleReceiver {
// 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.
er, ok := exampleReceivers[cfg]
if !ok {
er = &ExampleReceiver{}
// Remember the receiver in the map
exampleReceivers[cfg] = er
}
return er
}
// ExampleReceiver allows producing traces, metrics, logs and profiles for testing purposes.
type ExampleReceiver struct {
componentState
consumer.ConsumeTracesFunc
consumer.ConsumeMetricsFunc
consumer.ConsumeLogsFunc
xconsumer.ConsumeProfilesFunc
}
// This is the map of already created example receivers for particular configurations.
// We maintain this map because the receiver.Factory is asked trace and metric receivers separately
// when it gets CreateTraces() and CreateMetrics() but they must not
// create separate objects, they must use one Receiver object per configuration.
var exampleReceivers = map[component.Config]*ExampleReceiver{}
================================================
FILE: service/internal/testcomponents/example_receiver_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package testcomponents
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component/componenttest"
)
func TestExampleReceiver(t *testing.T) {
rcv := &ExampleReceiver{}
host := componenttest.NewNopHost()
assert.False(t, rcv.Started())
require.NoError(t, rcv.Start(context.Background(), host))
assert.True(t, rcv.Started())
assert.False(t, rcv.Stopped())
require.NoError(t, rcv.Shutdown(context.Background()))
assert.True(t, rcv.Stopped())
}
================================================
FILE: service/internal/testcomponents/example_router.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package testcomponents // import "go.opentelemetry.io/collector/service/internal/testcomponents"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/connector"
"go.opentelemetry.io/collector/connector/xconnector"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/pprofile"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/pipeline"
)
var routerType = component.MustNewType("examplerouter")
// ExampleRouterFactory is factory for ExampleRouter.
var ExampleRouterFactory = xconnector.NewFactory(
routerType,
createExampleRouterDefaultConfig,
xconnector.WithTracesToTraces(createExampleTracesRouter, component.StabilityLevelDevelopment),
xconnector.WithMetricsToMetrics(createExampleMetricsRouter, component.StabilityLevelDevelopment),
xconnector.WithLogsToLogs(createExampleLogsRouter, component.StabilityLevelDevelopment),
xconnector.WithProfilesToProfiles(createExampleProfilesRouter, component.StabilityLevelDevelopment),
)
type LeftRightConfig struct {
Left pipeline.ID `mapstructure:"left"`
Right pipeline.ID `mapstructure:"right"`
}
type ExampleRouterConfig struct {
Traces *LeftRightConfig `mapstructure:"traces"`
Metrics *LeftRightConfig `mapstructure:"metrics"`
Logs *LeftRightConfig `mapstructure:"logs"`
Profiles *LeftRightConfig `mapstructure:"profiles"`
}
func createExampleRouterDefaultConfig() component.Config {
return &ExampleRouterConfig{}
}
func createExampleTracesRouter(_ context.Context, _ connector.Settings, cfg component.Config, traces consumer.Traces) (connector.Traces, error) {
c := cfg.(ExampleRouterConfig)
r := traces.(connector.TracesRouterAndConsumer)
left, _ := r.Consumer(c.Traces.Left)
right, _ := r.Consumer(c.Traces.Right)
return &ExampleRouter{
tracesRight: right,
tracesLeft: left,
}, nil
}
func createExampleMetricsRouter(_ context.Context, _ connector.Settings, cfg component.Config, metrics consumer.Metrics) (connector.Metrics, error) {
c := cfg.(ExampleRouterConfig)
r := metrics.(connector.MetricsRouterAndConsumer)
left, _ := r.Consumer(c.Metrics.Left)
right, _ := r.Consumer(c.Metrics.Right)
return &ExampleRouter{
metricsRight: right,
metricsLeft: left,
}, nil
}
func createExampleLogsRouter(_ context.Context, _ connector.Settings, cfg component.Config, logs consumer.Logs) (connector.Logs, error) {
c := cfg.(ExampleRouterConfig)
r := logs.(connector.LogsRouterAndConsumer)
left, _ := r.Consumer(c.Logs.Left)
right, _ := r.Consumer(c.Logs.Right)
return &ExampleRouter{
logsRight: right,
logsLeft: left,
}, nil
}
func createExampleProfilesRouter(_ context.Context, _ connector.Settings, cfg component.Config, profiles xconsumer.Profiles) (xconnector.Profiles, error) {
c := cfg.(ExampleRouterConfig)
r := profiles.(xconnector.ProfilesRouterAndConsumer)
left, _ := r.Consumer(c.Profiles.Left)
right, _ := r.Consumer(c.Profiles.Right)
return &ExampleRouter{
profilesRight: right,
profilesLeft: left,
}, nil
}
type ExampleRouter struct {
componentState
tracesRight consumer.Traces
tracesLeft consumer.Traces
tracesNum int
metricsRight consumer.Metrics
metricsLeft consumer.Metrics
metricsNum int
logsRight consumer.Logs
logsLeft consumer.Logs
logsNum int
profilesRight xconsumer.Profiles
profilesLeft xconsumer.Profiles
profilesNum int
}
func (r *ExampleRouter) ConsumeTraces(ctx context.Context, td ptrace.Traces) error {
r.tracesNum++
if r.tracesNum%2 == 0 {
return r.tracesLeft.ConsumeTraces(ctx, td)
}
return r.tracesRight.ConsumeTraces(ctx, td)
}
func (r *ExampleRouter) ConsumeMetrics(ctx context.Context, md pmetric.Metrics) error {
r.metricsNum++
if r.metricsNum%2 == 0 {
return r.metricsLeft.ConsumeMetrics(ctx, md)
}
return r.metricsRight.ConsumeMetrics(ctx, md)
}
func (r *ExampleRouter) ConsumeLogs(ctx context.Context, ld plog.Logs) error {
r.logsNum++
if r.logsNum%2 == 0 {
return r.logsLeft.ConsumeLogs(ctx, ld)
}
return r.logsRight.ConsumeLogs(ctx, ld)
}
func (r *ExampleRouter) ConsumeProfiles(ctx context.Context, td pprofile.Profiles) error {
r.profilesNum++
if r.profilesNum%2 == 0 {
return r.profilesLeft.ConsumeProfiles(ctx, td)
}
return r.profilesRight.ConsumeProfiles(ctx, td)
}
func (r *ExampleRouter) Capabilities() consumer.Capabilities {
return consumer.Capabilities{MutatesData: false}
}
================================================
FILE: service/internal/testcomponents/example_router_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package testcomponents
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/connector"
"go.opentelemetry.io/collector/connector/connectortest"
"go.opentelemetry.io/collector/connector/xconnector"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/consumer/xconsumer"
"go.opentelemetry.io/collector/pdata/testdata"
"go.opentelemetry.io/collector/pipeline"
)
func TestExampleRouter(t *testing.T) {
conn := &ExampleRouter{}
host := componenttest.NewNopHost()
assert.False(t, conn.Started())
require.NoError(t, conn.Start(context.Background(), host))
assert.True(t, conn.Started())
assert.False(t, conn.Stopped())
require.NoError(t, conn.Shutdown(context.Background()))
assert.True(t, conn.Stopped())
}
func TestTracesRouter(t *testing.T) {
leftID := pipeline.NewIDWithName(pipeline.SignalTraces, "sink_left")
rightID := pipeline.NewIDWithName(pipeline.SignalTraces, "sink_right")
sinkLeft := new(consumertest.TracesSink)
sinkRight := new(consumertest.TracesSink)
// The service will build a router to give to every connector.
// Many connectors will just call router.ConsumeTraces,
// but some implementation will call RouteTraces instead.
router := connector.NewTracesRouter(
map[pipeline.ID]consumer.Traces{
leftID: sinkLeft,
rightID: sinkRight,
})
cfg := ExampleRouterConfig{Traces: &LeftRightConfig{Left: leftID, Right: rightID}}
tr, err := ExampleRouterFactory.CreateTracesToTraces(
context.Background(), connectortest.NewNopSettings(ExampleRouterFactory.Type()), cfg, router)
require.NoError(t, err)
assert.False(t, tr.Capabilities().MutatesData)
td := testdata.GenerateTraces(1)
require.NoError(t, tr.ConsumeTraces(context.Background(), td))
assert.Len(t, sinkRight.AllTraces(), 1)
assert.Empty(t, sinkLeft.AllTraces())
require.NoError(t, tr.ConsumeTraces(context.Background(), td))
assert.Len(t, sinkRight.AllTraces(), 1)
assert.Len(t, sinkLeft.AllTraces(), 1)
assert.NoError(t, tr.ConsumeTraces(context.Background(), td))
assert.NoError(t, tr.ConsumeTraces(context.Background(), td))
assert.NoError(t, tr.ConsumeTraces(context.Background(), td))
assert.Len(t, sinkRight.AllTraces(), 3)
assert.Len(t, sinkLeft.AllTraces(), 2)
}
func TestMetricsRouter(t *testing.T) {
leftID := pipeline.NewIDWithName(pipeline.SignalTraces, "sink_left")
rightID := pipeline.NewIDWithName(pipeline.SignalTraces, "sink_right")
sinkLeft := new(consumertest.MetricsSink)
sinkRight := new(consumertest.MetricsSink)
// The service will build a router to give to every connector.
// Many connectors will just call router.ConsumeMetrics,
// but some implementation will call RouteMetrics instead.
router := connector.NewMetricsRouter(
map[pipeline.ID]consumer.Metrics{
leftID: sinkLeft,
rightID: sinkRight,
})
cfg := ExampleRouterConfig{Metrics: &LeftRightConfig{Left: leftID, Right: rightID}}
mr, err := ExampleRouterFactory.CreateMetricsToMetrics(
context.Background(), connectortest.NewNopSettings(ExampleRouterFactory.Type()), cfg, router)
require.NoError(t, err)
assert.False(t, mr.Capabilities().MutatesData)
md := testdata.GenerateMetrics(1)
require.NoError(t, mr.ConsumeMetrics(context.Background(), md))
assert.Len(t, sinkRight.AllMetrics(), 1)
assert.Empty(t, sinkLeft.AllMetrics())
require.NoError(t, mr.ConsumeMetrics(context.Background(), md))
assert.Len(t, sinkRight.AllMetrics(), 1)
assert.Len(t, sinkLeft.AllMetrics(), 1)
assert.NoError(t, mr.ConsumeMetrics(context.Background(), md))
assert.NoError(t, mr.ConsumeMetrics(context.Background(), md))
assert.NoError(t, mr.ConsumeMetrics(context.Background(), md))
assert.Len(t, sinkRight.AllMetrics(), 3)
assert.Len(t, sinkLeft.AllMetrics(), 2)
}
func TestLogsRouter(t *testing.T) {
leftID := pipeline.NewIDWithName(pipeline.SignalTraces, "sink_left")
rightID := pipeline.NewIDWithName(pipeline.SignalTraces, "sink_right")
sinkLeft := new(consumertest.LogsSink)
sinkRight := new(consumertest.LogsSink)
// The service will build a router to give to every connector.
// Many connectors will just call router.ConsumeLogs,
// but some implementation will call RouteLogs instead.
router := connector.NewLogsRouter(
map[pipeline.ID]consumer.Logs{
leftID: sinkLeft,
rightID: sinkRight,
})
cfg := ExampleRouterConfig{Logs: &LeftRightConfig{Left: leftID, Right: rightID}}
lr, err := ExampleRouterFactory.CreateLogsToLogs(
context.Background(), connectortest.NewNopSettings(ExampleRouterFactory.Type()), cfg, router)
require.NoError(t, err)
assert.False(t, lr.Capabilities().MutatesData)
ld := testdata.GenerateLogs(1)
require.NoError(t, lr.ConsumeLogs(context.Background(), ld))
assert.Len(t, sinkRight.AllLogs(), 1)
assert.Empty(t, sinkLeft.AllLogs())
require.NoError(t, lr.ConsumeLogs(context.Background(), ld))
assert.Len(t, sinkRight.AllLogs(), 1)
assert.Len(t, sinkLeft.AllLogs(), 1)
assert.NoError(t, lr.ConsumeLogs(context.Background(), ld))
assert.NoError(t, lr.ConsumeLogs(context.Background(), ld))
assert.NoError(t, lr.ConsumeLogs(context.Background(), ld))
assert.Len(t, sinkRight.AllLogs(), 3)
assert.Len(t, sinkLeft.AllLogs(), 2)
}
func TestProfilesRouter(t *testing.T) {
leftID := pipeline.NewIDWithName(pipeline.SignalTraces, "sink_left")
rightID := pipeline.NewIDWithName(pipeline.SignalTraces, "sink_right")
sinkLeft := new(consumertest.ProfilesSink)
sinkRight := new(consumertest.ProfilesSink)
// The service will build a router to give to every connector.
// Many connectors will just call router.ConsumeProfiles,
// but some implementation will call RouteProfiles instead.
router := xconnector.NewProfilesRouter(
map[pipeline.ID]xconsumer.Profiles{
leftID: sinkLeft,
rightID: sinkRight,
})
cfg := ExampleRouterConfig{Profiles: &LeftRightConfig{Left: leftID, Right: rightID}}
tr, err := ExampleRouterFactory.CreateProfilesToProfiles(
context.Background(), connectortest.NewNopSettings(ExampleRouterFactory.Type()), cfg, router)
require.NoError(t, err)
assert.False(t, tr.Capabilities().MutatesData)
td := testdata.GenerateProfiles(1)
require.NoError(t, tr.ConsumeProfiles(context.Background(), td))
assert.Len(t, sinkRight.AllProfiles(), 1)
assert.Empty(t, sinkLeft.AllProfiles())
require.NoError(t, tr.ConsumeProfiles(context.Background(), td))
assert.Len(t, sinkRight.AllProfiles(), 1)
assert.Len(t, sinkLeft.AllProfiles(), 1)
assert.NoError(t, tr.ConsumeProfiles(context.Background(), td))
assert.NoError(t, tr.ConsumeProfiles(context.Background(), td))
assert.NoError(t, tr.ConsumeProfiles(context.Background(), td))
assert.Len(t, sinkRight.AllProfiles(), 3)
assert.Len(t, sinkLeft.AllProfiles(), 2)
}
================================================
FILE: service/internal/testcomponents/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package testcomponents
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: service/internal/testcomponents/stateful_component.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package testcomponents // import "go.opentelemetry.io/collector/service/internal/testcomponents"
import (
"context"
"go.opentelemetry.io/collector/component"
)
type componentState struct {
started bool
stopped bool
}
func (cs *componentState) Started() bool {
return cs.started
}
func (cs *componentState) Stopped() bool {
return cs.stopped
}
func (cs *componentState) Start(context.Context, component.Host) error {
cs.started = true
return nil
}
func (cs *componentState) Shutdown(context.Context) error {
cs.stopped = true
return nil
}
================================================
FILE: service/internal/zpages/package_test.go
================================================
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package zpages
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
================================================
FILE: service/internal/zpages/templates/component_header.html
================================================
{{$link := .Link}}
{{- if $link -}}
{{- else -}}
{{.Name}}
{{- end -}}
================================================
FILE: service/internal/zpages/templates/extensions_table.html
================================================
{{range $rowindex, $row := .Rows}}
{{- if even $rowindex}}
{{else}}
{{end -}}
| {{.FullName}} |
{{end}}
================================================
FILE: service/internal/zpages/templates/features_table.html
================================================
| ID |
| |
Enabled |
| |
Description |
| |
Stage |
| |
From Version |
| |
To Version |
| |
Reference URL |
{{range $rowindex, $row := .Rows}}
{{- if even $rowindex}}
{{else}}
{{end -}}
| {{$row.ID}} | | |
{{$row.Enabled}} | | |
{{$row.Description}} | | |
{{$row.Stage}} | | |
{{$row.FromVersion}} | | |
{{$row.ToVersion}} | | |
{{$row.ReferenceURL}} | | |
{{end}}
================================================
FILE: service/internal/zpages/templates/page_footer.html
================================================